Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 42 additions & 8 deletions packages/http-client-csharp/emitter/src/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import { createSdkContext, SdkContext } from "@azure-tools/typespec-client-generator-core";
import {
Diagnostic,
EmitContext,
getDirectoryPath,
joinPaths,
Expand Down Expand Up @@ -48,17 +49,35 @@ 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.
*
* @example
* ```typescript
* import { createCodeModel } from "@typespec/http-client-csharp";
*
* export async function $onEmit(context: EmitContext<MyEmitterOptions>) {
* 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
*/
export async function $onEmit(context: EmitContext<CSharpEmitterOptions>) {
export async function createCodeModel(
context: EmitContext<CSharpEmitterOptions>,
): 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
Expand All @@ -70,22 +89,25 @@ export async function $onEmit(context: EmitContext<CSharpEmitterOptions>) {
),
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)) {
fs.mkdirSync(generatedFolder, { recursive: true });
}

// 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
Expand Down Expand Up @@ -133,6 +155,18 @@ export async function $onEmit(context: EmitContext<CSharpEmitterOptions>) {
}
}
}

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<CSharpEmitterOptions>) {
const [, diagnostics] = await createCodeModel(context);
context.program.reportDiagnostics(diagnostics);
}

export function createConfiguration(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -22,11 +23,24 @@ 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 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
Expand Down Expand Up @@ -60,7 +74,7 @@ export function createModel(sdkContext: CSharpEmitterContext): CodeModel {
auth: processServiceAuthentication(sdkContext, sdkPackage),
};

return clientModel;
return [clientModel, sdkContext.logger.getDiagnostics()];
}

/**
Expand Down
24 changes: 21 additions & 3 deletions packages/http-client-csharp/emitter/src/lib/logger.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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 {
Expand Down Expand Up @@ -63,7 +75,13 @@ export class Logger {
reportDiagnostic<C extends keyof DiagnosticMessagesMap, M extends keyof DiagnosticMessagesMap[C]>(
diag: DiagnosticReport<DiagnosticMessagesMap, C, M>,
): 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 {
Expand Down
30 changes: 15 additions & 15 deletions packages/http-client-csharp/emitter/test/Unit/auth.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down Expand Up @@ -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];
Expand Down Expand Up @@ -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];
Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand All @@ -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)
Expand All @@ -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");
Expand Down Expand Up @@ -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");
Expand Down
Loading