Skip to content

Commit 1dc5711

Browse files
authored
Emit decorator list (#3905)
Fix #4031 When we specify the allowed-emit-decorators in emitter, TCGC will help to emit out decoration information for each item (client, property, operation), e.g final-state-var, our .NET input-Model need to contain this information. This PR will - emit decorators to all kind of models, operations and operations - add reference resolve strategy in TypeSpecInputDecoratorInfoConverter to support parse by reference
1 parent 2eac90c commit 1dc5711

File tree

45 files changed

+2253
-1141
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+2253
-1141
lines changed

packages/http-client-csharp/emitter/src/lib/client-model-builder.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ export function createModel(sdkContext: SdkContext<NetEmitterOptions>): CodeMode
102102
Protocol: {},
103103
Parent: parentNames.length > 0 ? parentNames[parentNames.length - 1] : undefined,
104104
Parameters: clientParameters,
105+
Decorators: client.decorators,
105106
};
106107
}
107108

packages/http-client-csharp/emitter/src/lib/converter.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ export function fromSdkModelType(
171171
flattenedNamePrefixes.length > 0
172172
? flattenedNamePrefixes.concat(property.name)
173173
: undefined,
174+
Decorators: property.decorators,
174175
};
175176

176177
return [modelProperty];
@@ -213,6 +214,7 @@ export function fromSdkEnumType(
213214
Description: enumType.description,
214215
IsExtensible: enumType.isFixed ? false : true,
215216
Usage: enumType.usage,
217+
Decorators: enumType.decorators,
216218
};
217219
if (addToCollection) enums.set(enumName, newInputEnumType);
218220
inputEnumType = newInputEnumType;
@@ -228,6 +230,7 @@ function fromSdkDateTimeType(dateTimeType: SdkDateTimeType): InputDateTimeType {
228230
WireType: fromSdkBuiltInType(dateTimeType.wireType),
229231
CrossLanguageDefinitionId: dateTimeType.crossLanguageDefinitionId,
230232
BaseType: dateTimeType.baseType ? fromSdkDateTimeType(dateTimeType.baseType) : undefined,
233+
Decorators: dateTimeType.decorators,
231234
};
232235
}
233236

@@ -239,6 +242,7 @@ function fromSdkDurationType(durationType: SdkDurationType): InputDurationType {
239242
WireType: fromSdkBuiltInType(durationType.wireType),
240243
CrossLanguageDefinitionId: durationType.crossLanguageDefinitionId,
241244
BaseType: durationType.baseType ? fromSdkDurationType(durationType.baseType) : undefined,
245+
Decorators: durationType.decorators,
242246
};
243247
}
244248

@@ -248,6 +252,7 @@ function fromTupleType(tupleType: SdkTupleType): InputPrimitiveType {
248252
Kind: "any",
249253
Name: "tuple",
250254
CrossLanguageDefinitionId: "",
255+
Decorators: tupleType.decorators,
251256
};
252257
}
253258

@@ -258,6 +263,7 @@ function fromSdkBuiltInType(builtInType: SdkBuiltInType): InputPrimitiveType {
258263
Encode: builtInType.encode !== builtInType.kind ? builtInType.encode : undefined, // In TCGC this is required, and when there is no encoding, it just has the same value as kind, we could remove this when TCGC decides to simplify
259264
CrossLanguageDefinitionId: builtInType.crossLanguageDefinitionId,
260265
BaseType: builtInType.baseType ? fromSdkBuiltInType(builtInType.baseType) : undefined,
266+
Decorators: builtInType.decorators,
261267
};
262268
}
263269

@@ -277,6 +283,7 @@ function fromUnionType(
277283
Kind: "union",
278284
Name: union.name,
279285
VariantTypes: variantTypes,
286+
Decorators: union.decorators,
280287
};
281288
}
282289

@@ -296,6 +303,7 @@ function fromSdkConstantType(
296303
// we might keep constant as-is, instead of creating an enum for it.
297304
convertConstantToEnum(constantType, enums, literalTypeContext),
298305
Value: constantType.value,
306+
Decorators: constantType.decorators,
299307
};
300308

