From 0ffed7370fb28477decf30a8ca03ec679344b39a Mon Sep 17 00:00:00 2001 From: Chris Kruining Date: Fri, 14 Jan 2022 17:41:08 +0100 Subject: [PATCH 1/2] [DO NOT MERGE YET] add native Typescript support Hi, I've run into the issue that my setup with remix (remix.run) is not capable of handling the esports from the generated js files (even with the support of grpc-web). At first I started working on a patch for grpc-web in order to fix it, however the deeper I dove into that the more it seems that the whole extension is a workaround. js now supports client-to-server streaming natively with the newly launched fetch upload streaming. Therefor I want to make this patch to update protoc to generate proper modern files To do this I want to: - generate typescript files for the messages and client stubs. - update the npm library to be fitting a more modern standard and implement the client side streaming. my goal is to just do the typescript part as not to blow up the scope for me to do personally since I am way out of my depth in c++. For this reason I am hoping for support to properly implement this --- .../protobuf/compiler/js/js_generator.cc | 269 ++++++++++++++++-- 1 file changed, 243 insertions(+), 26 deletions(-) diff --git a/src/google/protobuf/compiler/js/js_generator.cc b/src/google/protobuf/compiler/js/js_generator.cc index d2dac2f606f6..89b585bfa18f 100644 --- a/src/google/protobuf/compiler/js/js_generator.cc +++ b/src/google/protobuf/compiler/js/js_generator.cc @@ -1950,19 +1950,224 @@ void Generator::GenerateTestOnly(const GeneratorOptions& options, printer->Print("\n"); } -void Generator::GenerateClassesAndEnums(const GeneratorOptions& options, - io::Printer* printer, - const FileDescriptor* file) const { - for (int i = 0; i < file->message_type_count(); i++) { - GenerateClassConstructorAndDeclareExtensionFieldInfo(options, printer, - file->message_type(i)); - } - for (int i = 0; i < file->message_type_count(); i++) { - GenerateClass(options, printer, file->message_type(i)); - } - for (int i = 0; i < file->enum_type_count(); i++) { - GenerateEnum(options, printer, file->enum_type(i)); - } +void Generator::GenerateClassesAndEnums(const GeneratorOptions& options, io::Printer* printer, const FileDescriptor* file) const +{ + if (options.import_style == = GeneratorOptions::ImportStyle::kImportTypescript) + { + for (int i = 0; i < file->message_type_count(); i++) + { + GenerateTypescriptClass(options, printer, file->message_type(i)); + } + } + else + { + for (int i = 0; i < file->message_type_count(); i++) { + GenerateClassConstructorAndDeclareExtensionFieldInfo(options, printer, file->message_type(i)); + } + + for (int i = 0; i < file->message_type_count(); i++) + { + GenerateClass(options, printer, file->message_type(i)); + } + + for (int i = 0; i < file->enum_type_count(); i++) + { + GenerateEnum(options, printer, file->enum_type(i)); + } + } + +} + +void Generator::GenerateTypescriptClass(const GeneratorOptions& options, io::Printer* printer, const Descriptor* desc) const +{ + if (IgnoreMessage(desc)) + { + return; + } + + // Only proto3 is supported for now + if (desc->file()->syntax() != FileDescriptor::SYNTAX_PROTO3) + { + return; + } + + + printer->Print( + "/**\n" + " * Generated by JsPbCodeGenerator.\n" + " */\n" + "export class $classname$ extends jspb.Message {\n", + + "classprefix", GetMessagePathPrefix(options, desc), + "classname", desc->name(), + ); + printer->Indent(); + + // TODO - Print fields and accessors + printer->Print( + "public static readonly displayname: string = '$displayName$';\n", + + "displayName", GetMessagePath(options, desc), + ); + + for (int i = 0; i < desc->field_count(); i++) { + if (IgnoreField(desc->field(i))) { + continue; + } + + FieldDescriptor* field = desc->field(i); + std::string name = JSIdent(options, field, false, false, false); + std::string index = JSFieldIndex(field); + std::string type = JSFieldTypeAnnotation(options, field, + /* is_setter_argument = */ false, + /* force_present = */ false, + /* singular_if_not_packed = */ false); + + // Getter + { + printer->Print( + "get $name$(): $type$ {\n" + " return ", + + "name", name, + "type", type, + ); + printer->Annotate("gettername", field); + + GenerateFieldValueExpression(printer, "this", field, /* use_default = */ false); + + printer->Print("};\n"); + } + + // Setter + { + printer->Print( + "set $name$(value: $type$) {\n" + " jspb.Message.setField(this, $index$, value);" + "};\n\n", + + "name", name, + "index", index, + "type", type, + ); + printer->Annotate("settername", field); + } + } + + // Print constructor + printer->Print("constructor(optionalData: Array) {\n"); + printer->Indent(); + printer->Outdent(); + printer->Print("}\n"); + + // Print Methods + // TODO - Print methods + + //Close class + printer->Outdent(); + printer->Print("}\n\n"); + + // Print enums and nested messages + if (desc->enum_type_count() > 0 || desc->nested_type_count() > 0) { + printer->Print( + "export namespace $className$ {", + + "className", desc->name() + ); + printer->Indent(); + + // Print enums + for (int i = 0; i < desc->enum_type_count(); i++) { + EnumDescriptor* enumdesc = desc->enum_type(i); + + printer->Print( + "export enum $name$ {\n", + "name", enumdesc->name(), + ); + printer->Annotate("name", enumdesc); + printer->Indent(); + + std::set used_name; + std::vector valid_index; + for (int i = 0; i < enumdesc->value_count(); i++) { + if (enumdesc->options().allow_alias() && !used_name.insert(ToEnumCase(enumdesc->value(i)->name())).second) { + continue; + } + + valid_index.push_back(i); + } + + for (auto i : valid_index) { + const EnumValueDescriptor* value = enumdesc->value(i); + + printer->Print( + "$name$ = $value$,\n", + + "name", ToEnumCase(value->name()), + "value", StrCat(value->number()), + ); + printer->Annotate("name", value); + } + + printer->Outdent(); + printer->Print("}\n\n"); + } + + // Print nested classes + for (int i = 0; i < desc->nested_type_count(); i++) { + GenerateTypesciptClass(options, printer, desc->nested_type(i)); + } + + printer->Outdent(); + printer->Print("}\n\n"); + } + + + + + + + + + + + + + + if (!NamespaceOnly(desc)) + { + printer->Print("\n"); + GenerateClassFieldInfo(options, printer, desc); + + GenerateClassToObject(options, printer, desc); + // These must come *before* the extension-field info generation in + // GenerateClassRegistration so that references to the binary + // serialization/deserialization functions may be placed in the extension + // objects. + GenerateClassDeserializeBinary(options, printer, desc); + GenerateClassSerializeBinary(options, printer, desc); + } + + // Recurse on nested types. These must come *before* the extension-field + // info generation in GenerateClassRegistration so that extensions that + // reference nested types proceed the definitions of the nested types. + for (int i = 0; i < desc->enum_type_count(); i++) { + GenerateEnum(options, printer, desc->enum_type(i)); + } + for (int i = 0; i < desc->nested_type_count(); i++) { + GenerateClass(options, printer, desc->nested_type(i)); + } + + if (!NamespaceOnly(desc)) { + GenerateClassRegistration(options, printer, desc); + GenerateClassFields(options, printer, desc); + + if (options.import_style != GeneratorOptions::kImportClosure) { + for (int i = 0; i < desc->extension_count(); i++) { + GenerateExtension(options, printer, desc->extension(i)); + } + } + } } void Generator::GenerateClass(const GeneratorOptions& options, @@ -2038,7 +2243,8 @@ void Generator::GenerateClassConstructor(const GeneratorOptions& options, : (IsResponse(desc) ? "''" : "0"), "pivot", GetPivot(desc), "rptfields", RepeatedFieldsArrayName(options, desc), "oneoffields", - OneofFieldsArrayName(options, desc)); + OneofFieldsArrayName(options, desc) + ); printer->Print( "};\n" "goog.inherits($classname$, jspb.Message);\n" @@ -2051,7 +2257,8 @@ void Generator::GenerateClassConstructor(const GeneratorOptions& options, " */\n" " $classname$.displayName = '$classname$';\n" "}\n", - "classname", GetMessagePath(options, desc)); + "classname", GetMessagePath(options, desc) + ); } void Generator::GenerateClassConstructorAndDeclareExtensionFieldInfo( @@ -2600,6 +2807,8 @@ void Generator::GenerateClassField(const GeneratorOptions& options, // Message field: special handling in order to wrap the underlying data // array with a message object. + + // Print getter printer->Print( "/**\n" " * $fielddef$\n" @@ -2621,16 +2830,19 @@ void Generator::GenerateClassField(const GeneratorOptions& options, "\n" "\n", "class", GetMessagePath(options, field->containing_type()), - "gettername", "get" + JSGetterName(options, field), "type", - JSFieldTypeAnnotation(options, field, + "gettername", "get" + JSGetterName(options, field), + "type", JSFieldTypeAnnotation(options, field, /* is_setter_argument = */ false, /* force_present = */ false, /* singular_if_not_packed = */ false), - "rpt", (field->is_repeated() ? "Repeated" : ""), "index", - JSFieldIndex(field), "wrapperclass", SubmessageTypeRef(options, field), - "required", - (field->label() == FieldDescriptor::LABEL_REQUIRED ? ", 1" : "")); + "rpt", (field->is_repeated() ? "Repeated" : ""), + "index", JSFieldIndex(field), + "wrapperclass", SubmessageTypeRef(options, field), + "required", (field->label() == FieldDescriptor::LABEL_REQUIRED ? ", 1" : "") + ); printer->Annotate("gettername", field); + + // Print setter printer->Print( "/**\n" " * @param {$optionaltype$} value\n" @@ -2691,13 +2903,18 @@ void Generator::GenerateClassField(const GeneratorOptions& options, "$comment$" " * @return {$type$}\n" " */\n", - "fielddef", FieldDefinition(options, field), "comment", - FieldComments(field, bytes_mode), "type", typed_annotation); + "fielddef", FieldDefinition(options, field), + "comment", FieldComments(field, bytes_mode), + "type", typed_annotation + ); } - printer->Print("$class$.prototype.$gettername$ = function() {\n", "class", - GetMessagePath(options, field->containing_type()), - "gettername", "get" + JSGetterName(options, field)); + printer->Print( + "$class$.prototype.$gettername$ = function() {\n", + + "class", GetMessagePath(options, field->containing_type()), + "gettername", "get" + JSGetterName(options, field) + ); printer->Annotate("gettername", field); if (untyped) { From 59563dccb5a747f94a2aa9a3c7132909eb2735e8 Mon Sep 17 00:00:00 2001 From: Chris Kruining Date: Fri, 14 Jan 2022 17:43:16 +0100 Subject: [PATCH 2/2] initial patch for ts support --- .../protobuf/compiler/js/js_generator.cc | 83 ++++++------------- .../protobuf/compiler/js/js_generator.h | 9 +- 2 files changed, 32 insertions(+), 60 deletions(-) diff --git a/src/google/protobuf/compiler/js/js_generator.cc b/src/google/protobuf/compiler/js/js_generator.cc index 89b585bfa18f..425761d122d4 100644 --- a/src/google/protobuf/compiler/js/js_generator.cc +++ b/src/google/protobuf/compiler/js/js_generator.cc @@ -2016,27 +2016,29 @@ void Generator::GenerateTypescriptClass(const GeneratorOptions& options, io::Pri } FieldDescriptor* field = desc->field(i); - std::string name = JSIdent(options, field, false, false, false); + std::string identifier = JSIdent(options, field, false, false, false); std::string index = JSFieldIndex(field); std::string type = JSFieldTypeAnnotation(options, field, /* is_setter_argument = */ false, /* force_present = */ false, /* singular_if_not_packed = */ false); + GenerateClassField(options, printer, desc->field(i)); + // Getter { printer->Print( "get $name$(): $type$ {\n" " return ", - "name", name, + "name", identifier, "type", type, ); printer->Annotate("gettername", field); GenerateFieldValueExpression(printer, "this", field, /* use_default = */ false); - printer->Print("};\n"); + printer->Print("};\n\n"); } // Setter @@ -2046,11 +2048,28 @@ void Generator::GenerateTypescriptClass(const GeneratorOptions& options, io::Pri " jspb.Message.setField(this, $index$, value);" "};\n\n", - "name", name, + "name", identifier, "index", index, "type", type, ); printer->Annotate("settername", field); + + GenerateFieldValueExpression(printer, "this", field, /* use_default = */ false); + + printer->Print("};\n\n"); + + + printer->Print( + "$class$.prototype.$settername$ = function(value) {\n" + " return jspb.Message.setProto3$typetag$Field(this, $index$, " + "value);" + "\n" + "};\n" + "\n" + "\n", + "class", GetMessagePath(options, field->containing_type()), + "settername", "set" + JSGetterName(options, field), "typetag", + JSTypeTag(field), "index", JSFieldIndex(field)); } } @@ -2065,62 +2084,8 @@ void Generator::GenerateTypescriptClass(const GeneratorOptions& options, io::Pri //Close class printer->Outdent(); - printer->Print("}\n\n"); - - // Print enums and nested messages - if (desc->enum_type_count() > 0 || desc->nested_type_count() > 0) { - printer->Print( - "export namespace $className$ {", - - "className", desc->name() - ); - printer->Indent(); - - // Print enums - for (int i = 0; i < desc->enum_type_count(); i++) { - EnumDescriptor* enumdesc = desc->enum_type(i); - - printer->Print( - "export enum $name$ {\n", - "name", enumdesc->name(), - ); - printer->Annotate("name", enumdesc); - printer->Indent(); - - std::set used_name; - std::vector valid_index; - for (int i = 0; i < enumdesc->value_count(); i++) { - if (enumdesc->options().allow_alias() && !used_name.insert(ToEnumCase(enumdesc->value(i)->name())).second) { - continue; - } - - valid_index.push_back(i); - } - - for (auto i : valid_index) { - const EnumValueDescriptor* value = enumdesc->value(i); - - printer->Print( - "$name$ = $value$,\n", - - "name", ToEnumCase(value->name()), - "value", StrCat(value->number()), - ); - printer->Annotate("name", value); - } - - printer->Outdent(); - printer->Print("}\n\n"); - } - - // Print nested classes - for (int i = 0; i < desc->nested_type_count(); i++) { - GenerateTypesciptClass(options, printer, desc->nested_type(i)); - } + printer->Print("}\n"); - printer->Outdent(); - printer->Print("}\n\n"); - } diff --git a/src/google/protobuf/compiler/js/js_generator.h b/src/google/protobuf/compiler/js/js_generator.h index cd9631afb772..964fe351ee47 100644 --- a/src/google/protobuf/compiler/js/js_generator.h +++ b/src/google/protobuf/compiler/js/js_generator.h @@ -73,6 +73,7 @@ struct GeneratorOptions { kImportCommonJsStrict, // require() with no global export kImportBrowser, // no import statements kImportEs6, // import { member } from '' + kImportTypescript, // import { member } from '' } import_style; GeneratorOptions() @@ -93,7 +94,11 @@ struct GeneratorOptions { // Returns the file name extension to use for generated code. std::string GetFileNameExtension() const { - return import_style == kImportClosure ? extension : "_pb.js"; + return import_style == kImportClosure + ? extension + : import_style == kImportTypescript + ? "_pb.ts" + : "_pb.js"; } enum OutputMode { @@ -247,6 +252,8 @@ class PROTOC_EXPORT Generator : public CodeGenerator { bool use_default) const; // Generate definition for one class. + void GenerateTypescriptClass(const GeneratorOptions& options, io::Printer* printer, + const Descriptor* desc) const; void GenerateClass(const GeneratorOptions& options, io::Printer* printer, const Descriptor* desc) const; void GenerateClassConstructor(const GeneratorOptions& options,