Skip to content

Commit b6a0a02

Browse files
[http-server-csharp] add arrayDeclarationContext (#10327)
In my work in [the openai-openapi-pr repo](https://github.com/microsoft/openai-openapi-pr), the service emitter would not compile and gave the following error message: ``` Error: Emit context must have a scope set in order to create references to declarations. at compilerAssert (asset-emitter.js) at invokeReference (asset-emitter.js) at CSharpCodeEmitter.collectionDeclaration (service.js) at CSharpCodeEmitter.arrayDeclaration (service.js) ``` I fixed the issue when I added `arrayDeclarationContext` to the local `node_modules` in my repo. Here is the fix that I made as well as a test that demonstrates the issue.
1 parent ba787fc commit b6a0a02

3 files changed

Lines changed: 81 additions & 1 deletion

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
changeKind: fix
3+
packages:
4+
- "@typespec/http-server-csharp"
5+
---
6+
7+
add arrayDeclarationContext

packages/http-server-csharp/src/lib/service.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,15 @@ export async function $onEmit(context: EmitContext<CSharpServiceEmitterOptions>)
214214
return "TypeSpec.Service";
215215
}
216216

217+
arrayDeclarationContext(array: Model, name: string, elementType: Type) {
218+
const arrayName = ensureCSharpIdentifier(this.emitter.getProgram(), array, name);
219+
const arrayFile = this.emitter.createSourceFile(`generated/models/${arrayName}.cs`);
220+
arrayFile.meta[this.#sourceTypeKey] = CSharpSourceType.Model;
221+
arrayFile.meta["skipEmit"] = true;
222+
const arrayNamespace = this.#getOrAddNamespace(array.namespace);
223+
return this.#createModelContext(arrayNamespace, arrayFile, arrayName);
224+
}
225+
217226
arrayDeclaration(array: Model, name: string, elementType: Type): EmitterOutput<string> {
218227
return this.collectionDeclaration(elementType, array);
219228
}
@@ -1265,6 +1274,10 @@ export async function $onEmit(context: EmitContext<CSharpServiceEmitterOptions>)
12651274
return sourceFile.meta.emitted;
12661275
}
12671276

1277+
if (sourceFile.meta["skipEmit"]) {
1278+
return { path: sourceFile.path, contents: "" };
1279+
}
1280+
12681281
const emittedSourceFile: EmittedSourceFile = {
12691282
path: sourceFile.path,
12701283
contents: "",
@@ -1426,7 +1439,7 @@ export async function $onEmit(context: EmitContext<CSharpServiceEmitterOptions>)
14261439
break;
14271440
}
14281441
}
1429-
return super.writeOutput(emittedSourceFiles);
1442+
return super.writeOutput(emittedSourceFiles.filter((f) => !f.meta["skipEmit"]));
14301443
}
14311444
}
14321445

packages/http-server-csharp/test/generation.test.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@ function assertFileDoesNotContain(
4848
);
4949
}
5050

51+
function assertFileNotEmitted(fs: TestFileSystem, fileName: string): void {
52+
const result = [...fs.fs.entries()].filter((e) => e[0].includes(`/${fileName}`));
53+
assert.strictEqual(result.length, 0, `Expected ${fileName} to not be emitted, but it was`);
54+
}
55+
5156
async function compileAndValidateSingleModel(
5257
tester: TesterInstance,
5358
code: string,
@@ -3396,6 +3401,61 @@ describe("collection type: defined as emitter option", () => {
33963401
});
33973402
});
33983403

3404+
describe("arrayDeclarationContext", () => {
3405+
it("emits correct types for array model declarations", async () => {
3406+
const fs = await compileAndValidateMultiple(
3407+
tester,
3408+
`
3409+
model Tags is Array<string>;
3410+
@route("/tags") @get op getTags(): Tags;
3411+
`,
3412+
[["IContosoOperations.cs", ["Task<string[]> GetTagsAsync( )"]]],
3413+
);
3414+
assertFileNotEmitted(fs, "Tags.cs");
3415+
});
3416+
3417+
it("emits correct types for array model with custom namespace", async () => {
3418+
const fs = await compileAndValidateMultiple(
3419+
tester,
3420+
[
3421+
`
3422+
model Items is Array<int32>;
3423+
@route("/items") @get op getItems(): Items;
3424+
`,
3425+
"My.Custom.Ns",
3426+
],
3427+
[["INsOperations.cs", ["Task<int[]> GetItemsAsync( )"]]],
3428+
);
3429+
assertFileNotEmitted(fs, "Items.cs");
3430+
});
3431+
3432+
it("emits correct types for array model with complex element type", async () => {
3433+
const fs = await compileAndValidateMultiple(
3434+
tester,
3435+
`
3436+
model Widget {
3437+
id: int32;
3438+
name: string;
3439+
}
3440+
model WidgetList is Array<Widget>;
3441+
@route("/widgets") @get op getWidgets(): WidgetList;
3442+
`,
3443+
[
3444+
[
3445+
"Widget.cs",
3446+
[
3447+
"public partial class Widget",
3448+
"public int Id { get; set; }",
3449+
"public string Name { get; set; }",
3450+
],
3451+
],
3452+
["IContosoOperations.cs", ["Task<Widget[]> GetWidgetsAsync( )"]],
3453+
],
3454+
);
3455+
assertFileNotEmitted(fs, "WidgetList.cs");
3456+
});
3457+
});
3458+
33993459
it("emits class for model extending another model with no additional properties", async () => {
34003460
await compileAndValidateMultiple(
34013461
tester,

0 commit comments

Comments
 (0)