diff --git a/javascript/net/grpc/web/generator/grpc_generator.cc b/javascript/net/grpc/web/generator/grpc_generator.cc index da190d95..fdb5f473 100644 --- a/javascript/net/grpc/web/generator/grpc_generator.cc +++ b/javascript/net/grpc/web/generator/grpc_generator.cc @@ -833,116 +833,160 @@ void PrintProtoDtsOneofCase(Printer* printer, const OneofDescriptor* desc) { printer->Print("}\n"); } -void PrintProtoDtsMessage(Printer* printer, const Descriptor* desc, - const FileDescriptor* file) { - const string& class_name = desc->name(); - std::map vars; +void PrintProtoDtsMessage(Printer* printer, const Descriptor* desc, const FileDescriptor* file) { + const std::string& class_name = desc->name(); + std::map vars; vars["class_name"] = class_name; + // Print class declaration (methods will be declared in the class scope) printer->Print(vars, "export class $class_name$ extends jspb.Message {\n"); printer->Indent(); + + // --- Generate getters/setters/has/clear/add for each field --- for (int i = 0; i < desc->field_count(); i++) { const FieldDescriptor* field = desc->field(i); - vars["js_field_name"] = SafeAccessorName(JSFieldName(field)); + // Use SafeAccessorName on JSFieldName so reserved names get the $ suffix. + std::string safe_name = SafeAccessorName(JSFieldName(field)); + vars["js_field_name"] = safe_name; vars["js_field_type"] = JSFieldType(field, file); - if (field->type() != FieldDescriptor::TYPE_MESSAGE || - field->is_repeated()) { - printer->Print(vars, "get$js_field_name$(): $js_field_type$;\n"); + + // getX(): type or type | undefined for singular message presence + if (field->type() != FieldDescriptor::TYPE_MESSAGE || field->is_repeated()) { + printer->Print(vars, " get$js_field_name$(): $js_field_type$;\n"); } else { - printer->Print(vars, - "get$js_field_name$(): $js_field_type$ | undefined;\n"); + printer->Print(vars, " get$js_field_name$(): $js_field_type$ | undefined;\n"); } + + // bytes helpers if (field->type() == FieldDescriptor::TYPE_BYTES && !field->is_repeated()) { - printer->Print(vars, - "get$js_field_name$_asU8(): Uint8Array;\n" - "get$js_field_name$_asB64(): string;\n"); + // two additional accessors for bytes fields + printer->Print(vars, " get$js_field_name$_asU8(): Uint8Array;\n"); + printer->Print(vars, " get$js_field_name$_asB64(): string;\n"); } - if (!field->is_map() && (field->type() != FieldDescriptor::TYPE_MESSAGE || - field->is_repeated())) { - printer->Print(vars, - "set$js_field_name$(value: $js_field_type$): " - "$class_name$;\n"); + + // setX(...) + if (!field->is_map() && (field->type() != FieldDescriptor::TYPE_MESSAGE || field->is_repeated())) { + printer->Print(vars, " set$js_field_name$(value: $js_field_type$): $class_name$;\n"); } else if (!field->is_map()) { - printer->Print(vars, - "set$js_field_name$(value?: $js_field_type$): " - "$class_name$;\n"); + // message singular + printer->Print(vars, " set$js_field_name$(value?: $js_field_type$): $class_name$;\n"); } + + // hasX() if (field->has_presence()) { - printer->Print(vars, "has$js_field_name$(): boolean;\n"); + printer->Print(vars, " has$js_field_name$(): boolean;\n"); } - if (field->type() == FieldDescriptor::TYPE_MESSAGE || - field->has_presence() || field->is_repeated() || field->is_map()) { - printer->Print(vars, "clear$js_field_name$(): $class_name$;\n"); + + // clearX() + if (field->type() == FieldDescriptor::TYPE_MESSAGE || field->has_presence() || field->is_repeated() || field->is_map()) { + printer->Print(vars, " clear$js_field_name$(): $class_name$;\n"); } + + // repeated addX() if (field->is_repeated() && !field->is_map()) { + // element name/type for add + std::string elem_name = SafeAccessorName(JSElementName(field)); + std::string elem_type = JSElementType(field, file); + vars["js_field_name"] = elem_name; + vars["js_field_type"] = elem_type; - vars["js_field_name"] = SafeAccessorName(JSElementName(field)); - vars["js_field_type"] = JSElementType(field, file); if (field->type() != FieldDescriptor::TYPE_MESSAGE) { - printer->Print(vars, - "add$js_field_name$(value: $js_field_type$, " - "index?: number): $class_name$;\n"); + // add(value: T, index?: number): Message + printer->Print(vars, " add$js_field_name$(value: $js_field_type$, index?: number): $class_name$;\n"); } else { - printer->Print(vars, - "add$js_field_name$(value?: $js_field_type$, " - "index?: number): $js_field_type$;\n"); + // add(value?: T, index?: number): T + printer->Print(vars, " add$js_field_name$(value?: $js_field_type$, index?: number): $js_field_type$;\n"); } + + // restore js_field_name for subsequent iterations if needed + vars["js_field_name"] = SafeAccessorName(JSFieldName(field)); + vars["js_field_type"] = JSFieldType(field, file); } printer->Print("\n"); } + // --- Generate real oneof getCase() methods --- for (int i = 0; i < desc->real_oneof_decl_count(); i++) { - const OneofDescriptor *oneof = desc->real_oneof_decl(i); - vars["js_oneof_name"] = ToUpperCamel(ParseLowerUnderscore(oneof->name())); - printer->Print( - vars, "get$js_oneof_name$Case(): $class_name$.$js_oneof_name$Case;\n"); + const OneofDescriptor* oneof = desc->real_oneof_decl(i); + std::string js_oneof_name = ToUpperCamel(ParseLowerUnderscore(oneof->name())); + vars["js_oneof_name"] = js_oneof_name; + printer->Print(vars, " get$js_oneof_name$Case(): $class_name$.$js_oneof_name$Case;\n"); printer->Print("\n"); } - printer->Print( - vars, - "serializeBinary(): Uint8Array;\n" - "toObject(includeInstance?: boolean): " - "$class_name$.AsObject;\n" - "static toObject(includeInstance: boolean, msg: $class_name$): " - "$class_name$.AsObject;\n" - "static serializeBinaryToWriter(message: $class_name$, writer: " - "jspb.BinaryWriter): void;\n" - "static deserializeBinary(bytes: Uint8Array): $class_name$;\n" - "static deserializeBinaryFromReader(message: $class_name$, reader: " - "jspb.BinaryReader): $class_name$;\n"); + printer->Print(vars, + " serializeBinary(): Uint8Array;\n" + " toObject(includeInstance?: boolean): $class_name$.AsObject;\n" + " static toObject(includeInstance: boolean, msg: $class_name$): $class_name$.AsObject;\n" + " static serializeBinaryToWriter(message: $class_name$, writer: jspb.BinaryWriter): void;\n" + " static deserializeBinary(bytes: Uint8Array): $class_name$;\n" + " static deserializeBinaryFromReader(message: $class_name$, reader: jspb.BinaryReader): $class_name$;\n"); printer->Outdent(); printer->Print("}\n\n"); + // --- Namespace with AsObject and nested types/enums/oneof-case enums --- printer->Print(vars, "export namespace $class_name$ {\n"); printer->Indent(); - printer->Print("export type AsObject = {\n"); - printer->Indent(); - for (int i = 0; i < desc->field_count(); i++) { - const FieldDescriptor* field = desc->field(i); - - string js_field_name = CamelCaseJSFieldName(field); - if (IsReserved(js_field_name)) { - js_field_name = "pb_" + js_field_name; + + // Generate AsObject: + if (desc->oneof_decl_count() > 0) { + // Type-safe union for oneofs (string discriminant = field name) + printer->Print("export type AsObject = (\n"); + printer->Indent(); + + for (int oi = 0; oi < desc->oneof_decl_count(); oi++) { + const OneofDescriptor* oneof = desc->oneof_decl(oi); + for (int fi = 0; fi < oneof->field_count(); fi++) { + const FieldDescriptor* field = oneof->field(fi); + std::string js_field_name = CamelCaseJSFieldName(field); + if (IsReserved(js_field_name)) js_field_name = "pb_" + js_field_name; + vars["js_field_name"] = js_field_name; + vars["js_field_type"] = AsObjectFieldType(field, file); + + printer->Print(vars, " | { oneofKind: \"$js_field_name$\"; $js_field_name$: $js_field_type$ }\n"); + } } - vars["js_field_name"] = js_field_name; - vars["js_field_type"] = AsObjectFieldType(field, file); - if (!field->has_presence()) { - printer->Print(vars, "$js_field_name$: $js_field_type$;\n"); - } else { - printer->Print(vars, "$js_field_name$?: $js_field_type$;\n"); + printer->Print(" | { oneofKind: undefined }\n"); + printer->Outdent(); + printer->Print(") & {\n"); + printer->Indent(); + + // Add non-oneof fields to the intersection object + for (int i = 0; i < desc->field_count(); i++) { + const FieldDescriptor* field = desc->field(i); + if (!field->containing_oneof()) { + std::string js_field_name = CamelCaseJSFieldName(field); + if (IsReserved(js_field_name)) js_field_name = "pb_" + js_field_name; + vars["js_field_name"] = js_field_name; + vars["js_field_type"] = AsObjectFieldType(field, file); + // Preserve previous presence semantics: use optional for fields with presence or repeated/map? keep simple optional + printer->Print(vars, " $js_field_name$?: $js_field_type$;\n"); + } } + + printer->Outdent(); + printer->Print("};\n"); + } else { + // No oneofs: legacy object + printer->Print("export type AsObject = {\n"); + printer->Indent(); + for (int i = 0; i < desc->field_count(); i++) { + const FieldDescriptor* field = desc->field(i); + std::string js_field_name = CamelCaseJSFieldName(field); + if (IsReserved(js_field_name)) js_field_name = "pb_" + js_field_name; + vars["js_field_name"] = js_field_name; + vars["js_field_type"] = AsObjectFieldType(field, file); + printer->Print(vars, " $js_field_name$?: $js_field_type$;\n"); + } + printer->Outdent(); + printer->Print("};\n"); } - printer->Outdent(); - printer->Print("};\n"); for (int i = 0; i < desc->nested_type_count(); i++) { - if (desc->nested_type(i)->options().map_entry()) { - continue; - } + if (desc->nested_type(i)->options().map_entry()) continue; printer->Print("\n"); PrintProtoDtsMessage(printer, desc->nested_type(i), file); }