Skip to content

Commit a02b4f4

Browse files
committed
fix union enum issue and move tests
1 parent cad3fdb commit a02b4f4

3 files changed

Lines changed: 115 additions & 266 deletions

File tree

packages/http-server-csharp/src/components/enums/enums.test.tsx

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { t, type TesterInstance } from "@typespec/compiler/testing";
55
import { Output } from "@typespec/emitter-framework";
66
import { EnumDeclaration } from "@typespec/emitter-framework/csharp";
77
import { beforeEach, describe, expect, it } from "vitest";
8+
import { isUnionEnum } from "./enums.jsx";
89

910
let runner: TesterInstance;
1011

@@ -97,3 +98,98 @@ describe("EnumDeclaration", () => {
9798
`);
9899
});
99100
});
101+
102+
describe("isUnionEnum", () => {
103+
it("returns true for extensible union with string base and named variants", async () => {
104+
const { ReasoningEffort } = await runner.compile(t.code`
105+
union ${t.union("ReasoningEffort")} {
106+
string,
107+
none: "none",
108+
low: "low",
109+
medium: "medium",
110+
high: "high",
111+
}
112+
`);
113+
114+
expect(isUnionEnum(ReasoningEffort)).toBe(true);
115+
});
116+
117+
it("returns true for fixed union with named variants only", async () => {
118+
const { Priority } = await runner.compile(t.code`
119+
union ${t.union("Priority")} {
120+
low: "low",
121+
medium: "medium",
122+
high: "high",
123+
}
124+
`);
125+
126+
expect(isUnionEnum(Priority)).toBe(true);
127+
});
128+
129+
it("returns true for union with unnamed string literals", async () => {
130+
const { Priority } = await runner.compile(t.code`
131+
union ${t.union("Priority")} {
132+
"low",
133+
"medium",
134+
"high",
135+
}
136+
`);
137+
138+
expect(isUnionEnum(Priority)).toBe(true);
139+
});
140+
141+
it("returns true for union with unnamed string literals and null", async () => {
142+
const { ReasoningEffort } = await runner.compile(t.code`
143+
union ${t.union("ReasoningEffort")} {
144+
"none",
145+
"minimal",
146+
"low",
147+
"medium",
148+
"high",
149+
"xhigh",
150+
null,
151+
}
152+
`);
153+
154+
expect(isUnionEnum(ReasoningEffort)).toBe(true);
155+
});
156+
157+
it("returns true for union with named variants and null", async () => {
158+
const { ReasoningEffort } = await runner.compile(t.code`
159+
union ${t.union("ReasoningEffort")} {
160+
none: "none",
161+
medium: "medium",
162+
high: "high",
163+
null,
164+
}
165+
`);
166+
167+
expect(isUnionEnum(ReasoningEffort)).toBe(true);
168+
});
169+
170+
it("returns true for extensible union with string base, named variants, and null", async () => {
171+
const { ReasoningEffort } = await runner.compile(t.code`
172+
union ${t.union("ReasoningEffort")} {
173+
string,
174+
none: "none",
175+
medium: "medium",
176+
high: "high",
177+
null,
178+
}
179+
`);
180+
181+
expect(isUnionEnum(ReasoningEffort)).toBe(true);
182+
});
183+
184+
it("returns false for union with non-string variant types", async () => {
185+
const { Mixed } = await runner.compile(t.code`
186+
model Foo { x: string; }
187+
union ${t.union("Mixed")} {
188+
Foo,
189+
"bar",
190+
}
191+
`);
192+
193+
expect(isUnionEnum(Mixed)).toBe(false);
194+
});
195+
});

packages/http-server-csharp/src/components/enums/enums.tsx

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -136,32 +136,42 @@ export function Enums(props: EnumsProps): Children {
136136
/**
137137
* Returns true if a named union can be represented as a C# enum.
138138
* Requires: named union, every named variant has a string value,
139-
* and optionally one unnamed scalar `string` variant (open/extensible).
139+
* and optionally one unnamed scalar `string` variant (open/extensible)
140+
* and/or a `null` variant.
140141
*/
141142
export function isUnionEnum(union: Union): boolean {
142143
if (!union.name) return false;
143144

144145
const variants = Array.from(union.variants.values());
145-
let hasNamedStringVariant = false;
146+
let hasStringVariant = false;
146147

147148
for (const variant of variants) {
148149
// Allow a single open string scalar variant (extensible union)
149150
if (variant.type.kind === "Scalar" && variant.type.name === "string") {
150151
continue;
151152
}
153+
// Allow null variant (nullable union)
154+
if (variant.type.kind === "Intrinsic" && variant.type.name === "null") {
155+
continue;
156+
}
152157
// Named variant with a string literal value
153158
if (variant.type.kind === "String" && variant.name && typeof variant.name === "string") {
154-
hasNamedStringVariant = true;
159+
hasStringVariant = true;
160+
continue;
161+
}
162+
// Unnamed variant with a string literal value (e.g., union { "low", "medium", "high" })
163+
if (variant.type.kind === "String" && typeof variant.name === "symbol") {
164+
hasStringVariant = true;
155165
continue;
156166
}
157167
// Any other variant type means it's not a simple enum
158168
return false;
159169
}
160170

161-
return hasNamedStringVariant;
171+
return hasStringVariant;
162172
}
163173

164-
/** Gets the named string variants of a union-as-enum (skipping the open `string` variant). */
174+
/** Gets the named string variants of a union-as-enum (skipping the open `string` and `null` variants). */
165175
export function getUnionEnumMembers(
166176
union: Union,
167177
): { name: string; value: string; variant: import("@typespec/compiler").UnionVariant }[] {
@@ -172,7 +182,11 @@ export function getUnionEnumMembers(
172182
}[] = [];
173183
for (const variant of union.variants.values()) {
174184
if (variant.type.kind === "String" && variant.name && typeof variant.name === "string") {
185+
// Named variant with explicit key (e.g., none: "none")
175186
members.push({ name: variant.name, value: variant.type.value, variant });
187+
} else if (variant.type.kind === "String" && typeof variant.name === "symbol") {
188+
// Unnamed string literal variant (e.g., "none") — derive name from the value
189+
members.push({ name: variant.type.value, value: variant.type.value, variant });
176190
}
177191
}
178192
return members;

0 commit comments

Comments
 (0)