From d0c169b54bfd6049cf7fe2d781e95a8d4235ab47 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 20 Jan 2026 06:55:40 +0000 Subject: [PATCH 01/18] Initial plan From b0bde0a38821fd9c60ed9bfef3394ffbf295ac89 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 20 Jan 2026 07:05:10 +0000 Subject: [PATCH 02/18] Refactor emitter to export API with diagnostic return values Co-authored-by: ArcturusZhang <10554446+ArcturusZhang@users.noreply.github.com> --- .../http-client-csharp/emitter/src/emitter.ts | 38 ++++++++++--- .../emitter/src/lib/client-model-builder.ts | 7 +-- .../emitter/src/lib/logger.ts | 24 +++++++-- .../emitter/test/Unit/auth.test.ts | 30 +++++------ .../test/Unit/client-converter.test.ts | 8 +-- .../test/Unit/client-initialization.test.ts | 8 +-- .../test/Unit/client-model-builder.test.ts | 16 +++--- .../emitter/test/Unit/constant-type.test.ts | 8 +-- .../emitter/test/Unit/decorator-list.test.ts | 10 ++-- .../emitter/test/Unit/emitter.test.ts | 2 +- .../emitter/test/Unit/encode.test.ts | 12 ++--- .../emitter/test/Unit/input-parameter.test.ts | 54 +++++++++---------- .../emitter/test/Unit/model-type.test.ts | 36 ++++++------- .../test/Unit/operation-converter.test.ts | 14 ++--- .../test/Unit/operation-paging.test.ts | 24 ++++----- .../emitter/test/Unit/property-type.test.ts | 10 ++-- .../emitter/test/Unit/scalar.test.ts | 2 +- .../emitter/test/Unit/string-format.test.ts | 4 +- .../emitter/test/Unit/type-converter.test.ts | 6 +-- .../emitter/test/Unit/usage.test.ts | 30 +++++------ 20 files changed, 192 insertions(+), 151 deletions(-) diff --git a/packages/http-client-csharp/emitter/src/emitter.ts b/packages/http-client-csharp/emitter/src/emitter.ts index 8bd34db8889..4ab553d2f69 100644 --- a/packages/http-client-csharp/emitter/src/emitter.ts +++ b/packages/http-client-csharp/emitter/src/emitter.ts @@ -3,6 +3,7 @@ import { createSdkContext, SdkContext } from "@azure-tools/typespec-client-generator-core"; import { + Diagnostic, EmitContext, getDirectoryPath, joinPaths, @@ -48,17 +49,23 @@ function findProjectRoot(path: string): string | undefined { } /** - * The entry point for the emitter. This function is called by the typespec compiler. + * Creates a code model by executing the full emission logic. + * This function can be called by downstream emitters to generate a code model and collect diagnostics. * @param context - The emit context + * @returns A tuple containing void and any diagnostics that were generated during the emission * @beta */ -export async function $onEmit(context: EmitContext) { +export async function createCodeModel( + context: EmitContext, +): Promise<[void, readonly Diagnostic[]]> { const program: Program = context.program; const options = resolveOptions(context); const outputFolder = context.emitterOutputDir; /* set the log level. */ - const logger = new Logger(program, options.logLevel ?? LoggerLevel.INFO); + const logger = new Logger(program, options.logLevel ?? LoggerLevel.INFO, true); + + const diagnostics: Diagnostic[] = []; if (!program.compilerOptions.noEmit && !program.hasError()) { // Write out the dotnet model to the output path @@ -70,12 +77,15 @@ export async function $onEmit(context: EmitContext) { ), logger, ); - program.reportDiagnostics(sdkContext.diagnostics); + diagnostics.push(...sdkContext.diagnostics); - let root = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + diagnostics.push(...modelDiagnostics); if (root) { - root = options["update-code-model"](root, sdkContext); + const updatedRoot = options["update-code-model"](root, sdkContext); + diagnostics.push(...logger.getDiagnostics()); + const generatedFolder = resolvePath(outputFolder, "src", "Generated"); if (!fs.existsSync(generatedFolder)) { @@ -83,9 +93,9 @@ export async function $onEmit(context: EmitContext) { } // emit tspCodeModel.json - await writeCodeModel(sdkContext, root, outputFolder); + await writeCodeModel(sdkContext, updatedRoot, outputFolder); - const namespace = root.name; + const namespace = updatedRoot.name; const configurations: Configuration = createConfiguration(options, namespace, sdkContext); //emit configuration.json @@ -133,6 +143,18 @@ export async function $onEmit(context: EmitContext) { } } } + + return [undefined as void, diagnostics]; +} + +/** + * The entry point for the emitter. This function is called by the typespec compiler. + * @param context - The emit context + * @beta + */ +export async function $onEmit(context: EmitContext) { + const [, diagnostics] = await createCodeModel(context); + context.program.reportDiagnostics(diagnostics); } export function createConfiguration( diff --git a/packages/http-client-csharp/emitter/src/lib/client-model-builder.ts b/packages/http-client-csharp/emitter/src/lib/client-model-builder.ts index 94f1bfd767d..33c88272158 100644 --- a/packages/http-client-csharp/emitter/src/lib/client-model-builder.ts +++ b/packages/http-client-csharp/emitter/src/lib/client-model-builder.ts @@ -7,6 +7,7 @@ import { SdkHttpOperation, UsageFlags, } from "@azure-tools/typespec-client-generator-core"; +import { Diagnostic } from "@typespec/compiler"; import { CSharpEmitterContext } from "../sdk-context.js"; import { CodeModel } from "../type/code-model.js"; import { InputEnumType, InputLiteralType, InputModelType } from "../type/input-type.js"; @@ -23,10 +24,10 @@ import { /** * Creates the code model from the SDK context. * @param sdkContext - The SDK context - * @returns The code model + * @returns A tuple containing the code model and any diagnostics that were generated * @beta */ -export function createModel(sdkContext: CSharpEmitterContext): CodeModel { +export function createModel(sdkContext: CSharpEmitterContext): [CodeModel, readonly Diagnostic[]] { const sdkPackage = sdkContext.sdkPackage; // TO-DO: Consider exposing the namespace hierarchy in the code model https://github.com/microsoft/typespec/issues/8332 @@ -60,7 +61,7 @@ export function createModel(sdkContext: CSharpEmitterContext): CodeModel { auth: processServiceAuthentication(sdkContext, sdkPackage), }; - return clientModel; + return [clientModel, sdkContext.logger.getDiagnostics()]; } /** diff --git a/packages/http-client-csharp/emitter/src/lib/logger.ts b/packages/http-client-csharp/emitter/src/lib/logger.ts index 8b087a400f3..f99f726c061 100644 --- a/packages/http-client-csharp/emitter/src/lib/logger.ts +++ b/packages/http-client-csharp/emitter/src/lib/logger.ts @@ -1,8 +1,9 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. -import { DiagnosticReport, NoTarget, Program, Tracer } from "@typespec/compiler"; +import { Diagnostic, DiagnosticReport, NoTarget, Program, Tracer } from "@typespec/compiler"; import { + createDiagnostic, DiagnosticMessagesMap, getTracer, reportDiagnostic as libReportDiagnostic, @@ -17,11 +18,22 @@ export class Logger { private tracer: Tracer; private level: LoggerLevel; private program: Program; + private collectedDiagnostics: Diagnostic[] | undefined; - public constructor(program: Program, level: LoggerLevel) { + public constructor(program: Program, level: LoggerLevel, collectDiagnostics: boolean = false) { this.tracer = getTracer(program); this.level = level; this.program = program; + this.collectedDiagnostics = collectDiagnostics ? [] : undefined; + } + + /** + * Get collected diagnostics. Only available if the logger was created with collectDiagnostics=true. + * @returns The collected diagnostics. + * @beta + */ + public getDiagnostics(): readonly Diagnostic[] { + return this.collectedDiagnostics ?? []; } trace(level: LoggerLevel, message: string): void { @@ -63,7 +75,13 @@ export class Logger { reportDiagnostic( diag: DiagnosticReport, ): void { - libReportDiagnostic(this.program, diag); + if (this.collectedDiagnostics) { + // In collecting mode, store the diagnostic instead of reporting it + this.collectedDiagnostics.push(createDiagnostic(diag)); + } else { + // In normal mode, report the diagnostic directly + libReportDiagnostic(this.program, diag); + } } warn(message: string): void { diff --git a/packages/http-client-csharp/emitter/test/Unit/auth.test.ts b/packages/http-client-csharp/emitter/test/Unit/auth.test.ts index ee94b078d41..4aff753d192 100644 --- a/packages/http-client-csharp/emitter/test/Unit/auth.test.ts +++ b/packages/http-client-csharp/emitter/test/Unit/auth.test.ts @@ -30,7 +30,7 @@ describe("Test auth", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const diagnostics = context.program.diagnostics; const noAuthDiagnostics = diagnostics.filter( @@ -72,7 +72,7 @@ describe("Test auth", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const diagnostics = context.program.diagnostics; const noAuthDiagnostics = diagnostics.filter( @@ -114,7 +114,7 @@ describe("Test auth", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const diagnostics = context.program.diagnostics; const noAuthDiagnostics = diagnostics.filter( @@ -154,7 +154,7 @@ describe("Test auth", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const diagnostics = context.program.diagnostics; const noAuthDiagnostic = diagnostics.find( @@ -182,7 +182,7 @@ describe("Test auth", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const diagnostics = context.program.diagnostics; const noAuthDiagnostics = diagnostics.filter( @@ -216,7 +216,7 @@ describe("Test auth", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const diagnostics = context.program.diagnostics; const noAuthDiagnostics = diagnostics.filter( @@ -249,7 +249,7 @@ describe("Test auth", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const diagnostics = context.program.diagnostics; // Should have no auth-related diagnostics @@ -290,7 +290,7 @@ describe("Test auth", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); ok(root.auth?.oAuth2); strictEqual(root.auth.oAuth2.flows.length, 1); @@ -322,7 +322,7 @@ describe("Test auth", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); ok(root.auth?.oAuth2); strictEqual(root.auth.oAuth2.flows.length, 1); @@ -352,7 +352,7 @@ describe("Test auth", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); ok(root.auth?.oAuth2); strictEqual(root.auth.oAuth2.flows.length, 1); @@ -389,7 +389,7 @@ describe("Test auth", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); ok(root.auth?.oAuth2); strictEqual(root.auth.oAuth2.flows.length, 2); @@ -426,7 +426,7 @@ describe("Test auth", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); ok(root.auth?.oAuth2); strictEqual(root.auth.oAuth2.flows.length, 1); @@ -454,7 +454,7 @@ describe("Test auth", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); ok(root.auth?.oAuth2); strictEqual(root.auth.oAuth2.flows.length, 1); @@ -482,7 +482,7 @@ describe("Test auth", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); // Should have both OAuth2 and API key auth ok(root.auth?.oAuth2); @@ -513,7 +513,7 @@ describe("Test auth", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); ok(root.auth?.oAuth2); strictEqual(root.auth.oAuth2.flows.length, 1); diff --git a/packages/http-client-csharp/emitter/test/Unit/client-converter.test.ts b/packages/http-client-csharp/emitter/test/Unit/client-converter.test.ts index ab683388071..3e2546f9626 100644 --- a/packages/http-client-csharp/emitter/test/Unit/client-converter.test.ts +++ b/packages/http-client-csharp/emitter/test/Unit/client-converter.test.ts @@ -28,7 +28,7 @@ describe("isMultiServiceClient", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const client = root.clients[0]; ok(client, "Client should exist"); @@ -81,7 +81,7 @@ describe("isMultiServiceClient", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); strictEqual(root.name, "Service.MultiService", "Root namespace should be Service.MultiService"); const client = root.clients[0]; @@ -147,7 +147,7 @@ describe("isMultiServiceClient", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); strictEqual(root.name, "Service.MultiService", "Root namespace should be Service.MultiService"); const client = root.clients[0]; @@ -219,7 +219,7 @@ describe("isMultiServiceClient", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); strictEqual(root.name, "Service.MultiService", "Root namespace should be Service.MultiService"); const clients = root.clients; diff --git a/packages/http-client-csharp/emitter/test/Unit/client-initialization.test.ts b/packages/http-client-csharp/emitter/test/Unit/client-initialization.test.ts index 8715259052f..d9b508b9a16 100644 --- a/packages/http-client-csharp/emitter/test/Unit/client-initialization.test.ts +++ b/packages/http-client-csharp/emitter/test/Unit/client-initialization.test.ts @@ -34,7 +34,7 @@ describe("ClientInitialization", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const client = root.clients[0]; ok(client, "Client should exist"); @@ -59,7 +59,7 @@ describe("ClientInitialization", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const client = root.clients[0]; // initializedBy field should exist on the client (may be undefined or have a value) @@ -84,7 +84,7 @@ describe("ClientInitialization", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const client = root.clients[0]; ok(client.parameters, "Client should have parameters"); @@ -113,7 +113,7 @@ describe("ClientInitialization", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const client = root.clients[0]; ok("initializedBy" in client, "Parent client should have initializedBy field"); diff --git a/packages/http-client-csharp/emitter/test/Unit/client-model-builder.test.ts b/packages/http-client-csharp/emitter/test/Unit/client-model-builder.test.ts index 611fafc9ec9..dbd26ede69c 100644 --- a/packages/http-client-csharp/emitter/test/Unit/client-model-builder.test.ts +++ b/packages/http-client-csharp/emitter/test/Unit/client-model-builder.test.ts @@ -58,7 +58,7 @@ describe("fixNamingConflicts", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); // Find the real enum const realEnum = root.enums.find( @@ -142,7 +142,7 @@ describe("fixNamingConflicts", () => { namespace: targetNamespace, } as any); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); // Get all ErrorResponse models - fixNamingConflicts should have resolved the conflicts const errorModels = root.models.filter( @@ -194,7 +194,7 @@ describe("parseApiVersions", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); // The root apiVersions should include the version from the Versions enum // which is defined in the default namespace with version "2023-01-01-preview" @@ -228,7 +228,7 @@ describe("parseApiVersions", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); // The root apiVersions should include all versions from the TestVersions enum strictEqual(root.apiVersions.length, 3, "Root apiVersions should have 3 versions"); @@ -247,7 +247,7 @@ describe("parseApiVersions", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); // Single service client should have apiVersions from the @versioned decorator ok(root.apiVersions.length > 0, "Root apiVersions should not be empty for single service"); @@ -299,7 +299,7 @@ describe("parseApiVersions", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); ok(root.apiVersions.length === 0, "Root apiVersions should be empty for multiservice"); @@ -358,7 +358,7 @@ describe("parseApiVersions", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); ok(root.apiVersions.length === 0, "Root apiVersions should be empty for multiservice"); }); @@ -415,7 +415,7 @@ describe("parseApiVersions", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); ok( root.apiVersions.length === 0, diff --git a/packages/http-client-csharp/emitter/test/Unit/constant-type.test.ts b/packages/http-client-csharp/emitter/test/Unit/constant-type.test.ts index 595e936d2f5..3730f08dbdd 100644 --- a/packages/http-client-csharp/emitter/test/Unit/constant-type.test.ts +++ b/packages/http-client-csharp/emitter/test/Unit/constant-type.test.ts @@ -33,7 +33,7 @@ describe("Name for constant type", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const testModel = root.models.find((m) => m.name === "TestModel"); ok(testModel); const propertyType = testModel.properties[0].type; @@ -64,7 +64,7 @@ describe("Name for constant type", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const testModel1 = root.models.find((m) => m.name === "TestModel1"); ok(testModel1); const testModel2 = root.models.find((m) => m.name === "TestModel2"); @@ -103,7 +103,7 @@ describe("Constant enum conversion", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const testModel = root.models.find((m) => m.name === "TestModel"); ok(testModel); const propertyType = testModel.properties[0].type; @@ -134,7 +134,7 @@ describe("Constant enum conversion", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const testModel = root.models.find((m) => m.name === "TestModel"); ok(testModel); const propertyType = testModel.properties[0].type; diff --git a/packages/http-client-csharp/emitter/test/Unit/decorator-list.test.ts b/packages/http-client-csharp/emitter/test/Unit/decorator-list.test.ts index 37aaa5ad3fc..53df8aa6e86 100644 --- a/packages/http-client-csharp/emitter/test/Unit/decorator-list.test.ts +++ b/packages/http-client-csharp/emitter/test/Unit/decorator-list.test.ts @@ -34,7 +34,7 @@ describe("Test emitting decorator list", () => { const sdkContext = await createCSharpSdkContext(context, { additionalDecorators: ["Azure\\.ClientGenerator\\.Core\\.@clientName"], }); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const clients = root.clients; strictEqual(clients.length, 1); ok(clients[0].children); @@ -66,7 +66,7 @@ describe("Test emitting decorator list", () => { const sdkContext = await createCSharpSdkContext(context, { additionalDecorators: ["Azure\\.ClientGenerator\\.Core\\.@clientName"], }); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const methods = root.clients[0].methods; strictEqual(methods.length, 1); const operation = methods[0].operation; @@ -97,7 +97,7 @@ describe("Test emitting decorator list", () => { const sdkContext = await createCSharpSdkContext(context, { additionalDecorators: ["Azure\\.ClientGenerator\\.Core\\.@clientName"], }); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const models = root.models; strictEqual(models.length, 1); deepStrictEqual(models[0].decorators, [ @@ -127,7 +127,7 @@ describe("Test emitting decorator list", () => { const sdkContext = await createCSharpSdkContext(context, { additionalDecorators: ["Azure\\.ClientGenerator\\.Core\\.@clientName"], }); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const models = root.models; strictEqual(models.length, 1); deepStrictEqual(models[0].properties[0].decorators, [ @@ -153,7 +153,7 @@ describe("Test emitting decorator list", () => { const sdkContext = await createCSharpSdkContext(context, { additionalDecorators: ["Azure\\.ClientGenerator\\.Core\\.@clientName"], }); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const methods = root.clients[0].methods; strictEqual(methods.length, 1); const operation = methods[0].operation; diff --git a/packages/http-client-csharp/emitter/test/Unit/emitter.test.ts b/packages/http-client-csharp/emitter/test/Unit/emitter.test.ts index 6755c79fde1..7509337482f 100644 --- a/packages/http-client-csharp/emitter/test/Unit/emitter.test.ts +++ b/packages/http-client-csharp/emitter/test/Unit/emitter.test.ts @@ -58,7 +58,7 @@ describe("$onEmit tests", () => { })); vi.mock("../../src/lib/client-model-builder.js", () => ({ - createModel: vi.fn().mockReturnValue({ Name: "TestNamespace" }), + createModel: vi.fn().mockReturnValue([{ name: "TestNamespace" }, []]), })); program = { diff --git a/packages/http-client-csharp/emitter/test/Unit/encode.test.ts b/packages/http-client-csharp/emitter/test/Unit/encode.test.ts index 559d2d54dbb..6344bd3f4ca 100644 --- a/packages/http-client-csharp/emitter/test/Unit/encode.test.ts +++ b/packages/http-client-csharp/emitter/test/Unit/encode.test.ts @@ -32,7 +32,7 @@ describe("Test encode duration", () => { // validate method parameter const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const methodParamArray = root.clients[0].methods[0].parameters; strictEqual(1, methodParamArray.length); let type = methodParamArray[0].type; @@ -74,7 +74,7 @@ describe("Test encode duration", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); // validate method parameter const methodParamArray = root.clients[0].methods[0].parameters; strictEqual(1, methodParamArray.length); @@ -117,7 +117,7 @@ describe("Test encode duration", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); // validate method parameter const methodParamArray = root.clients[0].methods[0].parameters; strictEqual(1, methodParamArray.length); @@ -161,7 +161,7 @@ describe("Test encode duration", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const codeModel = createModel(sdkContext); + const [codeModel] = createModel(sdkContext); const models = codeModel.models; const durationModel = models.find((m) => m.name === "ISO8601DurationProperty"); ok(durationModel); @@ -190,7 +190,7 @@ describe("Test encode duration", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const codeModel = createModel(sdkContext); + const [codeModel] = createModel(sdkContext); const models = codeModel.models; const durationModel = models.find((m) => m.name === "Int32SecondsDurationProperty"); ok(durationModel); @@ -219,7 +219,7 @@ describe("Test encode duration", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const codeModel = createModel(sdkContext); + const [codeModel] = createModel(sdkContext); const models = codeModel.models; const durationModel = models.find((m) => m.name === "FloatSecondsDurationProperty"); ok(durationModel); diff --git a/packages/http-client-csharp/emitter/test/Unit/input-parameter.test.ts b/packages/http-client-csharp/emitter/test/Unit/input-parameter.test.ts index d552104b525..1c0977497ef 100644 --- a/packages/http-client-csharp/emitter/test/Unit/input-parameter.test.ts +++ b/packages/http-client-csharp/emitter/test/Unit/input-parameter.test.ts @@ -42,7 +42,7 @@ describe("Test Parameter Explode", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const inputParamArray = root.clients[0].methods[0].operation.parameters.filter( (p) => p.name === "param", ); @@ -72,7 +72,7 @@ describe("Test Parameter Explode", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const inputParamArray = root.clients[0].methods[0].operation.parameters.filter( (p) => p.name === "param", ); @@ -102,7 +102,7 @@ describe("Test Parameter Explode", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const inputParamArray = root.clients[0].methods[0].operation.parameters.filter( (p) => p.name === "param", ); @@ -136,7 +136,7 @@ describe("Test Parameter Explode", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const inputParamArray = root.clients[0].methods[0].operation.parameters.filter( (p) => p.name === "param", ); @@ -166,7 +166,7 @@ describe("Test Parameter Explode", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const inputParamArray = root.clients[0].methods[0].operation.parameters.filter( (p) => p.name === "param", ); @@ -196,7 +196,7 @@ describe("Test Parameter Explode", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const inputParamArray = root.clients[0].methods[0].operation.parameters.filter( (p) => p.name === "param", ); @@ -228,7 +228,7 @@ describe("Test Parameter Explode", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const inputParamArray = root.clients[0].methods[0].operation.parameters.filter( (p) => p.name === "param", ); @@ -258,7 +258,7 @@ describe("Test Parameter Explode", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const inputParamArray = root.clients[0].methods[0].operation.parameters.filter( (p) => p.name === "param", ); @@ -288,7 +288,7 @@ describe("Test Parameter Explode", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const inputParamArray = root.clients[0].methods[0].operation.parameters.filter( (p) => p.name === "param", ); @@ -320,7 +320,7 @@ describe("Test Parameter Explode", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const inputParamArray = root.clients[0].methods[0].operation.parameters.filter( (p) => p.name === "param", ); @@ -350,7 +350,7 @@ describe("Test Parameter Explode", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const inputParamArray = root.clients[0].methods[0].operation.parameters.filter( (p) => p.name === "param", ); @@ -380,7 +380,7 @@ describe("Test Parameter Explode", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const inputParamArray = root.clients[0].methods[0].operation.parameters.filter( (p) => p.name === "param", ); @@ -414,7 +414,7 @@ describe("Test Parameter Explode", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const inputParamArray = root.clients[0].methods[0].operation.parameters.filter( (p) => p.name === "param", ); @@ -445,7 +445,7 @@ describe("Test Parameter Explode", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const inputParamArray = root.clients[0].methods[0].operation.parameters.filter( (p) => p.name === "param", ); @@ -476,7 +476,7 @@ describe("Test Parameter Explode", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const inputParamArray = root.clients[0].methods[0].operation.parameters.filter( (p) => p.name === "param", ); @@ -509,7 +509,7 @@ describe("Test Parameter Explode", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const inputParamArray = root.clients[0].methods[0].operation.parameters.filter( (p) => p.name === "param", ); @@ -540,7 +540,7 @@ describe("Test Parameter Explode", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const inputParamArray = root.clients[0].methods[0].operation.parameters.filter( (p) => p.name === "param", ); @@ -571,7 +571,7 @@ describe("Test Parameter Explode", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const inputParamArray = root.clients[0].methods[0].operation.parameters.filter( (p) => p.name === "param", ); @@ -686,7 +686,7 @@ describe("Endpoint parameters", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const codeModel = createModel(sdkContext); + const [codeModel] = createModel(sdkContext); const client = codeModel.clients[0]; ok(client); ok(client.parameters); @@ -722,7 +722,7 @@ describe("Endpoint parameters", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const codeModel = createModel(sdkContext); + const [codeModel] = createModel(sdkContext); const client = codeModel.clients[0]; ok(client); ok(client.parameters); @@ -759,7 +759,7 @@ describe("Test Spread Parameters", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); ok(root); // validate service method @@ -802,7 +802,7 @@ describe("Test Spread Parameters", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); ok(root); // validate service method @@ -855,7 +855,7 @@ describe("Test Operation Parameters", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const operation = root.clients[0].methods[0].operation; const queryParam = operation.parameters.find((p) => p.name === "queryParam"); @@ -881,7 +881,7 @@ describe("Test Operation Parameters", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const operation = root.clients[0].methods[0].operation; const pathParam = operation.parameters.find((p) => p.name === "pathParam"); @@ -910,7 +910,7 @@ describe("Test Operation Parameters", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const operation = root.clients[0].methods[0].operation; const headerParam = operation.parameters.find((p) => p.name === "headerParam"); @@ -940,7 +940,7 @@ describe("Test Operation Parameters", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const operation = root.clients[0].methods[0].operation; const contentTypeParam = operation.parameters.find((p) => p.name === "contentType"); @@ -970,7 +970,7 @@ describe("Test Operation Parameters", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const operation = root.clients[0].methods[0].operation; const bodyParam = operation.parameters.find((p) => p.name === "bodyParam"); diff --git a/packages/http-client-csharp/emitter/test/Unit/model-type.test.ts b/packages/http-client-csharp/emitter/test/Unit/model-type.test.ts index c343dab4823..556cda60305 100644 --- a/packages/http-client-csharp/emitter/test/Unit/model-type.test.ts +++ b/packages/http-client-csharp/emitter/test/Unit/model-type.test.ts @@ -51,7 +51,7 @@ op test(@body input: Pet): Pet; ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const models = root.models; const petModel = models.find((m) => m.name === "Pet"); const catModel = models.find((m) => m.name === "Cat"); @@ -135,7 +135,7 @@ op test(@body input: Pet): Pet; ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const codeModel = createModel(sdkContext); + const [codeModel] = createModel(sdkContext); const models = codeModel.models; const pet = models.find((m) => m.name === "Pet"); assert(pet !== undefined); @@ -229,7 +229,7 @@ op test(@body input: Pet): Pet; ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const codeModel = createModel(sdkContext); + const [codeModel] = createModel(sdkContext); const models = codeModel.models; const pet = models.find((m) => m.name === "Pet"); assert(pet !== undefined); @@ -350,7 +350,7 @@ op op5(@body body: ExtendsFooArray): ExtendsFooArray; ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const models = root.models; const extendsUnknownModel = models.find((m) => m.name === "ExtendsUnknown"); const extendsStringModel = models.find((m) => m.name === "ExtendsString"); @@ -442,7 +442,7 @@ op op5(@body body: IsFooArray): IsFooArray; ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const models = root.models; const isUnknownModel = models.find((m) => m.name === "IsUnknown"); const isStringModel = models.find((m) => m.name === "IsString"); @@ -493,7 +493,7 @@ op op1(): void; ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const models = root.models; const isEmptyModel = models.find((m) => m.name === "Empty"); ok(isEmptyModel); @@ -522,7 +522,7 @@ model Foo { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const models = root.models; const model = models.find((m) => m.name === "Foo"); ok(model); @@ -560,7 +560,7 @@ describe("Anonymous models should be included in library", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); ok(root); // validate service method @@ -602,7 +602,7 @@ op testOperation(@bodyRoot body: HeaderModel): void; const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const models = root.models; const isEmptyModel = models.find((m) => m.name === "HeaderModel"); ok(isEmptyModel); @@ -647,7 +647,7 @@ op testOperation(@bodyRoot body: HeaderModel): void; const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const models = root.models; const isEmptyModel = models.find((m) => m.name === "HeaderModel"); ok(isEmptyModel); @@ -680,7 +680,7 @@ op testOperation(@bodyRoot body: HeaderModel): void; const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const models = root.models; const isEmptyModel = models.find((m) => m.name === "HeaderModel"); ok(isEmptyModel); @@ -707,7 +707,7 @@ op testOperation(@bodyRoot body: HeaderModel): void; const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const models = root.models; const isEmptyModel = models.find((m) => m.name === "HeaderModel"); ok(isEmptyModel); @@ -757,7 +757,7 @@ describe("typespec-client-generator-core: general decorators list", () => { const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const models = root.models; strictEqual(models.length, 1); deepStrictEqual(models[0].decorators, [ @@ -799,7 +799,7 @@ describe("Access decorator on enums", () => { const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const enums = root.enums; const colorEnum = enums.find((e) => e.name === "Color"); @@ -830,7 +830,7 @@ describe("Access decorator on enums", () => { const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const enums = root.enums; const colorEnum = enums.find((e) => e.name === "Color"); @@ -860,7 +860,7 @@ describe("Access decorator on enums", () => { const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const enums = root.enums; const statusEnum = enums.find((e) => e.name === "Status"); @@ -898,7 +898,7 @@ describe("Usage decorator on enums", () => { const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const enums = root.enums; const colorEnum = enums.find((e) => e.name === "Color"); @@ -928,7 +928,7 @@ describe("Usage decorator on enums", () => { const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const enums = root.enums; const colorEnum = enums.find((e) => e.name === "Color"); diff --git a/packages/http-client-csharp/emitter/test/Unit/operation-converter.test.ts b/packages/http-client-csharp/emitter/test/Unit/operation-converter.test.ts index d91f0cd31c1..08ac1f48adf 100644 --- a/packages/http-client-csharp/emitter/test/Unit/operation-converter.test.ts +++ b/packages/http-client-csharp/emitter/test/Unit/operation-converter.test.ts @@ -35,7 +35,7 @@ describe("Operation Converter", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); strictEqual(root.clients.length, 1); strictEqual(root.clients[0].methods.length, 1); @@ -106,7 +106,7 @@ describe("Operation Converter", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); strictEqual(root.clients.length, 1); strictEqual(root.clients[0].methods.length, 1); @@ -176,7 +176,7 @@ describe("Operation Converter", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); strictEqual(root.clients.length, 1); strictEqual(root.clients[0].methods.length, 1); @@ -231,7 +231,7 @@ describe("Operation Converter", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); strictEqual(root.clients.length, 1); strictEqual(root.clients[0].methods.length, 1); @@ -275,7 +275,7 @@ describe("Operation Converter", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); strictEqual(root.clients.length, 1); strictEqual(root.clients[0].methods.length, 1); @@ -314,7 +314,7 @@ describe("Operation Converter", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); strictEqual(root.clients.length, 1); strictEqual(root.clients[0].methods.length, 1); @@ -346,7 +346,7 @@ describe("Operation Converter", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); strictEqual(root.clients.length, 1); strictEqual(root.clients[0].methods.length, 1); diff --git a/packages/http-client-csharp/emitter/test/Unit/operation-paging.test.ts b/packages/http-client-csharp/emitter/test/Unit/operation-paging.test.ts index d5f5cd66d54..37290c74147 100644 --- a/packages/http-client-csharp/emitter/test/Unit/operation-paging.test.ts +++ b/packages/http-client-csharp/emitter/test/Unit/operation-paging.test.ts @@ -39,7 +39,7 @@ describe("Next link operations", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const method = root.clients[0].methods[0]; strictEqual(method.kind, "paging"); @@ -93,7 +93,7 @@ describe("Next link operations", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const method = root.clients[0].methods[0]; strictEqual(method.kind, "paging"); @@ -138,7 +138,7 @@ describe("Next link operations", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const method = root.clients[0].methods[0]; strictEqual(method.kind, "paging"); @@ -174,7 +174,7 @@ describe("Next link operations", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const method = root.clients[0].methods[0]; strictEqual(method.kind, "paging"); @@ -208,7 +208,7 @@ describe("Next link operations", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const method = root.clients[0].methods[0]; strictEqual(method.kind, "paging"); @@ -256,7 +256,7 @@ describe("Continuation token operations", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const method = root.clients[0].methods[0]; strictEqual(method.kind, "paging"); @@ -291,7 +291,7 @@ describe("Continuation token operations", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const method = root.clients[0].methods[0]; strictEqual(method.kind, "paging"); @@ -327,7 +327,7 @@ describe("Continuation token operations", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const method = root.clients[0].methods[0]; strictEqual(method.kind, "paging"); @@ -363,7 +363,7 @@ describe("Continuation token operations", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const method = root.clients[0].methods[0]; strictEqual(method.kind, "paging"); @@ -399,7 +399,7 @@ describe("Continuation token operations", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const method = root.clients[0].methods[0]; strictEqual(method.kind, "paging"); @@ -453,7 +453,7 @@ describe("PageSize parameter operations", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const method = root.clients[0].methods[0]; strictEqual(method.kind, "paging"); @@ -491,7 +491,7 @@ describe("PageSize parameter operations", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const method = root.clients[0].methods[0]; strictEqual(method.kind, "paging"); diff --git a/packages/http-client-csharp/emitter/test/Unit/property-type.test.ts b/packages/http-client-csharp/emitter/test/Unit/property-type.test.ts index 5de9f067d45..e3589538bd3 100644 --- a/packages/http-client-csharp/emitter/test/Unit/property-type.test.ts +++ b/packages/http-client-csharp/emitter/test/Unit/property-type.test.ts @@ -28,7 +28,7 @@ describe("Test GetInputType for array", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const inputParamArray = root.clients[0].methods[0].operation.parameters.filter( (p) => p.name === "input", ); @@ -49,7 +49,7 @@ describe("Test GetInputType for array", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const bodyType = root.clients[0].methods[0].operation.responses[0].bodyType; strictEqual(bodyType?.kind, "array"); strictEqual(bodyType.crossLanguageDefinitionId, "TypeSpec.Array"); @@ -86,7 +86,7 @@ describe("Test GetInputType for enum", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const inputParamArray = root.clients[0].methods[0].operation.parameters.filter( (p) => p.name === "input", ); @@ -130,7 +130,7 @@ describe("Test GetInputType for enum", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const inputParamArray = root.clients[0].methods[0].operation.parameters.filter( (p) => p.name === "input", ); @@ -169,7 +169,7 @@ describe("Test GetInputType for enum", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const inputParamArray = root.clients[0].methods[0].operation.parameters.filter( (p) => p.name === "input", ); diff --git a/packages/http-client-csharp/emitter/test/Unit/scalar.test.ts b/packages/http-client-csharp/emitter/test/Unit/scalar.test.ts index 015d4ca3c43..cea35899816 100644 --- a/packages/http-client-csharp/emitter/test/Unit/scalar.test.ts +++ b/packages/http-client-csharp/emitter/test/Unit/scalar.test.ts @@ -28,7 +28,7 @@ describe("Test GetInputType for scalar", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const inputParamArray = root.clients[0].methods[0].operation.parameters.filter( (p) => p.name === "location", ); diff --git a/packages/http-client-csharp/emitter/test/Unit/string-format.test.ts b/packages/http-client-csharp/emitter/test/Unit/string-format.test.ts index 2f86dc8409e..3eea60ca0d1 100644 --- a/packages/http-client-csharp/emitter/test/Unit/string-format.test.ts +++ b/packages/http-client-csharp/emitter/test/Unit/string-format.test.ts @@ -27,7 +27,7 @@ describe("Test string format", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const inputParamArray = root.clients[0].methods[0].operation.parameters.filter( (p) => p.name === "sourceUrl", ); @@ -51,7 +51,7 @@ describe("Test string format", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const codeModel = createModel(sdkContext); + const [codeModel] = createModel(sdkContext); const models = codeModel.models; const foo = models.find((m) => m.name === "Foo"); ok(foo); diff --git a/packages/http-client-csharp/emitter/test/Unit/type-converter.test.ts b/packages/http-client-csharp/emitter/test/Unit/type-converter.test.ts index 081708c903f..b6fb88bbdf9 100644 --- a/packages/http-client-csharp/emitter/test/Unit/type-converter.test.ts +++ b/packages/http-client-csharp/emitter/test/Unit/type-converter.test.ts @@ -69,7 +69,7 @@ describe("Enum value references", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const enumType = root.enums.find((e) => e.name === "TestEnum"); ok(enumType, "TestEnum should exist in the enums list"); strictEqual(enumType.values.length, 3, "TestEnum should have 3 values"); @@ -119,7 +119,7 @@ describe("External types", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const testModel = root.models.find((m) => m.name === "TestModel"); ok(testModel, "TestModel should exist"); @@ -164,7 +164,7 @@ describe("External types", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const testModel = root.models.find((m) => m.name === "TestModel"); ok(testModel, "TestModel should exist"); diff --git a/packages/http-client-csharp/emitter/test/Unit/usage.test.ts b/packages/http-client-csharp/emitter/test/Unit/usage.test.ts index a20ee81a401..ee0923b0174 100644 --- a/packages/http-client-csharp/emitter/test/Unit/usage.test.ts +++ b/packages/http-client-csharp/emitter/test/Unit/usage.test.ts @@ -33,7 +33,7 @@ describe("Test Usage", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const fooModel = root.models.find((model) => model.name === "Foo"); ok(fooModel); @@ -54,7 +54,7 @@ describe("Test Usage", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const fooModel = root.models.find((model) => model.name === "Foo"); ok(fooModel); @@ -75,7 +75,7 @@ describe("Test Usage", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const fooModel = root.models.find((model) => model.name === "Foo"); ok(fooModel); @@ -97,7 +97,7 @@ describe("Test Usage", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const fooModel = root.models.find((model) => model.name === "Foo"); ok(fooModel); @@ -124,7 +124,7 @@ describe("Test Usage", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const fooModel = root.models.find((model) => model.name === "Foo"); const templateModel = root.models.find((model) => model.name === "TemplateModelFoo"); @@ -154,7 +154,7 @@ describe("Test Usage", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const baseModel = root.models.find((model) => model.name === "BaseModel"); const fooModel = root.models.find((model) => model.name === "Foo"); @@ -191,7 +191,7 @@ describe("Test Usage", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const baseModel = root.models.find((model) => model.name === "BaseModel"); const fooModel = root.models.find((model) => model.name === "Foo"); const propertyModel = root.models.find((model) => model.name === "PropertyModel"); @@ -218,7 +218,7 @@ describe("Test Usage", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const fooAlias = root.models.find((model) => model.name === "TestRequest"); ok(fooAlias); @@ -266,7 +266,7 @@ describe("Test Usage", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const fooInfo = root.models.find((model) => model.name === "FooInfo"); const batchCreateFooListItemsRequest = root.models.find( (model) => model.name === "BatchCreateFooListItemsRequest", @@ -312,7 +312,7 @@ describe("Test Usage", () => { const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const fooModel = root.models.find((model) => model.name === "Foo"); ok(fooModel); @@ -371,7 +371,7 @@ describe("Test Usage", () => { const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const baseModel = root.models.find((model) => model.name === "BaseModelWithDiscriminator"); const derivedModel = root.models.find( (model) => model.name === "DerivedModelWithDiscriminatorA", @@ -441,7 +441,7 @@ describe("Test Usage", () => { const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const baseModel = root.models.find((model) => model.name === "BaseModelWithDiscriminator"); const derivedModel = root.models.find( (model) => model.name === "DerivedModelWithDiscriminatorA", @@ -478,7 +478,7 @@ describe("Test Usage", () => { const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const simpleEnumRenamed = root.enums.find((enumType) => enumType.name === "SimpleEnumRenamed"); ok(simpleEnumRenamed); @@ -502,7 +502,7 @@ describe("Test Usage", () => { const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const renamedModel = root.models.find((model) => model.name === "RenamedModel"); ok(renamedModel); @@ -660,7 +660,7 @@ interface LegacyLro { const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const root = createModel(sdkContext); + const [root] = createModel(sdkContext); const radiologyInsightsInferenceResult = root.models.find( (model) => model.name === "RadiologyInsightsInferenceResult", ); From 7c36f320c8e1fc00870120c5083a9023a1d1b0ba Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 20 Jan 2026 07:09:57 +0000 Subject: [PATCH 03/18] Add tests for new diagnostic collection API Co-authored-by: ArcturusZhang <10554446+ArcturusZhang@users.noreply.github.com> --- .../test/Unit/client-model-builder.test.ts | 59 +++++++++++++++++++ .../emitter/test/Unit/emitter.test.ts | 53 +++++++++++++++++ .../emitter/test/Unit/utils/test-util.ts | 25 +++++--- 3 files changed, 130 insertions(+), 7 deletions(-) diff --git a/packages/http-client-csharp/emitter/test/Unit/client-model-builder.test.ts b/packages/http-client-csharp/emitter/test/Unit/client-model-builder.test.ts index dbd26ede69c..b3cdb30e8ae 100644 --- a/packages/http-client-csharp/emitter/test/Unit/client-model-builder.test.ts +++ b/packages/http-client-csharp/emitter/test/Unit/client-model-builder.test.ts @@ -441,3 +441,62 @@ describe("parseApiVersions", () => { ok(barClient.apiVersions.includes("bv2"), "Bar client should include bv2"); }); }); + +describe("createModel diagnostic collection", () => { + let runner: TestHost; + + beforeEach(async () => { + runner = await createEmitterTestHost(); + }); + + it("should return a tuple with CodeModel and diagnostics array", async () => { + const program = await typeSpecCompile( + ` + model TestModel { + name: string; + } + + @route("/test") + op test(): TestModel; + `, + runner, + ); + const context = createEmitterContext(program); + const sdkContext = await createCSharpSdkContext(context, true); // Enable diagnostic collection + const result = createModel(sdkContext); + + // Verify the result is a tuple + ok(Array.isArray(result), "Result should be an array (tuple)"); + strictEqual(result.length, 2, "Result should have exactly 2 elements"); + + const [codeModel, diagnostics] = result; + + // Verify the code model + ok(codeModel, "CodeModel should be defined"); + strictEqual(codeModel.name, "Azure.Csharp.Testing", "CodeModel name should be Azure.Csharp.Testing"); + + // Verify diagnostics is an array + ok(Array.isArray(diagnostics), "Diagnostics should be an array"); + }); + + it("should collect diagnostics when using diagnostic collection mode", async () => { + const program = await typeSpecCompile( + ` + model TestModel { + name: string; + } + + @route("/test") + op test(): TestModel; + `, + runner, + ); + const context = createEmitterContext(program); + const sdkContext = await createCSharpSdkContext(context, true); // Enable diagnostic collection + const [, diagnostics] = createModel(sdkContext); + + // Verify diagnostics array exists (may be empty or contain diagnostics) + ok(diagnostics !== undefined, "Diagnostics should not be undefined"); + ok(Array.isArray(diagnostics), "Diagnostics should be an array"); + }); +}); diff --git a/packages/http-client-csharp/emitter/test/Unit/emitter.test.ts b/packages/http-client-csharp/emitter/test/Unit/emitter.test.ts index 7509337482f..7246e73cf2e 100644 --- a/packages/http-client-csharp/emitter/test/Unit/emitter.test.ts +++ b/packages/http-client-csharp/emitter/test/Unit/emitter.test.ts @@ -179,6 +179,59 @@ describe("$onEmit tests", () => { }); }); +describe("createCodeModel tests", () => { + let runner: TestHost; + let program: Program; + + beforeEach(async () => { + vi.restoreAllMocks(); + runner = await createEmitterTestHost(); + }); + + it("should return diagnostics array from createCodeModel", async () => { + program = await typeSpecCompile( + ` + model TestModel { + name: string; + } + + @route("/test") + op test(): TestModel; + `, + runner, + ); + const context = createEmitterContext(program); + const { createCodeModel } = await import("../../src/emitter.js"); + const [, diagnostics] = await createCodeModel(context); + + // Verify that diagnostics is an array + expect(Array.isArray(diagnostics)).toBe(true); + // Diagnostics array should be defined (may be empty or have diagnostics) + expect(diagnostics).toBeDefined(); + }); + + it("should collect diagnostics from createModel in createCodeModel", async () => { + program = await typeSpecCompile( + ` + model TestModel { + name: string; + } + + @route("/test") + op test(): TestModel; + `, + runner, + ); + const context = createEmitterContext(program); + const { createCodeModel } = await import("../../src/emitter.js"); + const [, diagnostics] = await createCodeModel(context); + + // The function should return diagnostics even if empty + expect(diagnostics).toBeDefined(); + expect(Array.isArray(diagnostics)).toBe(true); + }); +}); + describe("Test _validateDotNetSdk", () => { let runner: TestHost; let program: Program; diff --git a/packages/http-client-csharp/emitter/test/Unit/utils/test-util.ts b/packages/http-client-csharp/emitter/test/Unit/utils/test-util.ts index 3b6d60db3ca..8dd90fc57e0 100644 --- a/packages/http-client-csharp/emitter/test/Unit/utils/test-util.ts +++ b/packages/http-client-csharp/emitter/test/Unit/utils/test-util.ts @@ -119,14 +119,25 @@ export function createEmitterContext( /* We always need to pass in the emitter name now that it is required so making a helper to do this. */ export async function createCSharpSdkContext( program: EmitContext, - sdkContextOptions: CreateSdkContextOptions = {}, + sdkContextOptionsOrCollectDiagnostics: CreateSdkContextOptions | boolean = {}, + sdkContextOptions?: CreateSdkContextOptions, ): Promise { + // Handle backward compatibility - if second param is boolean, it's collectDiagnostics + let collectDiagnostics = false; + let options: CreateSdkContextOptions = {}; + + if (typeof sdkContextOptionsOrCollectDiagnostics === "boolean") { + collectDiagnostics = sdkContextOptionsOrCollectDiagnostics; + options = sdkContextOptions ?? {}; + } else { + options = sdkContextOptionsOrCollectDiagnostics; + } + const createSdkContext = await getCreateSdkContext(); - const context = await createSdkContext( - program, - "@typespec/http-client-csharp", - sdkContextOptions, - ); + const context = await createSdkContext(program, "@typespec/http-client-csharp", options); const Logger = await getLogger(); - return createCSharpEmitterContext(context, new Logger(program.program, LoggerLevel.INFO)); + return createCSharpEmitterContext( + context, + new Logger(program.program, LoggerLevel.INFO, collectDiagnostics), + ); } From d368866ec9c29309dd0a7db6b9653be908e6e550 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 20 Jan 2026 07:11:02 +0000 Subject: [PATCH 04/18] Add documentation for new diagnostic collection API Co-authored-by: ArcturusZhang <10554446+ArcturusZhang@users.noreply.github.com> --- packages/http-client-csharp/emitter/src/emitter.ts | 12 ++++++++++++ .../emitter/src/lib/client-model-builder.ts | 13 +++++++++++++ 2 files changed, 25 insertions(+) diff --git a/packages/http-client-csharp/emitter/src/emitter.ts b/packages/http-client-csharp/emitter/src/emitter.ts index 4ab553d2f69..60a26c0ed91 100644 --- a/packages/http-client-csharp/emitter/src/emitter.ts +++ b/packages/http-client-csharp/emitter/src/emitter.ts @@ -51,6 +51,18 @@ function findProjectRoot(path: string): string | undefined { /** * Creates a code model by executing the full emission logic. * This function can be called by downstream emitters to generate a code model and collect diagnostics. + * + * @example + * ```typescript + * import { createCodeModel } from "@typespec/http-client-csharp"; + * + * export async function $onEmit(context: EmitContext) { + * const [, diagnostics] = await createCodeModel(context); + * // Process diagnostics as needed + * context.program.reportDiagnostics(diagnostics); + * } + * ``` + * * @param context - The emit context * @returns A tuple containing void and any diagnostics that were generated during the emission * @beta diff --git a/packages/http-client-csharp/emitter/src/lib/client-model-builder.ts b/packages/http-client-csharp/emitter/src/lib/client-model-builder.ts index 33c88272158..1462b7e9953 100644 --- a/packages/http-client-csharp/emitter/src/lib/client-model-builder.ts +++ b/packages/http-client-csharp/emitter/src/lib/client-model-builder.ts @@ -23,6 +23,19 @@ import { /** * Creates the code model from the SDK context. + * This function follows TypeSpec best practices by returning diagnostics alongside the result. + * + * @example + * ```typescript + * import { createModel } from "@typespec/http-client-csharp"; + * import { Logger } from "@typespec/http-client-csharp/lib/logger"; + * + * const logger = new Logger(program, LoggerLevel.INFO, true); // Enable diagnostic collection + * const sdkContext = createCSharpEmitterContext(context, logger); + * const [codeModel, diagnostics] = createModel(sdkContext); + * // Process the code model and handle diagnostics + * ``` + * * @param sdkContext - The SDK context * @returns A tuple containing the code model and any diagnostics that were generated * @beta From feb41b6030f3273e91d2cd11e4508d406b65c64b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 27 Jan 2026 07:49:28 +0000 Subject: [PATCH 05/18] Refactor: move update-code-model callback from options to createCodeModel parameter Co-authored-by: ArcturusZhang <10554446+ArcturusZhang@users.noreply.github.com> --- .../http-client-csharp/emitter/src/emitter.ts | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/packages/http-client-csharp/emitter/src/emitter.ts b/packages/http-client-csharp/emitter/src/emitter.ts index 60a26c0ed91..53c1bd94b3f 100644 --- a/packages/http-client-csharp/emitter/src/emitter.ts +++ b/packages/http-client-csharp/emitter/src/emitter.ts @@ -26,6 +26,7 @@ import { Logger } from "./lib/logger.js"; import { execAsync, execCSharpGenerator } from "./lib/utils.js"; import { CSharpEmitterOptions, resolveOptions } from "./options.js"; import { createCSharpEmitterContext, CSharpEmitterContext } from "./sdk-context.js"; +import { CodeModel } from "./type/code-model.js"; import { Configuration } from "./type/configuration.js"; /** @@ -57,18 +58,24 @@ function findProjectRoot(path: string): string | undefined { * import { createCodeModel } from "@typespec/http-client-csharp"; * * export async function $onEmit(context: EmitContext) { - * const [, diagnostics] = await createCodeModel(context); + * const updateCodeModel = (model: CodeModel, context: CSharpEmitterContext) => { + * // Customize the code model here + * return model; + * }; + * const [, diagnostics] = await createCodeModel(context, updateCodeModel); * // Process diagnostics as needed * context.program.reportDiagnostics(diagnostics); * } * ``` * * @param context - The emit context + * @param updateCodeModel - Optional callback to modify the code model before emission. Defaults to identity function. * @returns A tuple containing void and any diagnostics that were generated during the emission * @beta */ export async function createCodeModel( context: EmitContext, + updateCodeModel?: (model: CodeModel, context: CSharpEmitterContext) => CodeModel, ): Promise<[void, readonly Diagnostic[]]> { const program: Program = context.program; const options = resolveOptions(context); @@ -95,7 +102,9 @@ export async function createCodeModel( diagnostics.push(...modelDiagnostics); if (root) { - const updatedRoot = options["update-code-model"](root, sdkContext); + // Use the provided callback or default to identity function + const updateCodeModelFn = updateCodeModel ?? ((model: CodeModel) => model); + const updatedRoot = updateCodeModelFn(root, sdkContext); diagnostics.push(...logger.getDiagnostics()); const generatedFolder = resolvePath(outputFolder, "src", "Generated"); @@ -165,7 +174,8 @@ export async function createCodeModel( * @beta */ export async function $onEmit(context: EmitContext) { - const [, diagnostics] = await createCodeModel(context); + const options = resolveOptions(context); + const [, diagnostics] = await createCodeModel(context, options["update-code-model"]); context.program.reportDiagnostics(diagnostics); } From 750acf0eb76a0dedb1abcf8a3e1481e624568410 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 27 Jan 2026 08:45:00 +0000 Subject: [PATCH 06/18] Remove reportDiagnostic from Logger and use diagnostic collection pattern - Removed reportDiagnostic, warn, and error methods from Logger class - Added __diagnostics array to CSharpEmitterContext for collecting diagnostics - Updated all code to use createDiagnostic and push to sdkContext.__diagnostics - Updated createModel to return diagnostics from __diagnostics instead of logger - Updated all tests to report returned diagnostics to program for assertions - Simplified Logger to only handle tracing, removed diagnostic collection mode Co-authored-by: ArcturusZhang <10554446+ArcturusZhang@users.noreply.github.com> --- .../emitter/src/code-model-writer.ts | 11 ++- .../http-client-csharp/emitter/src/emitter.ts | 54 +++++++----- .../emitter/src/lib/client-converter.ts | 13 +-- .../emitter/src/lib/client-model-builder.ts | 2 +- .../emitter/src/lib/logger.ts | 42 +-------- .../emitter/src/lib/operation-converter.ts | 81 +++++++++-------- .../emitter/src/lib/service-authentication.ts | 65 ++++++++------ .../emitter/src/lib/type-converter.ts | 25 +++--- .../emitter/src/sdk-context.ts | 4 +- .../emitter/test/Unit/auth.test.ts | 45 ++++++---- .../test/Unit/client-converter.test.ts | 12 ++- .../test/Unit/client-initialization.test.ts | 12 ++- .../test/Unit/client-model-builder.test.ts | 24 +++-- .../emitter/test/Unit/constant-type.test.ts | 12 ++- .../emitter/test/Unit/decorator-list.test.ts | 15 ++-- .../emitter/test/Unit/emitter.test.ts | 4 + .../emitter/test/Unit/encode.test.ts | 18 ++-- .../emitter/test/Unit/input-parameter.test.ts | 87 ++++++++++++------- .../emitter/test/Unit/model-type.test.ts | 54 ++++++++---- .../test/Unit/operation-converter.test.ts | 21 +++-- .../test/Unit/operation-paging.test.ts | 36 +++++--- .../emitter/test/Unit/property-type.test.ts | 15 ++-- .../emitter/test/Unit/scalar.test.ts | 3 +- .../emitter/test/Unit/string-format.test.ts | 6 +- .../emitter/test/Unit/type-converter.test.ts | 9 +- .../emitter/test/Unit/usage.test.ts | 45 ++++++---- 26 files changed, 438 insertions(+), 277 deletions(-) diff --git a/packages/http-client-csharp/emitter/src/code-model-writer.ts b/packages/http-client-csharp/emitter/src/code-model-writer.ts index 994d0bad2e7..0810ce0ba74 100644 --- a/packages/http-client-csharp/emitter/src/code-model-writer.ts +++ b/packages/http-client-csharp/emitter/src/code-model-writer.ts @@ -2,8 +2,9 @@ // Licensed under the MIT License. See License.txt in the project root for license information. import { UsageFlags } from "@azure-tools/typespec-client-generator-core"; -import { resolvePath } from "@typespec/compiler"; +import { NoTarget, resolvePath } from "@typespec/compiler"; import { configurationFileName, tspOutputFileName } from "./constants.js"; +import { createDiagnostic } from "./lib/lib.js"; import { CSharpEmitterContext } from "./sdk-context.js"; import { CodeModel } from "./type/code-model.js"; import { Configuration } from "./type/configuration.js"; @@ -73,7 +74,13 @@ function buildJson(context: CSharpEmitterContext, codeModel: CodeModel): any { function handleObject(obj: any, id: string | undefined, stack: any[]): any { if (stack.includes(obj)) { // we have a cyclical reference, we should not continue - context.logger.warn(`Cyclical reference detected in the code model (id: ${id}).`); + context.__diagnostics.push( + createDiagnostic({ + code: "general-warning", + format: { message: `Cyclical reference detected in the code model (id: ${id}).` }, + target: NoTarget, + }), + ); return undefined; } diff --git a/packages/http-client-csharp/emitter/src/emitter.ts b/packages/http-client-csharp/emitter/src/emitter.ts index 53c1bd94b3f..6bb32dbf53d 100644 --- a/packages/http-client-csharp/emitter/src/emitter.ts +++ b/packages/http-client-csharp/emitter/src/emitter.ts @@ -21,6 +21,7 @@ import { tspOutputFileName, } from "./constants.js"; import { createModel } from "./lib/client-model-builder.js"; +import { createDiagnostic } from "./lib/lib.js"; import { LoggerLevel } from "./lib/logger-level.js"; import { Logger } from "./lib/logger.js"; import { execAsync, execCSharpGenerator } from "./lib/utils.js"; @@ -105,7 +106,8 @@ export async function createCodeModel( // Use the provided callback or default to identity function const updateCodeModelFn = updateCodeModel ?? ((model: CodeModel) => model); const updatedRoot = updateCodeModelFn(root, sdkContext); - diagnostics.push(...logger.getDiagnostics()); + // Collect any diagnostics added during the callback execution + diagnostics.push(...sdkContext.__diagnostics); const generatedFolder = resolvePath(outputFolder, "src", "Generated"); @@ -228,15 +230,17 @@ export async function _validateDotNetSdk( return validateDotNetSdkVersionCore(sdkContext, result.stdout, minMajorVersion); } catch (error: any) { if (error && "code" in error && error["code"] === "ENOENT") { - sdkContext.logger.reportDiagnostic({ - code: "invalid-dotnet-sdk-dependency", - messageId: "missing", - format: { - dotnetMajorVersion: `${minMajorVersion}`, - downloadUrl: "https://dotnet.microsoft.com/", - }, - target: NoTarget, - }); + sdkContext.__diagnostics.push( + createDiagnostic({ + code: "invalid-dotnet-sdk-dependency", + messageId: "missing", + format: { + dotnetMajorVersion: `${minMajorVersion}`, + downloadUrl: "https://dotnet.microsoft.com/", + }, + target: NoTarget, + }), + ); } return false; } @@ -256,21 +260,29 @@ function validateDotNetSdkVersionCore( return false; } if (major < minMajorVersion) { - sdkContext.logger.reportDiagnostic({ - code: "invalid-dotnet-sdk-dependency", - messageId: "invalidVersion", - format: { - installedVersion: version, - dotnetMajorVersion: `${minMajorVersion}`, - downloadUrl: "https://dotnet.microsoft.com/", - }, - target: NoTarget, - }); + sdkContext.__diagnostics.push( + createDiagnostic({ + code: "invalid-dotnet-sdk-dependency", + messageId: "invalidVersion", + format: { + installedVersion: version, + dotnetMajorVersion: `${minMajorVersion}`, + downloadUrl: "https://dotnet.microsoft.com/", + }, + target: NoTarget, + }), + ); return false; } return true; } else { - sdkContext.logger.error("Cannot get the installed .NET SDK version."); + sdkContext.__diagnostics.push( + createDiagnostic({ + code: "general-error", + format: { message: "Cannot get the installed .NET SDK version." }, + target: NoTarget, + }), + ); return false; } } diff --git a/packages/http-client-csharp/emitter/src/lib/client-converter.ts b/packages/http-client-csharp/emitter/src/lib/client-converter.ts index ff75e604ce2..893f7170c6e 100644 --- a/packages/http-client-csharp/emitter/src/lib/client-converter.ts +++ b/packages/http-client-csharp/emitter/src/lib/client-converter.ts @@ -11,6 +11,7 @@ import { } from "@azure-tools/typespec-client-generator-core"; import { NoTarget } from "@typespec/compiler"; import { CSharpEmitterContext } from "../sdk-context.js"; +import { createDiagnostic } from "./lib.js"; import { InputParameterScope } from "../type/input-parameter-scope.js"; import { InputClient, @@ -136,11 +137,13 @@ function fromSdkClient( .replace("http://", "") .split("/")[0]; if (!/^\{\w+\}$/.test(endpointExpr)) { - sdkContext.logger.reportDiagnostic({ - code: "unsupported-endpoint-url", - format: { endpoint: type.serverUrl }, - target: NoTarget, - }); + sdkContext.__diagnostics.push( + createDiagnostic({ + code: "unsupported-endpoint-url", + format: { endpoint: type.serverUrl }, + target: NoTarget, + }), + ); return []; } const endpointVariableName = endpointExpr.substring(1, endpointExpr.length - 1); diff --git a/packages/http-client-csharp/emitter/src/lib/client-model-builder.ts b/packages/http-client-csharp/emitter/src/lib/client-model-builder.ts index 1462b7e9953..36834af643e 100644 --- a/packages/http-client-csharp/emitter/src/lib/client-model-builder.ts +++ b/packages/http-client-csharp/emitter/src/lib/client-model-builder.ts @@ -74,7 +74,7 @@ export function createModel(sdkContext: CSharpEmitterContext): [CodeModel, reado auth: processServiceAuthentication(sdkContext, sdkPackage), }; - return [clientModel, sdkContext.logger.getDiagnostics()]; + return [clientModel, sdkContext.__diagnostics]; } /** diff --git a/packages/http-client-csharp/emitter/src/lib/logger.ts b/packages/http-client-csharp/emitter/src/lib/logger.ts index f99f726c061..f9461e37d83 100644 --- a/packages/http-client-csharp/emitter/src/lib/logger.ts +++ b/packages/http-client-csharp/emitter/src/lib/logger.ts @@ -1,13 +1,8 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. -import { Diagnostic, DiagnosticReport, NoTarget, Program, Tracer } from "@typespec/compiler"; -import { - createDiagnostic, - DiagnosticMessagesMap, - getTracer, - reportDiagnostic as libReportDiagnostic, -} from "./lib.js"; +import { Diagnostic, Program, Tracer } from "@typespec/compiler"; +import { getTracer } from "./lib.js"; import { LoggerLevel } from "./logger-level.js"; /** @@ -18,22 +13,21 @@ export class Logger { private tracer: Tracer; private level: LoggerLevel; private program: Program; - private collectedDiagnostics: Diagnostic[] | undefined; public constructor(program: Program, level: LoggerLevel, collectDiagnostics: boolean = false) { this.tracer = getTracer(program); this.level = level; this.program = program; - this.collectedDiagnostics = collectDiagnostics ? [] : undefined; } /** * Get collected diagnostics. Only available if the logger was created with collectDiagnostics=true. * @returns The collected diagnostics. * @beta + * @deprecated This method is deprecated and will be removed. Use sdkContext.__diagnostics instead. */ public getDiagnostics(): readonly Diagnostic[] { - return this.collectedDiagnostics ?? []; + return []; } trace(level: LoggerLevel, message: string): void { @@ -71,32 +65,4 @@ export class Logger { this.tracer.trace(LoggerLevel.VERBOSE, message); } } - - reportDiagnostic( - diag: DiagnosticReport, - ): void { - if (this.collectedDiagnostics) { - // In collecting mode, store the diagnostic instead of reporting it - this.collectedDiagnostics.push(createDiagnostic(diag)); - } else { - // In normal mode, report the diagnostic directly - libReportDiagnostic(this.program, diag); - } - } - - warn(message: string): void { - this.reportDiagnostic({ - code: "general-warning", - format: { message: message }, - target: NoTarget, - }); - } - - error(message: string): void { - this.reportDiagnostic({ - code: "general-error", - format: { message: message }, - target: NoTarget, - }); - } } diff --git a/packages/http-client-csharp/emitter/src/lib/operation-converter.ts b/packages/http-client-csharp/emitter/src/lib/operation-converter.ts index 566bdb7ba64..8f0cc7e9f6a 100644 --- a/packages/http-client-csharp/emitter/src/lib/operation-converter.ts +++ b/packages/http-client-csharp/emitter/src/lib/operation-converter.ts @@ -29,6 +29,7 @@ import { getDeprecated, isErrorModel, NoTarget } from "@typespec/compiler"; import { HttpStatusCodeRange } from "@typespec/http"; import { getResourceOperation } from "@typespec/rest"; import { CSharpEmitterContext } from "../sdk-context.js"; +import { createDiagnostic } from "./lib.js"; import { collectionFormatToDelimMap } from "../type/collection-format.js"; import { HttpResponseHeader } from "../type/http-response-header.js"; import { InputConstant } from "../type/input-constant.js"; @@ -135,11 +136,13 @@ export function fromSdkServiceMethod( method = lroPagingMethod; break; default: - sdkContext.logger.reportDiagnostic({ - code: "unsupported-service-method", - format: { methodKind: methodKind }, - target: NoTarget, - }); + sdkContext.__diagnostics.push( + createDiagnostic({ + code: "unsupported-service-method", + format: { methodKind: methodKind }, + target: NoTarget, + }), + ); method = undefined; break; } @@ -164,13 +167,15 @@ export function fromSdkServiceMethodOperation( let generateConvenience = shouldGenerateConvenient(sdkContext, method.operation.__raw.operation); if (method.operation.verb === "patch" && generateConvenience) { - sdkContext.logger.reportDiagnostic({ - code: "unsupported-patch-convenience-method", - format: { - methodCrossLanguageDefinitionId: method.crossLanguageDefinitionId, - }, - target: method.__raw ?? NoTarget, - }); + sdkContext.__diagnostics.push( + createDiagnostic({ + code: "unsupported-patch-convenience-method", + format: { + methodCrossLanguageDefinitionId: method.crossLanguageDefinitionId, + }, + target: method.__raw ?? NoTarget, + }), + ); generateConvenience = false; } @@ -268,11 +273,13 @@ function getValueType(sdkContext: CSharpEmitterContext, value: any): SdkBuiltInK case "bigint": return "int64"; default: - sdkContext.logger.reportDiagnostic({ - code: "unsupported-default-value-type", - format: { valueType: typeof value }, - target: NoTarget, - }); + sdkContext.__diagnostics.push( + createDiagnostic({ + code: "unsupported-default-value-type", + format: { valueType: typeof value }, + target: NoTarget, + }), + ); return "unknown"; } } @@ -348,11 +355,13 @@ function fromSdkOperationParameters( const parameters: InputHttpParameter[] = []; for (const p of operation.parameters) { if (p.kind === "cookie") { - sdkContext.logger.reportDiagnostic({ - code: "unsupported-cookie-parameter", - format: { parameterName: p.name, path: operation.path }, - target: NoTarget, - }); + sdkContext.__diagnostics.push( + createDiagnostic({ + code: "unsupported-cookie-parameter", + format: { parameterName: p.name, path: operation.path }, + target: NoTarget, + }), + ); return parameters; } const param = fromParameter(sdkContext, p, rootApiVersions); @@ -395,11 +404,13 @@ export function fromParameter( parameter = fromBodyParameter(sdkContext, p, rootApiVersions); break; default: - sdkContext.logger.reportDiagnostic({ - code: "unsupported-parameter-kind", - format: { parameterKind }, - target: p.__raw ?? NoTarget, - }); + sdkContext.__diagnostics.push( + createDiagnostic({ + code: "unsupported-parameter-kind", + format: { parameterKind }, + target: p.__raw ?? NoTarget, + }), + ); parameter = undefined; break; } @@ -794,13 +805,15 @@ function getResponseLocation( } if (isHttpMetadata(context, p)) { - context.logger.reportDiagnostic({ - code: "unsupported-continuation-location", - format: { - crossLanguageDefinitionId: method.crossLanguageDefinitionId, - }, - target: NoTarget, - }); + context.__diagnostics.push( + createDiagnostic({ + code: "unsupported-continuation-location", + format: { + crossLanguageDefinitionId: method.crossLanguageDefinitionId, + }, + target: NoTarget, + }), + ); return ResponseLocation.None; } diff --git a/packages/http-client-csharp/emitter/src/lib/service-authentication.ts b/packages/http-client-csharp/emitter/src/lib/service-authentication.ts index 728cd30c5f2..3ac777c6587 100644 --- a/packages/http-client-csharp/emitter/src/lib/service-authentication.ts +++ b/packages/http-client-csharp/emitter/src/lib/service-authentication.ts @@ -10,6 +10,7 @@ import { import { NoTarget } from "@typespec/compiler"; import { Oauth2Auth, OAuth2Flow } from "@typespec/http"; import { CSharpEmitterContext } from "../sdk-context.js"; +import { createDiagnostic } from "./lib.js"; import { InputAuth } from "../type/input-auth.js"; import { InputOAuth2Flow } from "../type/input-oauth2-auth.js"; @@ -36,11 +37,13 @@ export function processServiceAuthentication( if (authClientParameter.type.kind === "credential") { const auth = processAuthType(sdkContext, authClientParameter.type); if (!auth && authClientParameter.type.scheme.type !== "noAuth") { - sdkContext.logger.reportDiagnostic({ - code: "unsupported-auth", - messageId: "onlyUnsupportedAuthProvided", - target: authClientParameter.type.__raw ?? NoTarget, - }); + sdkContext.__diagnostics.push( + createDiagnostic({ + code: "unsupported-auth", + messageId: "onlyUnsupportedAuthProvided", + target: authClientParameter.type.__raw ?? NoTarget, + }), + ); return inputAuth; } @@ -64,11 +67,13 @@ export function processServiceAuthentication( } if (!inputAuth?.apiKey && !inputAuth?.oAuth2) { - sdkContext.logger.reportDiagnostic({ - code: "unsupported-auth", - messageId: "onlyUnsupportedAuthProvided", - target: authClientParameter.type.__raw ?? NoTarget, - }); + sdkContext.__diagnostics.push( + createDiagnostic({ + code: "unsupported-auth", + messageId: "onlyUnsupportedAuthProvided", + target: authClientParameter.type.__raw ?? NoTarget, + }), + ); } return inputAuth; @@ -82,13 +87,15 @@ function processAuthType( switch (scheme.type) { case "apiKey": if (scheme.in !== "header") { - sdkContext.logger.reportDiagnostic({ - code: "unsupported-auth", - format: { - message: `Only header is supported for ApiKey authentication. ${scheme.in} is not supported.`, - }, - target: credentialType.__raw ?? NoTarget, - }); + sdkContext.__diagnostics.push( + createDiagnostic({ + code: "unsupported-auth", + format: { + message: `Only header is supported for ApiKey authentication. ${scheme.in} is not supported.`, + }, + target: credentialType.__raw ?? NoTarget, + }), + ); return undefined; } return { apiKey: { name: scheme.name, in: scheme.in } } as InputAuth; @@ -98,11 +105,13 @@ function processAuthType( const schemeOrApiKeyPrefix = scheme.scheme; switch (schemeOrApiKeyPrefix) { case "Basic": - sdkContext.logger.reportDiagnostic({ - code: "unsupported-auth", - format: { message: `${schemeOrApiKeyPrefix} auth method is currently not supported.` }, - target: credentialType.__raw ?? NoTarget, - }); + sdkContext.__diagnostics.push( + createDiagnostic({ + code: "unsupported-auth", + format: { message: `${schemeOrApiKeyPrefix} auth method is currently not supported.` }, + target: credentialType.__raw ?? NoTarget, + }), + ); return undefined; case "Bearer": return { @@ -123,11 +132,13 @@ function processAuthType( } } default: - sdkContext.logger.reportDiagnostic({ - code: "unsupported-auth", - format: { message: `un-supported authentication scheme ${scheme.type}` }, - target: credentialType.__raw ?? NoTarget, - }); + sdkContext.__diagnostics.push( + createDiagnostic({ + code: "unsupported-auth", + format: { message: `un-supported authentication scheme ${scheme.type}` }, + target: credentialType.__raw ?? NoTarget, + }), + ); return undefined; } } diff --git a/packages/http-client-csharp/emitter/src/lib/type-converter.ts b/packages/http-client-csharp/emitter/src/lib/type-converter.ts index f6b31db5578..d9eca44ca99 100644 --- a/packages/http-client-csharp/emitter/src/lib/type-converter.ts +++ b/packages/http-client-csharp/emitter/src/lib/type-converter.ts @@ -21,6 +21,7 @@ import { isHttpMetadata, } from "@azure-tools/typespec-client-generator-core"; import { Model, NoTarget } from "@typespec/compiler"; +import { createDiagnostic } from "./lib.js"; import { CSharpEmitterContext } from "../sdk-context.js"; import { InputArrayType, @@ -130,11 +131,13 @@ export function fromSdkType( retVar = fromSdkDurationType(sdkContext, sdkType); break; case "tuple": - sdkContext.logger.reportDiagnostic({ - code: "unsupported-sdk-type", - format: { sdkType: "tuple" }, - target: sdkType.__raw ?? NoTarget, - }); + sdkContext.__diagnostics.push( + createDiagnostic({ + code: "unsupported-sdk-type", + format: { sdkType: "tuple" }, + target: sdkType.__raw ?? NoTarget, + }), + ); const tupleType: InputPrimitiveType = { kind: "unknown", name: "tuple", @@ -150,11 +153,13 @@ export function fromSdkType( retVar = fromSdkEndpointType(); break; case "credential": - sdkContext.logger.reportDiagnostic({ - code: "unsupported-sdk-type", - format: { sdkType: "credential" }, - target: sdkType.__raw ?? NoTarget, - }); + sdkContext.__diagnostics.push( + createDiagnostic({ + code: "unsupported-sdk-type", + format: { sdkType: "credential" }, + target: sdkType.__raw ?? NoTarget, + }), + ); const credentialType: InputPrimitiveType = { kind: "unknown", name: "credential", diff --git a/packages/http-client-csharp/emitter/src/sdk-context.ts b/packages/http-client-csharp/emitter/src/sdk-context.ts index 1c8d3b03162..cdaae318ee2 100644 --- a/packages/http-client-csharp/emitter/src/sdk-context.ts +++ b/packages/http-client-csharp/emitter/src/sdk-context.ts @@ -14,7 +14,7 @@ import { SdkServiceMethod, SdkType, } from "@azure-tools/typespec-client-generator-core"; -import { Type } from "@typespec/compiler"; +import { Diagnostic, Type } from "@typespec/compiler"; import { Logger } from "./lib/logger.js"; import { CSharpEmitterOptions } from "./options.js"; import { InputOperation } from "./type/input-operation.js"; @@ -37,6 +37,7 @@ import { OperationResponse } from "./type/operation-response.js"; export interface CSharpEmitterContext extends SdkContext { logger: Logger; __typeCache: SdkTypeCache; + __diagnostics: Diagnostic[]; } /** @@ -53,6 +54,7 @@ export function createCSharpEmitterContext< ...context, logger, __typeCache: new SdkTypeCache(), + __diagnostics: [], }; } diff --git a/packages/http-client-csharp/emitter/test/Unit/auth.test.ts b/packages/http-client-csharp/emitter/test/Unit/auth.test.ts index 4aff753d192..f910fb1798e 100644 --- a/packages/http-client-csharp/emitter/test/Unit/auth.test.ts +++ b/packages/http-client-csharp/emitter/test/Unit/auth.test.ts @@ -30,7 +30,8 @@ describe("Test auth", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const diagnostics = context.program.diagnostics; const noAuthDiagnostics = diagnostics.filter( @@ -72,7 +73,8 @@ describe("Test auth", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const diagnostics = context.program.diagnostics; const noAuthDiagnostics = diagnostics.filter( @@ -114,7 +116,8 @@ describe("Test auth", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const diagnostics = context.program.diagnostics; const noAuthDiagnostics = diagnostics.filter( @@ -154,7 +157,8 @@ describe("Test auth", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const diagnostics = context.program.diagnostics; const noAuthDiagnostic = diagnostics.find( @@ -182,7 +186,8 @@ describe("Test auth", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const diagnostics = context.program.diagnostics; const noAuthDiagnostics = diagnostics.filter( @@ -216,7 +221,8 @@ describe("Test auth", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const diagnostics = context.program.diagnostics; const noAuthDiagnostics = diagnostics.filter( @@ -249,7 +255,8 @@ describe("Test auth", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const diagnostics = context.program.diagnostics; // Should have no auth-related diagnostics @@ -290,7 +297,8 @@ describe("Test auth", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); ok(root.auth?.oAuth2); strictEqual(root.auth.oAuth2.flows.length, 1); @@ -322,7 +330,8 @@ describe("Test auth", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); ok(root.auth?.oAuth2); strictEqual(root.auth.oAuth2.flows.length, 1); @@ -352,7 +361,8 @@ describe("Test auth", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); ok(root.auth?.oAuth2); strictEqual(root.auth.oAuth2.flows.length, 1); @@ -389,7 +399,8 @@ describe("Test auth", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); ok(root.auth?.oAuth2); strictEqual(root.auth.oAuth2.flows.length, 2); @@ -426,7 +437,8 @@ describe("Test auth", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); ok(root.auth?.oAuth2); strictEqual(root.auth.oAuth2.flows.length, 1); @@ -454,7 +466,8 @@ describe("Test auth", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); ok(root.auth?.oAuth2); strictEqual(root.auth.oAuth2.flows.length, 1); @@ -482,7 +495,8 @@ describe("Test auth", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); // Should have both OAuth2 and API key auth ok(root.auth?.oAuth2); @@ -513,7 +527,8 @@ describe("Test auth", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); ok(root.auth?.oAuth2); strictEqual(root.auth.oAuth2.flows.length, 1); diff --git a/packages/http-client-csharp/emitter/test/Unit/client-converter.test.ts b/packages/http-client-csharp/emitter/test/Unit/client-converter.test.ts index 3e2546f9626..6b78d8ee905 100644 --- a/packages/http-client-csharp/emitter/test/Unit/client-converter.test.ts +++ b/packages/http-client-csharp/emitter/test/Unit/client-converter.test.ts @@ -28,7 +28,8 @@ describe("isMultiServiceClient", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const client = root.clients[0]; ok(client, "Client should exist"); @@ -81,7 +82,8 @@ describe("isMultiServiceClient", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); strictEqual(root.name, "Service.MultiService", "Root namespace should be Service.MultiService"); const client = root.clients[0]; @@ -147,7 +149,8 @@ describe("isMultiServiceClient", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); strictEqual(root.name, "Service.MultiService", "Root namespace should be Service.MultiService"); const client = root.clients[0]; @@ -219,7 +222,8 @@ describe("isMultiServiceClient", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); strictEqual(root.name, "Service.MultiService", "Root namespace should be Service.MultiService"); const clients = root.clients; diff --git a/packages/http-client-csharp/emitter/test/Unit/client-initialization.test.ts b/packages/http-client-csharp/emitter/test/Unit/client-initialization.test.ts index d9b508b9a16..8d6aedeaa96 100644 --- a/packages/http-client-csharp/emitter/test/Unit/client-initialization.test.ts +++ b/packages/http-client-csharp/emitter/test/Unit/client-initialization.test.ts @@ -34,7 +34,8 @@ describe("ClientInitialization", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const client = root.clients[0]; ok(client, "Client should exist"); @@ -59,7 +60,8 @@ describe("ClientInitialization", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const client = root.clients[0]; // initializedBy field should exist on the client (may be undefined or have a value) @@ -84,7 +86,8 @@ describe("ClientInitialization", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const client = root.clients[0]; ok(client.parameters, "Client should have parameters"); @@ -113,7 +116,8 @@ describe("ClientInitialization", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const client = root.clients[0]; ok("initializedBy" in client, "Parent client should have initializedBy field"); diff --git a/packages/http-client-csharp/emitter/test/Unit/client-model-builder.test.ts b/packages/http-client-csharp/emitter/test/Unit/client-model-builder.test.ts index b3cdb30e8ae..d36ca4ffe3e 100644 --- a/packages/http-client-csharp/emitter/test/Unit/client-model-builder.test.ts +++ b/packages/http-client-csharp/emitter/test/Unit/client-model-builder.test.ts @@ -58,7 +58,8 @@ describe("fixNamingConflicts", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); // Find the real enum const realEnum = root.enums.find( @@ -142,7 +143,8 @@ describe("fixNamingConflicts", () => { namespace: targetNamespace, } as any); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); // Get all ErrorResponse models - fixNamingConflicts should have resolved the conflicts const errorModels = root.models.filter( @@ -194,7 +196,8 @@ describe("parseApiVersions", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); // The root apiVersions should include the version from the Versions enum // which is defined in the default namespace with version "2023-01-01-preview" @@ -228,7 +231,8 @@ describe("parseApiVersions", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); // The root apiVersions should include all versions from the TestVersions enum strictEqual(root.apiVersions.length, 3, "Root apiVersions should have 3 versions"); @@ -247,7 +251,8 @@ describe("parseApiVersions", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); // Single service client should have apiVersions from the @versioned decorator ok(root.apiVersions.length > 0, "Root apiVersions should not be empty for single service"); @@ -299,7 +304,8 @@ describe("parseApiVersions", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); ok(root.apiVersions.length === 0, "Root apiVersions should be empty for multiservice"); @@ -358,7 +364,8 @@ describe("parseApiVersions", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); ok(root.apiVersions.length === 0, "Root apiVersions should be empty for multiservice"); }); @@ -415,7 +422,8 @@ describe("parseApiVersions", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); ok( root.apiVersions.length === 0, diff --git a/packages/http-client-csharp/emitter/test/Unit/constant-type.test.ts b/packages/http-client-csharp/emitter/test/Unit/constant-type.test.ts index 3730f08dbdd..51617d0817c 100644 --- a/packages/http-client-csharp/emitter/test/Unit/constant-type.test.ts +++ b/packages/http-client-csharp/emitter/test/Unit/constant-type.test.ts @@ -33,7 +33,8 @@ describe("Name for constant type", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const testModel = root.models.find((m) => m.name === "TestModel"); ok(testModel); const propertyType = testModel.properties[0].type; @@ -64,7 +65,8 @@ describe("Name for constant type", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const testModel1 = root.models.find((m) => m.name === "TestModel1"); ok(testModel1); const testModel2 = root.models.find((m) => m.name === "TestModel2"); @@ -103,7 +105,8 @@ describe("Constant enum conversion", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const testModel = root.models.find((m) => m.name === "TestModel"); ok(testModel); const propertyType = testModel.properties[0].type; @@ -134,7 +137,8 @@ describe("Constant enum conversion", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const testModel = root.models.find((m) => m.name === "TestModel"); ok(testModel); const propertyType = testModel.properties[0].type; diff --git a/packages/http-client-csharp/emitter/test/Unit/decorator-list.test.ts b/packages/http-client-csharp/emitter/test/Unit/decorator-list.test.ts index 53df8aa6e86..a1dd9175862 100644 --- a/packages/http-client-csharp/emitter/test/Unit/decorator-list.test.ts +++ b/packages/http-client-csharp/emitter/test/Unit/decorator-list.test.ts @@ -34,7 +34,8 @@ describe("Test emitting decorator list", () => { const sdkContext = await createCSharpSdkContext(context, { additionalDecorators: ["Azure\\.ClientGenerator\\.Core\\.@clientName"], }); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const clients = root.clients; strictEqual(clients.length, 1); ok(clients[0].children); @@ -66,7 +67,8 @@ describe("Test emitting decorator list", () => { const sdkContext = await createCSharpSdkContext(context, { additionalDecorators: ["Azure\\.ClientGenerator\\.Core\\.@clientName"], }); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const methods = root.clients[0].methods; strictEqual(methods.length, 1); const operation = methods[0].operation; @@ -97,7 +99,8 @@ describe("Test emitting decorator list", () => { const sdkContext = await createCSharpSdkContext(context, { additionalDecorators: ["Azure\\.ClientGenerator\\.Core\\.@clientName"], }); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const models = root.models; strictEqual(models.length, 1); deepStrictEqual(models[0].decorators, [ @@ -127,7 +130,8 @@ describe("Test emitting decorator list", () => { const sdkContext = await createCSharpSdkContext(context, { additionalDecorators: ["Azure\\.ClientGenerator\\.Core\\.@clientName"], }); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const models = root.models; strictEqual(models.length, 1); deepStrictEqual(models[0].properties[0].decorators, [ @@ -153,7 +157,8 @@ describe("Test emitting decorator list", () => { const sdkContext = await createCSharpSdkContext(context, { additionalDecorators: ["Azure\\.ClientGenerator\\.Core\\.@clientName"], }); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const methods = root.clients[0].methods; strictEqual(methods.length, 1); const operation = methods[0].operation; diff --git a/packages/http-client-csharp/emitter/test/Unit/emitter.test.ts b/packages/http-client-csharp/emitter/test/Unit/emitter.test.ts index 7246e73cf2e..fde2a575e65 100644 --- a/packages/http-client-csharp/emitter/test/Unit/emitter.test.ts +++ b/packages/http-client-csharp/emitter/test/Unit/emitter.test.ts @@ -271,6 +271,8 @@ describe("Test _validateDotNetSdk", () => { const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); const result = await _validateDotNetSdk(sdkContext, minVersion); + // Report collected diagnostics to program + program.reportDiagnostics(sdkContext.__diagnostics); expect(result).toBe(false); strictEqual(program.diagnostics.length, 1); strictEqual( @@ -329,6 +331,8 @@ describe("Test _validateDotNetSdk", () => { const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); const result = await _validateDotNetSdk(sdkContext, minVersion); + // Report collected diagnostics to program + program.reportDiagnostics(sdkContext.__diagnostics); expect(result).toBe(false); strictEqual(program.diagnostics.length, 1); strictEqual( diff --git a/packages/http-client-csharp/emitter/test/Unit/encode.test.ts b/packages/http-client-csharp/emitter/test/Unit/encode.test.ts index 6344bd3f4ca..767fa13177f 100644 --- a/packages/http-client-csharp/emitter/test/Unit/encode.test.ts +++ b/packages/http-client-csharp/emitter/test/Unit/encode.test.ts @@ -32,7 +32,8 @@ describe("Test encode duration", () => { // validate method parameter const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const methodParamArray = root.clients[0].methods[0].parameters; strictEqual(1, methodParamArray.length); let type = methodParamArray[0].type; @@ -74,7 +75,8 @@ describe("Test encode duration", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); // validate method parameter const methodParamArray = root.clients[0].methods[0].parameters; strictEqual(1, methodParamArray.length); @@ -117,7 +119,8 @@ describe("Test encode duration", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); // validate method parameter const methodParamArray = root.clients[0].methods[0].parameters; strictEqual(1, methodParamArray.length); @@ -161,7 +164,8 @@ describe("Test encode duration", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [codeModel] = createModel(sdkContext); + const [codeModel, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const models = codeModel.models; const durationModel = models.find((m) => m.name === "ISO8601DurationProperty"); ok(durationModel); @@ -190,7 +194,8 @@ describe("Test encode duration", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [codeModel] = createModel(sdkContext); + const [codeModel, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const models = codeModel.models; const durationModel = models.find((m) => m.name === "Int32SecondsDurationProperty"); ok(durationModel); @@ -219,7 +224,8 @@ describe("Test encode duration", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [codeModel] = createModel(sdkContext); + const [codeModel, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const models = codeModel.models; const durationModel = models.find((m) => m.name === "FloatSecondsDurationProperty"); ok(durationModel); diff --git a/packages/http-client-csharp/emitter/test/Unit/input-parameter.test.ts b/packages/http-client-csharp/emitter/test/Unit/input-parameter.test.ts index 1c0977497ef..a4e55ebad61 100644 --- a/packages/http-client-csharp/emitter/test/Unit/input-parameter.test.ts +++ b/packages/http-client-csharp/emitter/test/Unit/input-parameter.test.ts @@ -42,7 +42,8 @@ describe("Test Parameter Explode", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const inputParamArray = root.clients[0].methods[0].operation.parameters.filter( (p) => p.name === "param", ); @@ -72,7 +73,8 @@ describe("Test Parameter Explode", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const inputParamArray = root.clients[0].methods[0].operation.parameters.filter( (p) => p.name === "param", ); @@ -102,7 +104,8 @@ describe("Test Parameter Explode", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const inputParamArray = root.clients[0].methods[0].operation.parameters.filter( (p) => p.name === "param", ); @@ -136,7 +139,8 @@ describe("Test Parameter Explode", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const inputParamArray = root.clients[0].methods[0].operation.parameters.filter( (p) => p.name === "param", ); @@ -166,7 +170,8 @@ describe("Test Parameter Explode", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const inputParamArray = root.clients[0].methods[0].operation.parameters.filter( (p) => p.name === "param", ); @@ -196,7 +201,8 @@ describe("Test Parameter Explode", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const inputParamArray = root.clients[0].methods[0].operation.parameters.filter( (p) => p.name === "param", ); @@ -228,7 +234,8 @@ describe("Test Parameter Explode", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const inputParamArray = root.clients[0].methods[0].operation.parameters.filter( (p) => p.name === "param", ); @@ -258,7 +265,8 @@ describe("Test Parameter Explode", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const inputParamArray = root.clients[0].methods[0].operation.parameters.filter( (p) => p.name === "param", ); @@ -288,7 +296,8 @@ describe("Test Parameter Explode", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const inputParamArray = root.clients[0].methods[0].operation.parameters.filter( (p) => p.name === "param", ); @@ -320,7 +329,8 @@ describe("Test Parameter Explode", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const inputParamArray = root.clients[0].methods[0].operation.parameters.filter( (p) => p.name === "param", ); @@ -350,7 +360,8 @@ describe("Test Parameter Explode", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const inputParamArray = root.clients[0].methods[0].operation.parameters.filter( (p) => p.name === "param", ); @@ -380,7 +391,8 @@ describe("Test Parameter Explode", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const inputParamArray = root.clients[0].methods[0].operation.parameters.filter( (p) => p.name === "param", ); @@ -414,7 +426,8 @@ describe("Test Parameter Explode", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const inputParamArray = root.clients[0].methods[0].operation.parameters.filter( (p) => p.name === "param", ); @@ -445,7 +458,8 @@ describe("Test Parameter Explode", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const inputParamArray = root.clients[0].methods[0].operation.parameters.filter( (p) => p.name === "param", ); @@ -476,7 +490,8 @@ describe("Test Parameter Explode", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const inputParamArray = root.clients[0].methods[0].operation.parameters.filter( (p) => p.name === "param", ); @@ -509,7 +524,8 @@ describe("Test Parameter Explode", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const inputParamArray = root.clients[0].methods[0].operation.parameters.filter( (p) => p.name === "param", ); @@ -540,7 +556,8 @@ describe("Test Parameter Explode", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const inputParamArray = root.clients[0].methods[0].operation.parameters.filter( (p) => p.name === "param", ); @@ -571,7 +588,8 @@ describe("Test Parameter Explode", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const inputParamArray = root.clients[0].methods[0].operation.parameters.filter( (p) => p.name === "param", ); @@ -609,8 +627,9 @@ describe("Test Cookie Parameters", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); + const [, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const diagnostics = context.program.diagnostics; - createModel(sdkContext); const unsupportedCookie = diagnostics.find( (d) => d.code === "@typespec/http-client-csharp/unsupported-cookie-parameter", @@ -653,8 +672,9 @@ describe("Endpoint parameters", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); + const [, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const diagnostics = context.program.diagnostics; - createModel(sdkContext); const unsupportedCookie = diagnostics.find( (d) => d.code === "@typespec/http-client-csharp/unsupported-endpoint-url", @@ -686,7 +706,8 @@ describe("Endpoint parameters", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [codeModel] = createModel(sdkContext); + const [codeModel, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const client = codeModel.clients[0]; ok(client); ok(client.parameters); @@ -722,7 +743,8 @@ describe("Endpoint parameters", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [codeModel] = createModel(sdkContext); + const [codeModel, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const client = codeModel.clients[0]; ok(client); ok(client.parameters); @@ -759,7 +781,8 @@ describe("Test Spread Parameters", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); ok(root); // validate service method @@ -802,7 +825,8 @@ describe("Test Spread Parameters", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); ok(root); // validate service method @@ -855,7 +879,8 @@ describe("Test Operation Parameters", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const operation = root.clients[0].methods[0].operation; const queryParam = operation.parameters.find((p) => p.name === "queryParam"); @@ -881,7 +906,8 @@ describe("Test Operation Parameters", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const operation = root.clients[0].methods[0].operation; const pathParam = operation.parameters.find((p) => p.name === "pathParam"); @@ -910,7 +936,8 @@ describe("Test Operation Parameters", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const operation = root.clients[0].methods[0].operation; const headerParam = operation.parameters.find((p) => p.name === "headerParam"); @@ -940,7 +967,8 @@ describe("Test Operation Parameters", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const operation = root.clients[0].methods[0].operation; const contentTypeParam = operation.parameters.find((p) => p.name === "contentType"); @@ -970,7 +998,8 @@ describe("Test Operation Parameters", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const operation = root.clients[0].methods[0].operation; const bodyParam = operation.parameters.find((p) => p.name === "bodyParam"); diff --git a/packages/http-client-csharp/emitter/test/Unit/model-type.test.ts b/packages/http-client-csharp/emitter/test/Unit/model-type.test.ts index 556cda60305..fdab7a03cdc 100644 --- a/packages/http-client-csharp/emitter/test/Unit/model-type.test.ts +++ b/packages/http-client-csharp/emitter/test/Unit/model-type.test.ts @@ -51,7 +51,8 @@ op test(@body input: Pet): Pet; ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const models = root.models; const petModel = models.find((m) => m.name === "Pet"); const catModel = models.find((m) => m.name === "Cat"); @@ -135,7 +136,8 @@ op test(@body input: Pet): Pet; ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [codeModel] = createModel(sdkContext); + const [codeModel, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const models = codeModel.models; const pet = models.find((m) => m.name === "Pet"); assert(pet !== undefined); @@ -229,7 +231,8 @@ op test(@body input: Pet): Pet; ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [codeModel] = createModel(sdkContext); + const [codeModel, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const models = codeModel.models; const pet = models.find((m) => m.name === "Pet"); assert(pet !== undefined); @@ -350,7 +353,8 @@ op op5(@body body: ExtendsFooArray): ExtendsFooArray; ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const models = root.models; const extendsUnknownModel = models.find((m) => m.name === "ExtendsUnknown"); const extendsStringModel = models.find((m) => m.name === "ExtendsString"); @@ -442,7 +446,8 @@ op op5(@body body: IsFooArray): IsFooArray; ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const models = root.models; const isUnknownModel = models.find((m) => m.name === "IsUnknown"); const isStringModel = models.find((m) => m.name === "IsString"); @@ -493,7 +498,8 @@ op op1(): void; ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const models = root.models; const isEmptyModel = models.find((m) => m.name === "Empty"); ok(isEmptyModel); @@ -522,7 +528,8 @@ model Foo { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const models = root.models; const model = models.find((m) => m.name === "Foo"); ok(model); @@ -560,7 +567,8 @@ describe("Anonymous models should be included in library", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); ok(root); // validate service method @@ -602,7 +610,8 @@ op testOperation(@bodyRoot body: HeaderModel): void; const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const models = root.models; const isEmptyModel = models.find((m) => m.name === "HeaderModel"); ok(isEmptyModel); @@ -647,7 +656,8 @@ op testOperation(@bodyRoot body: HeaderModel): void; const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const models = root.models; const isEmptyModel = models.find((m) => m.name === "HeaderModel"); ok(isEmptyModel); @@ -680,7 +690,8 @@ op testOperation(@bodyRoot body: HeaderModel): void; const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const models = root.models; const isEmptyModel = models.find((m) => m.name === "HeaderModel"); ok(isEmptyModel); @@ -707,7 +718,8 @@ op testOperation(@bodyRoot body: HeaderModel): void; const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const models = root.models; const isEmptyModel = models.find((m) => m.name === "HeaderModel"); ok(isEmptyModel); @@ -757,7 +769,8 @@ describe("typespec-client-generator-core: general decorators list", () => { const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const models = root.models; strictEqual(models.length, 1); deepStrictEqual(models[0].decorators, [ @@ -799,7 +812,8 @@ describe("Access decorator on enums", () => { const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const enums = root.enums; const colorEnum = enums.find((e) => e.name === "Color"); @@ -830,7 +844,8 @@ describe("Access decorator on enums", () => { const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const enums = root.enums; const colorEnum = enums.find((e) => e.name === "Color"); @@ -860,7 +875,8 @@ describe("Access decorator on enums", () => { const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const enums = root.enums; const statusEnum = enums.find((e) => e.name === "Status"); @@ -898,7 +914,8 @@ describe("Usage decorator on enums", () => { const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const enums = root.enums; const colorEnum = enums.find((e) => e.name === "Color"); @@ -928,7 +945,8 @@ describe("Usage decorator on enums", () => { const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const enums = root.enums; const colorEnum = enums.find((e) => e.name === "Color"); diff --git a/packages/http-client-csharp/emitter/test/Unit/operation-converter.test.ts b/packages/http-client-csharp/emitter/test/Unit/operation-converter.test.ts index 08ac1f48adf..899cb96199b 100644 --- a/packages/http-client-csharp/emitter/test/Unit/operation-converter.test.ts +++ b/packages/http-client-csharp/emitter/test/Unit/operation-converter.test.ts @@ -35,7 +35,8 @@ describe("Operation Converter", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); strictEqual(root.clients.length, 1); strictEqual(root.clients[0].methods.length, 1); @@ -106,7 +107,8 @@ describe("Operation Converter", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); strictEqual(root.clients.length, 1); strictEqual(root.clients[0].methods.length, 1); @@ -176,7 +178,8 @@ describe("Operation Converter", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); strictEqual(root.clients.length, 1); strictEqual(root.clients[0].methods.length, 1); @@ -231,7 +234,8 @@ describe("Operation Converter", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); strictEqual(root.clients.length, 1); strictEqual(root.clients[0].methods.length, 1); @@ -275,7 +279,8 @@ describe("Operation Converter", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); strictEqual(root.clients.length, 1); strictEqual(root.clients[0].methods.length, 1); @@ -314,7 +319,8 @@ describe("Operation Converter", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); strictEqual(root.clients.length, 1); strictEqual(root.clients[0].methods.length, 1); @@ -346,7 +352,8 @@ describe("Operation Converter", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); strictEqual(root.clients.length, 1); strictEqual(root.clients[0].methods.length, 1); diff --git a/packages/http-client-csharp/emitter/test/Unit/operation-paging.test.ts b/packages/http-client-csharp/emitter/test/Unit/operation-paging.test.ts index 37290c74147..7bcf7dc40d1 100644 --- a/packages/http-client-csharp/emitter/test/Unit/operation-paging.test.ts +++ b/packages/http-client-csharp/emitter/test/Unit/operation-paging.test.ts @@ -39,7 +39,8 @@ describe("Next link operations", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const method = root.clients[0].methods[0]; strictEqual(method.kind, "paging"); @@ -93,7 +94,8 @@ describe("Next link operations", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const method = root.clients[0].methods[0]; strictEqual(method.kind, "paging"); @@ -138,7 +140,8 @@ describe("Next link operations", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const method = root.clients[0].methods[0]; strictEqual(method.kind, "paging"); @@ -174,7 +177,8 @@ describe("Next link operations", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const method = root.clients[0].methods[0]; strictEqual(method.kind, "paging"); @@ -208,7 +212,8 @@ describe("Next link operations", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const method = root.clients[0].methods[0]; strictEqual(method.kind, "paging"); @@ -256,7 +261,8 @@ describe("Continuation token operations", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const method = root.clients[0].methods[0]; strictEqual(method.kind, "paging"); @@ -291,7 +297,8 @@ describe("Continuation token operations", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const method = root.clients[0].methods[0]; strictEqual(method.kind, "paging"); @@ -327,7 +334,8 @@ describe("Continuation token operations", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const method = root.clients[0].methods[0]; strictEqual(method.kind, "paging"); @@ -363,7 +371,8 @@ describe("Continuation token operations", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const method = root.clients[0].methods[0]; strictEqual(method.kind, "paging"); @@ -399,7 +408,8 @@ describe("Continuation token operations", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const method = root.clients[0].methods[0]; strictEqual(method.kind, "paging"); @@ -453,7 +463,8 @@ describe("PageSize parameter operations", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const method = root.clients[0].methods[0]; strictEqual(method.kind, "paging"); @@ -491,7 +502,8 @@ describe("PageSize parameter operations", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const method = root.clients[0].methods[0]; strictEqual(method.kind, "paging"); diff --git a/packages/http-client-csharp/emitter/test/Unit/property-type.test.ts b/packages/http-client-csharp/emitter/test/Unit/property-type.test.ts index e3589538bd3..dca2060117f 100644 --- a/packages/http-client-csharp/emitter/test/Unit/property-type.test.ts +++ b/packages/http-client-csharp/emitter/test/Unit/property-type.test.ts @@ -28,7 +28,8 @@ describe("Test GetInputType for array", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const inputParamArray = root.clients[0].methods[0].operation.parameters.filter( (p) => p.name === "input", ); @@ -49,7 +50,8 @@ describe("Test GetInputType for array", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const bodyType = root.clients[0].methods[0].operation.responses[0].bodyType; strictEqual(bodyType?.kind, "array"); strictEqual(bodyType.crossLanguageDefinitionId, "TypeSpec.Array"); @@ -86,7 +88,8 @@ describe("Test GetInputType for enum", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const inputParamArray = root.clients[0].methods[0].operation.parameters.filter( (p) => p.name === "input", ); @@ -130,7 +133,8 @@ describe("Test GetInputType for enum", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const inputParamArray = root.clients[0].methods[0].operation.parameters.filter( (p) => p.name === "input", ); @@ -169,7 +173,8 @@ describe("Test GetInputType for enum", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const inputParamArray = root.clients[0].methods[0].operation.parameters.filter( (p) => p.name === "input", ); diff --git a/packages/http-client-csharp/emitter/test/Unit/scalar.test.ts b/packages/http-client-csharp/emitter/test/Unit/scalar.test.ts index cea35899816..0391012f63e 100644 --- a/packages/http-client-csharp/emitter/test/Unit/scalar.test.ts +++ b/packages/http-client-csharp/emitter/test/Unit/scalar.test.ts @@ -28,7 +28,8 @@ describe("Test GetInputType for scalar", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const inputParamArray = root.clients[0].methods[0].operation.parameters.filter( (p) => p.name === "location", ); diff --git a/packages/http-client-csharp/emitter/test/Unit/string-format.test.ts b/packages/http-client-csharp/emitter/test/Unit/string-format.test.ts index 3eea60ca0d1..62e9d943a60 100644 --- a/packages/http-client-csharp/emitter/test/Unit/string-format.test.ts +++ b/packages/http-client-csharp/emitter/test/Unit/string-format.test.ts @@ -27,7 +27,8 @@ describe("Test string format", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const inputParamArray = root.clients[0].methods[0].operation.parameters.filter( (p) => p.name === "sourceUrl", ); @@ -51,7 +52,8 @@ describe("Test string format", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [codeModel] = createModel(sdkContext); + const [codeModel, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const models = codeModel.models; const foo = models.find((m) => m.name === "Foo"); ok(foo); diff --git a/packages/http-client-csharp/emitter/test/Unit/type-converter.test.ts b/packages/http-client-csharp/emitter/test/Unit/type-converter.test.ts index b6fb88bbdf9..131994a0b99 100644 --- a/packages/http-client-csharp/emitter/test/Unit/type-converter.test.ts +++ b/packages/http-client-csharp/emitter/test/Unit/type-converter.test.ts @@ -69,7 +69,8 @@ describe("Enum value references", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const enumType = root.enums.find((e) => e.name === "TestEnum"); ok(enumType, "TestEnum should exist in the enums list"); strictEqual(enumType.values.length, 3, "TestEnum should have 3 values"); @@ -119,7 +120,8 @@ describe("External types", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const testModel = root.models.find((m) => m.name === "TestModel"); ok(testModel, "TestModel should exist"); @@ -164,7 +166,8 @@ describe("External types", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const testModel = root.models.find((m) => m.name === "TestModel"); ok(testModel, "TestModel should exist"); diff --git a/packages/http-client-csharp/emitter/test/Unit/usage.test.ts b/packages/http-client-csharp/emitter/test/Unit/usage.test.ts index ee0923b0174..77df29eb1cc 100644 --- a/packages/http-client-csharp/emitter/test/Unit/usage.test.ts +++ b/packages/http-client-csharp/emitter/test/Unit/usage.test.ts @@ -33,7 +33,8 @@ describe("Test Usage", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const fooModel = root.models.find((model) => model.name === "Foo"); ok(fooModel); @@ -54,7 +55,8 @@ describe("Test Usage", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const fooModel = root.models.find((model) => model.name === "Foo"); ok(fooModel); @@ -75,7 +77,8 @@ describe("Test Usage", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const fooModel = root.models.find((model) => model.name === "Foo"); ok(fooModel); @@ -97,7 +100,8 @@ describe("Test Usage", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const fooModel = root.models.find((model) => model.name === "Foo"); ok(fooModel); @@ -124,7 +128,8 @@ describe("Test Usage", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const fooModel = root.models.find((model) => model.name === "Foo"); const templateModel = root.models.find((model) => model.name === "TemplateModelFoo"); @@ -154,7 +159,8 @@ describe("Test Usage", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const baseModel = root.models.find((model) => model.name === "BaseModel"); const fooModel = root.models.find((model) => model.name === "Foo"); @@ -191,7 +197,8 @@ describe("Test Usage", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const baseModel = root.models.find((model) => model.name === "BaseModel"); const fooModel = root.models.find((model) => model.name === "Foo"); const propertyModel = root.models.find((model) => model.name === "PropertyModel"); @@ -218,7 +225,8 @@ describe("Test Usage", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const fooAlias = root.models.find((model) => model.name === "TestRequest"); ok(fooAlias); @@ -266,7 +274,8 @@ describe("Test Usage", () => { ); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const fooInfo = root.models.find((model) => model.name === "FooInfo"); const batchCreateFooListItemsRequest = root.models.find( (model) => model.name === "BatchCreateFooListItemsRequest", @@ -312,7 +321,8 @@ describe("Test Usage", () => { const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const fooModel = root.models.find((model) => model.name === "Foo"); ok(fooModel); @@ -371,7 +381,8 @@ describe("Test Usage", () => { const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const baseModel = root.models.find((model) => model.name === "BaseModelWithDiscriminator"); const derivedModel = root.models.find( (model) => model.name === "DerivedModelWithDiscriminatorA", @@ -441,7 +452,8 @@ describe("Test Usage", () => { const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const baseModel = root.models.find((model) => model.name === "BaseModelWithDiscriminator"); const derivedModel = root.models.find( (model) => model.name === "DerivedModelWithDiscriminatorA", @@ -478,7 +490,8 @@ describe("Test Usage", () => { const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const simpleEnumRenamed = root.enums.find((enumType) => enumType.name === "SimpleEnumRenamed"); ok(simpleEnumRenamed); @@ -502,7 +515,8 @@ describe("Test Usage", () => { const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const renamedModel = root.models.find((model) => model.name === "RenamedModel"); ok(renamedModel); @@ -660,7 +674,8 @@ interface LegacyLro { const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const [root] = createModel(sdkContext); + const [root, modelDiagnostics] = createModel(sdkContext); + context.program.reportDiagnostics(modelDiagnostics); const radiologyInsightsInferenceResult = root.models.find( (model) => model.name === "RadiologyInsightsInferenceResult", ); From 4ba9cd266077d960a528f8f5035963f22fdb1c42 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 27 Jan 2026 09:07:17 +0000 Subject: [PATCH 07/18] Use createDiagnosticCollector following TypeSpec best practices - Replaced __diagnostics array with DiagnosticCollector from TypeSpec - Updated createModel to use diagnostics.wrap() for returning tuple - Functions now access diagnostics via sdkContext.__diagnostics.add() - Updated tests to set up diagnostic collector when needed - Follows the pattern from TCGC: createDiagnosticCollector at top level Co-authored-by: ArcturusZhang <10554446+ArcturusZhang@users.noreply.github.com> --- .../emitter/src/code-model-writer.ts | 3 ++- .../http-client-csharp/emitter/src/emitter.ts | 12 ++++++++---- .../emitter/src/lib/client-converter.ts | 3 ++- .../emitter/src/lib/client-model-builder.ts | 10 ++++++---- .../emitter/src/lib/operation-converter.ts | 18 ++++++++++++------ .../emitter/src/lib/service-authentication.ts | 18 ++++++++++-------- .../emitter/src/lib/type-converter.ts | 6 ++++-- .../emitter/src/sdk-context.ts | 5 ++--- .../emitter/test/Unit/emitter.test.ts | 10 +++++++--- 9 files changed, 53 insertions(+), 32 deletions(-) diff --git a/packages/http-client-csharp/emitter/src/code-model-writer.ts b/packages/http-client-csharp/emitter/src/code-model-writer.ts index 0810ce0ba74..da6f09f94a4 100644 --- a/packages/http-client-csharp/emitter/src/code-model-writer.ts +++ b/packages/http-client-csharp/emitter/src/code-model-writer.ts @@ -33,6 +33,7 @@ export async function writeCodeModel( * @param codeModel - The code model to build */ function buildJson(context: CSharpEmitterContext, codeModel: CodeModel): any { + const diagnostics = context.__diagnostics!; const objectsIds = new Map(); const stack: any[] = []; @@ -74,7 +75,7 @@ function buildJson(context: CSharpEmitterContext, codeModel: CodeModel): any { function handleObject(obj: any, id: string | undefined, stack: any[]): any { if (stack.includes(obj)) { // we have a cyclical reference, we should not continue - context.__diagnostics.push( + diagnostics.add( createDiagnostic({ code: "general-warning", format: { message: `Cyclical reference detected in the code model (id: ${id}).` }, diff --git a/packages/http-client-csharp/emitter/src/emitter.ts b/packages/http-client-csharp/emitter/src/emitter.ts index 6bb32dbf53d..ece5efff4a9 100644 --- a/packages/http-client-csharp/emitter/src/emitter.ts +++ b/packages/http-client-csharp/emitter/src/emitter.ts @@ -107,7 +107,9 @@ export async function createCodeModel( const updateCodeModelFn = updateCodeModel ?? ((model: CodeModel) => model); const updatedRoot = updateCodeModelFn(root, sdkContext); // Collect any diagnostics added during the callback execution - diagnostics.push(...sdkContext.__diagnostics); + if (sdkContext.__diagnostics) { + diagnostics.push(...sdkContext.__diagnostics.diagnostics); + } const generatedFolder = resolvePath(outputFolder, "src", "Generated"); @@ -225,12 +227,13 @@ export async function _validateDotNetSdk( sdkContext: CSharpEmitterContext, minMajorVersion: number, ): Promise { + const diagnostics = sdkContext.__diagnostics!; try { const result = await execAsync("dotnet", ["--version"], { stdio: "pipe" }); return validateDotNetSdkVersionCore(sdkContext, result.stdout, minMajorVersion); } catch (error: any) { if (error && "code" in error && error["code"] === "ENOENT") { - sdkContext.__diagnostics.push( + diagnostics.add( createDiagnostic({ code: "invalid-dotnet-sdk-dependency", messageId: "missing", @@ -251,6 +254,7 @@ function validateDotNetSdkVersionCore( version: string, minMajorVersion: number, ): boolean { + const diagnostics = sdkContext.__diagnostics!; if (version) { const dotIndex = version.indexOf("."); const firstPart = dotIndex === -1 ? version : version.substring(0, dotIndex); @@ -260,7 +264,7 @@ function validateDotNetSdkVersionCore( return false; } if (major < minMajorVersion) { - sdkContext.__diagnostics.push( + diagnostics.add( createDiagnostic({ code: "invalid-dotnet-sdk-dependency", messageId: "invalidVersion", @@ -276,7 +280,7 @@ function validateDotNetSdkVersionCore( } return true; } else { - sdkContext.__diagnostics.push( + diagnostics.add( createDiagnostic({ code: "general-error", format: { message: "Cannot get the installed .NET SDK version." }, diff --git a/packages/http-client-csharp/emitter/src/lib/client-converter.ts b/packages/http-client-csharp/emitter/src/lib/client-converter.ts index 893f7170c6e..f32e76f5f0d 100644 --- a/packages/http-client-csharp/emitter/src/lib/client-converter.ts +++ b/packages/http-client-csharp/emitter/src/lib/client-converter.ts @@ -131,13 +131,14 @@ function fromSdkClient( } function fromSdkEndpointType(type: SdkEndpointType): InputEndpointParameter[] { + const diagnostics = sdkContext.__diagnostics!; // TODO: support free-style endpoint url with multiple parameters const endpointExpr = type.serverUrl .replace("https://", "") .replace("http://", "") .split("/")[0]; if (!/^\{\w+\}$/.test(endpointExpr)) { - sdkContext.__diagnostics.push( + diagnostics.add( createDiagnostic({ code: "unsupported-endpoint-url", format: { endpoint: type.serverUrl }, diff --git a/packages/http-client-csharp/emitter/src/lib/client-model-builder.ts b/packages/http-client-csharp/emitter/src/lib/client-model-builder.ts index 36834af643e..470b3e82a7d 100644 --- a/packages/http-client-csharp/emitter/src/lib/client-model-builder.ts +++ b/packages/http-client-csharp/emitter/src/lib/client-model-builder.ts @@ -7,7 +7,7 @@ import { SdkHttpOperation, UsageFlags, } from "@azure-tools/typespec-client-generator-core"; -import { Diagnostic } from "@typespec/compiler"; +import { createDiagnosticCollector, Diagnostic } from "@typespec/compiler"; import { CSharpEmitterContext } from "../sdk-context.js"; import { CodeModel } from "../type/code-model.js"; import { InputEnumType, InputLiteralType, InputModelType } from "../type/input-type.js"; @@ -28,9 +28,7 @@ import { * @example * ```typescript * import { createModel } from "@typespec/http-client-csharp"; - * import { Logger } from "@typespec/http-client-csharp/lib/logger"; * - * const logger = new Logger(program, LoggerLevel.INFO, true); // Enable diagnostic collection * const sdkContext = createCSharpEmitterContext(context, logger); * const [codeModel, diagnostics] = createModel(sdkContext); * // Process the code model and handle diagnostics @@ -41,6 +39,10 @@ import { * @beta */ export function createModel(sdkContext: CSharpEmitterContext): [CodeModel, readonly Diagnostic[]] { + const diagnostics = createDiagnosticCollector(); + // Attach diagnostics collector to context for helper functions to use + sdkContext.__diagnostics = diagnostics; + const sdkPackage = sdkContext.sdkPackage; // TO-DO: Consider exposing the namespace hierarchy in the code model https://github.com/microsoft/typespec/issues/8332 @@ -74,7 +76,7 @@ export function createModel(sdkContext: CSharpEmitterContext): [CodeModel, reado auth: processServiceAuthentication(sdkContext, sdkPackage), }; - return [clientModel, sdkContext.__diagnostics]; + return diagnostics.wrap(clientModel); } /** diff --git a/packages/http-client-csharp/emitter/src/lib/operation-converter.ts b/packages/http-client-csharp/emitter/src/lib/operation-converter.ts index 8f0cc7e9f6a..1dda74bae45 100644 --- a/packages/http-client-csharp/emitter/src/lib/operation-converter.ts +++ b/packages/http-client-csharp/emitter/src/lib/operation-converter.ts @@ -73,6 +73,7 @@ export function fromSdkServiceMethod( rootApiVersions: string[], namespace: string, ): InputServiceMethod | undefined { + const diagnostics = sdkContext.__diagnostics!; let method = sdkContext.__typeCache.methods.get(sdkMethod); if (method) { return method; @@ -136,7 +137,7 @@ export function fromSdkServiceMethod( method = lroPagingMethod; break; default: - sdkContext.__diagnostics.push( + diagnostics.add( createDiagnostic({ code: "unsupported-service-method", format: { methodKind: methodKind }, @@ -160,6 +161,7 @@ export function fromSdkServiceMethodOperation( uri: string, rootApiVersions: string[], ): InputOperation { + const diagnostics = sdkContext.__diagnostics!; let operation = sdkContext.__typeCache.operations.get(method.operation); if (operation) { return operation; @@ -167,7 +169,7 @@ export function fromSdkServiceMethodOperation( let generateConvenience = shouldGenerateConvenient(sdkContext, method.operation.__raw.operation); if (method.operation.verb === "patch" && generateConvenience) { - sdkContext.__diagnostics.push( + diagnostics.add( createDiagnostic({ code: "unsupported-patch-convenience-method", format: { @@ -263,6 +265,7 @@ function createServiceMethod( } function getValueType(sdkContext: CSharpEmitterContext, value: any): SdkBuiltInKinds { + const diagnostics = sdkContext.__diagnostics!; switch (typeof value) { case "string": return "string"; @@ -273,7 +276,7 @@ function getValueType(sdkContext: CSharpEmitterContext, value: any): SdkBuiltInK case "bigint": return "int64"; default: - sdkContext.__diagnostics.push( + diagnostics.add( createDiagnostic({ code: "unsupported-default-value-type", format: { valueType: typeof value }, @@ -352,10 +355,11 @@ function fromSdkOperationParameters( operation: SdkHttpOperation, rootApiVersions: string[], ): InputHttpParameter[] { + const diagnostics = sdkContext.__diagnostics!; const parameters: InputHttpParameter[] = []; for (const p of operation.parameters) { if (p.kind === "cookie") { - sdkContext.__diagnostics.push( + diagnostics.add( createDiagnostic({ code: "unsupported-cookie-parameter", format: { parameterName: p.name, path: operation.path }, @@ -384,6 +388,7 @@ export function fromParameter( p: SdkHttpParameter | SdkModelPropertyType, rootApiVersions: string[], ): InputHttpParameter | undefined { + const diagnostics = sdkContext.__diagnostics!; let parameter = sdkContext.__typeCache.operationParameters.get(p); if (parameter) { return parameter; @@ -404,7 +409,7 @@ export function fromParameter( parameter = fromBodyParameter(sdkContext, p, rootApiVersions); break; default: - sdkContext.__diagnostics.push( + diagnostics.add( createDiagnostic({ code: "unsupported-parameter-kind", format: { parameterKind }, @@ -800,12 +805,13 @@ function getResponseLocation( method: SdkPagingServiceMethod | SdkLroPagingServiceMethod, p: SdkServiceResponseHeader | SdkModelPropertyType, ): ResponseLocation { + const diagnostics = context.__diagnostics!; if (p.kind === "responseheader") { return ResponseLocation.Header; } if (isHttpMetadata(context, p)) { - context.__diagnostics.push( + diagnostics.add( createDiagnostic({ code: "unsupported-continuation-location", format: { diff --git a/packages/http-client-csharp/emitter/src/lib/service-authentication.ts b/packages/http-client-csharp/emitter/src/lib/service-authentication.ts index 3ac777c6587..e5d50fbff71 100644 --- a/packages/http-client-csharp/emitter/src/lib/service-authentication.ts +++ b/packages/http-client-csharp/emitter/src/lib/service-authentication.ts @@ -7,7 +7,7 @@ import { SdkHttpOperation, SdkPackage, } from "@azure-tools/typespec-client-generator-core"; -import { NoTarget } from "@typespec/compiler"; +import { createDiagnosticCollector, DiagnosticCollector, NoTarget } from "@typespec/compiler"; import { Oauth2Auth, OAuth2Flow } from "@typespec/http"; import { CSharpEmitterContext } from "../sdk-context.js"; import { createDiagnostic } from "./lib.js"; @@ -18,6 +18,7 @@ export function processServiceAuthentication( sdkContext: CSharpEmitterContext, sdkPackage: SdkPackage, ): InputAuth | undefined { + const diagnostics = sdkContext.__diagnostics!; let authClientParameter: SdkCredentialParameter | undefined = undefined; for (const client of sdkPackage.clients) { for (const parameter of client.clientInitialization.parameters) { @@ -35,9 +36,9 @@ export function processServiceAuthentication( const inputAuth: InputAuth = {}; if (authClientParameter.type.kind === "credential") { - const auth = processAuthType(sdkContext, authClientParameter.type); + const auth = processAuthType(sdkContext, authClientParameter.type, diagnostics); if (!auth && authClientParameter.type.scheme.type !== "noAuth") { - sdkContext.__diagnostics.push( + diagnostics.add( createDiagnostic({ code: "unsupported-auth", messageId: "onlyUnsupportedAuthProvided", @@ -53,7 +54,7 @@ export function processServiceAuthentication( let containsNoAuth = false; for (const authType of authClientParameter.type.variantTypes) { containsNoAuth = containsNoAuth || authType.scheme.type === "noAuth"; - const auth = processAuthType(sdkContext, authType); + const auth = processAuthType(sdkContext, authType, diagnostics); if (auth?.apiKey) { inputAuth.apiKey = auth.apiKey; } @@ -67,7 +68,7 @@ export function processServiceAuthentication( } if (!inputAuth?.apiKey && !inputAuth?.oAuth2) { - sdkContext.__diagnostics.push( + diagnostics.add( createDiagnostic({ code: "unsupported-auth", messageId: "onlyUnsupportedAuthProvided", @@ -82,12 +83,13 @@ export function processServiceAuthentication( function processAuthType( sdkContext: CSharpEmitterContext, credentialType: SdkCredentialType, + diagnostics: DiagnosticCollector, ): InputAuth | undefined { const scheme = credentialType.scheme; switch (scheme.type) { case "apiKey": if (scheme.in !== "header") { - sdkContext.__diagnostics.push( + diagnostics.add( createDiagnostic({ code: "unsupported-auth", format: { @@ -105,7 +107,7 @@ function processAuthType( const schemeOrApiKeyPrefix = scheme.scheme; switch (schemeOrApiKeyPrefix) { case "Basic": - sdkContext.__diagnostics.push( + diagnostics.add( createDiagnostic({ code: "unsupported-auth", format: { message: `${schemeOrApiKeyPrefix} auth method is currently not supported.` }, @@ -132,7 +134,7 @@ function processAuthType( } } default: - sdkContext.__diagnostics.push( + diagnostics.add( createDiagnostic({ code: "unsupported-auth", format: { message: `un-supported authentication scheme ${scheme.type}` }, diff --git a/packages/http-client-csharp/emitter/src/lib/type-converter.ts b/packages/http-client-csharp/emitter/src/lib/type-converter.ts index d9eca44ca99..668c1e32e15 100644 --- a/packages/http-client-csharp/emitter/src/lib/type-converter.ts +++ b/packages/http-client-csharp/emitter/src/lib/type-converter.ts @@ -22,6 +22,7 @@ import { } from "@azure-tools/typespec-client-generator-core"; import { Model, NoTarget } from "@typespec/compiler"; import { createDiagnostic } from "./lib.js"; +import { DiagnosticCollector } from "@typespec/compiler"; import { CSharpEmitterContext } from "../sdk-context.js"; import { InputArrayType, @@ -77,6 +78,7 @@ export function fromSdkType( sdkProperty?: SdkModelPropertyTypeBase, namespace?: string, ): InputReturnType { + const diagnostics = sdkContext.__diagnostics!; let retVar = sdkContext.__typeCache.types.get(sdkType); if (retVar) { return retVar as any; @@ -131,7 +133,7 @@ export function fromSdkType( retVar = fromSdkDurationType(sdkContext, sdkType); break; case "tuple": - sdkContext.__diagnostics.push( + diagnostics.add( createDiagnostic({ code: "unsupported-sdk-type", format: { sdkType: "tuple" }, @@ -153,7 +155,7 @@ export function fromSdkType( retVar = fromSdkEndpointType(); break; case "credential": - sdkContext.__diagnostics.push( + diagnostics.add( createDiagnostic({ code: "unsupported-sdk-type", format: { sdkType: "credential" }, diff --git a/packages/http-client-csharp/emitter/src/sdk-context.ts b/packages/http-client-csharp/emitter/src/sdk-context.ts index cdaae318ee2..c2063dff0b4 100644 --- a/packages/http-client-csharp/emitter/src/sdk-context.ts +++ b/packages/http-client-csharp/emitter/src/sdk-context.ts @@ -14,7 +14,7 @@ import { SdkServiceMethod, SdkType, } from "@azure-tools/typespec-client-generator-core"; -import { Diagnostic, Type } from "@typespec/compiler"; +import { DiagnosticCollector, Type } from "@typespec/compiler"; import { Logger } from "./lib/logger.js"; import { CSharpEmitterOptions } from "./options.js"; import { InputOperation } from "./type/input-operation.js"; @@ -37,7 +37,7 @@ import { OperationResponse } from "./type/operation-response.js"; export interface CSharpEmitterContext extends SdkContext { logger: Logger; __typeCache: SdkTypeCache; - __diagnostics: Diagnostic[]; + __diagnostics?: DiagnosticCollector; } /** @@ -54,7 +54,6 @@ export function createCSharpEmitterContext< ...context, logger, __typeCache: new SdkTypeCache(), - __diagnostics: [], }; } diff --git a/packages/http-client-csharp/emitter/test/Unit/emitter.test.ts b/packages/http-client-csharp/emitter/test/Unit/emitter.test.ts index fde2a575e65..a02ca4e8848 100644 --- a/packages/http-client-csharp/emitter/test/Unit/emitter.test.ts +++ b/packages/http-client-csharp/emitter/test/Unit/emitter.test.ts @@ -1,6 +1,6 @@ vi.resetModules(); -import { EmitContext, Program } from "@typespec/compiler"; +import { createDiagnosticCollector, EmitContext, Program } from "@typespec/compiler"; import { TestHost } from "@typespec/compiler/testing"; import { strictEqual } from "assert"; import { statSync } from "fs"; @@ -270,9 +270,11 @@ describe("Test _validateDotNetSdk", () => { (execAsync as Mock).mockRejectedValueOnce(error); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); + // Set up diagnostic collector for the test + sdkContext.__diagnostics = createDiagnosticCollector(); const result = await _validateDotNetSdk(sdkContext, minVersion); // Report collected diagnostics to program - program.reportDiagnostics(sdkContext.__diagnostics); + program.reportDiagnostics(sdkContext.__diagnostics.diagnostics); expect(result).toBe(false); strictEqual(program.diagnostics.length, 1); strictEqual( @@ -330,9 +332,11 @@ describe("Test _validateDotNetSdk", () => { }); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); + // Set up diagnostic collector for the test + sdkContext.__diagnostics = createDiagnosticCollector(); const result = await _validateDotNetSdk(sdkContext, minVersion); // Report collected diagnostics to program - program.reportDiagnostics(sdkContext.__diagnostics); + program.reportDiagnostics(sdkContext.__diagnostics.diagnostics); expect(result).toBe(false); strictEqual(program.diagnostics.length, 1); strictEqual( From 97af9e88d924340aaeb13a0b46cdee189fc765ad Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 27 Jan 2026 09:11:14 +0000 Subject: [PATCH 08/18] Fix: use void 0 instead of undefined as void cast Co-authored-by: ArcturusZhang <10554446+ArcturusZhang@users.noreply.github.com> --- packages/http-client-csharp/emitter/src/emitter.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/http-client-csharp/emitter/src/emitter.ts b/packages/http-client-csharp/emitter/src/emitter.ts index ece5efff4a9..7c5d78a8575 100644 --- a/packages/http-client-csharp/emitter/src/emitter.ts +++ b/packages/http-client-csharp/emitter/src/emitter.ts @@ -169,7 +169,7 @@ export async function createCodeModel( } } - return [undefined as void, diagnostics]; + return [void 0, diagnostics]; } /** From bde168c39f5dc40d725155a618e974037fd8f64a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 27 Jan 2026 09:41:28 +0000 Subject: [PATCH 09/18] Refactor http-client-csharp emitter to follow TypeSpec diagnostic collection pattern Co-authored-by: ArcturusZhang <10554446+ArcturusZhang@users.noreply.github.com> --- .../emitter/src/code-model-writer.ts | 9 +- .../http-client-csharp/emitter/src/emitter.ts | 28 +++-- .../emitter/src/lib/client-converter.ts | 30 +++-- .../emitter/src/lib/client-model-builder.ts | 19 ++- .../emitter/src/lib/example-converter.ts | 25 ++-- .../emitter/src/lib/namespace-converter.ts | 2 + .../emitter/src/lib/operation-converter.ts | 112 +++++++++++------- .../emitter/src/lib/service-authentication.ts | 16 +-- .../emitter/src/lib/type-converter.ts | 87 ++++++++------ .../emitter/src/lib/typespec-server.ts | 7 +- .../emitter/src/sdk-context.ts | 1 - .../emitter/test/Unit/emitter.test.ts | 16 +-- .../test/Unit/namespace-converter.test.ts | 4 +- 13 files changed, 202 insertions(+), 154 deletions(-) diff --git a/packages/http-client-csharp/emitter/src/code-model-writer.ts b/packages/http-client-csharp/emitter/src/code-model-writer.ts index da6f09f94a4..92366b37161 100644 --- a/packages/http-client-csharp/emitter/src/code-model-writer.ts +++ b/packages/http-client-csharp/emitter/src/code-model-writer.ts @@ -2,7 +2,7 @@ // Licensed under the MIT License. See License.txt in the project root for license information. import { UsageFlags } from "@azure-tools/typespec-client-generator-core"; -import { NoTarget, resolvePath } from "@typespec/compiler"; +import { DiagnosticCollector, NoTarget, resolvePath } from "@typespec/compiler"; import { configurationFileName, tspOutputFileName } from "./constants.js"; import { createDiagnostic } from "./lib/lib.js"; import { CSharpEmitterContext } from "./sdk-context.js"; @@ -20,10 +20,11 @@ export async function writeCodeModel( context: CSharpEmitterContext, codeModel: CodeModel, outputFolder: string, + diagnostics: DiagnosticCollector, ) { await context.program.host.writeFile( resolvePath(outputFolder, tspOutputFileName), - prettierOutput(JSON.stringify(buildJson(context, codeModel), transformJSONProperties, 2)), + prettierOutput(JSON.stringify(buildJson(context, codeModel, diagnostics), transformJSONProperties, 2)), ); } @@ -31,9 +32,9 @@ export async function writeCodeModel( * This function builds a json from code model with refs and ids in it. * @param context - The CSharp emitter context * @param codeModel - The code model to build + * @param diagnostics - The diagnostic collector */ -function buildJson(context: CSharpEmitterContext, codeModel: CodeModel): any { - const diagnostics = context.__diagnostics!; +function buildJson(context: CSharpEmitterContext, codeModel: CodeModel, diagnostics: DiagnosticCollector): any { const objectsIds = new Map(); const stack: any[] = []; diff --git a/packages/http-client-csharp/emitter/src/emitter.ts b/packages/http-client-csharp/emitter/src/emitter.ts index 7c5d78a8575..ced465fd2ec 100644 --- a/packages/http-client-csharp/emitter/src/emitter.ts +++ b/packages/http-client-csharp/emitter/src/emitter.ts @@ -3,7 +3,9 @@ import { createSdkContext, SdkContext } from "@azure-tools/typespec-client-generator-core"; import { + createDiagnosticCollector, Diagnostic, + DiagnosticCollector, EmitContext, getDirectoryPath, joinPaths, @@ -106,10 +108,6 @@ export async function createCodeModel( // Use the provided callback or default to identity function const updateCodeModelFn = updateCodeModel ?? ((model: CodeModel) => model); const updatedRoot = updateCodeModelFn(root, sdkContext); - // Collect any diagnostics added during the callback execution - if (sdkContext.__diagnostics) { - diagnostics.push(...sdkContext.__diagnostics.diagnostics); - } const generatedFolder = resolvePath(outputFolder, "src", "Generated"); @@ -118,7 +116,9 @@ export async function createCodeModel( } // emit tspCodeModel.json - await writeCodeModel(sdkContext, updatedRoot, outputFolder); + const writeDiagnostics = createDiagnosticCollector(); + await writeCodeModel(sdkContext, updatedRoot, outputFolder, writeDiagnostics); + diagnostics.push(...writeDiagnostics.diagnostics); const namespace = updatedRoot.name; const configurations: Configuration = createConfiguration(options, namespace, sdkContext); @@ -148,7 +148,8 @@ export async function createCodeModel( debug: options.debug ?? false, }); if (result.exitCode !== 0) { - const isValid = await _validateDotNetSdk(sdkContext, _minSupportedDotNetSdkVersion); + const [isValid, validationDiagnostics] = await _validateDotNetSdk(sdkContext, _minSupportedDotNetSdkVersion); + diagnostics.push(...validationDiagnostics); // if the dotnet sdk is valid, the error is not dependency issue, log it as normal if (isValid) { throw new Error( @@ -157,7 +158,8 @@ export async function createCodeModel( } } } catch (error: any) { - const isValid = await _validateDotNetSdk(sdkContext, _minSupportedDotNetSdkVersion); + const [isValid, validationDiagnostics] = await _validateDotNetSdk(sdkContext, _minSupportedDotNetSdkVersion); + diagnostics.push(...validationDiagnostics); // if the dotnet sdk is valid, the error is not dependency issue, log it as normal if (isValid) throw new Error(error); } @@ -220,17 +222,17 @@ export function createConfiguration( * Report diagnostic if dotnet sdk is not installed or its version does not meet prerequisite * @param sdkContext - The SDK context * @param minVersionRequisite - The minimum required major version - * @param logger - The logger + * @returns A tuple containing whether the SDK is valid and any diagnostics * @internal */ export async function _validateDotNetSdk( sdkContext: CSharpEmitterContext, minMajorVersion: number, -): Promise { - const diagnostics = sdkContext.__diagnostics!; +): Promise<[boolean, readonly Diagnostic[]]> { + const diagnostics = createDiagnosticCollector(); try { const result = await execAsync("dotnet", ["--version"], { stdio: "pipe" }); - return validateDotNetSdkVersionCore(sdkContext, result.stdout, minMajorVersion); + return diagnostics.wrap(validateDotNetSdkVersionCore(sdkContext, result.stdout, minMajorVersion, diagnostics)); } catch (error: any) { if (error && "code" in error && error["code"] === "ENOENT") { diagnostics.add( @@ -245,7 +247,7 @@ export async function _validateDotNetSdk( }), ); } - return false; + return diagnostics.wrap(false); } } @@ -253,8 +255,8 @@ function validateDotNetSdkVersionCore( sdkContext: CSharpEmitterContext, version: string, minMajorVersion: number, + diagnostics: DiagnosticCollector, ): boolean { - const diagnostics = sdkContext.__diagnostics!; if (version) { const dotIndex = version.indexOf("."); const firstPart = dotIndex === -1 ? version : version.substring(0, dotIndex); diff --git a/packages/http-client-csharp/emitter/src/lib/client-converter.ts b/packages/http-client-csharp/emitter/src/lib/client-converter.ts index f32e76f5f0d..1e1b2640c11 100644 --- a/packages/http-client-csharp/emitter/src/lib/client-converter.ts +++ b/packages/http-client-csharp/emitter/src/lib/client-converter.ts @@ -9,7 +9,7 @@ import { SdkHttpOperation, SdkMethodParameter, } from "@azure-tools/typespec-client-generator-core"; -import { NoTarget } from "@typespec/compiler"; +import { DiagnosticCollector, NoTarget } from "@typespec/compiler"; import { CSharpEmitterContext } from "../sdk-context.js"; import { createDiagnostic } from "./lib.js"; import { InputParameterScope } from "../type/input-parameter-scope.js"; @@ -33,10 +33,11 @@ export function fromSdkClients( sdkContext: CSharpEmitterContext, clients: SdkClientType[], rootApiVersions: string[], + diagnostics: DiagnosticCollector, ): InputClient[] { const inputClients: InputClient[] = []; for (const client of clients) { - const inputClient = fromSdkClient(sdkContext, client, rootApiVersions); + const inputClient = fromSdkClient(sdkContext, client, rootApiVersions, diagnostics); inputClients.push(inputClient); } @@ -47,6 +48,7 @@ function fromSdkClient( sdkContext: CSharpEmitterContext, client: SdkClientType, rootApiVersions: string[], + diagnostics: DiagnosticCollector, ): InputClient { let inputClient: InputClient | undefined = sdkContext.__typeCache.clients.get(client); if (inputClient) { @@ -62,6 +64,7 @@ function fromSdkClient( sdkContext, client.clientInitialization.parameters, client.namespace, + diagnostics, ); inputClient = { @@ -71,7 +74,7 @@ function fromSdkClient( doc: client.doc, summary: client.summary, methods: client.methods - .map((m) => fromSdkServiceMethod(sdkContext, m, uri, rootApiVersions, client.namespace)) + .map((m) => fromSdkServiceMethod(sdkContext, m, uri, rootApiVersions, client.namespace, diagnostics)) .filter((m) => m !== undefined), parameters: clientParameters, initializedBy: client.clientInitialization.initializedBy, @@ -87,12 +90,12 @@ function fromSdkClient( // fill parent if (client.parent) { - inputClient.parent = fromSdkClient(sdkContext, client.parent, rootApiVersions); + inputClient.parent = fromSdkClient(sdkContext, client.parent, rootApiVersions, diagnostics); } // fill children if (client.children) { inputClient.children = client.children.map((c) => - fromSdkClient(sdkContext, c, rootApiVersions), + fromSdkClient(sdkContext, c, rootApiVersions, diagnostics), ); } @@ -102,17 +105,18 @@ function fromSdkClient( sdkContext: CSharpEmitterContext, parameters: (SdkEndpointParameter | SdkCredentialParameter | SdkMethodParameter)[], namespace: string, + diagnostics: DiagnosticCollector, ): InputParameter[] { const inputParameters: InputParameter[] = []; for (const param of parameters) { if (param.kind === "endpoint") { // Convert endpoint parameters - const endpointParams = fromSdkEndpointParameter(param); + const endpointParams = fromSdkEndpointParameter(param, diagnostics); inputParameters.push(...endpointParams); } else if (param.kind === "method") { // Convert method parameters - const methodParam = fromMethodParameter(sdkContext, param, namespace); + const methodParam = fromMethodParameter(sdkContext, param, namespace, diagnostics); inputParameters.push(methodParam); } // Note: credential parameters are handled separately in service-authentication.ts @@ -122,16 +126,15 @@ function fromSdkClient( return inputParameters; } - function fromSdkEndpointParameter(p: SdkEndpointParameter): InputEndpointParameter[] { + function fromSdkEndpointParameter(p: SdkEndpointParameter, diagnostics: DiagnosticCollector): InputEndpointParameter[] { if (p.type.kind === "union") { - return fromSdkEndpointType(p.type.variantTypes[0]); + return fromSdkEndpointType(p.type.variantTypes[0], diagnostics); } else { - return fromSdkEndpointType(p.type); + return fromSdkEndpointType(p.type, diagnostics); } } - function fromSdkEndpointType(type: SdkEndpointType): InputEndpointParameter[] { - const diagnostics = sdkContext.__diagnostics!; + function fromSdkEndpointType(type: SdkEndpointType, diagnostics: DiagnosticCollector): InputEndpointParameter[] { // TODO: support free-style endpoint url with multiple parameters const endpointExpr = type.serverUrl .replace("https://", "") @@ -159,7 +162,7 @@ function fromSdkClient( crossLanguageDefinitionId: parameter.type.kind === "string" ? "TypeSpec.string" : "TypeSpec.url", } - : fromSdkType(sdkContext, parameter.type); // TODO: consolidate with converter.fromSdkEndpointType + : fromSdkType(sdkContext, parameter.type, diagnostics); // TODO: consolidate with converter.fromSdkEndpointType parameters.push({ kind: "endpoint", name: parameter.name, @@ -175,6 +178,7 @@ function fromSdkClient( sdkContext, parameter.clientDefaultValue, parameterType, + diagnostics, ), serverUrlTemplate: type.serverUrl, skipUrlEncoding: false, diff --git a/packages/http-client-csharp/emitter/src/lib/client-model-builder.ts b/packages/http-client-csharp/emitter/src/lib/client-model-builder.ts index 470b3e82a7d..ba317e411e5 100644 --- a/packages/http-client-csharp/emitter/src/lib/client-model-builder.ts +++ b/packages/http-client-csharp/emitter/src/lib/client-model-builder.ts @@ -7,7 +7,7 @@ import { SdkHttpOperation, UsageFlags, } from "@azure-tools/typespec-client-generator-core"; -import { createDiagnosticCollector, Diagnostic } from "@typespec/compiler"; +import { createDiagnosticCollector, Diagnostic, DiagnosticCollector } from "@typespec/compiler"; import { CSharpEmitterContext } from "../sdk-context.js"; import { CodeModel } from "../type/code-model.js"; import { InputEnumType, InputLiteralType, InputModelType } from "../type/input-type.js"; @@ -40,15 +40,12 @@ import { */ export function createModel(sdkContext: CSharpEmitterContext): [CodeModel, readonly Diagnostic[]] { const diagnostics = createDiagnosticCollector(); - // Attach diagnostics collector to context for helper functions to use - sdkContext.__diagnostics = diagnostics; - const sdkPackage = sdkContext.sdkPackage; // TO-DO: Consider exposing the namespace hierarchy in the code model https://github.com/microsoft/typespec/issues/8332 - fromSdkNamespaces(sdkContext, sdkPackage.namespaces); + fromSdkNamespaces(sdkContext, sdkPackage.namespaces, diagnostics); // TO-DO: Consider using the TCGC model + enum cache once https://github.com/Azure/typespec-azure/issues/3180 is resolved - navigateModels(sdkContext); + navigateModels(sdkContext, diagnostics); const types = Array.from(sdkContext.__typeCache.types.values()); const [models, enums] = [ @@ -58,7 +55,7 @@ export function createModel(sdkContext: CSharpEmitterContext): [CodeModel, reado const rootClients = sdkPackage.clients; const rootApiVersions = parseApiVersions(sdkPackage.enums, rootClients); - const inputClients = fromSdkClients(sdkContext, rootClients, rootApiVersions); + const inputClients = fromSdkClients(sdkContext, rootClients, rootApiVersions, diagnostics); // TODO -- TCGC now does not have constants field in its sdkPackage, they might add it in the future. const constants = Array.from(sdkContext.__typeCache.constants.values()); @@ -73,7 +70,7 @@ export function createModel(sdkContext: CSharpEmitterContext): [CodeModel, reado constants: constants, models: models, clients: inputClients, - auth: processServiceAuthentication(sdkContext, sdkPackage), + auth: diagnostics.pipe(processServiceAuthentication(sdkContext, sdkPackage)), }; return diagnostics.wrap(clientModel); @@ -168,11 +165,11 @@ function fixNamingConflicts(models: InputModelType[], constants: InputLiteralTyp } } -function navigateModels(sdkContext: CSharpEmitterContext) { +function navigateModels(sdkContext: CSharpEmitterContext, diagnostics: DiagnosticCollector) { for (const m of sdkContext.sdkPackage.models) { - fromSdkType(sdkContext, m); + fromSdkType(sdkContext, m, diagnostics); } for (const e of sdkContext.sdkPackage.enums) { - fromSdkType(sdkContext, e); + fromSdkType(sdkContext, e, diagnostics); } } diff --git a/packages/http-client-csharp/emitter/src/lib/example-converter.ts b/packages/http-client-csharp/emitter/src/lib/example-converter.ts index 74b20a8c479..bdd78190b05 100644 --- a/packages/http-client-csharp/emitter/src/lib/example-converter.ts +++ b/packages/http-client-csharp/emitter/src/lib/example-converter.ts @@ -16,6 +16,7 @@ import { SdkUnionExampleValue, SdkUnknownExampleValue, } from "@azure-tools/typespec-client-generator-core"; +import { createDiagnosticCollector, DiagnosticCollector } from "@typespec/compiler"; import { CSharpEmitterContext } from "../sdk-context.js"; import { InputArrayExampleValue, @@ -48,6 +49,10 @@ export function fromSdkHttpExamples( sdkContext: CSharpEmitterContext, examples: SdkHttpOperationExample[], ): InputHttpOperationExample[] { + // Create a diagnostics collector for internal use + // Any errors in examples won't prevent the code model from being generated + const diagnostics = createDiagnosticCollector(); + return examples.map((example) => fromSdkHttpExample(example)); function fromSdkHttpExample(example: SdkHttpOperationExample): InputHttpOperationExample { @@ -76,7 +81,7 @@ export function fromSdkHttpExamples( responseValue: SdkHttpResponseExampleValue, ): OperationResponseExample { return { - response: fromSdkHttpOperationResponse(sdkContext, responseValue.response), + response: fromSdkHttpOperationResponse(sdkContext, responseValue.response, diagnostics), statusCode: responseValue.statusCode, bodyValue: responseValue.bodyValue ? fromSdkExample(responseValue.bodyValue) : undefined, }; @@ -108,7 +113,7 @@ export function fromSdkHttpExamples( function fromSdkStringExample(example: SdkStringExampleValue): InputStringExampleValue { return { kind: "string", - type: fromSdkType(sdkContext, example.type), + type: fromSdkType(sdkContext, example.type, diagnostics), value: example.value, }; } @@ -116,7 +121,7 @@ export function fromSdkHttpExamples( function fromSdkNumberExample(example: SdkNumberExampleValue): InputNumberExampleValue { return { kind: "number", - type: fromSdkType(sdkContext, example.type), + type: fromSdkType(sdkContext, example.type, diagnostics), value: example.value, }; } @@ -124,7 +129,7 @@ export function fromSdkHttpExamples( function fromSdkBooleanExample(example: SdkBooleanExampleValue): InputBooleanExampleValue { return { kind: example.kind, - type: fromSdkType(sdkContext, example.type) as InputPrimitiveType, + type: fromSdkType(sdkContext, example.type, diagnostics) as InputPrimitiveType, value: example.value, }; } @@ -132,7 +137,7 @@ export function fromSdkHttpExamples( function fromSdkUnionExample(example: SdkUnionExampleValue): InputUnionExampleValue { return { kind: example.kind, - type: fromSdkType(sdkContext, example.type) as InputUnionType, + type: fromSdkType(sdkContext, example.type, diagnostics) as InputUnionType, value: example.value, }; } @@ -140,7 +145,7 @@ export function fromSdkHttpExamples( function fromSdkArrayExample(example: SdkArrayExampleValue): InputArrayExampleValue { return { kind: example.kind, - type: fromSdkType(sdkContext, example.type) as InputArrayType, + type: fromSdkType(sdkContext, example.type, diagnostics) as InputArrayType, value: example.value.map((v) => fromSdkExample(v)), }; } @@ -150,7 +155,7 @@ export function fromSdkHttpExamples( ): InputDictionaryExampleValue { return { kind: example.kind, - type: fromSdkType(sdkContext, example.type) as InputDictionaryType, + type: fromSdkType(sdkContext, example.type, diagnostics) as InputDictionaryType, value: fromExampleRecord(example.value), }; } @@ -158,7 +163,7 @@ export function fromSdkHttpExamples( function fromSdkModelExample(example: SdkModelExampleValue): InputModelExampleValue { return { kind: example.kind, - type: fromSdkType(sdkContext, example.type) as InputModelType, + type: fromSdkType(sdkContext, example.type, diagnostics) as InputModelType, value: fromExampleRecord(example.value), additionalPropertiesValue: example.additionalPropertiesValue ? fromExampleRecord(example.additionalPropertiesValue) @@ -169,7 +174,7 @@ export function fromSdkHttpExamples( function fromSdkAnyExample(example: SdkUnknownExampleValue): InputUnknownExampleValue { return { kind: example.kind, - type: fromSdkType(sdkContext, example.type) as InputPrimitiveType, + type: fromSdkType(sdkContext, example.type, diagnostics) as InputPrimitiveType, value: example.value, }; } @@ -177,7 +182,7 @@ export function fromSdkHttpExamples( function fromSdkNullExample(example: SdkNullExampleValue): InputNullExampleValue { return { kind: example.kind, - type: fromSdkType(sdkContext, example.type) as InputNullableType, + type: fromSdkType(sdkContext, example.type, diagnostics) as InputNullableType, value: example.value, }; } diff --git a/packages/http-client-csharp/emitter/src/lib/namespace-converter.ts b/packages/http-client-csharp/emitter/src/lib/namespace-converter.ts index ef3f8f38195..fdb1cc0d0bd 100644 --- a/packages/http-client-csharp/emitter/src/lib/namespace-converter.ts +++ b/packages/http-client-csharp/emitter/src/lib/namespace-converter.ts @@ -1,10 +1,12 @@ import { SdkHttpOperation, SdkNamespace } from "@azure-tools/typespec-client-generator-core"; +import { DiagnosticCollector } from "@typespec/compiler"; import { CSharpEmitterContext } from "../sdk-context.js"; import { InputNamespace } from "../type/input-type.js"; export function fromSdkNamespaces( sdkContext: CSharpEmitterContext, namespaces: SdkNamespace[], + diagnostics: DiagnosticCollector, ): InputNamespace[] { const inputNamespaces: InputNamespace[] = []; for (const namespace of namespaces) { diff --git a/packages/http-client-csharp/emitter/src/lib/operation-converter.ts b/packages/http-client-csharp/emitter/src/lib/operation-converter.ts index 1dda74bae45..3359a17e2eb 100644 --- a/packages/http-client-csharp/emitter/src/lib/operation-converter.ts +++ b/packages/http-client-csharp/emitter/src/lib/operation-converter.ts @@ -25,7 +25,7 @@ import { shouldGenerateConvenient, shouldGenerateProtocol, } from "@azure-tools/typespec-client-generator-core"; -import { getDeprecated, isErrorModel, NoTarget } from "@typespec/compiler"; +import { DiagnosticCollector, getDeprecated, isErrorModel, NoTarget } from "@typespec/compiler"; import { HttpStatusCodeRange } from "@typespec/http"; import { getResourceOperation } from "@typespec/rest"; import { CSharpEmitterContext } from "../sdk-context.js"; @@ -72,8 +72,8 @@ export function fromSdkServiceMethod( uri: string, rootApiVersions: string[], namespace: string, + diagnostics: DiagnosticCollector, ): InputServiceMethod | undefined { - const diagnostics = sdkContext.__diagnostics!; let method = sdkContext.__typeCache.methods.get(sdkMethod); if (method) { return method; @@ -88,6 +88,7 @@ export function fromSdkServiceMethod( uri, rootApiVersions, namespace, + diagnostics, ); break; case "paging": @@ -97,6 +98,7 @@ export function fromSdkServiceMethod( uri, rootApiVersions, namespace, + diagnostics, ); pagingServiceMethod.pagingMetadata = loadPagingServiceMetadata( sdkContext, @@ -104,6 +106,7 @@ export function fromSdkServiceMethod( rootApiVersions, uri, namespace, + diagnostics, ); method = pagingServiceMethod; break; @@ -114,8 +117,9 @@ export function fromSdkServiceMethod( uri, rootApiVersions, namespace, + diagnostics, ); - lroServiceMethod.lroMetadata = loadLongRunningMetadata(sdkContext, sdkMethod); + lroServiceMethod.lroMetadata = loadLongRunningMetadata(sdkContext, sdkMethod, diagnostics); method = lroServiceMethod; break; case "lropaging": @@ -125,14 +129,16 @@ export function fromSdkServiceMethod( uri, rootApiVersions, namespace, + diagnostics, ); - lroPagingMethod.lroMetadata = loadLongRunningMetadata(sdkContext, sdkMethod); + lroPagingMethod.lroMetadata = loadLongRunningMetadata(sdkContext, sdkMethod, diagnostics); lroPagingMethod.pagingMetadata = loadPagingServiceMetadata( sdkContext, sdkMethod, rootApiVersions, uri, namespace, + diagnostics, ); method = lroPagingMethod; break; @@ -160,8 +166,8 @@ export function fromSdkServiceMethodOperation( method: SdkServiceMethod, uri: string, rootApiVersions: string[], + diagnostics: DiagnosticCollector, ): InputOperation { - const diagnostics = sdkContext.__diagnostics!; let operation = sdkContext.__typeCache.operations.get(method.operation); if (operation) { return operation; @@ -191,8 +197,8 @@ export function fromSdkServiceMethodOperation( summary: method.summary, doc: method.doc, accessibility: method.access, - parameters: fromSdkOperationParameters(sdkContext, method.operation, rootApiVersions), - responses: fromSdkHttpOperationResponses(sdkContext, method.operation.responses), + parameters: fromSdkOperationParameters(sdkContext, method.operation, rootApiVersions, diagnostics), + responses: fromSdkHttpOperationResponses(sdkContext, method.operation.responses, diagnostics), httpMethod: parseHttpRequestMethod(method.operation.verb), uri: uri, path: method.operation.path, @@ -217,6 +223,7 @@ export function getParameterDefaultValue( sdkContext: CSharpEmitterContext, clientDefaultValue: any, parameterType: InputType, + diagnostics: DiagnosticCollector, ): InputConstant | undefined { if ( clientDefaultValue === undefined || @@ -226,7 +233,7 @@ export function getParameterDefaultValue( return undefined; } - const kind = getValueType(sdkContext, clientDefaultValue); + const kind = getValueType(sdkContext, clientDefaultValue, diagnostics); return { type: { kind: kind, @@ -243,6 +250,7 @@ function createServiceMethod( uri: string, rootApiVersions: string[], namespace: string, + diagnostics: DiagnosticCollector, ): T { return { kind: method.kind, @@ -251,11 +259,11 @@ function createServiceMethod( apiVersions: method.apiVersions, doc: method.doc, summary: method.summary, - operation: fromSdkServiceMethodOperation(sdkContext, method, uri, rootApiVersions), - parameters: fromSdkServiceMethodParameters(sdkContext, method, rootApiVersions, namespace), - response: fromSdkServiceMethodResponse(sdkContext, method.response), + operation: fromSdkServiceMethodOperation(sdkContext, method, uri, rootApiVersions, diagnostics), + parameters: fromSdkServiceMethodParameters(sdkContext, method, rootApiVersions, namespace, diagnostics), + response: fromSdkServiceMethodResponse(sdkContext, method.response, diagnostics), exception: method.exception - ? fromSdkServiceMethodResponse(sdkContext, method.exception) + ? fromSdkServiceMethodResponse(sdkContext, method.exception, diagnostics) : undefined, isOverride: method.isOverride, generateConvenient: method.generateConvenient, @@ -264,8 +272,7 @@ function createServiceMethod( } as T; } -function getValueType(sdkContext: CSharpEmitterContext, value: any): SdkBuiltInKinds { - const diagnostics = sdkContext.__diagnostics!; +function getValueType(sdkContext: CSharpEmitterContext, value: any, diagnostics: DiagnosticCollector): SdkBuiltInKinds { switch (typeof value) { case "string": return "string"; @@ -292,11 +299,12 @@ function fromSdkServiceMethodParameters( method: SdkServiceMethod, rootApiVersions: string[], namespace: string, + diagnostics: DiagnosticCollector, ): InputMethodParameter[] { const parameters: InputMethodParameter[] = []; for (const p of method.parameters) { - const methodInputParameter = fromMethodParameter(sdkContext, p, namespace); + const methodInputParameter = fromMethodParameter(sdkContext, p, namespace, diagnostics); const operationHttpParameter = getHttpOperationParameter(method, p); if (!operationHttpParameter) { @@ -310,6 +318,7 @@ function fromSdkServiceMethodParameters( methodInputParameter, operationHttpParameter, rootApiVersions, + diagnostics, ); parameters.push(methodInputParameter); } @@ -322,6 +331,7 @@ function updateMethodParameter( methodParameter: InputMethodParameter, operationHttpParameter: SdkHttpParameter | SdkModelPropertyType, rootApiVersions: string[], + diagnostics: DiagnosticCollector, ): void { methodParameter.serializedName = getNameInRequest(operationHttpParameter); methodParameter.location = getParameterLocation(operationHttpParameter); @@ -333,7 +343,7 @@ function updateMethodParameter( if (methodParameter.location === RequestLocation.Body) { // Convert constants to enums if (methodParameter.type.kind === "constant") { - methodParameter.type = fromSdkType(sdkContext, operationHttpParameter.type); + methodParameter.type = fromSdkType(sdkContext, operationHttpParameter.type, diagnostics); } } } @@ -341,9 +351,10 @@ function updateMethodParameter( function fromSdkServiceMethodResponse( sdkContext: CSharpEmitterContext, methodResponse: SdkMethodResponse, + diagnostics: DiagnosticCollector, ): InputServiceMethodResponse { return { - type: getResponseType(sdkContext, methodResponse.type), + type: getResponseType(sdkContext, methodResponse.type, diagnostics), resultSegments: methodResponse.resultSegments?.map((segment) => getResponseSegmentName(segment), ), @@ -354,8 +365,8 @@ function fromSdkOperationParameters( sdkContext: CSharpEmitterContext, operation: SdkHttpOperation, rootApiVersions: string[], + diagnostics: DiagnosticCollector, ): InputHttpParameter[] { - const diagnostics = sdkContext.__diagnostics!; const parameters: InputHttpParameter[] = []; for (const p of operation.parameters) { if (p.kind === "cookie") { @@ -368,14 +379,14 @@ function fromSdkOperationParameters( ); return parameters; } - const param = fromParameter(sdkContext, p, rootApiVersions); + const param = fromParameter(sdkContext, p, rootApiVersions, diagnostics); if (param) { parameters.push(param); } } if (operation.bodyParam) { - const bodyParam = fromParameter(sdkContext, operation.bodyParam, rootApiVersions); + const bodyParam = fromParameter(sdkContext, operation.bodyParam, rootApiVersions, diagnostics); if (bodyParam) { parameters.push(bodyParam); } @@ -387,8 +398,8 @@ export function fromParameter( sdkContext: CSharpEmitterContext, p: SdkHttpParameter | SdkModelPropertyType, rootApiVersions: string[], + diagnostics: DiagnosticCollector, ): InputHttpParameter | undefined { - const diagnostics = sdkContext.__diagnostics!; let parameter = sdkContext.__typeCache.operationParameters.get(p); if (parameter) { return parameter; @@ -397,16 +408,16 @@ export function fromParameter( switch (parameterKind) { case "query": - parameter = fromQueryParameter(sdkContext, p, rootApiVersions); + parameter = fromQueryParameter(sdkContext, p, rootApiVersions, diagnostics); break; case "path": - parameter = fromPathParameter(sdkContext, p, rootApiVersions); + parameter = fromPathParameter(sdkContext, p, rootApiVersions, diagnostics); break; case "header": - parameter = fromHeaderParameter(sdkContext, p, rootApiVersions); + parameter = fromHeaderParameter(sdkContext, p, rootApiVersions, diagnostics); break; case "body": - parameter = fromBodyParameter(sdkContext, p, rootApiVersions); + parameter = fromBodyParameter(sdkContext, p, rootApiVersions, diagnostics); break; default: diagnostics.add( @@ -430,8 +441,9 @@ function fromQueryParameter( sdkContext: CSharpEmitterContext, p: SdkQueryParameter, rootApiVersions: string[], + diagnostics: DiagnosticCollector, ): InputQueryParameter { - const parameterType = fromSdkType(sdkContext, p.type); + const parameterType = fromSdkType(sdkContext, p.type, diagnostics); const retVar: InputQueryParameter = { kind: "query", @@ -442,7 +454,7 @@ function fromQueryParameter( type: parameterType, isApiVersion: p.isApiVersionParam, explode: isExploded(p), - defaultValue: getParameterDefaultValue(sdkContext, p.clientDefaultValue, parameterType), + defaultValue: getParameterDefaultValue(sdkContext, p.clientDefaultValue, parameterType, diagnostics), arraySerializationDelimiter: getArraySerializationDelimiter(p), optional: p.optional, scope: getParameterScope(p, parameterType, rootApiVersions.length > 0), @@ -459,8 +471,9 @@ function fromPathParameter( sdkContext: CSharpEmitterContext, p: SdkPathParameter, rootApiVersions: string[], + diagnostics: DiagnosticCollector, ): InputPathParameter { - const parameterType = fromSdkType(sdkContext, p.type); + const parameterType = fromSdkType(sdkContext, p.type, diagnostics); const retVar: InputPathParameter = { kind: "path", @@ -474,7 +487,7 @@ function fromPathParameter( style: p.style, allowReserved: p.allowReserved, skipUrlEncoding: p.allowReserved, - defaultValue: getParameterDefaultValue(sdkContext, p.clientDefaultValue, parameterType), + defaultValue: getParameterDefaultValue(sdkContext, p.clientDefaultValue, parameterType, diagnostics), optional: p.optional, scope: getParameterScope(p, parameterType, rootApiVersions.length > 0), decorators: p.decorators, @@ -490,8 +503,9 @@ function fromHeaderParameter( sdkContext: CSharpEmitterContext, p: SdkHeaderParameter, rootApiVersions: string[], + diagnostics: DiagnosticCollector, ): InputHeaderParameter { - const parameterType = fromSdkType(sdkContext, p.type); + const parameterType = fromSdkType(sdkContext, p.type, diagnostics); const retVar: InputHeaderParameter = { kind: "header", @@ -503,7 +517,7 @@ function fromHeaderParameter( isApiVersion: p.isApiVersionParam, collectionFormat: p.collectionFormat, arraySerializationDelimiter: getArraySerializationDelimiter(p), - defaultValue: getParameterDefaultValue(sdkContext, p.clientDefaultValue, parameterType), + defaultValue: getParameterDefaultValue(sdkContext, p.clientDefaultValue, parameterType, diagnostics), optional: p.optional, isContentType: isContentType(p), scope: getParameterScope(p, parameterType, rootApiVersions.length > 0), @@ -520,8 +534,9 @@ function fromBodyParameter( sdkContext: CSharpEmitterContext, p: SdkBodyParameter, rootApiVersions: string[], + diagnostics: DiagnosticCollector, ): InputBodyParameter { - const parameterType = fromSdkType(sdkContext, p.type); + const parameterType = fromSdkType(sdkContext, p.type, diagnostics); const retVar: InputBodyParameter = { kind: "body", @@ -548,13 +563,14 @@ export function fromMethodParameter( sdkContext: CSharpEmitterContext, p: SdkMethodParameter, namespace: string, + diagnostics: DiagnosticCollector, ): InputMethodParameter { let retVar = sdkContext.__typeCache.methodParmeters.get(p); if (retVar) { return retVar as InputMethodParameter; } - const parameterType = fromSdkType(sdkContext, p.type, p, namespace); + const parameterType = fromSdkType(sdkContext, p.type, diagnostics, p, namespace); retVar = { kind: "method", @@ -565,7 +581,7 @@ export function fromMethodParameter( type: parameterType, location: RequestLocation.None, isApiVersion: p.isApiVersionParam, - defaultValue: getParameterDefaultValue(sdkContext, p.clientDefaultValue, parameterType), + defaultValue: getParameterDefaultValue(sdkContext, p.clientDefaultValue, parameterType, diagnostics), optional: p.optional, scope: InputParameterScope.Method, crossLanguageDefinitionId: p.crossLanguageDefinitionId, @@ -581,6 +597,7 @@ export function fromMethodParameter( function loadLongRunningMetadata( sdkContext: CSharpEmitterContext, method: SdkLroServiceMethod | SdkLroPagingServiceMethod, + diagnostics: DiagnosticCollector, ): InputLongRunningServiceMetadata { return { finalStateVia: convertLroFinalStateVia(method.lroMetadata.finalStateVia), @@ -590,7 +607,7 @@ function loadLongRunningMetadata( statusCodes: method.operation.verb === "delete" ? [204] : [200], bodyType: method.lroMetadata.finalResponse?.envelopeResult !== undefined - ? fromSdkType(sdkContext, method.lroMetadata.finalResponse.envelopeResult) + ? fromSdkType(sdkContext, method.lroMetadata.finalResponse.envelopeResult, diagnostics) : undefined, } as OperationResponse, resultPath: method.lroMetadata.finalResultPath, @@ -600,10 +617,11 @@ function loadLongRunningMetadata( function fromSdkHttpOperationResponses( sdkContext: CSharpEmitterContext, operationResponses: SdkHttpResponse[], + diagnostics: DiagnosticCollector, ): OperationResponse[] { const responses: OperationResponse[] = []; for (const r of operationResponses) { - responses.push(fromSdkHttpOperationResponse(sdkContext, r)); + responses.push(fromSdkHttpOperationResponse(sdkContext, r, diagnostics)); } return responses; } @@ -611,6 +629,7 @@ function fromSdkHttpOperationResponses( export function fromSdkHttpOperationResponse( sdkContext: CSharpEmitterContext, sdkResponse: SdkHttpResponse, + diagnostics: DiagnosticCollector, ): OperationResponse { let retVar = sdkContext.__typeCache.responses.get(sdkResponse); if (retVar) { @@ -620,8 +639,8 @@ export function fromSdkHttpOperationResponse( const range = sdkResponse.statusCodes; retVar = { statusCodes: toStatusCodesArray(range), - bodyType: getResponseType(sdkContext, sdkResponse.type), - headers: fromSdkServiceResponseHeaders(sdkContext, sdkResponse.headers), + bodyType: getResponseType(sdkContext, sdkResponse.type, diagnostics), + headers: fromSdkServiceResponseHeaders(sdkContext, sdkResponse.headers, diagnostics), isErrorResponse: sdkResponse.type !== undefined && isErrorModel(sdkContext.program, sdkResponse.type.__raw!), contentTypes: sdkResponse.contentTypes, @@ -634,6 +653,7 @@ export function fromSdkHttpOperationResponse( function fromSdkServiceResponseHeaders( sdkContext: CSharpEmitterContext, headers: SdkServiceResponseHeader[], + diagnostics: DiagnosticCollector, ): HttpResponseHeader[] { return headers.map( (h) => @@ -642,7 +662,7 @@ function fromSdkServiceResponseHeaders( nameInResponse: h.serializedName, summary: h.summary, doc: h.doc, - type: fromSdkType(sdkContext, h.type), + type: fromSdkType(sdkContext, h.type, diagnostics), }) as HttpResponseHeader, ); } @@ -696,6 +716,7 @@ function loadPagingServiceMetadata( rootApiVersions: string[], uri: string, namespace: string, + diagnostics: DiagnosticCollector, ): InputPagingServiceMetadata { let nextLink: InputNextLink | undefined; if (method.pagingMetadata.nextLinkSegments) { @@ -707,6 +728,7 @@ function loadPagingServiceMetadata( context, method, method.pagingMetadata.nextLinkSegments[0], + diagnostics, ), }; @@ -717,6 +739,7 @@ function loadPagingServiceMetadata( uri, rootApiVersions, namespace, + diagnostics, ); } @@ -732,7 +755,7 @@ function loadPagingServiceMetadata( ] as SdkModelPropertyType; const operationParameter = getHttpOperationParameter(method, lastParameterSegment); if (operationParameter) { - const parameter = fromParameter(context, operationParameter, rootApiVersions); + const parameter = fromParameter(context, operationParameter, rootApiVersions, diagnostics); if (parameter) { nextLinkReInjectedParameters.push(parameter); } @@ -757,6 +780,7 @@ function loadPagingServiceMetadata( context, getHttpOperationParameter(method, lastParameterSegment)!, rootApiVersions, + diagnostics, ); if (continuationTokenParameter) { continuationToken = { @@ -768,6 +792,7 @@ function loadPagingServiceMetadata( context, method, method.pagingMetadata.continuationTokenResponseSegments?.[0], + diagnostics, ), }; } @@ -804,8 +829,8 @@ function getResponseLocation( context: CSharpEmitterContext, method: SdkPagingServiceMethod | SdkLroPagingServiceMethod, p: SdkServiceResponseHeader | SdkModelPropertyType, + diagnostics: DiagnosticCollector, ): ResponseLocation { - const diagnostics = context.__diagnostics!; if (p.kind === "responseheader") { return ResponseLocation.Header; } @@ -934,6 +959,7 @@ function getArraySerializationDelimiter( function getResponseType( sdkContext: CSharpEmitterContext, type: SdkType | undefined, + diagnostics: DiagnosticCollector, ): InputType | undefined { if (!type) { return undefined; @@ -941,8 +967,8 @@ function getResponseType( // handle anonymous union enum response types by defaulting to the enum value type in the case of if (type.kind === "enum" && type.isUnionAsEnum && type.isGeneratedName) { - return fromSdkType(sdkContext, type.valueType); + return fromSdkType(sdkContext, type.valueType, diagnostics); } - return fromSdkType(sdkContext, type); + return fromSdkType(sdkContext, type, diagnostics); } diff --git a/packages/http-client-csharp/emitter/src/lib/service-authentication.ts b/packages/http-client-csharp/emitter/src/lib/service-authentication.ts index e5d50fbff71..2800e4d1c8f 100644 --- a/packages/http-client-csharp/emitter/src/lib/service-authentication.ts +++ b/packages/http-client-csharp/emitter/src/lib/service-authentication.ts @@ -7,7 +7,7 @@ import { SdkHttpOperation, SdkPackage, } from "@azure-tools/typespec-client-generator-core"; -import { createDiagnosticCollector, DiagnosticCollector, NoTarget } from "@typespec/compiler"; +import { createDiagnosticCollector, Diagnostic, DiagnosticCollector, NoTarget } from "@typespec/compiler"; import { Oauth2Auth, OAuth2Flow } from "@typespec/http"; import { CSharpEmitterContext } from "../sdk-context.js"; import { createDiagnostic } from "./lib.js"; @@ -17,8 +17,8 @@ import { InputOAuth2Flow } from "../type/input-oauth2-auth.js"; export function processServiceAuthentication( sdkContext: CSharpEmitterContext, sdkPackage: SdkPackage, -): InputAuth | undefined { - const diagnostics = sdkContext.__diagnostics!; +): [InputAuth | undefined, readonly Diagnostic[]] { + const diagnostics = createDiagnosticCollector(); let authClientParameter: SdkCredentialParameter | undefined = undefined; for (const client of sdkPackage.clients) { for (const parameter of client.clientInitialization.parameters) { @@ -30,7 +30,7 @@ export function processServiceAuthentication( } if (!authClientParameter) { - return undefined; + return diagnostics.wrap(undefined); } const inputAuth: InputAuth = {}; @@ -46,9 +46,9 @@ export function processServiceAuthentication( }), ); - return inputAuth; + return diagnostics.wrap(inputAuth); } - return auth; + return diagnostics.wrap(auth); } let containsNoAuth = false; @@ -64,7 +64,7 @@ export function processServiceAuthentication( } if (containsNoAuth && !inputAuth.apiKey && !inputAuth.oAuth2) { - return undefined; + return diagnostics.wrap(undefined); } if (!inputAuth?.apiKey && !inputAuth?.oAuth2) { @@ -77,7 +77,7 @@ export function processServiceAuthentication( ); } - return inputAuth; + return diagnostics.wrap(inputAuth); } function processAuthType( diff --git a/packages/http-client-csharp/emitter/src/lib/type-converter.ts b/packages/http-client-csharp/emitter/src/lib/type-converter.ts index 668c1e32e15..0c97f2ae0b5 100644 --- a/packages/http-client-csharp/emitter/src/lib/type-converter.ts +++ b/packages/http-client-csharp/emitter/src/lib/type-converter.ts @@ -75,10 +75,10 @@ type InputReturnType = T extends { kind: "nullable" } export function fromSdkType( sdkContext: CSharpEmitterContext, sdkType: T, + diagnostics: DiagnosticCollector, sdkProperty?: SdkModelPropertyTypeBase, namespace?: string, ): InputReturnType { - const diagnostics = sdkContext.__diagnostics!; let retVar = sdkContext.__typeCache.types.get(sdkType); if (retVar) { return retVar as any; @@ -88,26 +88,26 @@ export function fromSdkType( case "nullable": const nullableType: InputNullableType = { kind: "nullable", - type: fromSdkType(sdkContext, sdkType.type, sdkProperty, namespace), + type: fromSdkType(sdkContext, sdkType.type, diagnostics, sdkProperty, namespace), namespace: sdkType.namespace, external: fromSdkExternalTypeInfo(sdkType), }; retVar = nullableType; break; case "model": - retVar = fromSdkModelType(sdkContext, sdkType); + retVar = fromSdkModelType(sdkContext, sdkType, diagnostics); break; case "enum": - retVar = fromSdkEnumType(sdkContext, sdkType); + retVar = fromSdkEnumType(sdkContext, sdkType, diagnostics); break; case "enumvalue": - retVar = fromSdkEnumValueType(sdkContext, sdkType); + retVar = fromSdkEnumValueType(sdkContext, sdkType, diagnostics); break; case "dict": - retVar = fromSdkDictionaryType(sdkContext, sdkType); + retVar = fromSdkDictionaryType(sdkContext, sdkType, diagnostics); break; case "array": - retVar = fromSdkArrayType(sdkContext, sdkType); + retVar = fromSdkArrayType(sdkContext, sdkType, diagnostics); break; case "constant": if ( @@ -117,20 +117,20 @@ export function fromSdkType( sdkType.valueType.kind !== "boolean" ) { // turn the constant into an extensible enum - retVar = createEnumType(sdkContext, sdkType, namespace!); + retVar = createEnumType(sdkContext, sdkType, namespace!, diagnostics); } else { - retVar = fromSdkConstantType(sdkContext, sdkType); + retVar = fromSdkConstantType(sdkContext, sdkType, diagnostics); } break; case "union": - retVar = fromUnionType(sdkContext, sdkType); + retVar = fromUnionType(sdkContext, sdkType, diagnostics); break; case "utcDateTime": case "offsetDateTime": - retVar = fromSdkDateTimeType(sdkContext, sdkType); + retVar = fromSdkDateTimeType(sdkContext, sdkType, diagnostics); break; case "duration": - retVar = fromSdkDurationType(sdkContext, sdkType); + retVar = fromSdkDurationType(sdkContext, sdkType, diagnostics); break; case "tuple": diagnostics.add( @@ -172,7 +172,7 @@ export function fromSdkType( retVar = credentialType; break; default: - retVar = fromSdkBuiltInType(sdkContext, sdkType); + retVar = fromSdkBuiltInType(sdkContext, sdkType, diagnostics); break; } @@ -184,6 +184,7 @@ export function fromSdkType( function fromSdkModelType( sdkContext: CSharpEmitterContext, modelType: SdkModelType, + diagnostics: DiagnosticCollector, ): InputModelType { // get all unique decorators for the model type from the namespace level and the model level let decorators: DecoratorInfo[] = modelType.decorators; @@ -209,12 +210,12 @@ function fromSdkModelType( sdkContext.__typeCache.updateSdkTypeReferences(modelType, inputModelType); inputModelType.additionalProperties = modelType.additionalProperties - ? fromSdkType(sdkContext, modelType.additionalProperties) + ? fromSdkType(sdkContext, modelType.additionalProperties, diagnostics) : undefined; const properties: InputModelProperty[] = []; for (const property of modelType.properties) { - const ourProperty = fromSdkModelProperty(sdkContext, property, modelType); + const ourProperty = fromSdkModelProperty(sdkContext, property, modelType, diagnostics); if (ourProperty) { properties.push(ourProperty); @@ -222,11 +223,11 @@ function fromSdkModelType( } inputModelType.discriminatorProperty = modelType.discriminatorProperty - ? fromSdkModelProperty(sdkContext, modelType.discriminatorProperty, modelType) + ? fromSdkModelProperty(sdkContext, modelType.discriminatorProperty, modelType, diagnostics) : undefined; inputModelType.baseModel = modelType.baseModel - ? fromSdkType(sdkContext, modelType.baseModel) + ? fromSdkType(sdkContext, modelType.baseModel, diagnostics) : undefined; inputModelType.properties = properties; @@ -235,7 +236,7 @@ function fromSdkModelType( const discriminatedSubtypes: Record = {}; for (const key in modelType.discriminatedSubtypes) { const subtype = modelType.discriminatedSubtypes[key]; - discriminatedSubtypes[key] = fromSdkType(sdkContext, subtype); + discriminatedSubtypes[key] = fromSdkType(sdkContext, subtype, diagnostics); } inputModelType.discriminatedSubtypes = discriminatedSubtypes; } @@ -247,6 +248,7 @@ function fromSdkModelProperty( sdkContext: CSharpEmitterContext, sdkProperty: SdkModelPropertyType, sdkModel: SdkModelType, + diagnostics: DiagnosticCollector, ): InputModelProperty | undefined { // TODO -- this returns undefined because some properties we do not support yet. let property = sdkContext.__typeCache.properties.get(sdkProperty) as @@ -266,7 +268,7 @@ function fromSdkModelProperty( serializedName: serializedName, summary: sdkProperty.summary, doc: sdkProperty.doc, - type: fromSdkType(sdkContext, sdkProperty.type, sdkProperty, sdkModel.namespace), + type: fromSdkType(sdkContext, sdkProperty.type, diagnostics, sdkProperty, sdkModel.namespace), optional: sdkProperty.optional, readOnly: isReadOnly(sdkProperty), discriminator: sdkProperty.discriminator, @@ -285,14 +287,15 @@ function fromSdkModelProperty( return property; } -function fromSdkEnumType(sdkContext: CSharpEmitterContext, enumType: SdkEnumType): InputEnumType { - return createEnumType(sdkContext, enumType, enumType.namespace); +function fromSdkEnumType(sdkContext: CSharpEmitterContext, enumType: SdkEnumType, diagnostics: DiagnosticCollector): InputEnumType { + return createEnumType(sdkContext, enumType, enumType.namespace, diagnostics); } function createEnumType( sdkContext: CSharpEmitterContext, sdkType: SdkConstantType | SdkEnumType, namespace: string, + diagnostics: DiagnosticCollector, ): InputEnumType { const values: InputEnumValueType[] = []; @@ -302,8 +305,8 @@ function createEnumType( crossLanguageDefinitionId: sdkType.kind === "enum" ? sdkType.crossLanguageDefinitionId : "", valueType: sdkType.kind === "enum" - ? (fromSdkType(sdkContext, sdkType.valueType) as InputPrimitiveType) - : fromSdkBuiltInType(sdkContext, sdkType.valueType), + ? (fromSdkType(sdkContext, sdkType.valueType, diagnostics) as InputPrimitiveType) + : fromSdkBuiltInType(sdkContext, sdkType.valueType, diagnostics), values: values, // constantType.access, TODO - constant type now does not have access. TCGC will add it later access: @@ -324,10 +327,10 @@ function createEnumType( if (sdkType.kind === "enum") { for (const v of sdkType.values) { - values.push(createEnumValueType(sdkContext, v, inputEnumType)); + values.push(createEnumValueType(sdkContext, v, inputEnumType, diagnostics)); } } else { - values.push(createEnumValueType(sdkContext, sdkType, inputEnumType)); + values.push(createEnumValueType(sdkContext, sdkType, inputEnumType, diagnostics)); } return inputEnumType; @@ -336,14 +339,15 @@ function createEnumType( function fromSdkDateTimeType( sdkContext: CSharpEmitterContext, dateTimeType: SdkDateTimeType, + diagnostics: DiagnosticCollector, ): InputDateTimeType { return { kind: dateTimeType.kind, name: dateTimeType.name, encode: dateTimeType.encode, - wireType: fromSdkType(sdkContext, dateTimeType.wireType), + wireType: fromSdkType(sdkContext, dateTimeType.wireType, diagnostics), crossLanguageDefinitionId: dateTimeType.crossLanguageDefinitionId, - baseType: dateTimeType.baseType ? fromSdkType(sdkContext, dateTimeType.baseType) : undefined, + baseType: dateTimeType.baseType ? fromSdkType(sdkContext, dateTimeType.baseType, diagnostics) : undefined, decorators: dateTimeType.decorators, external: fromSdkExternalTypeInfo(dateTimeType), }; @@ -352,14 +356,15 @@ function fromSdkDateTimeType( function fromSdkDurationType( sdkContext: CSharpEmitterContext, durationType: SdkDurationType, + diagnostics: DiagnosticCollector, ): InputDurationType { return { kind: durationType.kind, name: durationType.name, encode: durationType.encode, - wireType: fromSdkType(sdkContext, durationType.wireType), + wireType: fromSdkType(sdkContext, durationType.wireType, diagnostics), crossLanguageDefinitionId: durationType.crossLanguageDefinitionId, - baseType: durationType.baseType ? fromSdkType(sdkContext, durationType.baseType) : undefined, + baseType: durationType.baseType ? fromSdkType(sdkContext, durationType.baseType, diagnostics) : undefined, decorators: durationType.decorators, external: fromSdkExternalTypeInfo(durationType), }; @@ -368,22 +373,23 @@ function fromSdkDurationType( function fromSdkBuiltInType( sdkContext: CSharpEmitterContext, builtInType: SdkBuiltInType, + diagnostics: DiagnosticCollector, ): InputPrimitiveType { return { kind: builtInType.kind, name: builtInType.name, encode: builtInType.encode !== builtInType.kind ? builtInType.encode : undefined, crossLanguageDefinitionId: builtInType.crossLanguageDefinitionId, - baseType: builtInType.baseType ? fromSdkType(sdkContext, builtInType.baseType) : undefined, + baseType: builtInType.baseType ? fromSdkType(sdkContext, builtInType.baseType, diagnostics) : undefined, decorators: builtInType.decorators, external: fromSdkExternalTypeInfo(builtInType), }; } -function fromUnionType(sdkContext: CSharpEmitterContext, union: SdkUnionType): InputUnionType { +function fromUnionType(sdkContext: CSharpEmitterContext, union: SdkUnionType, diagnostics: DiagnosticCollector): InputUnionType { const variantTypes: InputType[] = []; for (const value of union.variantTypes) { - const variantType = fromSdkType(sdkContext, value); + const variantType = fromSdkType(sdkContext, value, diagnostics); variantTypes.push(variantType); } @@ -400,6 +406,7 @@ function fromUnionType(sdkContext: CSharpEmitterContext, union: SdkUnionType): I function fromSdkConstantType( sdkContext: CSharpEmitterContext, constantType: SdkConstantType, + diagnostics: DiagnosticCollector, ): InputLiteralType { const literalType = { kind: constantType.kind, @@ -407,7 +414,7 @@ function fromSdkConstantType( namespace: "", // constantType.namespace, TODO - constant type now does not have namespace. TCGC will add it later access: undefined, // constantType.access, TODO - constant type now does not have access. TCGC will add it later usage: UsageFlags.None, // constantType.usage, TODO - constant type now does not have usage. TCGC will add it later - valueType: fromSdkType(sdkContext, constantType.valueType), + valueType: fromSdkType(sdkContext, constantType.valueType, diagnostics), value: constantType.value, decorators: constantType.decorators, }; @@ -420,14 +427,16 @@ function fromSdkConstantType( function fromSdkEnumValueType( sdkContext: CSharpEmitterContext, enumValueType: SdkEnumValueType, + diagnostics: DiagnosticCollector, ): InputEnumValueType { - return createEnumValueType(sdkContext, enumValueType, enumValueType.enumType); + return createEnumValueType(sdkContext, enumValueType, enumValueType.enumType, diagnostics); } function createEnumValueType( sdkContext: CSharpEmitterContext, sdkType: SdkEnumValueType | SdkConstantType, enumType: InputEnumType, + diagnostics: DiagnosticCollector, ): InputEnumValueType { return { kind: "enumvalue", @@ -439,7 +448,7 @@ function createEnumValueType( : sdkType.name, value: typeof sdkType.value === "boolean" ? (sdkType.value ? 1 : 0) : sdkType.value, valueType: - sdkType.kind === "constant" ? sdkType.valueType : fromSdkType(sdkContext, sdkType.valueType), + sdkType.kind === "constant" ? sdkType.valueType : fromSdkType(sdkContext, sdkType.valueType, diagnostics), enumType: enumType, summary: sdkType.summary, doc: sdkType.doc, @@ -450,11 +459,12 @@ function createEnumValueType( function fromSdkDictionaryType( sdkContext: CSharpEmitterContext, dictionaryType: SdkDictionaryType, + diagnostics: DiagnosticCollector, ): InputDictionaryType { return { kind: "dict", - keyType: fromSdkType(sdkContext, dictionaryType.keyType), - valueType: fromSdkType(sdkContext, dictionaryType.valueType), + keyType: fromSdkType(sdkContext, dictionaryType.keyType, diagnostics), + valueType: fromSdkType(sdkContext, dictionaryType.valueType, diagnostics), decorators: dictionaryType.decorators, external: fromSdkExternalTypeInfo(dictionaryType), }; @@ -463,11 +473,12 @@ function fromSdkDictionaryType( function fromSdkArrayType( sdkContext: CSharpEmitterContext, arrayType: SdkArrayType, + diagnostics: DiagnosticCollector, ): InputArrayType { return { kind: "array", name: arrayType.name, - valueType: fromSdkType(sdkContext, arrayType.valueType), + valueType: fromSdkType(sdkContext, arrayType.valueType, diagnostics), crossLanguageDefinitionId: arrayType.crossLanguageDefinitionId, decorators: arrayType.decorators, external: fromSdkExternalTypeInfo(arrayType), diff --git a/packages/http-client-csharp/emitter/src/lib/typespec-server.ts b/packages/http-client-csharp/emitter/src/lib/typespec-server.ts index 77cfe50b341..d5e76b8cb49 100644 --- a/packages/http-client-csharp/emitter/src/lib/typespec-server.ts +++ b/packages/http-client-csharp/emitter/src/lib/typespec-server.ts @@ -2,7 +2,7 @@ // Licensed under the MIT License. See License.txt in the project root for license information. import { getClientType } from "@azure-tools/typespec-client-generator-core"; -import { getDoc, getSummary, Value } from "@typespec/compiler"; +import { createDiagnosticCollector, getDoc, getSummary, Value } from "@typespec/compiler"; import { HttpServer } from "@typespec/http"; import { getExtensions } from "@typespec/openapi"; import { CSharpEmitterContext } from "../sdk-context.js"; @@ -21,6 +21,9 @@ export function resolveServers( sdkContext: CSharpEmitterContext, servers: HttpServer[], ): TypeSpecServer[] { + // Create a diagnostics collector for internal use + const diagnostics = createDiagnosticCollector(); + return servers.map((server) => { const parameters: InputEndpointParameter[] = []; let url: string = server.url; @@ -35,7 +38,7 @@ export function resolveServers( name: "url", crossLanguageDefinitionId: "TypeSpec.url", } - : fromSdkType(sdkContext, getClientType(sdkContext, prop)); + : fromSdkType(sdkContext, getClientType(sdkContext, prop), diagnostics); if (value) { defaultValue = { diff --git a/packages/http-client-csharp/emitter/src/sdk-context.ts b/packages/http-client-csharp/emitter/src/sdk-context.ts index c2063dff0b4..2718ebfe32d 100644 --- a/packages/http-client-csharp/emitter/src/sdk-context.ts +++ b/packages/http-client-csharp/emitter/src/sdk-context.ts @@ -37,7 +37,6 @@ import { OperationResponse } from "./type/operation-response.js"; export interface CSharpEmitterContext extends SdkContext { logger: Logger; __typeCache: SdkTypeCache; - __diagnostics?: DiagnosticCollector; } /** diff --git a/packages/http-client-csharp/emitter/test/Unit/emitter.test.ts b/packages/http-client-csharp/emitter/test/Unit/emitter.test.ts index a02ca4e8848..40e1a78578e 100644 --- a/packages/http-client-csharp/emitter/test/Unit/emitter.test.ts +++ b/packages/http-client-csharp/emitter/test/Unit/emitter.test.ts @@ -1,6 +1,6 @@ vi.resetModules(); -import { createDiagnosticCollector, EmitContext, Program } from "@typespec/compiler"; +import { createDiagnosticCollector, Diagnostic, EmitContext, Program } from "@typespec/compiler"; import { TestHost } from "@typespec/compiler/testing"; import { strictEqual } from "assert"; import { statSync } from "fs"; @@ -236,7 +236,7 @@ describe("Test _validateDotNetSdk", () => { let runner: TestHost; let program: Program; const minVersion = 8; - let _validateDotNetSdk: (arg0: any, arg1: number) => Promise; + let _validateDotNetSdk: (arg0: any, arg1: number) => Promise<[boolean, readonly Diagnostic[]]>; beforeEach(async () => { vi.resetModules(); @@ -270,11 +270,9 @@ describe("Test _validateDotNetSdk", () => { (execAsync as Mock).mockRejectedValueOnce(error); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - // Set up diagnostic collector for the test - sdkContext.__diagnostics = createDiagnosticCollector(); - const result = await _validateDotNetSdk(sdkContext, minVersion); + const [result, diagnostics] = await _validateDotNetSdk(sdkContext, minVersion); // Report collected diagnostics to program - program.reportDiagnostics(sdkContext.__diagnostics.diagnostics); + program.reportDiagnostics(diagnostics); expect(result).toBe(false); strictEqual(program.diagnostics.length, 1); strictEqual( @@ -332,11 +330,9 @@ describe("Test _validateDotNetSdk", () => { }); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - // Set up diagnostic collector for the test - sdkContext.__diagnostics = createDiagnosticCollector(); - const result = await _validateDotNetSdk(sdkContext, minVersion); + const [result, diagnostics] = await _validateDotNetSdk(sdkContext, minVersion); // Report collected diagnostics to program - program.reportDiagnostics(sdkContext.__diagnostics.diagnostics); + program.reportDiagnostics(diagnostics); expect(result).toBe(false); strictEqual(program.diagnostics.length, 1); strictEqual( diff --git a/packages/http-client-csharp/emitter/test/Unit/namespace-converter.test.ts b/packages/http-client-csharp/emitter/test/Unit/namespace-converter.test.ts index 95583fb6fc2..7c85bcfbede 100644 --- a/packages/http-client-csharp/emitter/test/Unit/namespace-converter.test.ts +++ b/packages/http-client-csharp/emitter/test/Unit/namespace-converter.test.ts @@ -1,4 +1,5 @@ import { TestHost } from "@typespec/compiler/testing"; +import { createDiagnosticCollector } from "@typespec/compiler"; import { ok, strictEqual } from "assert"; import { beforeEach, describe, it } from "vitest"; import { fromSdkNamespaces } from "../../src/lib/namespace-converter.js"; @@ -50,7 +51,8 @@ describe("Namespace Converter", () => { const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); const sdkPackage = sdkContext.sdkPackage; - const parsedNamespaces = fromSdkNamespaces(sdkContext, sdkPackage.namespaces); + const diagnostics = createDiagnosticCollector(); + const parsedNamespaces = fromSdkNamespaces(sdkContext, sdkPackage.namespaces, diagnostics); strictEqual(parsedNamespaces.length, 1); From 36617286204df4af8a53ffeb5699efc542a163b1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 27 Jan 2026 09:46:11 +0000 Subject: [PATCH 10/18] Fix remaining tests for diagnostic collection pattern Co-authored-by: ArcturusZhang <10554446+ArcturusZhang@users.noreply.github.com> --- .../http-client-csharp/emitter/test/Unit/emitter.test.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/http-client-csharp/emitter/test/Unit/emitter.test.ts b/packages/http-client-csharp/emitter/test/Unit/emitter.test.ts index 40e1a78578e..56bf635ff2d 100644 --- a/packages/http-client-csharp/emitter/test/Unit/emitter.test.ts +++ b/packages/http-client-csharp/emitter/test/Unit/emitter.test.ts @@ -296,10 +296,10 @@ describe("Test _validateDotNetSdk", () => { }); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const result = await _validateDotNetSdk(sdkContext, minVersion); + const [result, diagnostics] = await _validateDotNetSdk(sdkContext, minVersion); expect(result).toBe(true); /* no diagnostics */ - strictEqual(program.diagnostics.length, 0); + strictEqual(diagnostics.length, 0); }); it("should return true for installed SDK version whose major greaters than min supported version", async () => { @@ -313,10 +313,10 @@ describe("Test _validateDotNetSdk", () => { }); const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); - const result = await _validateDotNetSdk(sdkContext, minVersion); + const [result, diagnostics] = await _validateDotNetSdk(sdkContext, minVersion); expect(result).toBe(true); /* no diagnostics */ - strictEqual(program.diagnostics.length, 0); + strictEqual(diagnostics.length, 0); }); it("should return false and report diagnostic for invalid .NET SDK version", async () => { From 433e28f5b18c254aa1523c952e17637b7150c20f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 28 Jan 2026 06:04:00 +0000 Subject: [PATCH 11/18] Fix diagnostic collection pattern to follow TypeSpec best practices This PR refactors the diagnostic collection pattern throughout the http-client-csharp emitter to follow TypeSpec best practices as recommended by code review feedback. Key Changes: - Each function creates its own DiagnosticCollector at the beginning - Functions return tuples [Result, readonly Diagnostic[]] instead of accepting DiagnosticCollector as a parameter - Callers use diagnostics.pipe() to unwrap and collect diagnostics - Functions return with diagnostics.wrap() at the end Files Modified: - lib/type-converter.ts: 13 functions updated - lib/client-converter.ts: 4 functions updated - lib/operation-converter.ts: 20+ functions updated - lib/namespace-converter.ts: 1 function updated - lib/client-model-builder.ts: Updated to use pipe() - code-model-writer.ts: 2 functions updated - emitter.ts: Updated to handle new signatures - lib/example-converter.ts: Updated to use pipe() - lib/typespec-server.ts: Updated to use pipe() Benefits: - Prevents issues with parallel execution - Each function has clear ownership of its diagnostics - More composable and type-safe - Consistent with TypeSpec ecosystem patterns All tests pass successfully. --- .../emitter/src/code-model-writer.ts | 15 +- .../http-client-csharp/emitter/src/emitter.ts | 5 +- .../emitter/src/lib/client-converter.ts | 60 ++-- .../emitter/src/lib/client-model-builder.ts | 16 +- .../emitter/src/lib/example-converter.ts | 20 +- .../emitter/src/lib/namespace-converter.ts | 8 +- .../emitter/src/lib/operation-converter.ts | 309 +++++++++--------- .../emitter/src/lib/type-converter.ts | 169 +++++----- .../emitter/src/lib/typespec-server.ts | 2 +- .../test/Unit/namespace-converter.test.ts | 3 +- 10 files changed, 311 insertions(+), 296 deletions(-) diff --git a/packages/http-client-csharp/emitter/src/code-model-writer.ts b/packages/http-client-csharp/emitter/src/code-model-writer.ts index 92366b37161..4884276778f 100644 --- a/packages/http-client-csharp/emitter/src/code-model-writer.ts +++ b/packages/http-client-csharp/emitter/src/code-model-writer.ts @@ -2,7 +2,7 @@ // Licensed under the MIT License. See License.txt in the project root for license information. import { UsageFlags } from "@azure-tools/typespec-client-generator-core"; -import { DiagnosticCollector, NoTarget, resolvePath } from "@typespec/compiler"; +import { createDiagnosticCollector, Diagnostic, NoTarget, resolvePath } from "@typespec/compiler"; import { configurationFileName, tspOutputFileName } from "./constants.js"; import { createDiagnostic } from "./lib/lib.js"; import { CSharpEmitterContext } from "./sdk-context.js"; @@ -20,25 +20,26 @@ export async function writeCodeModel( context: CSharpEmitterContext, codeModel: CodeModel, outputFolder: string, - diagnostics: DiagnosticCollector, -) { +): Promise { + const diagnostics = createDiagnosticCollector(); await context.program.host.writeFile( resolvePath(outputFolder, tspOutputFileName), - prettierOutput(JSON.stringify(buildJson(context, codeModel, diagnostics), transformJSONProperties, 2)), + prettierOutput(JSON.stringify(diagnostics.pipe(buildJson(context, codeModel)), transformJSONProperties, 2)), ); + return diagnostics.diagnostics; } /** * This function builds a json from code model with refs and ids in it. * @param context - The CSharp emitter context * @param codeModel - The code model to build - * @param diagnostics - The diagnostic collector */ -function buildJson(context: CSharpEmitterContext, codeModel: CodeModel, diagnostics: DiagnosticCollector): any { +function buildJson(context: CSharpEmitterContext, codeModel: CodeModel): [any, readonly Diagnostic[]] { + const diagnostics = createDiagnosticCollector(); const objectsIds = new Map(); const stack: any[] = []; - return doBuildJson(codeModel, stack); + return diagnostics.wrap(doBuildJson(codeModel, stack)); function doBuildJson(obj: any, stack: any[]): any { // check if this is a primitive type or null or undefined diff --git a/packages/http-client-csharp/emitter/src/emitter.ts b/packages/http-client-csharp/emitter/src/emitter.ts index ced465fd2ec..3f0107f862d 100644 --- a/packages/http-client-csharp/emitter/src/emitter.ts +++ b/packages/http-client-csharp/emitter/src/emitter.ts @@ -116,9 +116,8 @@ export async function createCodeModel( } // emit tspCodeModel.json - const writeDiagnostics = createDiagnosticCollector(); - await writeCodeModel(sdkContext, updatedRoot, outputFolder, writeDiagnostics); - diagnostics.push(...writeDiagnostics.diagnostics); + const writeDiagnostics = await writeCodeModel(sdkContext, updatedRoot, outputFolder); + diagnostics.push(...writeDiagnostics); const namespace = updatedRoot.name; const configurations: Configuration = createConfiguration(options, namespace, sdkContext); diff --git a/packages/http-client-csharp/emitter/src/lib/client-converter.ts b/packages/http-client-csharp/emitter/src/lib/client-converter.ts index 1e1b2640c11..b681251de0a 100644 --- a/packages/http-client-csharp/emitter/src/lib/client-converter.ts +++ b/packages/http-client-csharp/emitter/src/lib/client-converter.ts @@ -9,7 +9,7 @@ import { SdkHttpOperation, SdkMethodParameter, } from "@azure-tools/typespec-client-generator-core"; -import { DiagnosticCollector, NoTarget } from "@typespec/compiler"; +import { createDiagnosticCollector, Diagnostic, NoTarget } from "@typespec/compiler"; import { CSharpEmitterContext } from "../sdk-context.js"; import { createDiagnostic } from "./lib.js"; import { InputParameterScope } from "../type/input-parameter-scope.js"; @@ -33,26 +33,26 @@ export function fromSdkClients( sdkContext: CSharpEmitterContext, clients: SdkClientType[], rootApiVersions: string[], - diagnostics: DiagnosticCollector, -): InputClient[] { +): [InputClient[], readonly Diagnostic[]] { + const diagnostics = createDiagnosticCollector(); const inputClients: InputClient[] = []; for (const client of clients) { - const inputClient = fromSdkClient(sdkContext, client, rootApiVersions, diagnostics); + const inputClient = diagnostics.pipe(fromSdkClient(sdkContext, client, rootApiVersions)); inputClients.push(inputClient); } - return inputClients; + return diagnostics.wrap(inputClients); } function fromSdkClient( sdkContext: CSharpEmitterContext, client: SdkClientType, rootApiVersions: string[], - diagnostics: DiagnosticCollector, -): InputClient { +): [InputClient, readonly Diagnostic[]] { + const diagnostics = createDiagnosticCollector(); let inputClient: InputClient | undefined = sdkContext.__typeCache.clients.get(client); if (inputClient) { - return inputClient; + return diagnostics.wrap(inputClient); } const endpointParameter = client.clientInitialization.parameters.find( (p) => p.kind === "endpoint", @@ -60,12 +60,11 @@ function fromSdkClient( const uri = getMethodUri(endpointParameter); // Convert all clientInitialization parameters - const clientParameters = fromSdkClientInitializationParameters( + const clientParameters = diagnostics.pipe(fromSdkClientInitializationParameters( sdkContext, client.clientInitialization.parameters, client.namespace, - diagnostics, - ); + )); inputClient = { kind: "client", @@ -74,7 +73,7 @@ function fromSdkClient( doc: client.doc, summary: client.summary, methods: client.methods - .map((m) => fromSdkServiceMethod(sdkContext, m, uri, rootApiVersions, client.namespace, diagnostics)) + .map((m) => diagnostics.pipe(fromSdkServiceMethod(sdkContext, m, uri, rootApiVersions, client.namespace))) .filter((m) => m !== undefined), parameters: clientParameters, initializedBy: client.clientInitialization.initializedBy, @@ -90,51 +89,53 @@ function fromSdkClient( // fill parent if (client.parent) { - inputClient.parent = fromSdkClient(sdkContext, client.parent, rootApiVersions, diagnostics); + inputClient.parent = diagnostics.pipe(fromSdkClient(sdkContext, client.parent, rootApiVersions)); } // fill children if (client.children) { inputClient.children = client.children.map((c) => - fromSdkClient(sdkContext, c, rootApiVersions, diagnostics), + diagnostics.pipe(fromSdkClient(sdkContext, c, rootApiVersions)), ); } - return inputClient; + return diagnostics.wrap(inputClient); function fromSdkClientInitializationParameters( sdkContext: CSharpEmitterContext, parameters: (SdkEndpointParameter | SdkCredentialParameter | SdkMethodParameter)[], namespace: string, - diagnostics: DiagnosticCollector, - ): InputParameter[] { + ): [InputParameter[], readonly Diagnostic[]] { + const diagnostics = createDiagnosticCollector(); const inputParameters: InputParameter[] = []; for (const param of parameters) { if (param.kind === "endpoint") { // Convert endpoint parameters - const endpointParams = fromSdkEndpointParameter(param, diagnostics); + const endpointParams = diagnostics.pipe(fromSdkEndpointParameter(param)); inputParameters.push(...endpointParams); } else if (param.kind === "method") { // Convert method parameters - const methodParam = fromMethodParameter(sdkContext, param, namespace, diagnostics); + const methodParam = diagnostics.pipe(fromMethodParameter(sdkContext, param, namespace)); inputParameters.push(methodParam); } // Note: credential parameters are handled separately in service-authentication.ts // and are not included in the client parameters list } - return inputParameters; + return diagnostics.wrap(inputParameters); } - function fromSdkEndpointParameter(p: SdkEndpointParameter, diagnostics: DiagnosticCollector): InputEndpointParameter[] { + function fromSdkEndpointParameter(p: SdkEndpointParameter): [InputEndpointParameter[], readonly Diagnostic[]] { + const diagnostics = createDiagnosticCollector(); if (p.type.kind === "union") { - return fromSdkEndpointType(p.type.variantTypes[0], diagnostics); + return diagnostics.wrap(diagnostics.pipe(fromSdkEndpointType(p.type.variantTypes[0]))); } else { - return fromSdkEndpointType(p.type, diagnostics); + return diagnostics.wrap(diagnostics.pipe(fromSdkEndpointType(p.type))); } } - function fromSdkEndpointType(type: SdkEndpointType, diagnostics: DiagnosticCollector): InputEndpointParameter[] { + function fromSdkEndpointType(type: SdkEndpointType): [InputEndpointParameter[], readonly Diagnostic[]] { + const diagnostics = createDiagnosticCollector(); // TODO: support free-style endpoint url with multiple parameters const endpointExpr = type.serverUrl .replace("https://", "") @@ -148,7 +149,7 @@ function fromSdkClient( target: NoTarget, }), ); - return []; + return diagnostics.wrap([]); } const endpointVariableName = endpointExpr.substring(1, endpointExpr.length - 1); @@ -162,7 +163,7 @@ function fromSdkClient( crossLanguageDefinitionId: parameter.type.kind === "string" ? "TypeSpec.string" : "TypeSpec.url", } - : fromSdkType(sdkContext, parameter.type, diagnostics); // TODO: consolidate with converter.fromSdkEndpointType + : diagnostics.pipe(fromSdkType(sdkContext, parameter.type)); // TODO: consolidate with converter.fromSdkEndpointType parameters.push({ kind: "endpoint", name: parameter.name, @@ -174,19 +175,18 @@ function fromSdkClient( optional: parameter.optional, scope: InputParameterScope.Client, isEndpoint: isEndpoint, - defaultValue: getParameterDefaultValue( + defaultValue: diagnostics.pipe(getParameterDefaultValue( sdkContext, parameter.clientDefaultValue, parameterType, - diagnostics, - ), + )), serverUrlTemplate: type.serverUrl, skipUrlEncoding: false, readOnly: isReadOnly(parameter), crossLanguageDefinitionId: parameter.crossLanguageDefinitionId, }); } - return parameters; + return diagnostics.wrap(parameters); } } diff --git a/packages/http-client-csharp/emitter/src/lib/client-model-builder.ts b/packages/http-client-csharp/emitter/src/lib/client-model-builder.ts index ba317e411e5..4f8cd114594 100644 --- a/packages/http-client-csharp/emitter/src/lib/client-model-builder.ts +++ b/packages/http-client-csharp/emitter/src/lib/client-model-builder.ts @@ -7,7 +7,7 @@ import { SdkHttpOperation, UsageFlags, } from "@azure-tools/typespec-client-generator-core"; -import { createDiagnosticCollector, Diagnostic, DiagnosticCollector } from "@typespec/compiler"; +import { createDiagnosticCollector, Diagnostic } from "@typespec/compiler"; import { CSharpEmitterContext } from "../sdk-context.js"; import { CodeModel } from "../type/code-model.js"; import { InputEnumType, InputLiteralType, InputModelType } from "../type/input-type.js"; @@ -43,9 +43,9 @@ export function createModel(sdkContext: CSharpEmitterContext): [CodeModel, reado const sdkPackage = sdkContext.sdkPackage; // TO-DO: Consider exposing the namespace hierarchy in the code model https://github.com/microsoft/typespec/issues/8332 - fromSdkNamespaces(sdkContext, sdkPackage.namespaces, diagnostics); + diagnostics.pipe(fromSdkNamespaces(sdkContext, sdkPackage.namespaces)); // TO-DO: Consider using the TCGC model + enum cache once https://github.com/Azure/typespec-azure/issues/3180 is resolved - navigateModels(sdkContext, diagnostics); + diagnostics.pipe(navigateModels(sdkContext)); const types = Array.from(sdkContext.__typeCache.types.values()); const [models, enums] = [ @@ -55,7 +55,7 @@ export function createModel(sdkContext: CSharpEmitterContext): [CodeModel, reado const rootClients = sdkPackage.clients; const rootApiVersions = parseApiVersions(sdkPackage.enums, rootClients); - const inputClients = fromSdkClients(sdkContext, rootClients, rootApiVersions, diagnostics); + const inputClients = diagnostics.pipe(fromSdkClients(sdkContext, rootClients, rootApiVersions)); // TODO -- TCGC now does not have constants field in its sdkPackage, they might add it in the future. const constants = Array.from(sdkContext.__typeCache.constants.values()); @@ -165,11 +165,13 @@ function fixNamingConflicts(models: InputModelType[], constants: InputLiteralTyp } } -function navigateModels(sdkContext: CSharpEmitterContext, diagnostics: DiagnosticCollector) { +function navigateModels(sdkContext: CSharpEmitterContext): [void, readonly Diagnostic[]] { + const diagnostics = createDiagnosticCollector(); for (const m of sdkContext.sdkPackage.models) { - fromSdkType(sdkContext, m, diagnostics); + diagnostics.pipe(fromSdkType(sdkContext, m)); } for (const e of sdkContext.sdkPackage.enums) { - fromSdkType(sdkContext, e, diagnostics); + diagnostics.pipe(fromSdkType(sdkContext, e)); } + return diagnostics.wrap(undefined as void); } diff --git a/packages/http-client-csharp/emitter/src/lib/example-converter.ts b/packages/http-client-csharp/emitter/src/lib/example-converter.ts index bdd78190b05..c1300ee2082 100644 --- a/packages/http-client-csharp/emitter/src/lib/example-converter.ts +++ b/packages/http-client-csharp/emitter/src/lib/example-converter.ts @@ -81,7 +81,7 @@ export function fromSdkHttpExamples( responseValue: SdkHttpResponseExampleValue, ): OperationResponseExample { return { - response: fromSdkHttpOperationResponse(sdkContext, responseValue.response, diagnostics), + response: diagnostics.pipe(fromSdkHttpOperationResponse(sdkContext, responseValue.response)), statusCode: responseValue.statusCode, bodyValue: responseValue.bodyValue ? fromSdkExample(responseValue.bodyValue) : undefined, }; @@ -113,7 +113,7 @@ export function fromSdkHttpExamples( function fromSdkStringExample(example: SdkStringExampleValue): InputStringExampleValue { return { kind: "string", - type: fromSdkType(sdkContext, example.type, diagnostics), + type: diagnostics.pipe(fromSdkType(sdkContext, example.type)), value: example.value, }; } @@ -121,7 +121,7 @@ export function fromSdkHttpExamples( function fromSdkNumberExample(example: SdkNumberExampleValue): InputNumberExampleValue { return { kind: "number", - type: fromSdkType(sdkContext, example.type, diagnostics), + type: diagnostics.pipe(fromSdkType(sdkContext, example.type)), value: example.value, }; } @@ -129,7 +129,7 @@ export function fromSdkHttpExamples( function fromSdkBooleanExample(example: SdkBooleanExampleValue): InputBooleanExampleValue { return { kind: example.kind, - type: fromSdkType(sdkContext, example.type, diagnostics) as InputPrimitiveType, + type: diagnostics.pipe(fromSdkType(sdkContext, example.type)) as InputPrimitiveType, value: example.value, }; } @@ -137,7 +137,7 @@ export function fromSdkHttpExamples( function fromSdkUnionExample(example: SdkUnionExampleValue): InputUnionExampleValue { return { kind: example.kind, - type: fromSdkType(sdkContext, example.type, diagnostics) as InputUnionType, + type: diagnostics.pipe(fromSdkType(sdkContext, example.type)) as InputUnionType, value: example.value, }; } @@ -145,7 +145,7 @@ export function fromSdkHttpExamples( function fromSdkArrayExample(example: SdkArrayExampleValue): InputArrayExampleValue { return { kind: example.kind, - type: fromSdkType(sdkContext, example.type, diagnostics) as InputArrayType, + type: diagnostics.pipe(fromSdkType(sdkContext, example.type)) as InputArrayType, value: example.value.map((v) => fromSdkExample(v)), }; } @@ -155,7 +155,7 @@ export function fromSdkHttpExamples( ): InputDictionaryExampleValue { return { kind: example.kind, - type: fromSdkType(sdkContext, example.type, diagnostics) as InputDictionaryType, + type: diagnostics.pipe(fromSdkType(sdkContext, example.type)) as InputDictionaryType, value: fromExampleRecord(example.value), }; } @@ -163,7 +163,7 @@ export function fromSdkHttpExamples( function fromSdkModelExample(example: SdkModelExampleValue): InputModelExampleValue { return { kind: example.kind, - type: fromSdkType(sdkContext, example.type, diagnostics) as InputModelType, + type: diagnostics.pipe(fromSdkType(sdkContext, example.type)) as InputModelType, value: fromExampleRecord(example.value), additionalPropertiesValue: example.additionalPropertiesValue ? fromExampleRecord(example.additionalPropertiesValue) @@ -174,7 +174,7 @@ export function fromSdkHttpExamples( function fromSdkAnyExample(example: SdkUnknownExampleValue): InputUnknownExampleValue { return { kind: example.kind, - type: fromSdkType(sdkContext, example.type, diagnostics) as InputPrimitiveType, + type: diagnostics.pipe(fromSdkType(sdkContext, example.type)) as InputPrimitiveType, value: example.value, }; } @@ -182,7 +182,7 @@ export function fromSdkHttpExamples( function fromSdkNullExample(example: SdkNullExampleValue): InputNullExampleValue { return { kind: example.kind, - type: fromSdkType(sdkContext, example.type, diagnostics) as InputNullableType, + type: diagnostics.pipe(fromSdkType(sdkContext, example.type)) as InputNullableType, value: example.value, }; } diff --git a/packages/http-client-csharp/emitter/src/lib/namespace-converter.ts b/packages/http-client-csharp/emitter/src/lib/namespace-converter.ts index fdb1cc0d0bd..cfc2eb761a0 100644 --- a/packages/http-client-csharp/emitter/src/lib/namespace-converter.ts +++ b/packages/http-client-csharp/emitter/src/lib/namespace-converter.ts @@ -1,20 +1,20 @@ import { SdkHttpOperation, SdkNamespace } from "@azure-tools/typespec-client-generator-core"; -import { DiagnosticCollector } from "@typespec/compiler"; +import { createDiagnosticCollector, Diagnostic } from "@typespec/compiler"; import { CSharpEmitterContext } from "../sdk-context.js"; import { InputNamespace } from "../type/input-type.js"; export function fromSdkNamespaces( sdkContext: CSharpEmitterContext, namespaces: SdkNamespace[], - diagnostics: DiagnosticCollector, -): InputNamespace[] { +): [InputNamespace[], readonly Diagnostic[]] { + const diagnostics = createDiagnosticCollector(); const inputNamespaces: InputNamespace[] = []; for (const namespace of namespaces) { const inputNamespace = fromSdkNamespace(sdkContext, namespace); inputNamespaces.push(inputNamespace); } - return inputNamespaces; + return diagnostics.wrap(inputNamespaces); } function fromSdkNamespace( diff --git a/packages/http-client-csharp/emitter/src/lib/operation-converter.ts b/packages/http-client-csharp/emitter/src/lib/operation-converter.ts index 3359a17e2eb..ac1dcf22406 100644 --- a/packages/http-client-csharp/emitter/src/lib/operation-converter.ts +++ b/packages/http-client-csharp/emitter/src/lib/operation-converter.ts @@ -25,7 +25,13 @@ import { shouldGenerateConvenient, shouldGenerateProtocol, } from "@azure-tools/typespec-client-generator-core"; -import { DiagnosticCollector, getDeprecated, isErrorModel, NoTarget } from "@typespec/compiler"; +import { + createDiagnosticCollector, + Diagnostic, + getDeprecated, + isErrorModel, + NoTarget, +} from "@typespec/compiler"; import { HttpStatusCodeRange } from "@typespec/http"; import { getResourceOperation } from "@typespec/rest"; import { CSharpEmitterContext } from "../sdk-context.js"; @@ -72,74 +78,69 @@ export function fromSdkServiceMethod( uri: string, rootApiVersions: string[], namespace: string, - diagnostics: DiagnosticCollector, -): InputServiceMethod | undefined { +): [InputServiceMethod | undefined, readonly Diagnostic[]] { + const diagnostics = createDiagnosticCollector(); + let method = sdkContext.__typeCache.methods.get(sdkMethod); if (method) { - return method; + return diagnostics.wrap(method); } const methodKind = sdkMethod.kind; switch (methodKind) { case "basic": - method = createServiceMethod( + method = diagnostics.pipe(createServiceMethod( sdkContext, sdkMethod, uri, rootApiVersions, namespace, - diagnostics, - ); + )); break; case "paging": - const pagingServiceMethod = createServiceMethod( + const pagingServiceMethod = diagnostics.pipe(createServiceMethod( sdkContext, sdkMethod, uri, rootApiVersions, namespace, - diagnostics, - ); - pagingServiceMethod.pagingMetadata = loadPagingServiceMetadata( + )); + pagingServiceMethod.pagingMetadata = diagnostics.pipe(loadPagingServiceMetadata( sdkContext, sdkMethod, rootApiVersions, uri, namespace, - diagnostics, - ); + )); method = pagingServiceMethod; break; case "lro": - const lroServiceMethod = createServiceMethod( + const lroServiceMethod = diagnostics.pipe(createServiceMethod( sdkContext, sdkMethod, uri, rootApiVersions, namespace, - diagnostics, - ); - lroServiceMethod.lroMetadata = loadLongRunningMetadata(sdkContext, sdkMethod, diagnostics); + )); + lroServiceMethod.lroMetadata = diagnostics.pipe(loadLongRunningMetadata(sdkContext, sdkMethod)); method = lroServiceMethod; break; case "lropaging": - const lroPagingMethod = createServiceMethod( + const lroPagingMethod = diagnostics.pipe(createServiceMethod( sdkContext, sdkMethod, uri, rootApiVersions, namespace, - diagnostics, - ); - lroPagingMethod.lroMetadata = loadLongRunningMetadata(sdkContext, sdkMethod, diagnostics); - lroPagingMethod.pagingMetadata = loadPagingServiceMetadata( + )); + lroPagingMethod.lroMetadata = diagnostics.pipe(loadLongRunningMetadata(sdkContext, sdkMethod)); + lroPagingMethod.pagingMetadata = diagnostics.pipe(loadPagingServiceMetadata( sdkContext, sdkMethod, rootApiVersions, uri, namespace, - diagnostics, - ); + )); method = lroPagingMethod; break; default: @@ -158,7 +159,7 @@ export function fromSdkServiceMethod( sdkContext.__typeCache.updateSdkMethodReferences(sdkMethod, method); } - return method; + return diagnostics.wrap(method); } export function fromSdkServiceMethodOperation( @@ -166,11 +167,12 @@ export function fromSdkServiceMethodOperation( method: SdkServiceMethod, uri: string, rootApiVersions: string[], - diagnostics: DiagnosticCollector, -): InputOperation { +): [InputOperation, readonly Diagnostic[]] { + const diagnostics = createDiagnosticCollector(); + let operation = sdkContext.__typeCache.operations.get(method.operation); if (operation) { - return operation; + return diagnostics.wrap(operation); } let generateConvenience = shouldGenerateConvenient(sdkContext, method.operation.__raw.operation); @@ -197,8 +199,8 @@ export function fromSdkServiceMethodOperation( summary: method.summary, doc: method.doc, accessibility: method.access, - parameters: fromSdkOperationParameters(sdkContext, method.operation, rootApiVersions, diagnostics), - responses: fromSdkHttpOperationResponses(sdkContext, method.operation.responses, diagnostics), + parameters: diagnostics.pipe(fromSdkOperationParameters(sdkContext, method.operation, rootApiVersions)), + responses: diagnostics.pipe(fromSdkHttpOperationResponses(sdkContext, method.operation.responses)), httpMethod: parseHttpRequestMethod(method.operation.verb), uri: uri, path: method.operation.path, @@ -216,32 +218,33 @@ export function fromSdkServiceMethodOperation( sdkContext.__typeCache.updateSdkOperationReferences(method.operation, operation); - return operation; + return diagnostics.wrap(operation); } export function getParameterDefaultValue( sdkContext: CSharpEmitterContext, clientDefaultValue: any, parameterType: InputType, - diagnostics: DiagnosticCollector, -): InputConstant | undefined { +): [InputConstant | undefined, readonly Diagnostic[]] { + const diagnostics = createDiagnosticCollector(); + if ( clientDefaultValue === undefined || // a constant parameter should overwrite client default value parameterType.kind === "constant" ) { - return undefined; + return diagnostics.wrap(undefined); } - const kind = getValueType(sdkContext, clientDefaultValue, diagnostics); - return { + const kind = diagnostics.pipe(getValueType(sdkContext, clientDefaultValue)); + return diagnostics.wrap({ type: { kind: kind, name: kind, crossLanguageDefinitionId: `TypeSpec.${kind}`, }, value: clientDefaultValue, - }; + }); } function createServiceMethod( @@ -250,38 +253,41 @@ function createServiceMethod( uri: string, rootApiVersions: string[], namespace: string, - diagnostics: DiagnosticCollector, -): T { - return { +): [T, readonly Diagnostic[]] { + const diagnostics = createDiagnosticCollector(); + + return diagnostics.wrap({ kind: method.kind, name: method.name, accessibility: method.access, apiVersions: method.apiVersions, doc: method.doc, summary: method.summary, - operation: fromSdkServiceMethodOperation(sdkContext, method, uri, rootApiVersions, diagnostics), - parameters: fromSdkServiceMethodParameters(sdkContext, method, rootApiVersions, namespace, diagnostics), - response: fromSdkServiceMethodResponse(sdkContext, method.response, diagnostics), + operation: diagnostics.pipe(fromSdkServiceMethodOperation(sdkContext, method, uri, rootApiVersions)), + parameters: diagnostics.pipe(fromSdkServiceMethodParameters(sdkContext, method, rootApiVersions, namespace)), + response: diagnostics.pipe(fromSdkServiceMethodResponse(sdkContext, method.response)), exception: method.exception - ? fromSdkServiceMethodResponse(sdkContext, method.exception, diagnostics) + ? diagnostics.pipe(fromSdkServiceMethodResponse(sdkContext, method.exception)) : undefined, isOverride: method.isOverride, generateConvenient: method.generateConvenient, generateProtocol: method.generateProtocol, crossLanguageDefinitionId: method.crossLanguageDefinitionId, - } as T; + } as T); } -function getValueType(sdkContext: CSharpEmitterContext, value: any, diagnostics: DiagnosticCollector): SdkBuiltInKinds { +function getValueType(sdkContext: CSharpEmitterContext, value: any): [SdkBuiltInKinds, readonly Diagnostic[]] { + const diagnostics = createDiagnosticCollector(); + switch (typeof value) { case "string": - return "string"; + return diagnostics.wrap("string"); case "number": - return "int32"; + return diagnostics.wrap("int32"); case "boolean": - return "boolean"; + return diagnostics.wrap("boolean"); case "bigint": - return "int64"; + return diagnostics.wrap("int64"); default: diagnostics.add( createDiagnostic({ @@ -290,7 +296,7 @@ function getValueType(sdkContext: CSharpEmitterContext, value: any, diagnostics: target: NoTarget, }), ); - return "unknown"; + return diagnostics.wrap("unknown"); } } @@ -299,12 +305,12 @@ function fromSdkServiceMethodParameters( method: SdkServiceMethod, rootApiVersions: string[], namespace: string, - diagnostics: DiagnosticCollector, -): InputMethodParameter[] { +): [InputMethodParameter[], readonly Diagnostic[]] { + const diagnostics = createDiagnosticCollector(); const parameters: InputMethodParameter[] = []; for (const p of method.parameters) { - const methodInputParameter = fromMethodParameter(sdkContext, p, namespace, diagnostics); + const methodInputParameter = diagnostics.pipe(fromMethodParameter(sdkContext, p, namespace)); const operationHttpParameter = getHttpOperationParameter(method, p); if (!operationHttpParameter) { @@ -323,7 +329,7 @@ function fromSdkServiceMethodParameters( parameters.push(methodInputParameter); } - return parameters; + return diagnostics.wrap(parameters); } function updateMethodParameter( @@ -331,7 +337,7 @@ function updateMethodParameter( methodParameter: InputMethodParameter, operationHttpParameter: SdkHttpParameter | SdkModelPropertyType, rootApiVersions: string[], - diagnostics: DiagnosticCollector, + diagnostics: ReturnType, ): void { methodParameter.serializedName = getNameInRequest(operationHttpParameter); methodParameter.location = getParameterLocation(operationHttpParameter); @@ -343,7 +349,7 @@ function updateMethodParameter( if (methodParameter.location === RequestLocation.Body) { // Convert constants to enums if (methodParameter.type.kind === "constant") { - methodParameter.type = fromSdkType(sdkContext, operationHttpParameter.type, diagnostics); + methodParameter.type = diagnostics.pipe(fromSdkType(sdkContext, operationHttpParameter.type)); } } } @@ -351,23 +357,25 @@ function updateMethodParameter( function fromSdkServiceMethodResponse( sdkContext: CSharpEmitterContext, methodResponse: SdkMethodResponse, - diagnostics: DiagnosticCollector, -): InputServiceMethodResponse { - return { - type: getResponseType(sdkContext, methodResponse.type, diagnostics), +): [InputServiceMethodResponse, readonly Diagnostic[]] { + const diagnostics = createDiagnosticCollector(); + + return diagnostics.wrap({ + type: diagnostics.pipe(getResponseType(sdkContext, methodResponse.type)), resultSegments: methodResponse.resultSegments?.map((segment) => getResponseSegmentName(segment), ), - }; + }); } function fromSdkOperationParameters( sdkContext: CSharpEmitterContext, operation: SdkHttpOperation, rootApiVersions: string[], - diagnostics: DiagnosticCollector, -): InputHttpParameter[] { +): [InputHttpParameter[], readonly Diagnostic[]] { + const diagnostics = createDiagnosticCollector(); const parameters: InputHttpParameter[] = []; + for (const p of operation.parameters) { if (p.kind === "cookie") { diagnostics.add( @@ -377,47 +385,48 @@ function fromSdkOperationParameters( target: NoTarget, }), ); - return parameters; + return diagnostics.wrap(parameters); } - const param = fromParameter(sdkContext, p, rootApiVersions, diagnostics); + const param = diagnostics.pipe(fromParameter(sdkContext, p, rootApiVersions)); if (param) { parameters.push(param); } } if (operation.bodyParam) { - const bodyParam = fromParameter(sdkContext, operation.bodyParam, rootApiVersions, diagnostics); + const bodyParam = diagnostics.pipe(fromParameter(sdkContext, operation.bodyParam, rootApiVersions)); if (bodyParam) { parameters.push(bodyParam); } } - return parameters; + return diagnostics.wrap(parameters); } export function fromParameter( sdkContext: CSharpEmitterContext, p: SdkHttpParameter | SdkModelPropertyType, rootApiVersions: string[], - diagnostics: DiagnosticCollector, -): InputHttpParameter | undefined { +): [InputHttpParameter | undefined, readonly Diagnostic[]] { + const diagnostics = createDiagnosticCollector(); + let parameter = sdkContext.__typeCache.operationParameters.get(p); if (parameter) { - return parameter; + return diagnostics.wrap(parameter); } const parameterKind = p.kind; switch (parameterKind) { case "query": - parameter = fromQueryParameter(sdkContext, p, rootApiVersions, diagnostics); + parameter = diagnostics.pipe(fromQueryParameter(sdkContext, p, rootApiVersions)); break; case "path": - parameter = fromPathParameter(sdkContext, p, rootApiVersions, diagnostics); + parameter = diagnostics.pipe(fromPathParameter(sdkContext, p, rootApiVersions)); break; case "header": - parameter = fromHeaderParameter(sdkContext, p, rootApiVersions, diagnostics); + parameter = diagnostics.pipe(fromHeaderParameter(sdkContext, p, rootApiVersions)); break; case "body": - parameter = fromBodyParameter(sdkContext, p, rootApiVersions, diagnostics); + parameter = diagnostics.pipe(fromBodyParameter(sdkContext, p, rootApiVersions)); break; default: diagnostics.add( @@ -434,16 +443,16 @@ export function fromParameter( if (parameter) { sdkContext.__typeCache.operationParameters.set(p, parameter); } - return parameter; + return diagnostics.wrap(parameter); } function fromQueryParameter( sdkContext: CSharpEmitterContext, p: SdkQueryParameter, rootApiVersions: string[], - diagnostics: DiagnosticCollector, -): InputQueryParameter { - const parameterType = fromSdkType(sdkContext, p.type, diagnostics); +): [InputQueryParameter, readonly Diagnostic[]] { + const diagnostics = createDiagnosticCollector(); + const parameterType = diagnostics.pipe(fromSdkType(sdkContext, p.type)); const retVar: InputQueryParameter = { kind: "query", @@ -454,7 +463,7 @@ function fromQueryParameter( type: parameterType, isApiVersion: p.isApiVersionParam, explode: isExploded(p), - defaultValue: getParameterDefaultValue(sdkContext, p.clientDefaultValue, parameterType, diagnostics), + defaultValue: diagnostics.pipe(getParameterDefaultValue(sdkContext, p.clientDefaultValue, parameterType)), arraySerializationDelimiter: getArraySerializationDelimiter(p), optional: p.optional, scope: getParameterScope(p, parameterType, rootApiVersions.length > 0), @@ -464,16 +473,16 @@ function fromQueryParameter( }; sdkContext.__typeCache.updateSdkOperationParameterReferences(p, retVar); - return retVar; + return diagnostics.wrap(retVar); } function fromPathParameter( sdkContext: CSharpEmitterContext, p: SdkPathParameter, rootApiVersions: string[], - diagnostics: DiagnosticCollector, -): InputPathParameter { - const parameterType = fromSdkType(sdkContext, p.type, diagnostics); +): [InputPathParameter, readonly Diagnostic[]] { + const diagnostics = createDiagnosticCollector(); + const parameterType = diagnostics.pipe(fromSdkType(sdkContext, p.type)); const retVar: InputPathParameter = { kind: "path", @@ -487,7 +496,7 @@ function fromPathParameter( style: p.style, allowReserved: p.allowReserved, skipUrlEncoding: p.allowReserved, - defaultValue: getParameterDefaultValue(sdkContext, p.clientDefaultValue, parameterType, diagnostics), + defaultValue: diagnostics.pipe(getParameterDefaultValue(sdkContext, p.clientDefaultValue, parameterType)), optional: p.optional, scope: getParameterScope(p, parameterType, rootApiVersions.length > 0), decorators: p.decorators, @@ -496,16 +505,16 @@ function fromPathParameter( }; sdkContext.__typeCache.updateSdkOperationParameterReferences(p, retVar); - return retVar; + return diagnostics.wrap(retVar); } function fromHeaderParameter( sdkContext: CSharpEmitterContext, p: SdkHeaderParameter, rootApiVersions: string[], - diagnostics: DiagnosticCollector, -): InputHeaderParameter { - const parameterType = fromSdkType(sdkContext, p.type, diagnostics); +): [InputHeaderParameter, readonly Diagnostic[]] { + const diagnostics = createDiagnosticCollector(); + const parameterType = diagnostics.pipe(fromSdkType(sdkContext, p.type)); const retVar: InputHeaderParameter = { kind: "header", @@ -517,7 +526,7 @@ function fromHeaderParameter( isApiVersion: p.isApiVersionParam, collectionFormat: p.collectionFormat, arraySerializationDelimiter: getArraySerializationDelimiter(p), - defaultValue: getParameterDefaultValue(sdkContext, p.clientDefaultValue, parameterType, diagnostics), + defaultValue: diagnostics.pipe(getParameterDefaultValue(sdkContext, p.clientDefaultValue, parameterType)), optional: p.optional, isContentType: isContentType(p), scope: getParameterScope(p, parameterType, rootApiVersions.length > 0), @@ -527,16 +536,16 @@ function fromHeaderParameter( }; sdkContext.__typeCache.updateSdkOperationParameterReferences(p, retVar); - return retVar; + return diagnostics.wrap(retVar); } function fromBodyParameter( sdkContext: CSharpEmitterContext, p: SdkBodyParameter, rootApiVersions: string[], - diagnostics: DiagnosticCollector, -): InputBodyParameter { - const parameterType = fromSdkType(sdkContext, p.type, diagnostics); +): [InputBodyParameter, readonly Diagnostic[]] { + const diagnostics = createDiagnosticCollector(); + const parameterType = diagnostics.pipe(fromSdkType(sdkContext, p.type)); const retVar: InputBodyParameter = { kind: "body", @@ -556,21 +565,22 @@ function fromBodyParameter( }; sdkContext.__typeCache.updateSdkOperationParameterReferences(p, retVar); - return retVar; + return diagnostics.wrap(retVar); } export function fromMethodParameter( sdkContext: CSharpEmitterContext, p: SdkMethodParameter, namespace: string, - diagnostics: DiagnosticCollector, -): InputMethodParameter { +): [InputMethodParameter, readonly Diagnostic[]] { + const diagnostics = createDiagnosticCollector(); + let retVar = sdkContext.__typeCache.methodParmeters.get(p); if (retVar) { - return retVar as InputMethodParameter; + return diagnostics.wrap(retVar as InputMethodParameter); } - const parameterType = fromSdkType(sdkContext, p.type, diagnostics, p, namespace); + const parameterType = diagnostics.pipe(fromSdkType(sdkContext, p.type, p, namespace)); retVar = { kind: "method", @@ -581,7 +591,7 @@ export function fromMethodParameter( type: parameterType, location: RequestLocation.None, isApiVersion: p.isApiVersionParam, - defaultValue: getParameterDefaultValue(sdkContext, p.clientDefaultValue, parameterType, diagnostics), + defaultValue: diagnostics.pipe(getParameterDefaultValue(sdkContext, p.clientDefaultValue, parameterType)), optional: p.optional, scope: InputParameterScope.Method, crossLanguageDefinitionId: p.crossLanguageDefinitionId, @@ -591,15 +601,16 @@ export function fromMethodParameter( }; sdkContext.__typeCache.updateSdkMethodParameterReferences(p, retVar); - return retVar; + return diagnostics.wrap(retVar); } function loadLongRunningMetadata( sdkContext: CSharpEmitterContext, method: SdkLroServiceMethod | SdkLroPagingServiceMethod, - diagnostics: DiagnosticCollector, -): InputLongRunningServiceMetadata { - return { +): [InputLongRunningServiceMetadata, readonly Diagnostic[]] { + const diagnostics = createDiagnosticCollector(); + + return diagnostics.wrap({ finalStateVia: convertLroFinalStateVia(method.lroMetadata.finalStateVia), finalResponse: { // in swagger, we allow delete to return some meaningful body content @@ -607,64 +618,67 @@ function loadLongRunningMetadata( statusCodes: method.operation.verb === "delete" ? [204] : [200], bodyType: method.lroMetadata.finalResponse?.envelopeResult !== undefined - ? fromSdkType(sdkContext, method.lroMetadata.finalResponse.envelopeResult, diagnostics) + ? diagnostics.pipe(fromSdkType(sdkContext, method.lroMetadata.finalResponse.envelopeResult)) : undefined, } as OperationResponse, resultPath: method.lroMetadata.finalResultPath, - }; + }); } function fromSdkHttpOperationResponses( sdkContext: CSharpEmitterContext, operationResponses: SdkHttpResponse[], - diagnostics: DiagnosticCollector, -): OperationResponse[] { +): [OperationResponse[], readonly Diagnostic[]] { + const diagnostics = createDiagnosticCollector(); const responses: OperationResponse[] = []; + for (const r of operationResponses) { - responses.push(fromSdkHttpOperationResponse(sdkContext, r, diagnostics)); + responses.push(diagnostics.pipe(fromSdkHttpOperationResponse(sdkContext, r))); } - return responses; + return diagnostics.wrap(responses); } export function fromSdkHttpOperationResponse( sdkContext: CSharpEmitterContext, sdkResponse: SdkHttpResponse, - diagnostics: DiagnosticCollector, -): OperationResponse { +): [OperationResponse, readonly Diagnostic[]] { + const diagnostics = createDiagnosticCollector(); + let retVar = sdkContext.__typeCache.responses.get(sdkResponse); if (retVar) { - return retVar; + return diagnostics.wrap(retVar); } const range = sdkResponse.statusCodes; retVar = { statusCodes: toStatusCodesArray(range), - bodyType: getResponseType(sdkContext, sdkResponse.type, diagnostics), - headers: fromSdkServiceResponseHeaders(sdkContext, sdkResponse.headers, diagnostics), + bodyType: diagnostics.pipe(getResponseType(sdkContext, sdkResponse.type)), + headers: diagnostics.pipe(fromSdkServiceResponseHeaders(sdkContext, sdkResponse.headers)), isErrorResponse: sdkResponse.type !== undefined && isErrorModel(sdkContext.program, sdkResponse.type.__raw!), contentTypes: sdkResponse.contentTypes, }; sdkContext.__typeCache.updateSdkResponseReferences(sdkResponse, retVar); - return retVar; + return diagnostics.wrap(retVar); } function fromSdkServiceResponseHeaders( sdkContext: CSharpEmitterContext, headers: SdkServiceResponseHeader[], - diagnostics: DiagnosticCollector, -): HttpResponseHeader[] { - return headers.map( +): [HttpResponseHeader[], readonly Diagnostic[]] { + const diagnostics = createDiagnosticCollector(); + + return diagnostics.wrap(headers.map( (h) => ({ name: h.__raw!.name, nameInResponse: h.serializedName, summary: h.summary, doc: h.doc, - type: fromSdkType(sdkContext, h.type, diagnostics), + type: diagnostics.pipe(fromSdkType(sdkContext, h.type)), }) as HttpResponseHeader, - ); + )); } function toStatusCodesArray(range: number | HttpStatusCodeRange): number[] { @@ -716,31 +730,30 @@ function loadPagingServiceMetadata( rootApiVersions: string[], uri: string, namespace: string, - diagnostics: DiagnosticCollector, -): InputPagingServiceMetadata { +): [InputPagingServiceMetadata, readonly Diagnostic[]] { + const diagnostics = createDiagnosticCollector(); + let nextLink: InputNextLink | undefined; if (method.pagingMetadata.nextLinkSegments) { nextLink = { responseSegments: method.pagingMetadata.nextLinkSegments.map((segment) => getResponseSegmentName(segment), ), - responseLocation: getResponseLocation( + responseLocation: diagnostics.pipe(getResponseLocation( context, method, method.pagingMetadata.nextLinkSegments[0], - diagnostics, - ), + )), }; if (method.pagingMetadata.nextLinkOperation) { - nextLink.operation = fromSdkServiceMethod( + nextLink.operation = diagnostics.pipe(fromSdkServiceMethod( context, method.pagingMetadata.nextLinkOperation, uri, rootApiVersions, namespace, - diagnostics, - ); + )); } if ( @@ -755,7 +768,7 @@ function loadPagingServiceMetadata( ] as SdkModelPropertyType; const operationParameter = getHttpOperationParameter(method, lastParameterSegment); if (operationParameter) { - const parameter = fromParameter(context, operationParameter, rootApiVersions, diagnostics); + const parameter = diagnostics.pipe(fromParameter(context, operationParameter, rootApiVersions)); if (parameter) { nextLinkReInjectedParameters.push(parameter); } @@ -776,24 +789,22 @@ function loadPagingServiceMetadata( const lastParameterSegment = method.pagingMetadata.continuationTokenParameterSegments[ method.pagingMetadata.continuationTokenParameterSegments.length - 1 ] as SdkModelPropertyType; - const continuationTokenParameter = fromParameter( + const continuationTokenParameter = diagnostics.pipe(fromParameter( context, getHttpOperationParameter(method, lastParameterSegment)!, rootApiVersions, - diagnostics, - ); + )); if (continuationTokenParameter) { continuationToken = { parameter: continuationTokenParameter, responseSegments: method.pagingMetadata.continuationTokenResponseSegments!.map((segment) => getResponseSegmentName(segment), ), - responseLocation: getResponseLocation( + responseLocation: diagnostics.pipe(getResponseLocation( context, method, method.pagingMetadata.continuationTokenResponseSegments?.[0], - diagnostics, - ), + )), }; } } @@ -805,12 +816,12 @@ function loadPagingServiceMetadata( ); } - return { + return diagnostics.wrap({ itemPropertySegments: method.response.resultSegments!.map((s) => getResponseSegmentName(s)), nextLink: nextLink, continuationToken: continuationToken, pageSizeParameterSegments: pageSizeParameterSegments, - }; + }); } function getResponseSegmentName(segment: SdkServiceResponseHeader | SdkModelPropertyType): string { @@ -829,10 +840,11 @@ function getResponseLocation( context: CSharpEmitterContext, method: SdkPagingServiceMethod | SdkLroPagingServiceMethod, p: SdkServiceResponseHeader | SdkModelPropertyType, - diagnostics: DiagnosticCollector, -): ResponseLocation { +): [ResponseLocation, readonly Diagnostic[]] { + const diagnostics = createDiagnosticCollector(); + if (p.kind === "responseheader") { - return ResponseLocation.Header; + return diagnostics.wrap(ResponseLocation.Header); } if (isHttpMetadata(context, p)) { @@ -845,10 +857,10 @@ function getResponseLocation( target: NoTarget, }), ); - return ResponseLocation.None; + return diagnostics.wrap(ResponseLocation.None); } - return ResponseLocation.Body; + return diagnostics.wrap(ResponseLocation.Body); } // TODO: https://github.com/Azure/typespec-azure/issues/1441 @@ -959,16 +971,17 @@ function getArraySerializationDelimiter( function getResponseType( sdkContext: CSharpEmitterContext, type: SdkType | undefined, - diagnostics: DiagnosticCollector, -): InputType | undefined { +): [InputType | undefined, readonly Diagnostic[]] { + const diagnostics = createDiagnosticCollector(); + if (!type) { - return undefined; + return diagnostics.wrap(undefined); } // handle anonymous union enum response types by defaulting to the enum value type in the case of if (type.kind === "enum" && type.isUnionAsEnum && type.isGeneratedName) { - return fromSdkType(sdkContext, type.valueType, diagnostics); + return fromSdkType(sdkContext, type.valueType); } - return fromSdkType(sdkContext, type, diagnostics); + return fromSdkType(sdkContext, type); } diff --git a/packages/http-client-csharp/emitter/src/lib/type-converter.ts b/packages/http-client-csharp/emitter/src/lib/type-converter.ts index 0c97f2ae0b5..81cc99620a1 100644 --- a/packages/http-client-csharp/emitter/src/lib/type-converter.ts +++ b/packages/http-client-csharp/emitter/src/lib/type-converter.ts @@ -20,9 +20,8 @@ import { getAccessOverride, isHttpMetadata, } from "@azure-tools/typespec-client-generator-core"; -import { Model, NoTarget } from "@typespec/compiler"; +import { createDiagnosticCollector, Diagnostic, Model, NoTarget } from "@typespec/compiler"; import { createDiagnostic } from "./lib.js"; -import { DiagnosticCollector } from "@typespec/compiler"; import { CSharpEmitterContext } from "../sdk-context.js"; import { InputArrayType, @@ -75,39 +74,39 @@ type InputReturnType = T extends { kind: "nullable" } export function fromSdkType( sdkContext: CSharpEmitterContext, sdkType: T, - diagnostics: DiagnosticCollector, sdkProperty?: SdkModelPropertyTypeBase, namespace?: string, -): InputReturnType { +): [InputReturnType, readonly Diagnostic[]] { + const diagnostics = createDiagnosticCollector(); let retVar = sdkContext.__typeCache.types.get(sdkType); if (retVar) { - return retVar as any; + return diagnostics.wrap(retVar as any); } switch (sdkType.kind) { case "nullable": const nullableType: InputNullableType = { kind: "nullable", - type: fromSdkType(sdkContext, sdkType.type, diagnostics, sdkProperty, namespace), + type: diagnostics.pipe(fromSdkType(sdkContext, sdkType.type, sdkProperty, namespace)), namespace: sdkType.namespace, external: fromSdkExternalTypeInfo(sdkType), }; retVar = nullableType; break; case "model": - retVar = fromSdkModelType(sdkContext, sdkType, diagnostics); + retVar = diagnostics.pipe(fromSdkModelType(sdkContext, sdkType)); break; case "enum": - retVar = fromSdkEnumType(sdkContext, sdkType, diagnostics); + retVar = diagnostics.pipe(fromSdkEnumType(sdkContext, sdkType)); break; case "enumvalue": - retVar = fromSdkEnumValueType(sdkContext, sdkType, diagnostics); + retVar = diagnostics.pipe(fromSdkEnumValueType(sdkContext, sdkType)); break; case "dict": - retVar = fromSdkDictionaryType(sdkContext, sdkType, diagnostics); + retVar = diagnostics.pipe(fromSdkDictionaryType(sdkContext, sdkType)); break; case "array": - retVar = fromSdkArrayType(sdkContext, sdkType, diagnostics); + retVar = diagnostics.pipe(fromSdkArrayType(sdkContext, sdkType)); break; case "constant": if ( @@ -117,20 +116,20 @@ export function fromSdkType( sdkType.valueType.kind !== "boolean" ) { // turn the constant into an extensible enum - retVar = createEnumType(sdkContext, sdkType, namespace!, diagnostics); + retVar = diagnostics.pipe(createEnumType(sdkContext, sdkType, namespace!)); } else { - retVar = fromSdkConstantType(sdkContext, sdkType, diagnostics); + retVar = diagnostics.pipe(fromSdkConstantType(sdkContext, sdkType)); } break; case "union": - retVar = fromUnionType(sdkContext, sdkType, diagnostics); + retVar = diagnostics.pipe(fromUnionType(sdkContext, sdkType)); break; case "utcDateTime": case "offsetDateTime": - retVar = fromSdkDateTimeType(sdkContext, sdkType, diagnostics); + retVar = diagnostics.pipe(fromSdkDateTimeType(sdkContext, sdkType)); break; case "duration": - retVar = fromSdkDurationType(sdkContext, sdkType, diagnostics); + retVar = diagnostics.pipe(fromSdkDurationType(sdkContext, sdkType)); break; case "tuple": diagnostics.add( @@ -172,20 +171,20 @@ export function fromSdkType( retVar = credentialType; break; default: - retVar = fromSdkBuiltInType(sdkContext, sdkType, diagnostics); + retVar = diagnostics.pipe(fromSdkBuiltInType(sdkContext, sdkType)); break; } sdkContext.__typeCache.updateSdkTypeReferences(sdkType, retVar); // we have to cast to any because TypeScript's type narrowing does not automatically infer the return type for conditional types - return retVar as any; + return diagnostics.wrap(retVar as any); } function fromSdkModelType( sdkContext: CSharpEmitterContext, modelType: SdkModelType, - diagnostics: DiagnosticCollector, -): InputModelType { +): [InputModelType, readonly Diagnostic[]] { + const diagnostics = createDiagnosticCollector(); // get all unique decorators for the model type from the namespace level and the model level let decorators: DecoratorInfo[] = modelType.decorators; const namespace = sdkContext.__typeCache.namespaces.get(modelType.namespace); @@ -210,12 +209,12 @@ function fromSdkModelType( sdkContext.__typeCache.updateSdkTypeReferences(modelType, inputModelType); inputModelType.additionalProperties = modelType.additionalProperties - ? fromSdkType(sdkContext, modelType.additionalProperties, diagnostics) + ? diagnostics.pipe(fromSdkType(sdkContext, modelType.additionalProperties)) : undefined; const properties: InputModelProperty[] = []; for (const property of modelType.properties) { - const ourProperty = fromSdkModelProperty(sdkContext, property, modelType, diagnostics); + const ourProperty = diagnostics.pipe(fromSdkModelProperty(sdkContext, property, modelType)); if (ourProperty) { properties.push(ourProperty); @@ -223,11 +222,11 @@ function fromSdkModelType( } inputModelType.discriminatorProperty = modelType.discriminatorProperty - ? fromSdkModelProperty(sdkContext, modelType.discriminatorProperty, modelType, diagnostics) + ? diagnostics.pipe(fromSdkModelProperty(sdkContext, modelType.discriminatorProperty, modelType)) : undefined; inputModelType.baseModel = modelType.baseModel - ? fromSdkType(sdkContext, modelType.baseModel, diagnostics) + ? diagnostics.pipe(fromSdkType(sdkContext, modelType.baseModel)) : undefined; inputModelType.properties = properties; @@ -236,26 +235,26 @@ function fromSdkModelType( const discriminatedSubtypes: Record = {}; for (const key in modelType.discriminatedSubtypes) { const subtype = modelType.discriminatedSubtypes[key]; - discriminatedSubtypes[key] = fromSdkType(sdkContext, subtype, diagnostics); + discriminatedSubtypes[key] = diagnostics.pipe(fromSdkType(sdkContext, subtype)); } inputModelType.discriminatedSubtypes = discriminatedSubtypes; } - return inputModelType; + return diagnostics.wrap(inputModelType); } function fromSdkModelProperty( sdkContext: CSharpEmitterContext, sdkProperty: SdkModelPropertyType, sdkModel: SdkModelType, - diagnostics: DiagnosticCollector, -): InputModelProperty | undefined { +): [InputModelProperty | undefined, readonly Diagnostic[]] { + const diagnostics = createDiagnosticCollector(); // TODO -- this returns undefined because some properties we do not support yet. let property = sdkContext.__typeCache.properties.get(sdkProperty) as | InputModelProperty | undefined; if (property) { - return property; + return diagnostics.wrap(property); } const serializedName = @@ -268,7 +267,7 @@ function fromSdkModelProperty( serializedName: serializedName, summary: sdkProperty.summary, doc: sdkProperty.doc, - type: fromSdkType(sdkContext, sdkProperty.type, diagnostics, sdkProperty, sdkModel.namespace), + type: diagnostics.pipe(fromSdkType(sdkContext, sdkProperty.type, sdkProperty, sdkModel.namespace)), optional: sdkProperty.optional, readOnly: isReadOnly(sdkProperty), discriminator: sdkProperty.discriminator, @@ -284,19 +283,20 @@ function fromSdkModelProperty( sdkContext.__typeCache.updateSdkPropertyReferences(sdkProperty, property); } - return property; + return diagnostics.wrap(property); } -function fromSdkEnumType(sdkContext: CSharpEmitterContext, enumType: SdkEnumType, diagnostics: DiagnosticCollector): InputEnumType { - return createEnumType(sdkContext, enumType, enumType.namespace, diagnostics); +function fromSdkEnumType(sdkContext: CSharpEmitterContext, enumType: SdkEnumType): [InputEnumType, readonly Diagnostic[]] { + const diagnostics = createDiagnosticCollector(); + return diagnostics.wrap(diagnostics.pipe(createEnumType(sdkContext, enumType, enumType.namespace))); } function createEnumType( sdkContext: CSharpEmitterContext, sdkType: SdkConstantType | SdkEnumType, namespace: string, - diagnostics: DiagnosticCollector, -): InputEnumType { +): [InputEnumType, readonly Diagnostic[]] { + const diagnostics = createDiagnosticCollector(); const values: InputEnumValueType[] = []; const inputEnumType: InputEnumType = { @@ -305,8 +305,8 @@ function createEnumType( crossLanguageDefinitionId: sdkType.kind === "enum" ? sdkType.crossLanguageDefinitionId : "", valueType: sdkType.kind === "enum" - ? (fromSdkType(sdkContext, sdkType.valueType, diagnostics) as InputPrimitiveType) - : fromSdkBuiltInType(sdkContext, sdkType.valueType, diagnostics), + ? (diagnostics.pipe(fromSdkType(sdkContext, sdkType.valueType)) as InputPrimitiveType) + : diagnostics.pipe(fromSdkBuiltInType(sdkContext, sdkType.valueType)), values: values, // constantType.access, TODO - constant type now does not have access. TCGC will add it later access: @@ -327,118 +327,119 @@ function createEnumType( if (sdkType.kind === "enum") { for (const v of sdkType.values) { - values.push(createEnumValueType(sdkContext, v, inputEnumType, diagnostics)); + values.push(diagnostics.pipe(createEnumValueType(sdkContext, v, inputEnumType))); } } else { - values.push(createEnumValueType(sdkContext, sdkType, inputEnumType, diagnostics)); + values.push(diagnostics.pipe(createEnumValueType(sdkContext, sdkType, inputEnumType))); } - return inputEnumType; + return diagnostics.wrap(inputEnumType); } function fromSdkDateTimeType( sdkContext: CSharpEmitterContext, dateTimeType: SdkDateTimeType, - diagnostics: DiagnosticCollector, -): InputDateTimeType { - return { +): [InputDateTimeType, readonly Diagnostic[]] { + const diagnostics = createDiagnosticCollector(); + return diagnostics.wrap({ kind: dateTimeType.kind, name: dateTimeType.name, encode: dateTimeType.encode, - wireType: fromSdkType(sdkContext, dateTimeType.wireType, diagnostics), + wireType: diagnostics.pipe(fromSdkType(sdkContext, dateTimeType.wireType)), crossLanguageDefinitionId: dateTimeType.crossLanguageDefinitionId, - baseType: dateTimeType.baseType ? fromSdkType(sdkContext, dateTimeType.baseType, diagnostics) : undefined, + baseType: dateTimeType.baseType ? diagnostics.pipe(fromSdkType(sdkContext, dateTimeType.baseType)) : undefined, decorators: dateTimeType.decorators, external: fromSdkExternalTypeInfo(dateTimeType), - }; + }); } function fromSdkDurationType( sdkContext: CSharpEmitterContext, durationType: SdkDurationType, - diagnostics: DiagnosticCollector, -): InputDurationType { - return { +): [InputDurationType, readonly Diagnostic[]] { + const diagnostics = createDiagnosticCollector(); + return diagnostics.wrap({ kind: durationType.kind, name: durationType.name, encode: durationType.encode, - wireType: fromSdkType(sdkContext, durationType.wireType, diagnostics), + wireType: diagnostics.pipe(fromSdkType(sdkContext, durationType.wireType)), crossLanguageDefinitionId: durationType.crossLanguageDefinitionId, - baseType: durationType.baseType ? fromSdkType(sdkContext, durationType.baseType, diagnostics) : undefined, + baseType: durationType.baseType ? diagnostics.pipe(fromSdkType(sdkContext, durationType.baseType)) : undefined, decorators: durationType.decorators, external: fromSdkExternalTypeInfo(durationType), - }; + }); } function fromSdkBuiltInType( sdkContext: CSharpEmitterContext, builtInType: SdkBuiltInType, - diagnostics: DiagnosticCollector, -): InputPrimitiveType { - return { +): [InputPrimitiveType, readonly Diagnostic[]] { + const diagnostics = createDiagnosticCollector(); + return diagnostics.wrap({ kind: builtInType.kind, name: builtInType.name, encode: builtInType.encode !== builtInType.kind ? builtInType.encode : undefined, crossLanguageDefinitionId: builtInType.crossLanguageDefinitionId, - baseType: builtInType.baseType ? fromSdkType(sdkContext, builtInType.baseType, diagnostics) : undefined, + baseType: builtInType.baseType ? diagnostics.pipe(fromSdkType(sdkContext, builtInType.baseType)) : undefined, decorators: builtInType.decorators, external: fromSdkExternalTypeInfo(builtInType), - }; + }); } -function fromUnionType(sdkContext: CSharpEmitterContext, union: SdkUnionType, diagnostics: DiagnosticCollector): InputUnionType { +function fromUnionType(sdkContext: CSharpEmitterContext, union: SdkUnionType): [InputUnionType, readonly Diagnostic[]] { + const diagnostics = createDiagnosticCollector(); const variantTypes: InputType[] = []; for (const value of union.variantTypes) { - const variantType = fromSdkType(sdkContext, value, diagnostics); + const variantType = diagnostics.pipe(fromSdkType(sdkContext, value)); variantTypes.push(variantType); } - return { + return diagnostics.wrap({ kind: "union", name: union.name, variantTypes: variantTypes, namespace: union.namespace, decorators: union.decorators, external: fromSdkExternalTypeInfo(union), - }; + }); } function fromSdkConstantType( sdkContext: CSharpEmitterContext, constantType: SdkConstantType, - diagnostics: DiagnosticCollector, -): InputLiteralType { +): [InputLiteralType, readonly Diagnostic[]] { + const diagnostics = createDiagnosticCollector(); const literalType = { kind: constantType.kind, name: constantType.name, namespace: "", // constantType.namespace, TODO - constant type now does not have namespace. TCGC will add it later access: undefined, // constantType.access, TODO - constant type now does not have access. TCGC will add it later usage: UsageFlags.None, // constantType.usage, TODO - constant type now does not have usage. TCGC will add it later - valueType: fromSdkType(sdkContext, constantType.valueType, diagnostics), + valueType: diagnostics.pipe(fromSdkType(sdkContext, constantType.valueType)), value: constantType.value, decorators: constantType.decorators, }; sdkContext.__typeCache.updateConstantCache(constantType, literalType); - return literalType; + return diagnostics.wrap(literalType); } function fromSdkEnumValueType( sdkContext: CSharpEmitterContext, enumValueType: SdkEnumValueType, - diagnostics: DiagnosticCollector, -): InputEnumValueType { - return createEnumValueType(sdkContext, enumValueType, enumValueType.enumType, diagnostics); +): [InputEnumValueType, readonly Diagnostic[]] { + const diagnostics = createDiagnosticCollector(); + return diagnostics.wrap(diagnostics.pipe(createEnumValueType(sdkContext, enumValueType, enumValueType.enumType))); } function createEnumValueType( sdkContext: CSharpEmitterContext, sdkType: SdkEnumValueType | SdkConstantType, enumType: InputEnumType, - diagnostics: DiagnosticCollector, -): InputEnumValueType { - return { +): [InputEnumValueType, readonly Diagnostic[]] { + const diagnostics = createDiagnosticCollector(); + return diagnostics.wrap({ kind: "enumvalue", name: sdkType.kind === "constant" @@ -448,41 +449,41 @@ function createEnumValueType( : sdkType.name, value: typeof sdkType.value === "boolean" ? (sdkType.value ? 1 : 0) : sdkType.value, valueType: - sdkType.kind === "constant" ? sdkType.valueType : fromSdkType(sdkContext, sdkType.valueType, diagnostics), + sdkType.kind === "constant" ? sdkType.valueType : diagnostics.pipe(fromSdkType(sdkContext, sdkType.valueType)), enumType: enumType, summary: sdkType.summary, doc: sdkType.doc, decorators: sdkType.decorators, - }; + }); } function fromSdkDictionaryType( sdkContext: CSharpEmitterContext, dictionaryType: SdkDictionaryType, - diagnostics: DiagnosticCollector, -): InputDictionaryType { - return { +): [InputDictionaryType, readonly Diagnostic[]] { + const diagnostics = createDiagnosticCollector(); + return diagnostics.wrap({ kind: "dict", - keyType: fromSdkType(sdkContext, dictionaryType.keyType, diagnostics), - valueType: fromSdkType(sdkContext, dictionaryType.valueType, diagnostics), + keyType: diagnostics.pipe(fromSdkType(sdkContext, dictionaryType.keyType)), + valueType: diagnostics.pipe(fromSdkType(sdkContext, dictionaryType.valueType)), decorators: dictionaryType.decorators, external: fromSdkExternalTypeInfo(dictionaryType), - }; + }); } function fromSdkArrayType( sdkContext: CSharpEmitterContext, arrayType: SdkArrayType, - diagnostics: DiagnosticCollector, -): InputArrayType { - return { +): [InputArrayType, readonly Diagnostic[]] { + const diagnostics = createDiagnosticCollector(); + return diagnostics.wrap({ kind: "array", name: arrayType.name, - valueType: fromSdkType(sdkContext, arrayType.valueType, diagnostics), + valueType: diagnostics.pipe(fromSdkType(sdkContext, arrayType.valueType)), crossLanguageDefinitionId: arrayType.crossLanguageDefinitionId, decorators: arrayType.decorators, external: fromSdkExternalTypeInfo(arrayType), - }; + }); } function fromSdkEndpointType(): InputPrimitiveType { diff --git a/packages/http-client-csharp/emitter/src/lib/typespec-server.ts b/packages/http-client-csharp/emitter/src/lib/typespec-server.ts index d5e76b8cb49..e1f5e5c8a84 100644 --- a/packages/http-client-csharp/emitter/src/lib/typespec-server.ts +++ b/packages/http-client-csharp/emitter/src/lib/typespec-server.ts @@ -38,7 +38,7 @@ export function resolveServers( name: "url", crossLanguageDefinitionId: "TypeSpec.url", } - : fromSdkType(sdkContext, getClientType(sdkContext, prop), diagnostics); + : diagnostics.pipe(fromSdkType(sdkContext, getClientType(sdkContext, prop))); if (value) { defaultValue = { diff --git a/packages/http-client-csharp/emitter/test/Unit/namespace-converter.test.ts b/packages/http-client-csharp/emitter/test/Unit/namespace-converter.test.ts index 7c85bcfbede..96359b0d73c 100644 --- a/packages/http-client-csharp/emitter/test/Unit/namespace-converter.test.ts +++ b/packages/http-client-csharp/emitter/test/Unit/namespace-converter.test.ts @@ -51,8 +51,7 @@ describe("Namespace Converter", () => { const context = createEmitterContext(program); const sdkContext = await createCSharpSdkContext(context); const sdkPackage = sdkContext.sdkPackage; - const diagnostics = createDiagnosticCollector(); - const parsedNamespaces = fromSdkNamespaces(sdkContext, sdkPackage.namespaces, diagnostics); + const [parsedNamespaces, diagnostics] = fromSdkNamespaces(sdkContext, sdkPackage.namespaces); strictEqual(parsedNamespaces.length, 1); From 1bde6f845b684731bcd97f9c8e06486d0d3b0331 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 28 Jan 2026 07:41:34 +0000 Subject: [PATCH 12/18] Refactor createCodeModel to use diagnostic collector pattern properly - Use createDiagnosticCollector() instead of manual Diagnostic[] array - Use diagnostics.pipe() to unwrap tuple-returning functions - Use diagnostics.wrap() for return value - Use for loops to add arrays of diagnostics instead of spread - Follows the same pattern as createModel and other functions Co-authored-by: ArcturusZhang <10554446+ArcturusZhang@users.noreply.github.com> --- .../http-client-csharp/emitter/src/emitter.ts | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/packages/http-client-csharp/emitter/src/emitter.ts b/packages/http-client-csharp/emitter/src/emitter.ts index 3f0107f862d..98edd1c0c28 100644 --- a/packages/http-client-csharp/emitter/src/emitter.ts +++ b/packages/http-client-csharp/emitter/src/emitter.ts @@ -80,6 +80,7 @@ export async function createCodeModel( context: EmitContext, updateCodeModel?: (model: CodeModel, context: CSharpEmitterContext) => CodeModel, ): Promise<[void, readonly Diagnostic[]]> { + const diagnostics = createDiagnosticCollector(); const program: Program = context.program; const options = resolveOptions(context); const outputFolder = context.emitterOutputDir; @@ -87,8 +88,6 @@ export async function createCodeModel( /* set the log level. */ const logger = new Logger(program, options.logLevel ?? LoggerLevel.INFO, true); - const diagnostics: Diagnostic[] = []; - if (!program.compilerOptions.noEmit && !program.hasError()) { // Write out the dotnet model to the output path const sdkContext = createCSharpEmitterContext( @@ -99,10 +98,11 @@ export async function createCodeModel( ), logger, ); - diagnostics.push(...sdkContext.diagnostics); + for (const diag of sdkContext.diagnostics) { + diagnostics.add(diag); + } - const [root, modelDiagnostics] = createModel(sdkContext); - diagnostics.push(...modelDiagnostics); + const root = diagnostics.pipe(createModel(sdkContext)); if (root) { // Use the provided callback or default to identity function @@ -116,8 +116,9 @@ export async function createCodeModel( } // emit tspCodeModel.json - const writeDiagnostics = await writeCodeModel(sdkContext, updatedRoot, outputFolder); - diagnostics.push(...writeDiagnostics); + for (const diag of await writeCodeModel(sdkContext, updatedRoot, outputFolder)) { + diagnostics.add(diag); + } const namespace = updatedRoot.name; const configurations: Configuration = createConfiguration(options, namespace, sdkContext); @@ -147,8 +148,7 @@ export async function createCodeModel( debug: options.debug ?? false, }); if (result.exitCode !== 0) { - const [isValid, validationDiagnostics] = await _validateDotNetSdk(sdkContext, _minSupportedDotNetSdkVersion); - diagnostics.push(...validationDiagnostics); + const isValid = diagnostics.pipe(await _validateDotNetSdk(sdkContext, _minSupportedDotNetSdkVersion)); // if the dotnet sdk is valid, the error is not dependency issue, log it as normal if (isValid) { throw new Error( @@ -157,8 +157,7 @@ export async function createCodeModel( } } } catch (error: any) { - const [isValid, validationDiagnostics] = await _validateDotNetSdk(sdkContext, _minSupportedDotNetSdkVersion); - diagnostics.push(...validationDiagnostics); + const isValid = diagnostics.pipe(await _validateDotNetSdk(sdkContext, _minSupportedDotNetSdkVersion)); // if the dotnet sdk is valid, the error is not dependency issue, log it as normal if (isValid) throw new Error(error); } @@ -170,7 +169,7 @@ export async function createCodeModel( } } - return [void 0, diagnostics]; + return diagnostics.wrap(void 0); } /** From 1422867209987f2efb84d0177107c1a4d6761fe7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 28 Jan 2026 08:28:28 +0000 Subject: [PATCH 13/18] Co-authored-by: ArcturusZhang <10554446+ArcturusZhang@users.noreply.github.com> --- .../emitter/src/code-model-writer.ts | 19 ++++--------- .../http-client-csharp/emitter/src/emitter.ts | 23 +++++++-------- .../emitter/src/lib/example-converter.ts | 7 +++-- .../emitter/src/lib/logger.ts | 16 ++++------- .../emitter/src/lib/operation-converter.ts | 2 +- .../emitter/src/lib/service-authentication.ts | 28 +++++++++---------- .../emitter/src/lib/typespec-server.ts | 8 ++++-- .../test/Unit/client-model-builder.test.ts | 4 +-- .../emitter/test/Unit/utils/test-util.ts | 18 ++---------- 9 files changed, 50 insertions(+), 75 deletions(-) diff --git a/packages/http-client-csharp/emitter/src/code-model-writer.ts b/packages/http-client-csharp/emitter/src/code-model-writer.ts index 4884276778f..d6a46edf1e5 100644 --- a/packages/http-client-csharp/emitter/src/code-model-writer.ts +++ b/packages/http-client-csharp/emitter/src/code-model-writer.ts @@ -20,13 +20,13 @@ export async function writeCodeModel( context: CSharpEmitterContext, codeModel: CodeModel, outputFolder: string, -): Promise { +): Promise<[void, readonly Diagnostic[]]> { const diagnostics = createDiagnosticCollector(); await context.program.host.writeFile( resolvePath(outputFolder, tspOutputFileName), - prettierOutput(JSON.stringify(diagnostics.pipe(buildJson(context, codeModel)), transformJSONProperties, 2)), + prettierOutput(JSON.stringify(buildJson(context, codeModel), transformJSONProperties, 2)), ); - return diagnostics.diagnostics; + return diagnostics.wrap(undefined); } /** @@ -34,12 +34,11 @@ export async function writeCodeModel( * @param context - The CSharp emitter context * @param codeModel - The code model to build */ -function buildJson(context: CSharpEmitterContext, codeModel: CodeModel): [any, readonly Diagnostic[]] { - const diagnostics = createDiagnosticCollector(); +function buildJson(context: CSharpEmitterContext, codeModel: CodeModel): any { const objectsIds = new Map(); const stack: any[] = []; - return diagnostics.wrap(doBuildJson(codeModel, stack)); + return doBuildJson(codeModel, stack); function doBuildJson(obj: any, stack: any[]): any { // check if this is a primitive type or null or undefined @@ -77,13 +76,7 @@ function buildJson(context: CSharpEmitterContext, codeModel: CodeModel): [any, r function handleObject(obj: any, id: string | undefined, stack: any[]): any { if (stack.includes(obj)) { // we have a cyclical reference, we should not continue - diagnostics.add( - createDiagnostic({ - code: "general-warning", - format: { message: `Cyclical reference detected in the code model (id: ${id}).` }, - target: NoTarget, - }), - ); + context.logger.warn(`Cyclical reference detected in the code model (id: ${id}).`); return undefined; } diff --git a/packages/http-client-csharp/emitter/src/emitter.ts b/packages/http-client-csharp/emitter/src/emitter.ts index 98edd1c0c28..c48230b6d78 100644 --- a/packages/http-client-csharp/emitter/src/emitter.ts +++ b/packages/http-client-csharp/emitter/src/emitter.ts @@ -5,7 +5,6 @@ import { createSdkContext, SdkContext } from "@azure-tools/typespec-client-gener import { createDiagnosticCollector, Diagnostic, - DiagnosticCollector, EmitContext, getDirectoryPath, joinPaths, @@ -86,7 +85,7 @@ export async function createCodeModel( const outputFolder = context.emitterOutputDir; /* set the log level. */ - const logger = new Logger(program, options.logLevel ?? LoggerLevel.INFO, true); + const logger = new Logger(program, options.logLevel ?? LoggerLevel.INFO); if (!program.compilerOptions.noEmit && !program.hasError()) { // Write out the dotnet model to the output path @@ -116,9 +115,7 @@ export async function createCodeModel( } // emit tspCodeModel.json - for (const diag of await writeCodeModel(sdkContext, updatedRoot, outputFolder)) { - diagnostics.add(diag); - } + diagnostics.pipe(await writeCodeModel(sdkContext, updatedRoot, outputFolder)); const namespace = updatedRoot.name; const configurations: Configuration = createConfiguration(options, namespace, sdkContext); @@ -169,7 +166,7 @@ export async function createCodeModel( } } - return diagnostics.wrap(void 0); + return diagnostics.wrap(undefined); } /** @@ -230,7 +227,7 @@ export async function _validateDotNetSdk( const diagnostics = createDiagnosticCollector(); try { const result = await execAsync("dotnet", ["--version"], { stdio: "pipe" }); - return diagnostics.wrap(validateDotNetSdkVersionCore(sdkContext, result.stdout, minMajorVersion, diagnostics)); + return diagnostics.wrap(diagnostics.pipe(validateDotNetSdkVersionCore(sdkContext, result.stdout, minMajorVersion))); } catch (error: any) { if (error && "code" in error && error["code"] === "ENOENT") { diagnostics.add( @@ -253,15 +250,15 @@ function validateDotNetSdkVersionCore( sdkContext: CSharpEmitterContext, version: string, minMajorVersion: number, - diagnostics: DiagnosticCollector, -): boolean { +): [boolean, readonly Diagnostic[]] { + const diagnostics = createDiagnosticCollector(); if (version) { const dotIndex = version.indexOf("."); const firstPart = dotIndex === -1 ? version : version.substring(0, dotIndex); const major = Number(firstPart); if (isNaN(major)) { - return false; + return diagnostics.wrap(false); } if (major < minMajorVersion) { diagnostics.add( @@ -276,9 +273,9 @@ function validateDotNetSdkVersionCore( target: NoTarget, }), ); - return false; + return diagnostics.wrap(false); } - return true; + return diagnostics.wrap(true); } else { diagnostics.add( createDiagnostic({ @@ -287,7 +284,7 @@ function validateDotNetSdkVersionCore( target: NoTarget, }), ); - return false; + return diagnostics.wrap(false); } } diff --git a/packages/http-client-csharp/emitter/src/lib/example-converter.ts b/packages/http-client-csharp/emitter/src/lib/example-converter.ts index c1300ee2082..e4f61940d43 100644 --- a/packages/http-client-csharp/emitter/src/lib/example-converter.ts +++ b/packages/http-client-csharp/emitter/src/lib/example-converter.ts @@ -16,7 +16,7 @@ import { SdkUnionExampleValue, SdkUnknownExampleValue, } from "@azure-tools/typespec-client-generator-core"; -import { createDiagnosticCollector, DiagnosticCollector } from "@typespec/compiler"; +import { createDiagnosticCollector, Diagnostic } from "@typespec/compiler"; import { CSharpEmitterContext } from "../sdk-context.js"; import { InputArrayExampleValue, @@ -48,12 +48,13 @@ import { fromSdkType } from "./type-converter.js"; export function fromSdkHttpExamples( sdkContext: CSharpEmitterContext, examples: SdkHttpOperationExample[], -): InputHttpOperationExample[] { +): [InputHttpOperationExample[], readonly Diagnostic[]] { // Create a diagnostics collector for internal use // Any errors in examples won't prevent the code model from being generated const diagnostics = createDiagnosticCollector(); - return examples.map((example) => fromSdkHttpExample(example)); + const result = examples.map((example) => fromSdkHttpExample(example)); + return diagnostics.wrap(result); function fromSdkHttpExample(example: SdkHttpOperationExample): InputHttpOperationExample { return { diff --git a/packages/http-client-csharp/emitter/src/lib/logger.ts b/packages/http-client-csharp/emitter/src/lib/logger.ts index f9461e37d83..654f0bf1aea 100644 --- a/packages/http-client-csharp/emitter/src/lib/logger.ts +++ b/packages/http-client-csharp/emitter/src/lib/logger.ts @@ -14,22 +14,12 @@ export class Logger { private level: LoggerLevel; private program: Program; - public constructor(program: Program, level: LoggerLevel, collectDiagnostics: boolean = false) { + public constructor(program: Program, level: LoggerLevel) { this.tracer = getTracer(program); this.level = level; this.program = program; } - /** - * Get collected diagnostics. Only available if the logger was created with collectDiagnostics=true. - * @returns The collected diagnostics. - * @beta - * @deprecated This method is deprecated and will be removed. Use sdkContext.__diagnostics instead. - */ - public getDiagnostics(): readonly Diagnostic[] { - return []; - } - trace(level: LoggerLevel, message: string): void { switch (level) { case LoggerLevel.INFO: @@ -65,4 +55,8 @@ export class Logger { this.tracer.trace(LoggerLevel.VERBOSE, message); } } + + warn(message: string): void { + this.tracer.trace("warning", message); + } } diff --git a/packages/http-client-csharp/emitter/src/lib/operation-converter.ts b/packages/http-client-csharp/emitter/src/lib/operation-converter.ts index ac1dcf22406..990043f9dc7 100644 --- a/packages/http-client-csharp/emitter/src/lib/operation-converter.ts +++ b/packages/http-client-csharp/emitter/src/lib/operation-converter.ts @@ -212,7 +212,7 @@ export function fromSdkServiceMethodOperation( crossLanguageDefinitionId: method.crossLanguageDefinitionId, decorators: method.decorators, examples: method.operation.examples - ? fromSdkHttpExamples(sdkContext, method.operation.examples) + ? diagnostics.pipe(fromSdkHttpExamples(sdkContext, method.operation.examples)) : undefined, }; diff --git a/packages/http-client-csharp/emitter/src/lib/service-authentication.ts b/packages/http-client-csharp/emitter/src/lib/service-authentication.ts index 2800e4d1c8f..7f7b73e17f0 100644 --- a/packages/http-client-csharp/emitter/src/lib/service-authentication.ts +++ b/packages/http-client-csharp/emitter/src/lib/service-authentication.ts @@ -7,7 +7,7 @@ import { SdkHttpOperation, SdkPackage, } from "@azure-tools/typespec-client-generator-core"; -import { createDiagnosticCollector, Diagnostic, DiagnosticCollector, NoTarget } from "@typespec/compiler"; +import { createDiagnosticCollector, Diagnostic, NoTarget } from "@typespec/compiler"; import { Oauth2Auth, OAuth2Flow } from "@typespec/http"; import { CSharpEmitterContext } from "../sdk-context.js"; import { createDiagnostic } from "./lib.js"; @@ -36,7 +36,7 @@ export function processServiceAuthentication( const inputAuth: InputAuth = {}; if (authClientParameter.type.kind === "credential") { - const auth = processAuthType(sdkContext, authClientParameter.type, diagnostics); + const auth = diagnostics.pipe(processAuthType(sdkContext, authClientParameter.type)); if (!auth && authClientParameter.type.scheme.type !== "noAuth") { diagnostics.add( createDiagnostic({ @@ -54,7 +54,7 @@ export function processServiceAuthentication( let containsNoAuth = false; for (const authType of authClientParameter.type.variantTypes) { containsNoAuth = containsNoAuth || authType.scheme.type === "noAuth"; - const auth = processAuthType(sdkContext, authType, diagnostics); + const auth = diagnostics.pipe(processAuthType(sdkContext, authType)); if (auth?.apiKey) { inputAuth.apiKey = auth.apiKey; } @@ -83,8 +83,8 @@ export function processServiceAuthentication( function processAuthType( sdkContext: CSharpEmitterContext, credentialType: SdkCredentialType, - diagnostics: DiagnosticCollector, -): InputAuth | undefined { +): [InputAuth | undefined, readonly Diagnostic[]] { + const diagnostics = createDiagnosticCollector(); const scheme = credentialType.scheme; switch (scheme.type) { case "apiKey": @@ -98,11 +98,11 @@ function processAuthType( target: credentialType.__raw ?? NoTarget, }), ); - return undefined; + return diagnostics.wrap(undefined); } - return { apiKey: { name: scheme.name, in: scheme.in } } as InputAuth; + return diagnostics.wrap({ apiKey: { name: scheme.name, in: scheme.in } } as InputAuth); case "oauth2": - return processOAuth2(scheme); + return diagnostics.wrap(processOAuth2(scheme)); case "http": { const schemeOrApiKeyPrefix = scheme.scheme; switch (schemeOrApiKeyPrefix) { @@ -114,23 +114,23 @@ function processAuthType( target: credentialType.__raw ?? NoTarget, }), ); - return undefined; + return diagnostics.wrap(undefined); case "Bearer": - return { + return diagnostics.wrap({ apiKey: { name: "Authorization", in: "header", prefix: "Bearer", }, - }; + }); default: - return { + return diagnostics.wrap({ apiKey: { name: "Authorization", in: "header", prefix: schemeOrApiKeyPrefix, }, - }; + }); } } default: @@ -141,7 +141,7 @@ function processAuthType( target: credentialType.__raw ?? NoTarget, }), ); - return undefined; + return diagnostics.wrap(undefined); } } diff --git a/packages/http-client-csharp/emitter/src/lib/typespec-server.ts b/packages/http-client-csharp/emitter/src/lib/typespec-server.ts index e1f5e5c8a84..b7afef9f730 100644 --- a/packages/http-client-csharp/emitter/src/lib/typespec-server.ts +++ b/packages/http-client-csharp/emitter/src/lib/typespec-server.ts @@ -2,7 +2,7 @@ // Licensed under the MIT License. See License.txt in the project root for license information. import { getClientType } from "@azure-tools/typespec-client-generator-core"; -import { createDiagnosticCollector, getDoc, getSummary, Value } from "@typespec/compiler"; +import { createDiagnosticCollector, Diagnostic, getDoc, getSummary, Value } from "@typespec/compiler"; import { HttpServer } from "@typespec/http"; import { getExtensions } from "@typespec/openapi"; import { CSharpEmitterContext } from "../sdk-context.js"; @@ -20,11 +20,11 @@ export interface TypeSpecServer { export function resolveServers( sdkContext: CSharpEmitterContext, servers: HttpServer[], -): TypeSpecServer[] { +): [TypeSpecServer[], readonly Diagnostic[]] { // Create a diagnostics collector for internal use const diagnostics = createDiagnosticCollector(); - return servers.map((server) => { + const result = servers.map((server) => { const parameters: InputEndpointParameter[] = []; let url: string = server.url; const endpoint: string = url.replace("http://", "").replace("https://", "").split("/")[0]; @@ -104,6 +104,8 @@ export function resolveServers( parameters, }; }); + + return diagnostics.wrap(result); } function getDefaultValue(value: Value): any { diff --git a/packages/http-client-csharp/emitter/test/Unit/client-model-builder.test.ts b/packages/http-client-csharp/emitter/test/Unit/client-model-builder.test.ts index d36ca4ffe3e..d314421880d 100644 --- a/packages/http-client-csharp/emitter/test/Unit/client-model-builder.test.ts +++ b/packages/http-client-csharp/emitter/test/Unit/client-model-builder.test.ts @@ -470,7 +470,7 @@ describe("createModel diagnostic collection", () => { runner, ); const context = createEmitterContext(program); - const sdkContext = await createCSharpSdkContext(context, true); // Enable diagnostic collection + const sdkContext = await createCSharpSdkContext(context); const result = createModel(sdkContext); // Verify the result is a tuple @@ -500,7 +500,7 @@ describe("createModel diagnostic collection", () => { runner, ); const context = createEmitterContext(program); - const sdkContext = await createCSharpSdkContext(context, true); // Enable diagnostic collection + const sdkContext = await createCSharpSdkContext(context); const [, diagnostics] = createModel(sdkContext); // Verify diagnostics array exists (may be empty or contain diagnostics) diff --git a/packages/http-client-csharp/emitter/test/Unit/utils/test-util.ts b/packages/http-client-csharp/emitter/test/Unit/utils/test-util.ts index 8dd90fc57e0..e1e2541800f 100644 --- a/packages/http-client-csharp/emitter/test/Unit/utils/test-util.ts +++ b/packages/http-client-csharp/emitter/test/Unit/utils/test-util.ts @@ -119,25 +119,13 @@ export function createEmitterContext( /* We always need to pass in the emitter name now that it is required so making a helper to do this. */ export async function createCSharpSdkContext( program: EmitContext, - sdkContextOptionsOrCollectDiagnostics: CreateSdkContextOptions | boolean = {}, - sdkContextOptions?: CreateSdkContextOptions, + sdkContextOptions: CreateSdkContextOptions = {}, ): Promise { - // Handle backward compatibility - if second param is boolean, it's collectDiagnostics - let collectDiagnostics = false; - let options: CreateSdkContextOptions = {}; - - if (typeof sdkContextOptionsOrCollectDiagnostics === "boolean") { - collectDiagnostics = sdkContextOptionsOrCollectDiagnostics; - options = sdkContextOptions ?? {}; - } else { - options = sdkContextOptionsOrCollectDiagnostics; - } - const createSdkContext = await getCreateSdkContext(); - const context = await createSdkContext(program, "@typespec/http-client-csharp", options); + const context = await createSdkContext(program, "@typespec/http-client-csharp", sdkContextOptions); const Logger = await getLogger(); return createCSharpEmitterContext( context, - new Logger(program.program, LoggerLevel.INFO, collectDiagnostics), + new Logger(program.program, LoggerLevel.INFO), ); } From 59709e0372dc007877baf5eb27d617c447b3a948 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 28 Jan 2026 08:51:32 +0000 Subject: [PATCH 14/18] Final cleanup: revert writeCodeModel and remove update-code-model option - writeCodeModel no longer returns diagnostics (buildJson uses logger.warn, not diagnostics) - Removed update-code-model option from CSharpEmitterOptions completely - Removed update-code-model from schema, defaultOptions, and skipKeys - Removed updateCodeModel parameter from createCodeModel function - Removed test for update-code-model callback - Kept for loop for adding TCGC diagnostics (no bulk add method exists) Co-authored-by: ArcturusZhang <10554446+ArcturusZhang@users.noreply.github.com> --- .../emitter/src/code-model-writer.ts | 4 +--- .../http-client-csharp/emitter/src/emitter.ts | 20 ++++--------------- .../http-client-csharp/emitter/src/options.ts | 10 ---------- .../emitter/test/Unit/emitter.test.ts | 10 ---------- 4 files changed, 5 insertions(+), 39 deletions(-) diff --git a/packages/http-client-csharp/emitter/src/code-model-writer.ts b/packages/http-client-csharp/emitter/src/code-model-writer.ts index d6a46edf1e5..cb58710ea2b 100644 --- a/packages/http-client-csharp/emitter/src/code-model-writer.ts +++ b/packages/http-client-csharp/emitter/src/code-model-writer.ts @@ -20,13 +20,11 @@ export async function writeCodeModel( context: CSharpEmitterContext, codeModel: CodeModel, outputFolder: string, -): Promise<[void, readonly Diagnostic[]]> { - const diagnostics = createDiagnosticCollector(); +) { await context.program.host.writeFile( resolvePath(outputFolder, tspOutputFileName), prettierOutput(JSON.stringify(buildJson(context, codeModel), transformJSONProperties, 2)), ); - return diagnostics.wrap(undefined); } /** diff --git a/packages/http-client-csharp/emitter/src/emitter.ts b/packages/http-client-csharp/emitter/src/emitter.ts index c48230b6d78..e9d19779049 100644 --- a/packages/http-client-csharp/emitter/src/emitter.ts +++ b/packages/http-client-csharp/emitter/src/emitter.ts @@ -60,24 +60,18 @@ function findProjectRoot(path: string): string | undefined { * import { createCodeModel } from "@typespec/http-client-csharp"; * * export async function $onEmit(context: EmitContext) { - * const updateCodeModel = (model: CodeModel, context: CSharpEmitterContext) => { - * // Customize the code model here - * return model; - * }; - * const [, diagnostics] = await createCodeModel(context, updateCodeModel); + * const [, diagnostics] = await createCodeModel(context); * // Process diagnostics as needed * context.program.reportDiagnostics(diagnostics); * } * ``` * * @param context - The emit context - * @param updateCodeModel - Optional callback to modify the code model before emission. Defaults to identity function. * @returns A tuple containing void and any diagnostics that were generated during the emission * @beta */ export async function createCodeModel( context: EmitContext, - updateCodeModel?: (model: CodeModel, context: CSharpEmitterContext) => CodeModel, ): Promise<[void, readonly Diagnostic[]]> { const diagnostics = createDiagnosticCollector(); const program: Program = context.program; @@ -104,10 +98,6 @@ export async function createCodeModel( const root = diagnostics.pipe(createModel(sdkContext)); if (root) { - // Use the provided callback or default to identity function - const updateCodeModelFn = updateCodeModel ?? ((model: CodeModel) => model); - const updatedRoot = updateCodeModelFn(root, sdkContext); - const generatedFolder = resolvePath(outputFolder, "src", "Generated"); if (!fs.existsSync(generatedFolder)) { @@ -115,9 +105,9 @@ export async function createCodeModel( } // emit tspCodeModel.json - diagnostics.pipe(await writeCodeModel(sdkContext, updatedRoot, outputFolder)); + await writeCodeModel(sdkContext, root, outputFolder); - const namespace = updatedRoot.name; + const namespace = root.name; const configurations: Configuration = createConfiguration(options, namespace, sdkContext); //emit configuration.json @@ -175,8 +165,7 @@ export async function createCodeModel( * @beta */ export async function $onEmit(context: EmitContext) { - const options = resolveOptions(context); - const [, diagnostics] = await createCodeModel(context, options["update-code-model"]); + const [, diagnostics] = await createCodeModel(context); context.program.reportDiagnostics(diagnostics); } @@ -187,7 +176,6 @@ export function createConfiguration( ): Configuration { const skipKeys = [ "new-project", - "update-code-model", "sdk-context-options", "save-inputs", "generator-name", diff --git a/packages/http-client-csharp/emitter/src/options.ts b/packages/http-client-csharp/emitter/src/options.ts index 42196440cac..9ae08884a9e 100644 --- a/packages/http-client-csharp/emitter/src/options.ts +++ b/packages/http-client-csharp/emitter/src/options.ts @@ -1,10 +1,8 @@ import { CreateSdkContextOptions } from "@azure-tools/typespec-client-generator-core"; import { EmitContext, JSONSchemaType } from "@typespec/compiler"; import { _defaultGeneratorName } from "./constants.js"; -import { CSharpEmitterContext } from "./index.js"; import { DYNAMIC_MODEL_DECORATOR_PATTERN } from "./lib/decorators.js"; import { LoggerLevel } from "./lib/logger-level.js"; -import { CodeModel } from "./type/code-model.js"; /** * The emitter options for the CSharp emitter. @@ -20,7 +18,6 @@ export interface CSharpEmitterOptions { "disable-xml-docs"?: boolean; "generator-name"?: string; "emitter-extension-path"?: string; - "update-code-model"?: (model: CodeModel, context: CSharpEmitterContext) => CodeModel; "sdk-context-options"?: CreateSdkContextOptions; "generate-protocol-methods"?: boolean; "generate-convenience-methods"?: boolean; @@ -116,12 +113,6 @@ export const CSharpEmitterOptionsSchema: JSONSchemaType = description: "Allows emitter authors to specify the path to a custom emitter package, allowing you to extend the emitter behavior. This should be set to `import.meta.url` if you are using a custom emitter.", }, - "update-code-model": { - type: "object", - nullable: true, - description: - "Allows emitter authors to specify a custom function to modify the generated code model before emitting. This is useful for modifying the code model before it is passed to the generator.", - }, license: { type: "object", additionalProperties: false, @@ -160,7 +151,6 @@ export const defaultOptions = { debug: undefined, logLevel: LoggerLevel.INFO, "generator-name": _defaultGeneratorName, - "update-code-model": (model: CodeModel, context: CSharpEmitterContext) => model, "sdk-context-options": { additionalDecorators: [DYNAMIC_MODEL_DECORATOR_PATTERN], }, diff --git a/packages/http-client-csharp/emitter/test/Unit/emitter.test.ts b/packages/http-client-csharp/emitter/test/Unit/emitter.test.ts index 56bf635ff2d..1f8bc49bbba 100644 --- a/packages/http-client-csharp/emitter/test/Unit/emitter.test.ts +++ b/packages/http-client-csharp/emitter/test/Unit/emitter.test.ts @@ -84,16 +84,6 @@ describe("$onEmit tests", () => { $onEmit = (await import("../../src/emitter.js")).$onEmit; }); - it("should apply the update-code-model callback just once", async () => { - const context: EmitContext = createEmitterContext(program); - const updateCallback = vi.fn().mockImplementation((model: CodeModel) => { - return model; - }); - context.options["update-code-model"] = updateCallback; - await $onEmit(context); - expect(updateCallback).toHaveBeenCalledTimes(1); - }); - it("should apply sdk-context-options", async () => { const context: EmitContext = createEmitterContext(program); const additionalDecorators = ["Decorator1", "Decorator2"]; From 239dba66e4840a3bcf936e7cc201c0f76323db6f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 28 Jan 2026 09:29:06 +0000 Subject: [PATCH 15/18] Add back updateCodeModel callback parameter to createCodeModel - Restored updateCodeModel optional parameter to createCodeModel function - Parameter allows downstream emitters to customize code model - Updated documentation with usage example - Added test for updateCodeModel callback functionality - Callback NOT added to options - only available as function parameter Co-authored-by: ArcturusZhang <10554446+ArcturusZhang@users.noreply.github.com> --- .../http-client-csharp/emitter/src/emitter.ts | 15 ++++++++++++--- .../emitter/test/Unit/emitter.test.ts | 19 +++++++++++++++++-- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/packages/http-client-csharp/emitter/src/emitter.ts b/packages/http-client-csharp/emitter/src/emitter.ts index e9d19779049..6d2664df72a 100644 --- a/packages/http-client-csharp/emitter/src/emitter.ts +++ b/packages/http-client-csharp/emitter/src/emitter.ts @@ -60,18 +60,24 @@ function findProjectRoot(path: string): string | undefined { * import { createCodeModel } from "@typespec/http-client-csharp"; * * export async function $onEmit(context: EmitContext) { - * const [, diagnostics] = await createCodeModel(context); + * const updateCodeModel = (model: CodeModel, context: CSharpEmitterContext) => { + * // Customize the code model here + * return model; + * }; + * const [, diagnostics] = await createCodeModel(context, updateCodeModel); * // Process diagnostics as needed * context.program.reportDiagnostics(diagnostics); * } * ``` * * @param context - The emit context + * @param updateCodeModel - Optional callback to modify the code model before emission * @returns A tuple containing void and any diagnostics that were generated during the emission * @beta */ export async function createCodeModel( context: EmitContext, + updateCodeModel?: (model: CodeModel, context: CSharpEmitterContext) => CodeModel, ): Promise<[void, readonly Diagnostic[]]> { const diagnostics = createDiagnosticCollector(); const program: Program = context.program; @@ -98,6 +104,9 @@ export async function createCodeModel( const root = diagnostics.pipe(createModel(sdkContext)); if (root) { + // Apply optional code model update callback + const updatedRoot = updateCodeModel ? updateCodeModel(root, sdkContext) : root; + const generatedFolder = resolvePath(outputFolder, "src", "Generated"); if (!fs.existsSync(generatedFolder)) { @@ -105,9 +114,9 @@ export async function createCodeModel( } // emit tspCodeModel.json - await writeCodeModel(sdkContext, root, outputFolder); + await writeCodeModel(sdkContext, updatedRoot, outputFolder); - const namespace = root.name; + const namespace = updatedRoot.name; const configurations: Configuration = createConfiguration(options, namespace, sdkContext); //emit configuration.json diff --git a/packages/http-client-csharp/emitter/test/Unit/emitter.test.ts b/packages/http-client-csharp/emitter/test/Unit/emitter.test.ts index 1f8bc49bbba..941da354792 100644 --- a/packages/http-client-csharp/emitter/test/Unit/emitter.test.ts +++ b/packages/http-client-csharp/emitter/test/Unit/emitter.test.ts @@ -19,6 +19,10 @@ import { describe("$onEmit tests", () => { let program: Program; let $onEmit: (arg0: EmitContext) => any; + let createCodeModel: ( + context: EmitContext, + updateCodeModel?: (model: CodeModel, context: any) => CodeModel, + ) => any; beforeEach(async () => { // Reset the dynamically imported module to ensure a clean state vi.resetModules(); @@ -79,9 +83,20 @@ describe("$onEmit tests", () => { reportDiagnostics: vi.fn(), } as unknown as Program; - // dynamically import the module to get the $onEmit function + // dynamically import the module to get the $onEmit and createCodeModel functions // we avoid importing it at the top to allow mocking of dependencies - $onEmit = (await import("../../src/emitter.js")).$onEmit; + const emitterModule = await import("../../src/emitter.js"); + $onEmit = emitterModule.$onEmit; + createCodeModel = emitterModule.createCodeModel; + }); + + it("should apply the updateCodeModel callback", async () => { + const context: EmitContext = createEmitterContext(program); + const updateCallback = vi.fn().mockImplementation((model: CodeModel) => { + return model; + }); + await createCodeModel(context, updateCallback); + expect(updateCallback).toHaveBeenCalledTimes(1); }); it("should apply sdk-context-options", async () => { From 4569ec6ac2c05b885f64597190a7df66223ceb3a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 29 Jan 2026 03:39:57 +0000 Subject: [PATCH 16/18] Rename createCodeModel to $emitCodeModel and export it - Renamed createCodeModel to $emitCodeModel (better reflects that it performs emission and returns void) - Exported $emitCodeModel in index.ts for downstream emitters - Updated all references in emitter.ts and tests - Updated documentation with new function name - All tests pass (174 TypeScript + 998 generator tests) Co-authored-by: ArcturusZhang <10554446+ArcturusZhang@users.noreply.github.com> --- .../http-client-csharp/emitter/src/emitter.ts | 8 +++---- .../http-client-csharp/emitter/src/index.ts | 2 +- .../emitter/test/Unit/emitter.test.ts | 22 +++++++++---------- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/packages/http-client-csharp/emitter/src/emitter.ts b/packages/http-client-csharp/emitter/src/emitter.ts index 6d2664df72a..663fa55ac01 100644 --- a/packages/http-client-csharp/emitter/src/emitter.ts +++ b/packages/http-client-csharp/emitter/src/emitter.ts @@ -57,14 +57,14 @@ function findProjectRoot(path: string): string | undefined { * * @example * ```typescript - * import { createCodeModel } from "@typespec/http-client-csharp"; + * import { $emitCodeModel } from "@typespec/http-client-csharp"; * * export async function $onEmit(context: EmitContext) { * const updateCodeModel = (model: CodeModel, context: CSharpEmitterContext) => { * // Customize the code model here * return model; * }; - * const [, diagnostics] = await createCodeModel(context, updateCodeModel); + * const [, diagnostics] = await $emitCodeModel(context, updateCodeModel); * // Process diagnostics as needed * context.program.reportDiagnostics(diagnostics); * } @@ -75,7 +75,7 @@ function findProjectRoot(path: string): string | undefined { * @returns A tuple containing void and any diagnostics that were generated during the emission * @beta */ -export async function createCodeModel( +export async function $emitCodeModel( context: EmitContext, updateCodeModel?: (model: CodeModel, context: CSharpEmitterContext) => CodeModel, ): Promise<[void, readonly Diagnostic[]]> { @@ -174,7 +174,7 @@ export async function createCodeModel( * @beta */ export async function $onEmit(context: EmitContext) { - const [, diagnostics] = await createCodeModel(context); + const [, diagnostics] = await $emitCodeModel(context); context.program.reportDiagnostics(diagnostics); } diff --git a/packages/http-client-csharp/emitter/src/index.ts b/packages/http-client-csharp/emitter/src/index.ts index 5a0b24094c7..060fee5e0fc 100644 --- a/packages/http-client-csharp/emitter/src/index.ts +++ b/packages/http-client-csharp/emitter/src/index.ts @@ -3,7 +3,7 @@ export { writeCodeModel } from "./code-model-writer.js"; export { configurationFileName, tspOutputFileName } from "./constants.js"; -export { $onEmit } from "./emitter.js"; +export { $emitCodeModel, $onEmit } from "./emitter.js"; // we export `createModel` only for autorest.csharp because it uses the emitter to generate the code model file but not calling the dll here // we could remove this export when in the future we deprecate autorest.csharp export { createModel } from "./lib/client-model-builder.js"; diff --git a/packages/http-client-csharp/emitter/test/Unit/emitter.test.ts b/packages/http-client-csharp/emitter/test/Unit/emitter.test.ts index 941da354792..b06de888cbd 100644 --- a/packages/http-client-csharp/emitter/test/Unit/emitter.test.ts +++ b/packages/http-client-csharp/emitter/test/Unit/emitter.test.ts @@ -19,7 +19,7 @@ import { describe("$onEmit tests", () => { let program: Program; let $onEmit: (arg0: EmitContext) => any; - let createCodeModel: ( + let $emitCodeModel: ( context: EmitContext, updateCodeModel?: (model: CodeModel, context: any) => CodeModel, ) => any; @@ -83,11 +83,11 @@ describe("$onEmit tests", () => { reportDiagnostics: vi.fn(), } as unknown as Program; - // dynamically import the module to get the $onEmit and createCodeModel functions + // dynamically import the module to get the $onEmit and $emitCodeModel functions // we avoid importing it at the top to allow mocking of dependencies const emitterModule = await import("../../src/emitter.js"); $onEmit = emitterModule.$onEmit; - createCodeModel = emitterModule.createCodeModel; + $emitCodeModel = emitterModule.$emitCodeModel; }); it("should apply the updateCodeModel callback", async () => { @@ -95,7 +95,7 @@ describe("$onEmit tests", () => { const updateCallback = vi.fn().mockImplementation((model: CodeModel) => { return model; }); - await createCodeModel(context, updateCallback); + await $emitCodeModel(context, updateCallback); expect(updateCallback).toHaveBeenCalledTimes(1); }); @@ -184,7 +184,7 @@ describe("$onEmit tests", () => { }); }); -describe("createCodeModel tests", () => { +describe("$emitCodeModel tests", () => { let runner: TestHost; let program: Program; @@ -193,7 +193,7 @@ describe("createCodeModel tests", () => { runner = await createEmitterTestHost(); }); - it("should return diagnostics array from createCodeModel", async () => { + it("should return diagnostics array from $emitCodeModel", async () => { program = await typeSpecCompile( ` model TestModel { @@ -206,8 +206,8 @@ describe("createCodeModel tests", () => { runner, ); const context = createEmitterContext(program); - const { createCodeModel } = await import("../../src/emitter.js"); - const [, diagnostics] = await createCodeModel(context); + const { $emitCodeModel } = await import("../../src/emitter.js"); + const [, diagnostics] = await $emitCodeModel(context); // Verify that diagnostics is an array expect(Array.isArray(diagnostics)).toBe(true); @@ -215,7 +215,7 @@ describe("createCodeModel tests", () => { expect(diagnostics).toBeDefined(); }); - it("should collect diagnostics from createModel in createCodeModel", async () => { + it("should collect diagnostics from createModel in $emitCodeModel", async () => { program = await typeSpecCompile( ` model TestModel { @@ -228,8 +228,8 @@ describe("createCodeModel tests", () => { runner, ); const context = createEmitterContext(program); - const { createCodeModel } = await import("../../src/emitter.js"); - const [, diagnostics] = await createCodeModel(context); + const { $emitCodeModel } = await import("../../src/emitter.js"); + const [, diagnostics] = await $emitCodeModel(context); // The function should return diagnostics even if empty expect(diagnostics).toBeDefined(); From d0c4c82fbfdd94ee02091267bff0878f84ce2334 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 30 Jan 2026 08:47:45 +0000 Subject: [PATCH 17/18] Add comprehensive documentation for downstream emitter usage - Created new downstream-emitters.md guide with detailed usage instructions - Covers basic usage, custom code model transformations, diagnostic handling - Includes complete examples and best practices - Added API reference and troubleshooting section - Updated main README to link to new downstream emitters guide - Removed obsolete update-code-model option from README - All tests pass (1,172 total tests) Co-authored-by: ArcturusZhang <10554446+ArcturusZhang@users.noreply.github.com> --- .../.tspd/docs/downstream-emitters.md | 390 ++++++++++++++++++ packages/http-client-csharp/readme.md | 10 +- 2 files changed, 394 insertions(+), 6 deletions(-) create mode 100644 packages/http-client-csharp/.tspd/docs/downstream-emitters.md diff --git a/packages/http-client-csharp/.tspd/docs/downstream-emitters.md b/packages/http-client-csharp/.tspd/docs/downstream-emitters.md new file mode 100644 index 00000000000..72b33975fbd --- /dev/null +++ b/packages/http-client-csharp/.tspd/docs/downstream-emitters.md @@ -0,0 +1,390 @@ +# Using @typespec/http-client-csharp as a Downstream Emitter + +This guide explains how to use `@typespec/http-client-csharp` as a library in your own TypeSpec emitter to generate C# HTTP client code with custom modifications. + +## Overview + +The `@typespec/http-client-csharp` emitter provides exported APIs that follow TypeSpec best practices for diagnostic collection. This allows downstream emitters to: + +1. Generate C# HTTP client code programmatically +2. Customize the code model before code generation +3. Collect and handle diagnostics properly +4. Compose multiple emitters together + +## Installation + +```bash +npm install @typespec/http-client-csharp +``` + +## Basic Usage + +### Simple Emission + +The simplest way to use the emitter is to call `$emitCodeModel` directly: + +```typescript +import { $emitCodeModel } from "@typespec/http-client-csharp"; +import { EmitContext } from "@typespec/compiler"; + +export async function $onEmit(context: EmitContext) { + // Emit C# code and collect diagnostics + const [, diagnostics] = await $emitCodeModel(context); + + // Report diagnostics to the TypeSpec program + context.program.reportDiagnostics(diagnostics); +} +``` + +### Custom Code Model Transformation + +You can provide a callback to modify the code model before code generation: + +```typescript +import { $emitCodeModel, CodeModel, CSharpEmitterContext } from "@typespec/http-client-csharp"; +import { EmitContext } from "@typespec/compiler"; + +export async function $onEmit(context: EmitContext) { + // Define a function to customize the code model + const updateCodeModel = ( + model: CodeModel, + sdkContext: CSharpEmitterContext + ): CodeModel => { + // Modify the code model + // For example: add custom properties, change naming conventions, etc. + + // Customize client names + for (const client of model.clients) { + client.name = `Custom${client.name}`; + } + + // Customize model names + for (const modelType of model.models) { + modelType.name = `Generated${modelType.name}`; + } + + return model; + }; + + // Emit with custom transformation + const [, diagnostics] = await $emitCodeModel(context, updateCodeModel); + context.program.reportDiagnostics(diagnostics); +} +``` + +## Advanced Usage + +### Generating Code Model Only + +If you only need the code model without performing the full emission: + +```typescript +import { createModel, createCSharpEmitterContext } from "@typespec/http-client-csharp"; +import { createSdkContext } from "@azure-tools/typespec-client-generator-core"; +import { EmitContext } from "@typespec/compiler"; + +export async function $onEmit(context: EmitContext) { + // Create SDK context + const tcgcContext = await createSdkContext( + context, + "@typespec/http-client-csharp" + ); + + // Create C# emitter context + const sdkContext = createCSharpEmitterContext(tcgcContext, logger); + + // Generate the code model + const [codeModel, diagnostics] = createModel(sdkContext); + + // Use the code model for your purposes + console.log(`Generated ${codeModel.clients.length} clients`); + console.log(`Generated ${codeModel.models.length} models`); + + // Report diagnostics + context.program.reportDiagnostics(diagnostics); +} +``` + +## Code Model Structure + +The `CodeModel` interface contains the following key properties: + +```typescript +interface CodeModel { + // Namespace name for the generated code + name: string; + + // API versions supported + apiVersions: string[]; + + // Enum types + enums: InputEnumType[]; + + // Constant values + constants: InputConstant[]; + + // Model types (classes/structs) + models: InputModelType[]; + + // HTTP clients + clients: InputClient[]; + + // Authentication configuration + auth?: InputAuth; +} +``` + +### Customization Examples + +#### Example 1: Adding Custom Metadata + +```typescript +const updateCodeModel = (model: CodeModel, context: CSharpEmitterContext): CodeModel => { + // Add version information to all models + for (const modelType of model.models) { + // Access model properties and customize + modelType.description = `${modelType.description}\n\nGenerated for API version: ${model.apiVersions[0]}`; + } + + return model; +}; +``` + +#### Example 2: Filtering Clients + +```typescript +const updateCodeModel = (model: CodeModel, context: CSharpEmitterContext): CodeModel => { + // Only keep clients with specific names + model.clients = model.clients.filter(client => + client.name.startsWith("Public") + ); + + return model; +}; +``` + +#### Example 3: Custom Authentication + +```typescript +const updateCodeModel = (model: CodeModel, context: CSharpEmitterContext): CodeModel => { + // Customize authentication configuration + if (model.auth) { + // Modify auth settings + if (model.auth.apiKey) { + model.auth.apiKey.name = "X-Custom-API-Key"; + } + } + + return model; +}; +``` + +## Diagnostic Handling + +The emitter follows TypeSpec best practices for diagnostic collection: + +### Diagnostic Collection Pattern + +All functions that return diagnostics follow the pattern: `[Result, readonly Diagnostic[]]` + +```typescript +import { createModel } from "@typespec/http-client-csharp"; + +// createModel returns a tuple: [CodeModel, readonly Diagnostic[]] +const [codeModel, diagnostics] = createModel(sdkContext); + +// Check for errors +const hasErrors = diagnostics.some(d => d.severity === "error"); +if (hasErrors) { + console.error("Code model generation failed with errors"); +} + +// Report all diagnostics to TypeSpec +context.program.reportDiagnostics(diagnostics); +``` + +### Custom Diagnostic Handling + +You can filter or transform diagnostics before reporting: + +```typescript +const [, diagnostics] = await $emitCodeModel(context, updateCodeModel); + +// Filter out warnings +const errorsOnly = diagnostics.filter(d => d.severity === "error"); + +// Add custom context to diagnostics +const customDiagnostics = diagnostics.map(d => ({ + ...d, + message: `[CustomEmitter] ${d.message}` +})); + +// Report filtered/transformed diagnostics +context.program.reportDiagnostics(customDiagnostics); +``` + +## Emitter Options + +When using `$emitCodeModel`, you can configure options through the `EmitContext`: + +```typescript +export async function $onEmit(context: EmitContext) { + // Access options from context + const options = context.options; + + // Options will be passed through to the C# emitter + // including: package-name, api-version, generate-protocol-methods, etc. + + const [, diagnostics] = await $emitCodeModel(context, updateCodeModel); + context.program.reportDiagnostics(diagnostics); +} +``` + +## Complete Example: Custom Emitter Package + +Here's a complete example of a custom emitter package: + +**package.json:** +```json +{ + "name": "my-custom-csharp-emitter", + "version": "1.0.0", + "type": "module", + "main": "dist/index.js", + "dependencies": { + "@typespec/http-client-csharp": "^1.0.0", + "@typespec/compiler": "^0.60.0" + } +} +``` + +**src/index.ts:** +```typescript +import { $emitCodeModel, CodeModel, CSharpEmitterContext } from "@typespec/http-client-csharp"; +import { EmitContext } from "@typespec/compiler"; + +export interface MyEmitterOptions { + // Your custom options + customPrefix?: string; + includeVersion?: boolean; +} + +export async function $onEmit(context: EmitContext) { + const customPrefix = context.options.customPrefix ?? "Generated"; + const includeVersion = context.options.includeVersion ?? true; + + const updateCodeModel = ( + model: CodeModel, + sdkContext: CSharpEmitterContext + ): CodeModel => { + // Apply custom transformations + for (const client of model.clients) { + client.name = `${customPrefix}${client.name}`; + } + + if (includeVersion && model.apiVersions.length > 0) { + for (const modelType of model.models) { + modelType.description = `${modelType.description || ""}\n\nAPI Version: ${model.apiVersions[0]}`; + } + } + + return model; + }; + + // Emit with customizations + const [, diagnostics] = await $emitCodeModel(context, updateCodeModel); + + // Report diagnostics + context.program.reportDiagnostics(diagnostics); +} +``` + +**TypeSpec project using the custom emitter:** + +```yaml +# tspconfig.yaml +emit: + - "my-custom-csharp-emitter" +options: + my-custom-csharp-emitter: + customPrefix: "Contoso" + includeVersion: true +``` + +## Best Practices + +1. **Always handle diagnostics**: Report all diagnostics from the emitter to ensure users see errors and warnings. + +2. **Preserve immutability**: When modifying the code model, avoid mutating nested objects directly. Create new objects when needed. + +3. **Validate transformations**: Check that your code model transformations produce valid output. + +4. **Document customizations**: Clearly document what customizations your emitter applies. + +5. **Test thoroughly**: Test your emitter with various TypeSpec specifications to ensure robustness. + +6. **Follow TypeSpec patterns**: Use `createDiagnosticCollector()` and the tuple return pattern in your own functions that generate diagnostics. + +## API Reference + +### Exported Functions + +- **`$emitCodeModel(context, updateCodeModel?)`**: Main emission function + - Returns: `Promise<[void, readonly Diagnostic[]]>` + - Parameters: + - `context: EmitContext` - The emit context + - `updateCodeModel?: (model: CodeModel, context: CSharpEmitterContext) => CodeModel` - Optional callback to modify code model + +- **`createModel(sdkContext)`**: Generate code model only + - Returns: `[CodeModel, readonly Diagnostic[]]` + - Parameters: + - `sdkContext: CSharpEmitterContext` - The C# emitter context + +### Exported Types + +- `CodeModel` - The code model interface +- `CSharpEmitterContext` - The emitter context interface +- `CSharpEmitterOptions` - Configuration options interface +- `InputClient` - HTTP client interface +- `InputModelType` - Model type interface + +## Support and Resources + +- [Main README](../../readme.md) +- [Customization Guide](./customization.md) +- [TypeSpec Documentation](https://typespec.io/) +- [Report Issues](https://github.com/microsoft/typespec/issues) + +## Migration Guide + +If you were previously using internal APIs or patterns, here's how to migrate: + +### Before (accessing internal APIs): +```typescript +// ❌ Don't use internal implementation details +import { createModel } from "@typespec/http-client-csharp/lib/client-model-builder"; +``` + +### After (using exported APIs): +```typescript +// ✅ Use public exported APIs +import { $emitCodeModel, createModel } from "@typespec/http-client-csharp"; +``` + +## Troubleshooting + +### Issue: Diagnostics not appearing + +**Solution**: Make sure you're calling `context.program.reportDiagnostics(diagnostics)` with the diagnostics returned from `$emitCodeModel`. + +### Issue: Code model is empty + +**Solution**: Check that your TypeSpec specification is valid and that you've properly configured the SDK context options. + +### Issue: Generated code has errors + +**Solution**: Validate your code model transformations. The C# generator expects specific structures - avoid breaking required properties. + +### Issue: TypeScript compilation errors + +**Solution**: Ensure you have the correct type definitions installed and are using compatible versions of `@typespec/compiler` and `@typespec/http-client-csharp`. diff --git a/packages/http-client-csharp/readme.md b/packages/http-client-csharp/readme.md index 77f8efc84b9..db8fecf7b85 100644 --- a/packages/http-client-csharp/readme.md +++ b/packages/http-client-csharp/readme.md @@ -15,6 +15,10 @@ npm install @typespec/http-client-csharp - Install [Node.js](https://nodejs.org/download/) 20 or above. (Verify by running `node --version`) - Install [**.NET 8.0 SDK**](https://dotnet.microsoft.com/download/dotnet/8.0) for your specific platform. (or a higher version) +### Using as a Downstream Emitter + +For detailed instructions on how to use this package as a library in your own emitter, see the [Downstream Emitters Guide](https://github.com/microsoft/typespec/blob/main/packages/http-client-csharp/.tspd/docs/downstream-emitters.md). + ### Customizing Generated Code For detailed instructions on how to customize the generated C# code, see the [Customization Guide](https://github.com/microsoft/typespec/blob/main/packages/http-client-csharp/.tspd/docs/customization.md). @@ -125,12 +129,6 @@ The name of the generator. By default this is set to `ScmCodeModelGenerator`. Ge Allows emitter authors to specify the path to a custom emitter package, allowing you to extend the emitter behavior. This should be set to `import.meta.url` if you are using a custom emitter. -### `update-code-model` - -**Type:** `object` - -Allows emitter authors to specify a custom function to modify the generated code model before emitting. This is useful for modifying the code model before it is passed to the generator. - ### `license` **Type:** `object` From cad76cc29a19e5523ad0d77cf2bc23b2e4a9f927 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 30 Jan 2026 08:57:52 +0000 Subject: [PATCH 18/18] Remove documentation file and rename $emitCodeModel to emitCodeModel - Removed .tspd/docs/downstream-emitters.md file per reviewer feedback - Renamed $emitCodeModel to emitCodeModel (removed $ prefix) - Updated all references in emitter.ts, index.ts, tests, and README - Removed link to deleted documentation from README - All tests pass (1,172 total tests) Co-authored-by: ArcturusZhang <10554446+ArcturusZhang@users.noreply.github.com> --- .../.tspd/docs/downstream-emitters.md | 390 ------------------ .../http-client-csharp/emitter/src/emitter.ts | 8 +- .../http-client-csharp/emitter/src/index.ts | 2 +- .../emitter/test/Unit/emitter.test.ts | 22 +- packages/http-client-csharp/readme.md | 4 - 5 files changed, 16 insertions(+), 410 deletions(-) delete mode 100644 packages/http-client-csharp/.tspd/docs/downstream-emitters.md diff --git a/packages/http-client-csharp/.tspd/docs/downstream-emitters.md b/packages/http-client-csharp/.tspd/docs/downstream-emitters.md deleted file mode 100644 index 72b33975fbd..00000000000 --- a/packages/http-client-csharp/.tspd/docs/downstream-emitters.md +++ /dev/null @@ -1,390 +0,0 @@ -# Using @typespec/http-client-csharp as a Downstream Emitter - -This guide explains how to use `@typespec/http-client-csharp` as a library in your own TypeSpec emitter to generate C# HTTP client code with custom modifications. - -## Overview - -The `@typespec/http-client-csharp` emitter provides exported APIs that follow TypeSpec best practices for diagnostic collection. This allows downstream emitters to: - -1. Generate C# HTTP client code programmatically -2. Customize the code model before code generation -3. Collect and handle diagnostics properly -4. Compose multiple emitters together - -## Installation - -```bash -npm install @typespec/http-client-csharp -``` - -## Basic Usage - -### Simple Emission - -The simplest way to use the emitter is to call `$emitCodeModel` directly: - -```typescript -import { $emitCodeModel } from "@typespec/http-client-csharp"; -import { EmitContext } from "@typespec/compiler"; - -export async function $onEmit(context: EmitContext) { - // Emit C# code and collect diagnostics - const [, diagnostics] = await $emitCodeModel(context); - - // Report diagnostics to the TypeSpec program - context.program.reportDiagnostics(diagnostics); -} -``` - -### Custom Code Model Transformation - -You can provide a callback to modify the code model before code generation: - -```typescript -import { $emitCodeModel, CodeModel, CSharpEmitterContext } from "@typespec/http-client-csharp"; -import { EmitContext } from "@typespec/compiler"; - -export async function $onEmit(context: EmitContext) { - // Define a function to customize the code model - const updateCodeModel = ( - model: CodeModel, - sdkContext: CSharpEmitterContext - ): CodeModel => { - // Modify the code model - // For example: add custom properties, change naming conventions, etc. - - // Customize client names - for (const client of model.clients) { - client.name = `Custom${client.name}`; - } - - // Customize model names - for (const modelType of model.models) { - modelType.name = `Generated${modelType.name}`; - } - - return model; - }; - - // Emit with custom transformation - const [, diagnostics] = await $emitCodeModel(context, updateCodeModel); - context.program.reportDiagnostics(diagnostics); -} -``` - -## Advanced Usage - -### Generating Code Model Only - -If you only need the code model without performing the full emission: - -```typescript -import { createModel, createCSharpEmitterContext } from "@typespec/http-client-csharp"; -import { createSdkContext } from "@azure-tools/typespec-client-generator-core"; -import { EmitContext } from "@typespec/compiler"; - -export async function $onEmit(context: EmitContext) { - // Create SDK context - const tcgcContext = await createSdkContext( - context, - "@typespec/http-client-csharp" - ); - - // Create C# emitter context - const sdkContext = createCSharpEmitterContext(tcgcContext, logger); - - // Generate the code model - const [codeModel, diagnostics] = createModel(sdkContext); - - // Use the code model for your purposes - console.log(`Generated ${codeModel.clients.length} clients`); - console.log(`Generated ${codeModel.models.length} models`); - - // Report diagnostics - context.program.reportDiagnostics(diagnostics); -} -``` - -## Code Model Structure - -The `CodeModel` interface contains the following key properties: - -```typescript -interface CodeModel { - // Namespace name for the generated code - name: string; - - // API versions supported - apiVersions: string[]; - - // Enum types - enums: InputEnumType[]; - - // Constant values - constants: InputConstant[]; - - // Model types (classes/structs) - models: InputModelType[]; - - // HTTP clients - clients: InputClient[]; - - // Authentication configuration - auth?: InputAuth; -} -``` - -### Customization Examples - -#### Example 1: Adding Custom Metadata - -```typescript -const updateCodeModel = (model: CodeModel, context: CSharpEmitterContext): CodeModel => { - // Add version information to all models - for (const modelType of model.models) { - // Access model properties and customize - modelType.description = `${modelType.description}\n\nGenerated for API version: ${model.apiVersions[0]}`; - } - - return model; -}; -``` - -#### Example 2: Filtering Clients - -```typescript -const updateCodeModel = (model: CodeModel, context: CSharpEmitterContext): CodeModel => { - // Only keep clients with specific names - model.clients = model.clients.filter(client => - client.name.startsWith("Public") - ); - - return model; -}; -``` - -#### Example 3: Custom Authentication - -```typescript -const updateCodeModel = (model: CodeModel, context: CSharpEmitterContext): CodeModel => { - // Customize authentication configuration - if (model.auth) { - // Modify auth settings - if (model.auth.apiKey) { - model.auth.apiKey.name = "X-Custom-API-Key"; - } - } - - return model; -}; -``` - -## Diagnostic Handling - -The emitter follows TypeSpec best practices for diagnostic collection: - -### Diagnostic Collection Pattern - -All functions that return diagnostics follow the pattern: `[Result, readonly Diagnostic[]]` - -```typescript -import { createModel } from "@typespec/http-client-csharp"; - -// createModel returns a tuple: [CodeModel, readonly Diagnostic[]] -const [codeModel, diagnostics] = createModel(sdkContext); - -// Check for errors -const hasErrors = diagnostics.some(d => d.severity === "error"); -if (hasErrors) { - console.error("Code model generation failed with errors"); -} - -// Report all diagnostics to TypeSpec -context.program.reportDiagnostics(diagnostics); -``` - -### Custom Diagnostic Handling - -You can filter or transform diagnostics before reporting: - -```typescript -const [, diagnostics] = await $emitCodeModel(context, updateCodeModel); - -// Filter out warnings -const errorsOnly = diagnostics.filter(d => d.severity === "error"); - -// Add custom context to diagnostics -const customDiagnostics = diagnostics.map(d => ({ - ...d, - message: `[CustomEmitter] ${d.message}` -})); - -// Report filtered/transformed diagnostics -context.program.reportDiagnostics(customDiagnostics); -``` - -## Emitter Options - -When using `$emitCodeModel`, you can configure options through the `EmitContext`: - -```typescript -export async function $onEmit(context: EmitContext) { - // Access options from context - const options = context.options; - - // Options will be passed through to the C# emitter - // including: package-name, api-version, generate-protocol-methods, etc. - - const [, diagnostics] = await $emitCodeModel(context, updateCodeModel); - context.program.reportDiagnostics(diagnostics); -} -``` - -## Complete Example: Custom Emitter Package - -Here's a complete example of a custom emitter package: - -**package.json:** -```json -{ - "name": "my-custom-csharp-emitter", - "version": "1.0.0", - "type": "module", - "main": "dist/index.js", - "dependencies": { - "@typespec/http-client-csharp": "^1.0.0", - "@typespec/compiler": "^0.60.0" - } -} -``` - -**src/index.ts:** -```typescript -import { $emitCodeModel, CodeModel, CSharpEmitterContext } from "@typespec/http-client-csharp"; -import { EmitContext } from "@typespec/compiler"; - -export interface MyEmitterOptions { - // Your custom options - customPrefix?: string; - includeVersion?: boolean; -} - -export async function $onEmit(context: EmitContext) { - const customPrefix = context.options.customPrefix ?? "Generated"; - const includeVersion = context.options.includeVersion ?? true; - - const updateCodeModel = ( - model: CodeModel, - sdkContext: CSharpEmitterContext - ): CodeModel => { - // Apply custom transformations - for (const client of model.clients) { - client.name = `${customPrefix}${client.name}`; - } - - if (includeVersion && model.apiVersions.length > 0) { - for (const modelType of model.models) { - modelType.description = `${modelType.description || ""}\n\nAPI Version: ${model.apiVersions[0]}`; - } - } - - return model; - }; - - // Emit with customizations - const [, diagnostics] = await $emitCodeModel(context, updateCodeModel); - - // Report diagnostics - context.program.reportDiagnostics(diagnostics); -} -``` - -**TypeSpec project using the custom emitter:** - -```yaml -# tspconfig.yaml -emit: - - "my-custom-csharp-emitter" -options: - my-custom-csharp-emitter: - customPrefix: "Contoso" - includeVersion: true -``` - -## Best Practices - -1. **Always handle diagnostics**: Report all diagnostics from the emitter to ensure users see errors and warnings. - -2. **Preserve immutability**: When modifying the code model, avoid mutating nested objects directly. Create new objects when needed. - -3. **Validate transformations**: Check that your code model transformations produce valid output. - -4. **Document customizations**: Clearly document what customizations your emitter applies. - -5. **Test thoroughly**: Test your emitter with various TypeSpec specifications to ensure robustness. - -6. **Follow TypeSpec patterns**: Use `createDiagnosticCollector()` and the tuple return pattern in your own functions that generate diagnostics. - -## API Reference - -### Exported Functions - -- **`$emitCodeModel(context, updateCodeModel?)`**: Main emission function - - Returns: `Promise<[void, readonly Diagnostic[]]>` - - Parameters: - - `context: EmitContext` - The emit context - - `updateCodeModel?: (model: CodeModel, context: CSharpEmitterContext) => CodeModel` - Optional callback to modify code model - -- **`createModel(sdkContext)`**: Generate code model only - - Returns: `[CodeModel, readonly Diagnostic[]]` - - Parameters: - - `sdkContext: CSharpEmitterContext` - The C# emitter context - -### Exported Types - -- `CodeModel` - The code model interface -- `CSharpEmitterContext` - The emitter context interface -- `CSharpEmitterOptions` - Configuration options interface -- `InputClient` - HTTP client interface -- `InputModelType` - Model type interface - -## Support and Resources - -- [Main README](../../readme.md) -- [Customization Guide](./customization.md) -- [TypeSpec Documentation](https://typespec.io/) -- [Report Issues](https://github.com/microsoft/typespec/issues) - -## Migration Guide - -If you were previously using internal APIs or patterns, here's how to migrate: - -### Before (accessing internal APIs): -```typescript -// ❌ Don't use internal implementation details -import { createModel } from "@typespec/http-client-csharp/lib/client-model-builder"; -``` - -### After (using exported APIs): -```typescript -// ✅ Use public exported APIs -import { $emitCodeModel, createModel } from "@typespec/http-client-csharp"; -``` - -## Troubleshooting - -### Issue: Diagnostics not appearing - -**Solution**: Make sure you're calling `context.program.reportDiagnostics(diagnostics)` with the diagnostics returned from `$emitCodeModel`. - -### Issue: Code model is empty - -**Solution**: Check that your TypeSpec specification is valid and that you've properly configured the SDK context options. - -### Issue: Generated code has errors - -**Solution**: Validate your code model transformations. The C# generator expects specific structures - avoid breaking required properties. - -### Issue: TypeScript compilation errors - -**Solution**: Ensure you have the correct type definitions installed and are using compatible versions of `@typespec/compiler` and `@typespec/http-client-csharp`. diff --git a/packages/http-client-csharp/emitter/src/emitter.ts b/packages/http-client-csharp/emitter/src/emitter.ts index 663fa55ac01..c324ad666d6 100644 --- a/packages/http-client-csharp/emitter/src/emitter.ts +++ b/packages/http-client-csharp/emitter/src/emitter.ts @@ -57,14 +57,14 @@ function findProjectRoot(path: string): string | undefined { * * @example * ```typescript - * import { $emitCodeModel } from "@typespec/http-client-csharp"; + * import { emitCodeModel } from "@typespec/http-client-csharp"; * * export async function $onEmit(context: EmitContext) { * const updateCodeModel = (model: CodeModel, context: CSharpEmitterContext) => { * // Customize the code model here * return model; * }; - * const [, diagnostics] = await $emitCodeModel(context, updateCodeModel); + * const [, diagnostics] = await emitCodeModel(context, updateCodeModel); * // Process diagnostics as needed * context.program.reportDiagnostics(diagnostics); * } @@ -75,7 +75,7 @@ function findProjectRoot(path: string): string | undefined { * @returns A tuple containing void and any diagnostics that were generated during the emission * @beta */ -export async function $emitCodeModel( +export async function emitCodeModel( context: EmitContext, updateCodeModel?: (model: CodeModel, context: CSharpEmitterContext) => CodeModel, ): Promise<[void, readonly Diagnostic[]]> { @@ -174,7 +174,7 @@ export async function $emitCodeModel( * @beta */ export async function $onEmit(context: EmitContext) { - const [, diagnostics] = await $emitCodeModel(context); + const [, diagnostics] = await emitCodeModel(context); context.program.reportDiagnostics(diagnostics); } diff --git a/packages/http-client-csharp/emitter/src/index.ts b/packages/http-client-csharp/emitter/src/index.ts index 060fee5e0fc..ce40cf1b1db 100644 --- a/packages/http-client-csharp/emitter/src/index.ts +++ b/packages/http-client-csharp/emitter/src/index.ts @@ -3,7 +3,7 @@ export { writeCodeModel } from "./code-model-writer.js"; export { configurationFileName, tspOutputFileName } from "./constants.js"; -export { $emitCodeModel, $onEmit } from "./emitter.js"; +export { emitCodeModel, $onEmit } from "./emitter.js"; // we export `createModel` only for autorest.csharp because it uses the emitter to generate the code model file but not calling the dll here // we could remove this export when in the future we deprecate autorest.csharp export { createModel } from "./lib/client-model-builder.js"; diff --git a/packages/http-client-csharp/emitter/test/Unit/emitter.test.ts b/packages/http-client-csharp/emitter/test/Unit/emitter.test.ts index b06de888cbd..4ceeddda514 100644 --- a/packages/http-client-csharp/emitter/test/Unit/emitter.test.ts +++ b/packages/http-client-csharp/emitter/test/Unit/emitter.test.ts @@ -19,7 +19,7 @@ import { describe("$onEmit tests", () => { let program: Program; let $onEmit: (arg0: EmitContext) => any; - let $emitCodeModel: ( + let emitCodeModel: ( context: EmitContext, updateCodeModel?: (model: CodeModel, context: any) => CodeModel, ) => any; @@ -83,11 +83,11 @@ describe("$onEmit tests", () => { reportDiagnostics: vi.fn(), } as unknown as Program; - // dynamically import the module to get the $onEmit and $emitCodeModel functions + // dynamically import the module to get the $onEmit and emitCodeModel functions // we avoid importing it at the top to allow mocking of dependencies const emitterModule = await import("../../src/emitter.js"); $onEmit = emitterModule.$onEmit; - $emitCodeModel = emitterModule.$emitCodeModel; + emitCodeModel = emitterModule.emitCodeModel; }); it("should apply the updateCodeModel callback", async () => { @@ -95,7 +95,7 @@ describe("$onEmit tests", () => { const updateCallback = vi.fn().mockImplementation((model: CodeModel) => { return model; }); - await $emitCodeModel(context, updateCallback); + await emitCodeModel(context, updateCallback); expect(updateCallback).toHaveBeenCalledTimes(1); }); @@ -184,7 +184,7 @@ describe("$onEmit tests", () => { }); }); -describe("$emitCodeModel tests", () => { +describe("emitCodeModel tests", () => { let runner: TestHost; let program: Program; @@ -193,7 +193,7 @@ describe("$emitCodeModel tests", () => { runner = await createEmitterTestHost(); }); - it("should return diagnostics array from $emitCodeModel", async () => { + it("should return diagnostics array from emitCodeModel", async () => { program = await typeSpecCompile( ` model TestModel { @@ -206,8 +206,8 @@ describe("$emitCodeModel tests", () => { runner, ); const context = createEmitterContext(program); - const { $emitCodeModel } = await import("../../src/emitter.js"); - const [, diagnostics] = await $emitCodeModel(context); + const { emitCodeModel } = await import("../../src/emitter.js"); + const [, diagnostics] = await emitCodeModel(context); // Verify that diagnostics is an array expect(Array.isArray(diagnostics)).toBe(true); @@ -215,7 +215,7 @@ describe("$emitCodeModel tests", () => { expect(diagnostics).toBeDefined(); }); - it("should collect diagnostics from createModel in $emitCodeModel", async () => { + it("should collect diagnostics from createModel in emitCodeModel", async () => { program = await typeSpecCompile( ` model TestModel { @@ -228,8 +228,8 @@ describe("$emitCodeModel tests", () => { runner, ); const context = createEmitterContext(program); - const { $emitCodeModel } = await import("../../src/emitter.js"); - const [, diagnostics] = await $emitCodeModel(context); + const { emitCodeModel } = await import("../../src/emitter.js"); + const [, diagnostics] = await emitCodeModel(context); // The function should return diagnostics even if empty expect(diagnostics).toBeDefined(); diff --git a/packages/http-client-csharp/readme.md b/packages/http-client-csharp/readme.md index db8fecf7b85..30e8bea7724 100644 --- a/packages/http-client-csharp/readme.md +++ b/packages/http-client-csharp/readme.md @@ -15,10 +15,6 @@ npm install @typespec/http-client-csharp - Install [Node.js](https://nodejs.org/download/) 20 or above. (Verify by running `node --version`) - Install [**.NET 8.0 SDK**](https://dotnet.microsoft.com/download/dotnet/8.0) for your specific platform. (or a higher version) -### Using as a Downstream Emitter - -For detailed instructions on how to use this package as a library in your own emitter, see the [Downstream Emitters Guide](https://github.com/microsoft/typespec/blob/main/packages/http-client-csharp/.tspd/docs/downstream-emitters.md). - ### Customizing Generated Code For detailed instructions on how to customize the generated C# code, see the [Customization Guide](https://github.com/microsoft/typespec/blob/main/packages/http-client-csharp/.tspd/docs/customization.md).