From 4940d3ccdc2a9c5c7063a0d6caccbe9e630f3199 Mon Sep 17 00:00:00 2001 From: sophia-ramsey Date: Thu, 9 Apr 2026 10:28:47 -0700 Subject: [PATCH 1/2] [http-server-csharp] add arrayDeclarationContext --- .../http-server-csharp/src/lib/service.ts | 8 ++ .../test/generation.test.ts | 130 ++++++++++++++++++ 2 files changed, 138 insertions(+) diff --git a/packages/http-server-csharp/src/lib/service.ts b/packages/http-server-csharp/src/lib/service.ts index efa2d09a9c3..c51f6141018 100644 --- a/packages/http-server-csharp/src/lib/service.ts +++ b/packages/http-server-csharp/src/lib/service.ts @@ -213,6 +213,14 @@ export async function $onEmit(context: EmitContext) #getDefaultNamespace(): string { return "TypeSpec.Service"; } + + arrayDeclarationContext(array: Model, name: string, elementType: Type) { + const arrayName = ensureCSharpIdentifier(this.emitter.getProgram(), array, name); + const arrayFile = this.emitter.createSourceFile(`generated/models/${arrayName}.cs`); + arrayFile.meta[this.#sourceTypeKey] = CSharpSourceType.Model; + const arrayNamespace = this.#getOrAddNamespace(array.namespace); + return this.#createModelContext(arrayNamespace, arrayFile, arrayName); + } arrayDeclaration(array: Model, name: string, elementType: Type): EmitterOutput { return this.collectionDeclaration(elementType, array); diff --git a/packages/http-server-csharp/test/generation.test.ts b/packages/http-server-csharp/test/generation.test.ts index 5cb6dfacfca..afb8bc3b927 100644 --- a/packages/http-server-csharp/test/generation.test.ts +++ b/packages/http-server-csharp/test/generation.test.ts @@ -3396,6 +3396,136 @@ describe("collection type: defined as emitter option", () => { }); }); +describe("arrayDeclarationContext", () => { + it("generates a dedicated file for array model declarations", async () => { + await compileAndValidateMultiple( + tester, + ` + model Tags is Array; + @route("/tags") @get op getTags(): Tags; + `, + [ + [ + "Tags.cs", + [ + "// Generated by @typespec/http-server-csharp", + "using System;", + "using System.Text.Json;", + "using System.Text.Json.Serialization;", + "using TypeSpec.Helpers.JsonConverters;", + "using TypeSpec.Helpers;", + ], + ], + ["IContosoOperations.cs", ["Task GetTagsAsync( )"]], + ], + ); + }); + + it("generates a dedicated file for array model with custom namespace", async () => { + await compileAndValidateMultiple( + tester, + [ + ` + model Items is Array; + @route("/items") @get op getItems(): Items; + `, + "My.Custom.Ns", + ], + [ + [ + "Items.cs", + [ + "// Generated by @typespec/http-server-csharp", + "using System;", + ], + ], + ["INsOperations.cs", ["Task GetItemsAsync( )"]], + ], + ); + }); + + it("generates a dedicated file for array model with complex element type", async () => { + await compileAndValidateMultiple( + tester, + ` + model Widget { + id: int32; + name: string; + } + model WidgetList is Array; + @route("/widgets") @get op getWidgets(): WidgetList; + `, + [ + [ + "Widget.cs", + [ + "public partial class Widget", + "public int Id { get; set; }", + "public string Name { get; set; }", + ], + ], + [ + "WidgetList.cs", + [ + "// Generated by @typespec/http-server-csharp", + "using System;", + ], + ], + ["IContosoOperations.cs", ["Task GetWidgetsAsync( )"]], + ], + ); + }); + + it("generates a dedicated file for named array with union element type", async () => { + await compileAndValidateMultiple( + tester, + ` + model ToolCall { + id: string; + type: "function"; + name: string; + } + + model CustomToolCall { + id: string; + type: "custom"; + payload: string; + } + + /** The tool calls generated by the model, such as function calls. */ + model ToolCalls is (ToolCall | CustomToolCall)[]; + + model AssistantMessage { + role: "assistant"; + tool_calls?: ToolCalls; + } + + @route("/chat") op chat(): AssistantMessage; + `, + [ + [ + "AssistantMessage.cs", + [ + `public string Role { get; } = "assistant";`, + ], + ], + [ + "ToolCalls.cs", + [ + "// Generated by @typespec/http-server-csharp", + ], + ], + [ + "IContosoOperations.cs", + [ + "Task ChatAsync( );", + ], + ], + ], + ); + }); +}); + it("emits class for model extending another model with no additional properties", async () => { await compileAndValidateMultiple( tester, From 91d34307907d30c5f0a885c31fe92e1c00b91c85 Mon Sep 17 00:00:00 2001 From: sophia-ramsey Date: Thu, 9 Apr 2026 13:44:20 -0700 Subject: [PATCH 2/2] format and chronus --- ...rayDeclarationContext-2026-3-9-13-37-52.md | 7 ++++ .../http-server-csharp/src/lib/service.ts | 12 +++--- .../test/generation.test.ts | 37 +++---------------- 3 files changed, 18 insertions(+), 38 deletions(-) create mode 100644 .chronus/changes/sramsey-csharp-service-arrayDeclarationContext-2026-3-9-13-37-52.md diff --git a/.chronus/changes/sramsey-csharp-service-arrayDeclarationContext-2026-3-9-13-37-52.md b/.chronus/changes/sramsey-csharp-service-arrayDeclarationContext-2026-3-9-13-37-52.md new file mode 100644 index 00000000000..807cfda1846 --- /dev/null +++ b/.chronus/changes/sramsey-csharp-service-arrayDeclarationContext-2026-3-9-13-37-52.md @@ -0,0 +1,7 @@ +--- +changeKind: fix +packages: + - "@typespec/http-server-csharp" +--- + +add arrayDeclarationContext \ No newline at end of file diff --git a/packages/http-server-csharp/src/lib/service.ts b/packages/http-server-csharp/src/lib/service.ts index c51f6141018..a3f0fbb35b5 100644 --- a/packages/http-server-csharp/src/lib/service.ts +++ b/packages/http-server-csharp/src/lib/service.ts @@ -213,13 +213,13 @@ export async function $onEmit(context: EmitContext) #getDefaultNamespace(): string { return "TypeSpec.Service"; } - + arrayDeclarationContext(array: Model, name: string, elementType: Type) { - const arrayName = ensureCSharpIdentifier(this.emitter.getProgram(), array, name); - const arrayFile = this.emitter.createSourceFile(`generated/models/${arrayName}.cs`); - arrayFile.meta[this.#sourceTypeKey] = CSharpSourceType.Model; - const arrayNamespace = this.#getOrAddNamespace(array.namespace); - return this.#createModelContext(arrayNamespace, arrayFile, arrayName); + const arrayName = ensureCSharpIdentifier(this.emitter.getProgram(), array, name); + const arrayFile = this.emitter.createSourceFile(`generated/models/${arrayName}.cs`); + arrayFile.meta[this.#sourceTypeKey] = CSharpSourceType.Model; + const arrayNamespace = this.#getOrAddNamespace(array.namespace); + return this.#createModelContext(arrayNamespace, arrayFile, arrayName); } arrayDeclaration(array: Model, name: string, elementType: Type): EmitterOutput { diff --git a/packages/http-server-csharp/test/generation.test.ts b/packages/http-server-csharp/test/generation.test.ts index afb8bc3b927..9e6caf5a629 100644 --- a/packages/http-server-csharp/test/generation.test.ts +++ b/packages/http-server-csharp/test/generation.test.ts @@ -3432,13 +3432,7 @@ describe("arrayDeclarationContext", () => { "My.Custom.Ns", ], [ - [ - "Items.cs", - [ - "// Generated by @typespec/http-server-csharp", - "using System;", - ], - ], + ["Items.cs", ["// Generated by @typespec/http-server-csharp", "using System;"]], ["INsOperations.cs", ["Task GetItemsAsync( )"]], ], ); @@ -3464,13 +3458,7 @@ describe("arrayDeclarationContext", () => { "public string Name { get; set; }", ], ], - [ - "WidgetList.cs", - [ - "// Generated by @typespec/http-server-csharp", - "using System;", - ], - ], + ["WidgetList.cs", ["// Generated by @typespec/http-server-csharp", "using System;"]], ["IContosoOperations.cs", ["Task GetWidgetsAsync( )"]], ], ); @@ -3503,24 +3491,9 @@ describe("arrayDeclarationContext", () => { @route("/chat") op chat(): AssistantMessage; `, [ - [ - "AssistantMessage.cs", - [ - `public string Role { get; } = "assistant";`, - ], - ], - [ - "ToolCalls.cs", - [ - "// Generated by @typespec/http-server-csharp", - ], - ], - [ - "IContosoOperations.cs", - [ - "Task ChatAsync( );", - ], - ], + ["AssistantMessage.cs", [`public string Role { get; } = "assistant";`]], + ["ToolCalls.cs", ["// Generated by @typespec/http-server-csharp"]], + ["IContosoOperations.cs", ["Task ChatAsync( );"]], ], ); });