Skip to content

Commit dbcea81

Browse files
stephentoubCopilot
andauthored
Add deprecated schema support to all four code generators (#1099)
* Add deprecated schema support to all four code generators Translate deprecated: true from JSON Schema nodes into language-specific deprecation markers during code generation: - C#: [Obsolete] attribute on types, properties, methods, enums, and API groups. Added #pragma warning disable CS0612/CS0618 to generated file headers to avoid TreatWarningsAsErrors build failures. - TypeScript: /** @deprecated */ JSDoc on types, methods, and handler interfaces. - Python: # Deprecated: comments on types, methods, fields, enums, and handler Protocol methods. - Go: // Deprecated: comments on types, methods, fields, enums, and handler interface methods. Shared utilities added to utils.ts: deprecated field on RpcMethod, isSchemaDeprecated() for property/type-level checks, and isNodeFullyDeprecated() for API group-level checks (mirrors the existing experimental pattern). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Avoid deprecating shared types referenced via \ Only add types to the deprecated annotation set when the method's params/result schema is inline (not a \ to a shared definition). This prevents shared types used by both deprecated and non-deprecated methods from being incorrectly tagged as deprecated. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Regenerate output files after codegen script changes Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 883cc02 commit dbcea81

File tree

7 files changed

+246
-33
lines changed

7 files changed

+246
-33
lines changed

dotnet/src/Generated/Rpc.cs

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dotnet/src/Generated/SessionEvents.cs

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

scripts/codegen/csharp.ts

Lines changed: 54 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ import {
2525
refTypeName,
2626
isRpcMethod,
2727
isNodeFullyExperimental,
28+
isNodeFullyDeprecated,
29+
isSchemaDeprecated,
2830
isObjectSchema,
2931
isVoidSchema,
3032
REPO_ROOT,
@@ -316,14 +318,15 @@ let generatedEnums = new Map<string, { enumName: string; values: string[] }>();
316318
/** Schema definitions available during session event generation (for $ref resolution). */
317319
let sessionDefinitions: DefinitionCollections = { definitions: {}, $defs: {} };
318320

319-
function getOrCreateEnum(parentClassName: string, propName: string, values: string[], enumOutput: string[], description?: string, explicitName?: string): string {
321+
function getOrCreateEnum(parentClassName: string, propName: string, values: string[], enumOutput: string[], description?: string, explicitName?: string, deprecated?: boolean): string {
320322
const enumName = explicitName ?? `${parentClassName}${propName}`;
321323
const existing = generatedEnums.get(enumName);
322324
if (existing) return existing.enumName;
323325
generatedEnums.set(enumName, { enumName, values });
324326

325327
const lines: string[] = [];
326328
lines.push(...xmlDocEnumComment(description, ""));
329+
if (deprecated) lines.push(`[Obsolete]`);
327330
lines.push(`[JsonConverter(typeof(JsonStringEnumConverter<${enumName}>))]`, `public enum ${enumName}`, `{`);
328331
for (const value of values) {
329332
lines.push(` /// <summary>The <c>${escapeXml(value)}</c> variant.</summary>`);
@@ -458,6 +461,7 @@ function generateDerivedClass(
458461
const required = new Set(schema.required || []);
459462

460463
lines.push(...xmlDocCommentWithFallback(schema.description, `The <c>${escapeXml(discriminatorValue)}</c> variant of <see cref="${baseClassName}"/>.`, ""));
464+
if (isSchemaDeprecated(schema)) lines.push(`[Obsolete]`);
461465
lines.push(`public partial class ${className} : ${baseClassName}`);
462466
lines.push(`{`);
463467
lines.push(` /// <inheritdoc />`);
@@ -476,6 +480,7 @@ function generateDerivedClass(
476480

477481
lines.push(...xmlDocPropertyComment((propSchema as JSONSchema7).description, propName, " "));
478482
lines.push(...emitDataAnnotations(propSchema as JSONSchema7, " "));
483+
if (isSchemaDeprecated(propSchema as JSONSchema7)) lines.push(` [Obsolete]`);
479484
if (isDurationProperty(propSchema as JSONSchema7)) lines.push(` [JsonConverter(typeof(MillisecondsTimeSpanConverter))]`);
480485
if (!isReq) lines.push(` [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]`);
481486
lines.push(` [JsonPropertyName("${propName}")]`);
@@ -499,6 +504,7 @@ function generateNestedClass(
499504
const required = new Set(schema.required || []);
500505
const lines: string[] = [];
501506
lines.push(...xmlDocCommentWithFallback(schema.description, `Nested data type for <c>${className}</c>.`, ""));
507+
if (isSchemaDeprecated(schema)) lines.push(`[Obsolete]`);
502508
lines.push(`public partial class ${className}`, `{`);
503509

504510
for (const [propName, propSchema] of Object.entries(schema.properties || {})) {
@@ -510,6 +516,7 @@ function generateNestedClass(
510516

511517
lines.push(...xmlDocPropertyComment(prop.description, propName, " "));
512518
lines.push(...emitDataAnnotations(prop, " "));
519+
if (isSchemaDeprecated(prop)) lines.push(` [Obsolete]`);
513520
if (isDurationProperty(prop)) lines.push(` [JsonConverter(typeof(MillisecondsTimeSpanConverter))]`);
514521
if (!isReq) lines.push(` [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]`);
515522
lines.push(` [JsonPropertyName("${propName}")]`);
@@ -539,7 +546,7 @@ function resolveSessionPropertyType(
539546
}
540547

541548
if (refSchema.enum && Array.isArray(refSchema.enum)) {
542-
const enumName = getOrCreateEnum(className, "", refSchema.enum as string[], enumOutput, refSchema.description);
549+
const enumName = getOrCreateEnum(className, "", refSchema.enum as string[], enumOutput, refSchema.description, undefined, isSchemaDeprecated(refSchema));
543550
return isRequired ? enumName : `${enumName}?`;
544551
}
545552

@@ -573,7 +580,7 @@ function resolveSessionPropertyType(
573580
return hasNull || !isRequired ? "object?" : "object";
574581
}
575582
if (propSchema.enum && Array.isArray(propSchema.enum)) {
576-
const enumName = getOrCreateEnum(parentClassName, propName, propSchema.enum as string[], enumOutput, propSchema.description, propSchema.title as string | undefined);
583+
const enumName = getOrCreateEnum(parentClassName, propName, propSchema.enum as string[], enumOutput, propSchema.description, propSchema.title as string | undefined, isSchemaDeprecated(propSchema));
577584
return isRequired ? enumName : `${enumName}?`;
578585
}
579586
if (propSchema.type === "object" && propSchema.properties) {
@@ -607,6 +614,9 @@ function generateDataClass(variant: EventVariant, knownTypes: Map<string, string
607614
} else {
608615
lines.push(...rawXmlDocSummary(`Event payload for <see cref="${variant.className}"/>.`, ""));
609616
}
617+
if (isSchemaDeprecated(variant.dataSchema)) {
618+
lines.push(`[Obsolete]`);
619+
}
610620
lines.push(`public partial class ${variant.dataClassName}`, `{`);
611621

612622
for (const [propName, propSchema] of Object.entries(variant.dataSchema.properties)) {
@@ -617,6 +627,7 @@ function generateDataClass(variant: EventVariant, knownTypes: Map<string, string
617627

618628
lines.push(...xmlDocPropertyComment((propSchema as JSONSchema7).description, propName, " "));
619629
lines.push(...emitDataAnnotations(propSchema as JSONSchema7, " "));
630+
if (isSchemaDeprecated(propSchema as JSONSchema7)) lines.push(` [Obsolete]`);
620631
if (isDurationProperty(propSchema as JSONSchema7)) lines.push(` [JsonConverter(typeof(MillisecondsTimeSpanConverter))]`);
621632
if (!isReq) lines.push(` [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]`);
622633
lines.push(` [JsonPropertyName("${propName}")]`);
@@ -659,6 +670,9 @@ function generateSessionEventsCode(schema: JSONSchema7): string {
659670
// AUTO-GENERATED FILE - DO NOT EDIT
660671
// Generated from: session-events.schema.json
661672
673+
#pragma warning disable CS0612 // Type or member is obsolete
674+
#pragma warning disable CS0618 // Type or member is obsolete (with message)
675+
662676
using System.ComponentModel.DataAnnotations;
663677
using System.Diagnostics;
664678
using System.Diagnostics.CodeAnalysis;
@@ -806,7 +820,7 @@ function resolveRpcType(schema: JSONSchema7, isRequired: boolean, parentClassNam
806820
}
807821

808822
if (refSchema.enum && Array.isArray(refSchema.enum)) {
809-
const enumName = getOrCreateEnum(typeName, "", refSchema.enum as string[], rpcEnumOutput, refSchema.description);
823+
const enumName = getOrCreateEnum(typeName, "", refSchema.enum as string[], rpcEnumOutput, refSchema.description, undefined, isSchemaDeprecated(refSchema));
810824
return isRequired ? enumName : `${enumName}?`;
811825
}
812826

@@ -835,6 +849,7 @@ function resolveRpcType(schema: JSONSchema7, isRequired: boolean, parentClassNam
835849
rpcEnumOutput,
836850
schema.description,
837851
schema.title as string | undefined,
852+
isSchemaDeprecated(schema),
838853
);
839854
return isRequired ? enumName : `${enumName}?`;
840855
}
@@ -890,6 +905,9 @@ function emitRpcClass(
890905
if (experimentalRpcTypes.has(className)) {
891906
lines.push(`[Experimental(Diagnostics.Experimental)]`);
892907
}
908+
if (isSchemaDeprecated(schema) || isSchemaDeprecated(effectiveSchema)) {
909+
lines.push(`[Obsolete]`);
910+
}
893911
lines.push(`${visibility} sealed class ${className}`, `{`);
894912

895913
const props = Object.entries(effectiveSchema.properties || {});
@@ -903,6 +921,7 @@ function emitRpcClass(
903921

904922
lines.push(...xmlDocPropertyComment(prop.description, propName, " "));
905923
lines.push(...emitDataAnnotations(prop, " "));
924+
if (isSchemaDeprecated(prop)) lines.push(` [Obsolete]`);
906925
if (isDurationProperty(prop)) lines.push(` [JsonConverter(typeof(MillisecondsTimeSpanConverter))]`);
907926
lines.push(` [JsonPropertyName("${propName}")]`);
908927

@@ -934,7 +953,7 @@ function emitRpcClass(
934953
*/
935954
function emitNonObjectResultType(typeName: string, schema: JSONSchema7, classes: string[]): string {
936955
if (schema.enum && Array.isArray(schema.enum)) {
937-
const enumName = getOrCreateEnum("", typeName, schema.enum as string[], rpcEnumOutput, schema.description, typeName);
956+
const enumName = getOrCreateEnum("", typeName, schema.enum as string[], rpcEnumOutput, schema.description, typeName, isSchemaDeprecated(schema));
938957
emittedRpcEnumResultTypes.add(enumName);
939958
return enumName;
940959
}
@@ -971,7 +990,7 @@ function emitServerRpcClasses(node: Record<string, unknown>, classes: string[]):
971990
// Top-level methods (like ping)
972991
for (const [key, value] of topLevelMethods) {
973992
if (!isRpcMethod(value)) continue;
974-
emitServerInstanceMethod(key, value, srLines, classes, " ", false);
993+
emitServerInstanceMethod(key, value, srLines, classes, " ", false, false);
975994
}
976995

977996
// Group properties
@@ -1000,9 +1019,13 @@ function emitServerApiClass(className: string, node: Record<string, unknown>, cl
10001019

10011020
lines.push(`/// <summary>Provides server-scoped ${displayName} APIs.</summary>`);
10021021
const groupExperimental = isNodeFullyExperimental(node);
1022+
const groupDeprecated = isNodeFullyDeprecated(node);
10031023
if (groupExperimental) {
10041024
lines.push(`[Experimental(Diagnostics.Experimental)]`);
10051025
}
1026+
if (groupDeprecated) {
1027+
lines.push(`[Obsolete]`);
1028+
}
10061029
lines.push(`public sealed class ${className}`);
10071030
lines.push(`{`);
10081031
lines.push(` private readonly JsonRpc _rpc;`);
@@ -1018,7 +1041,7 @@ function emitServerApiClass(className: string, node: Record<string, unknown>, cl
10181041

10191042
for (const [key, value] of Object.entries(node)) {
10201043
if (!isRpcMethod(value)) continue;
1021-
emitServerInstanceMethod(key, value, lines, classes, " ", groupExperimental);
1044+
emitServerInstanceMethod(key, value, lines, classes, " ", groupExperimental, groupDeprecated);
10221045
}
10231046

10241047
for (const [subGroupName] of subGroups) {
@@ -1045,7 +1068,8 @@ function emitServerInstanceMethod(
10451068
lines: string[],
10461069
classes: string[],
10471070
indent: string,
1048-
groupExperimental: boolean
1071+
groupExperimental: boolean,
1072+
groupDeprecated: boolean
10491073
): void {
10501074
const methodName = toPascalCase(name);
10511075
const resultSchema = getMethodResultSchema(method);
@@ -1079,6 +1103,9 @@ function emitServerInstanceMethod(
10791103
if (method.stability === "experimental" && !groupExperimental) {
10801104
lines.push(`${indent}[Experimental(Diagnostics.Experimental)]`);
10811105
}
1106+
if (method.deprecated && !groupDeprecated) {
1107+
lines.push(`${indent}[Obsolete]`);
1108+
}
10821109

10831110
const sigParams: string[] = [];
10841111
const bodyAssignments: string[] = [];
@@ -1129,7 +1156,7 @@ function emitSessionRpcClasses(node: Record<string, unknown>, classes: string[])
11291156
// Emit top-level session RPC methods directly on the SessionRpc class
11301157
const topLevelLines: string[] = [];
11311158
for (const [key, value] of topLevelMethods) {
1132-
emitSessionMethod(key, value as RpcMethod, topLevelLines, classes, " ", false);
1159+
emitSessionMethod(key, value as RpcMethod, topLevelLines, classes, " ", false, false);
11331160
}
11341161
srLines.push(...topLevelLines);
11351162

@@ -1142,7 +1169,7 @@ function emitSessionRpcClasses(node: Record<string, unknown>, classes: string[])
11421169
return result;
11431170
}
11441171

1145-
function emitSessionMethod(key: string, method: RpcMethod, lines: string[], classes: string[], indent: string, groupExperimental: boolean): void {
1172+
function emitSessionMethod(key: string, method: RpcMethod, lines: string[], classes: string[], indent: string, groupExperimental: boolean, groupDeprecated: boolean): void {
11461173
const methodName = toPascalCase(key);
11471174
const resultSchema = getMethodResultSchema(method);
11481175
let resultClassName = !isVoidSchema(resultSchema) ? resultTypeName(method) : "";
@@ -1180,6 +1207,9 @@ function emitSessionMethod(key: string, method: RpcMethod, lines: string[], clas
11801207
if (method.stability === "experimental" && !groupExperimental) {
11811208
lines.push(`${indent}[Experimental(Diagnostics.Experimental)]`);
11821209
}
1210+
if (method.deprecated && !groupDeprecated) {
1211+
lines.push(`${indent}[Obsolete]`);
1212+
}
11831213
const sigParams: string[] = [];
11841214
const bodyAssignments = [`SessionId = _sessionId`];
11851215

@@ -1206,10 +1236,12 @@ function emitSessionApiClass(className: string, node: Record<string, unknown>, c
12061236
const parts: string[] = [];
12071237
const displayName = className.replace(/Api$/, "");
12081238
const groupExperimental = isNodeFullyExperimental(node);
1239+
const groupDeprecated = isNodeFullyDeprecated(node);
12091240
const experimentalAttr = groupExperimental ? `[Experimental(Diagnostics.Experimental)]\n` : "";
1241+
const deprecatedAttr = groupDeprecated ? `[Obsolete]\n` : "";
12101242
const subGroups = Object.entries(node).filter(([, v]) => typeof v === "object" && v !== null && !isRpcMethod(v));
12111243

1212-
const lines = [`/// <summary>Provides session-scoped ${displayName} APIs.</summary>`, `${experimentalAttr}public sealed class ${className}`, `{`, ` private readonly JsonRpc _rpc;`, ` private readonly string _sessionId;`, ""];
1244+
const lines = [`/// <summary>Provides session-scoped ${displayName} APIs.</summary>`, `${experimentalAttr}${deprecatedAttr}public sealed class ${className}`, `{`, ` private readonly JsonRpc _rpc;`, ` private readonly string _sessionId;`, ""];
12131245
lines.push(` internal ${className}(JsonRpc rpc, string sessionId)`, ` {`, ` _rpc = rpc;`, ` _sessionId = sessionId;`);
12141246
for (const [subGroupName] of subGroups) {
12151247
const subClassName = className.replace(/Api$/, "") + toPascalCase(subGroupName) + "Api";
@@ -1219,7 +1251,7 @@ function emitSessionApiClass(className: string, node: Record<string, unknown>, c
12191251

12201252
for (const [key, value] of Object.entries(node)) {
12211253
if (!isRpcMethod(value)) continue;
1222-
emitSessionMethod(key, value, lines, classes, " ", groupExperimental);
1254+
emitSessionMethod(key, value, lines, classes, " ", groupExperimental, groupDeprecated);
12231255
}
12241256

12251257
for (const [subGroupName] of subGroups) {
@@ -1290,10 +1322,14 @@ function emitClientSessionApiRegistration(clientSchema: Record<string, unknown>,
12901322
for (const { groupName, groupNode, methods } of groups) {
12911323
const interfaceName = clientHandlerInterfaceName(groupName);
12921324
const groupExperimental = isNodeFullyExperimental(groupNode);
1325+
const groupDeprecated = isNodeFullyDeprecated(groupNode);
12931326
lines.push(`/// <summary>Handles \`${groupName}\` client session API methods.</summary>`);
12941327
if (groupExperimental) {
12951328
lines.push(`[Experimental(Diagnostics.Experimental)]`);
12961329
}
1330+
if (groupDeprecated) {
1331+
lines.push(`[Obsolete]`);
1332+
}
12971333
lines.push(`public interface ${interfaceName}`);
12981334
lines.push(`{`);
12991335
for (const method of methods) {
@@ -1305,6 +1341,9 @@ function emitClientSessionApiRegistration(clientSchema: Record<string, unknown>,
13051341
if (method.stability === "experimental" && !groupExperimental) {
13061342
lines.push(` [Experimental(Diagnostics.Experimental)]`);
13071343
}
1344+
if (method.deprecated && !groupDeprecated) {
1345+
lines.push(` [Obsolete]`);
1346+
}
13081347
if (hasParams) {
13091348
lines.push(` ${taskType} ${clientHandlerMethodName(method.rpcMethod)}(${paramsTypeName(method)} request, CancellationToken cancellationToken = default);`);
13101349
} else {
@@ -1400,6 +1439,9 @@ function generateRpcCode(schema: ApiSchema): string {
14001439
// AUTO-GENERATED FILE - DO NOT EDIT
14011440
// Generated from: api.schema.json
14021441
1442+
#pragma warning disable CS0612 // Type or member is obsolete
1443+
#pragma warning disable CS0618 // Type or member is obsolete (with message)
1444+
14031445
using System.ComponentModel.DataAnnotations;
14041446
using System.Diagnostics.CodeAnalysis;
14051447
using System.Text.Json;

0 commit comments

Comments
 (0)