301309
function convertConstantToEnum(
@@ -325,6 +333,7 @@ function fromSdkConstantType(
325333
Description: `The ${enumName}`, // TODO -- what should we put here?
326334
IsExtensible: true,
327335
Usage: literalTypeContext.Usage,
336+
Decorators: constantType.decorators,
328337
};
329338
enums.set(enumName, enumType);
330339
return enumType;
@@ -344,6 +353,7 @@ function fromSdkEnumValueTypeToConstantType(
344353
? fromSdkBuiltInType(enumValueType.valueType as SdkBuiltInType) // TODO: TCGC fix
345354
: fromSdkEnumType(enumValueType.enumType, context, enums),
346355
Value: enumValueType.value,
356+
Decorators: enumValueType.decorators,
347357
};
348358
}
349359

@@ -352,6 +362,7 @@ function fromSdkEnumValueType(enumValueType: SdkEnumValueType): InputEnumTypeVal
352362
Name: enumValueType.name,
353363
Value: enumValueType.value,
354364
Description: enumValueType.description,
365+
Decorators: enumValueType.decorators,
355366
};
356367
}
357368

@@ -365,6 +376,7 @@ function fromSdkDictionaryType(
365376
Kind: "dict",
366377
KeyType: fromSdkType(dictionaryType.keyType, context, models, enums),
367378
ValueType: fromSdkType(dictionaryType.valueType, context, models, enums),
379+
Decorators: dictionaryType.decorators,
368380
};
369381
}
370382

@@ -379,6 +391,7 @@ function fromSdkArrayType(
379391
Name: arrayType.name,
380392
ValueType: fromSdkType(arrayType.valueType, context, models, enums),
381393
CrossLanguageDefinitionId: arrayType.crossLanguageDefinitionId,
394+
Decorators: arrayType.decorators,
382395
};
383396
}
384397

packages/http-client-csharp/emitter/src/lib/operation-converter.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ export function fromSdkServiceMethod(
100100
GenerateProtocolMethod: shouldGenerateProtocol(sdkContext, method.operation.__raw.operation),
101101
GenerateConvenienceMethod: generateConvenience,
102102
CrossLanguageDefinitionId: method.crossLanguageDefintionId,
103+
Decorators: method.decorators,
103104
};
104105
}
105106

@@ -203,6 +204,7 @@ function fromSdkHttpOperationParameter(
203204
IsRequired: !p.optional,
204205
Kind: getParameterKind(p, parameterType, rootApiVersions.length > 0),
205206
DefaultValue: getParameterDefaultValue(p.clientDefaultValue, parameterType),
207+
Decorators: p.decorators,
206208
} as InputParameter;
207209
}
208210

packages/http-client-csharp/emitter/src/type/input-client.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT License. See License.txt in the project root for license information.
33

4+
import { DecoratorInfo } from "@azure-tools/typespec-client-generator-core";
45
import { InputOperation } from "./input-operation.js";
56
import { InputParameter } from "./input-parameter.js";
67
import { Protocols } from "./protocols.js";
@@ -12,4 +13,5 @@ export interface InputClient {
1213
Protocol?: Protocols;
1314
Parent?: string;
1415
Parameters?: InputParameter[];
16+
Decorators?: DecoratorInfo[];
1517
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT License. See License.txt in the project root for license information.
33

4+
import { DecoratorInfo } from "@azure-tools/typespec-client-generator-core";
5+
46
export interface InputEnumTypeValue {
57
Name: string;
68
Value: any;
79
Description?: string;
10+
Decorators?: DecoratorInfo[];
811
}

packages/http-client-csharp/emitter/src/type/input-model-property.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT License. See License.txt in the project root for license information.
33

4+
import { DecoratorInfo } from "@azure-tools/typespec-client-generator-core";
45
import { InputType } from "./input-type.js";
56

67
export interface InputModelProperty {
@@ -12,4 +13,5 @@ export interface InputModelProperty {
1213
IsReadOnly: boolean;
1314
IsDiscriminator?: boolean;
1415
FlattenedNames?: string[];
16+
Decorators?: DecoratorInfo[];
1517
}

packages/http-client-csharp/emitter/src/type/input-operation.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT License. See License.txt in the project root for license information.
33

4+
import { DecoratorInfo } from "@azure-tools/typespec-client-generator-core";
45
import { BodyMediaType } from "./body-media-type.js";
56
import { InputParameter } from "./input-parameter.js";
67
import { OperationLongRunning } from "./operation-long-running.js";
@@ -35,4 +36,5 @@ export interface InputOperation {
3536
GenerateProtocolMethod: boolean;
3637
GenerateConvenienceMethod: boolean;
3738
CrossLanguageDefinitionId: string;
39+
Decorators?: DecoratorInfo[];
3840
}

packages/http-client-csharp/emitter/src/type/input-parameter.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT License. See License.txt in the project root for license information.
33

4+
import { DecoratorInfo } from "@azure-tools/typespec-client-generator-core";
45
import { InputConstant } from "./input-constant.js";
56
import { InputOperationParameterKind } from "./input-operation-parameter-kind.js";
67
import { InputType } from "./input-type.js";
@@ -27,4 +28,5 @@ export interface InputParameter {
2728
Explode: boolean;
2829
ArraySerializationDelimiter?: string;
2930
HeaderCollectionPrefix?: string;
31+
Decorators?: DecoratorInfo[];
3032
}
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
import { TestHost } from "@typespec/compiler/testing";
2+
import { deepStrictEqual, strictEqual } from "assert";
3+
import { beforeEach, describe, it } from "vitest";
4+
import { createModel } from "../../src/lib/client-model-builder.js";
5+
import {
6+
createEmitterContext,
7+
createEmitterTestHost,
8+
createNetSdkContext,
9+
typeSpecCompile,
10+
} from "./utils/test-util.js";
11+
12+
describe("Test emitting decorator list", () => {
13+
let runner: TestHost;
14+
15+
beforeEach(async () => {
16+
runner = await createEmitterTestHost();
17+
});
18+
19+
it("emit decorator list on a client", async () => {
20+
const program = await typeSpecCompile(
21+
`
22+
@clientName("CsharpBookClient")
23+
interface BookClient {
24+
op test(): void;
25+
}
26+
`,
27+
runner,
28+
{ IsTCGCNeeded: true, IsXmlNeeded: true }
29+
);
30+
const context = createEmitterContext(program);
31+
const sdkContext = await createNetSdkContext(context, {
32+
additionalDecorators: ["Azure\\.ClientGenerator\\.Core\\.@clientName"],
33+
});
34+
const root = createModel(sdkContext);
35+
const clients = root.Clients;
36+
strictEqual(clients.length, 2);
37+
deepStrictEqual(clients[1].Decorators, [
38+
{
39+
name: "Azure.ClientGenerator.Core.@clientName",
40+
arguments: {
41+
rename: "CsharpBookClient",
42+
},
43+
},
44+
]);
45+
});
46+
47+
it("emit decorator list on a operation", async () => {
48+
const program = await typeSpecCompile(
49+
`
50+
model Book {
51+
content: string;
52+
}
53+
@clientName("ClientTestOperation")
54+
op test(): Book;
55+
`,
56+
runner,
57+
{ IsTCGCNeeded: true, IsXmlNeeded: true }
58+
);
59+
const context = createEmitterContext(program);
60+
const sdkContext = await createNetSdkContext(context, {
61+
additionalDecorators: ["Azure\\.ClientGenerator\\.Core\\.@clientName"],
62+
});
63+
const root = createModel(sdkContext);
64+
const operations = root.Clients[0].Operations;
65+
strictEqual(operations.length, 1);
66+
deepStrictEqual(operations[0].Decorators, [
67+
{
68+
name: "Azure.ClientGenerator.Core.@clientName",
69+
arguments: {
70+
rename: "ClientTestOperation",
71+
},
72+
},
73+
]);
74+
});
75+
76+
it("emit decorator list on a model", async () => {
77+
const program = await typeSpecCompile(
78+
`
79+
@clientName("ClientBook")
80+
model Book {
81+
content: string;
82+
}
83+
84+
op test(): Book;
85+
`,
86+
runner,
87+
{ IsTCGCNeeded: true, IsXmlNeeded: true }
88+
);
89+
const context = createEmitterContext(program);
90+
const sdkContext = await createNetSdkContext(context, {
91+
additionalDecorators: ["Azure\\.ClientGenerator\\.Core\\.@clientName"],
92+
});
93+
const root = createModel(sdkContext);
94+
const models = root.Models;
95+
strictEqual(models.length, 1);
96+
deepStrictEqual(models[0].Decorators, [
97+
{
98+
name: "Azure.ClientGenerator.Core.@clientName",
99+
arguments: {
100+
rename: "ClientBook",
101+
},
102+
},
103+
]);
104+
});
105+
106+
it("emit decorator list on a model property", async () => {
107+
const program = await typeSpecCompile(
108+
`
109+
model Book {
110+
@clientName("ClientContent")
111+
content: string;
112+
}
113+
114+
op test(): Book;
115+
`,
116+
runner,
117+
{ IsTCGCNeeded: true, IsXmlNeeded: true }
118+
);
119+
const context = createEmitterContext(program);
120+
const sdkContext = await createNetSdkContext(context, {
121+
additionalDecorators: ["Azure\\.ClientGenerator\\.Core\\.@clientName"],
122+
});
123+
const root = createModel(sdkContext);
124+
const models = root.Models;
125+
strictEqual(models.length, 1);
126+
deepStrictEqual(models[0].Properties[0].Decorators, [
127+
{
128+
name: "Azure.ClientGenerator.Core.@clientName",
129+
arguments: {
130+
rename: "ClientContent",
131+
},
132+
},
133+
]);
134+
});
135+
136+
it("emit decorator list on a parameter", async () => {
137+
const program = await typeSpecCompile(
138+
`
139+
@clientName("ClientTestOperation")
140+
op test(@clientName("ClientId") @header id: string): void;
141+
`,
142+
runner,
143+
{ IsTCGCNeeded: true, IsXmlNeeded: true }
144+
);
145+
const context = createEmitterContext(program);
146+
const sdkContext = await createNetSdkContext(context, {
147+
additionalDecorators: ["Azure\\.ClientGenerator\\.Core\\.@clientName"],
148+
});
149+
const root = createModel(sdkContext);
150+
const operations = root.Clients[0].Operations;
151+
strictEqual(operations.length, 1);
152+
const idParameters = operations[0].Parameters.filter((p) => p.Name === "ClientId");
153+
strictEqual(idParameters.length, 1);
154+
deepStrictEqual(idParameters[0].Decorators, [
155+
{
156+
name: "Azure.ClientGenerator.Core.@clientName",
157+
arguments: {
158+
rename: "ClientId",
159+
},
160+
},
161+
]);
162+
});
163+
});

packages/http-client-csharp/emitter/test/Unit/utils/test-util.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import { AzureCoreTestLibrary } from "@azure-tools/typespec-azure-core/testing";
2-
import { createSdkContext, SdkContext } from "@azure-tools/typespec-client-generator-core";
2+
import {
3+
createSdkContext,
4+
CreateSdkContextOptions,
5+
SdkContext,
6+
} from "@azure-tools/typespec-client-generator-core";
37
import { SdkTestLibrary } from "@azure-tools/typespec-client-generator-core/testing";
48
import {
59
CompilerOptions,
@@ -131,8 +135,9 @@ export function navigateModels(
131135

132136
/* We always need to pass in the emitter name now that it is required so making a helper to do this. */
133137
export async function createNetSdkContext(
134-
program: EmitContext<NetEmitterOptions>
138+
program: EmitContext<NetEmitterOptions>,
139+
sdkContextOptions: CreateSdkContextOptions = {}
135140
): Promise<SdkContext<NetEmitterOptions>> {
136141
Logger.initialize(program.program, LoggerLevel.INFO);
137-
return await createSdkContext(program, "@typespec/http-client-csharp");
142+
return await createSdkContext(program, "@typespec/http-client-csharp", sdkContextOptions);
138143
}

0 commit comments

Comments
 (0)