From 71e76b30a1cbc0d51d217b65d9538a31f830e8cc Mon Sep 17 00:00:00 2001 From: Red Daly Date: Sun, 4 Dec 2022 14:18:44 -0800 Subject: [PATCH 01/30] WIP on ES6-style code generation (imports and implementation). --- generator/js_generator.cc | 365 ++++++++++++++++++++++++++++---------- generator/js_generator.h | 10 ++ 2 files changed, 286 insertions(+), 89 deletions(-) diff --git a/generator/js_generator.cc b/generator/js_generator.cc index 97b5844..25fab3d 100644 --- a/generator/js_generator.cc +++ b/generator/js_generator.cc @@ -1726,6 +1726,8 @@ void Generator::GenerateProvides(const GeneratorOptions& options, it != provided->end(); ++it) { if (options.import_style == GeneratorOptions::kImportClosure) { printer->Print("goog.provide('$name$');\n", "name", *it); + } else if (options.import_style == GeneratorOptions::kImportEs6) { + printer->Print("// DEBUG: in ES6 mode, no need for provide $name$');\n", "name", *it); } else { // We aren't using Closure's import system, but we use goog.exportSymbol() // to construct the expected tree of objects, eg. @@ -1778,7 +1780,9 @@ void Generator::GenerateRequiresForLibrary( const GeneratorOptions& options, io::Printer* printer, const std::vector& files, std::set* provided) const { - GOOGLE_CHECK_EQ(options.import_style, GeneratorOptions::kImportClosure); + GOOGLE_CHECK_OK( + options.import_style == GeneratorOptions::kImportClosure || + options.import_style == GeneratorOptions::kImportEs6); // For Closure imports we need to import every message type individually. std::set required; std::set forwards; @@ -1954,9 +1958,13 @@ void Generator::GenerateTestOnly(const GeneratorOptions& options, 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)); + // In ES6 module mode, class constructors are generated within + // GenerateClass. + if (options.import_style != GeneratorOptions::kImportEs6) { + 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)); @@ -1969,6 +1977,10 @@ void Generator::GenerateClassesAndEnums(const GeneratorOptions& options, void Generator::GenerateClass(const GeneratorOptions& options, io::Printer* printer, const Descriptor* desc) const { + if (options.import_style == GeneratorOptions::kImportEs6) { + GenerateClassEs6(options, printer, desc); + return; + } if (IgnoreMessage(desc)) { return; } @@ -2008,51 +2020,122 @@ void Generator::GenerateClass(const GeneratorOptions& options, } } +void Generator::GenerateClassEs6(const GeneratorOptions& options, + io::Printer* printer, + const Descriptor* desc) const { + if (IgnoreMessage(desc)) { + return; + } + + printer->Print("\n"); + printer->Print( + "/**\n" + " * ES6 class generated by js_generator.cc.\n" + " *\n" + " * @param {Array=} opt_data Optional initial data array, typically " + "from a\n" + " * server response, or constructed directly in Javascript. The array " + "is used\n" + " * in place and becomes part of the constructed object. It is not " + "cloned.\n" + " * If no data is provided, the constructed object will be empty, but " + "still\n" + " * valid.\n" + " * @extends {jspb.Message}\n" + " * @constructor\n" + " */\n" + + " // DO NOT SUBMIT: \n" + " // classprefix = $classprefix$\n" + " // classname = $classname$ \n" + + "export class $classname$ extends jspb.Message {\n", + "classname", desc->name()); + + printer->Indent(); + + GenerateClassFieldInfo(options, printer, desc); + + // DO NOT SUBMIT 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)); + } + + GenerateClassRegistration(options, printer, desc); + GenerateClassFields(options, printer, desc); + for (int i = 0; i < desc->extension_count(); i++) { + GenerateExtension(options, printer, desc->extension(i)); + } + + printer->Outdent(); + printer->Print("} // end class $classname$\n", + "classname", desc->name()); // end class $classname$ extends jspb.Message +} + void Generator::GenerateClassConstructor(const GeneratorOptions& options, io::Printer* printer, const Descriptor* desc) const { printer->Print( - "/**\n" - " * Generated by JsPbCodeGenerator.\n" - " * @param {Array=} opt_data Optional initial data array, typically " - "from a\n" - " * server response, or constructed directly in Javascript. The array " - "is used\n" - " * in place and becomes part of the constructed object. It is not " - "cloned.\n" - " * If no data is provided, the constructed object will be empty, but " - "still\n" - " * valid.\n" - " * @extends {jspb.Message}\n" - " * @constructor\n" - " */\n" - "$classprefix$$classname$ = function(opt_data) {\n", - "classprefix", GetMessagePathPrefix(options, desc), "classname", - desc->name()); - printer->Annotate("classname", desc); - std::string message_id = GetMessageId(desc); - printer->Print( - " jspb.Message.initialize(this, opt_data, $messageId$, $pivot$, " - "$rptfields$, $oneoffields$);\n", - "messageId", - !message_id.empty() ? ("'" + message_id + "'") - : (IsResponse(desc) ? "''" : "0"), - "pivot", GetPivot(desc), "rptfields", - RepeatedFieldsArrayName(options, desc), "oneoffields", - OneofFieldsArrayName(options, desc)); - printer->Print( - "};\n" - "goog.inherits($classname$, jspb.Message);\n" - "if (goog.DEBUG && !COMPILED) {\n" - // displayName overrides Function.prototype.displayName - // http://google3/javascript/externs/es3.js?l=511 - " /**\n" - " * @public\n" - " * @override\n" - " */\n" - " $classname$.displayName = '$classname$';\n" - "}\n", - "classname", GetMessagePath(options, desc)); + "/**\n" + " * Generated by JsPbCodeGenerator.\n" + " * @param {Array=} opt_data Optional initial data array, typically " + "from a\n" + " * server response, or constructed directly in Javascript. The array " + "is used\n" + " * in place and becomes part of the constructed object. It is not " + "cloned.\n" + " * If no data is provided, the constructed object will be empty, but " + "still\n" + " * valid.\n" + " * @extends {jspb.Message}\n" + " * @constructor\n" + " */\n" + + " // DO NOT SUBMIT: \n" + " // classprefix = $classprefix$\n" + " // classname = $classname$ \n" + + "$classprefix$$classname$ = function(opt_data) {\n", + "classprefix", GetMessagePathPrefix(options, desc), "classname", + desc->name()); +printer->Annotate("classname", desc); +std::string message_id = GetMessageId(desc); +printer->Print( + " jspb.Message.initialize(this, opt_data, $messageId$, $pivot$, " + "$rptfields$, $oneoffields$);\n", + "messageId", + !message_id.empty() ? ("'" + message_id + "'") + : (IsResponse(desc) ? "''" : "0"), + "pivot", GetPivot(desc), "rptfields", + RepeatedFieldsArrayName(options, desc), "oneoffields", + OneofFieldsArrayName(options, desc)); +printer->Print( + "};\n" + "goog.inherits($classname$, jspb.Message);\n" + "if (goog.DEBUG && !COMPILED) {\n" + // displayName overrides Function.prototype.displayName + // http://google3/javascript/externs/es3.js?l=511 + " /**\n" + " * @public\n" + " * @override\n" + " */\n" + " $classname$.displayName = '$classname$';\n" + "}\n", + "classname", GetMessagePath(options, desc)); } void Generator::GenerateClassConstructorAndDeclareExtensionFieldInfo( @@ -2545,6 +2628,11 @@ void GenerateBytesWrapper(const GeneratorOptions& options, io::Printer* printer, void Generator::GenerateClassField(const GeneratorOptions& options, io::Printer* printer, const FieldDescriptor* field) const { + +const char * endBrace = + (options.import_style == GeneratorOptions::kImportEs6) ? + "}" : "};"; + if (field->is_map()) { const FieldDescriptor* key_field = MapFieldKey(field); const FieldDescriptor* value_field = MapFieldValue(field); @@ -2567,14 +2655,28 @@ void Generator::GenerateClassField(const GeneratorOptions& options, " * empty, instead returning `undefined`\n" " * @return {!jspb.Map<$keytype$,$valuetype$>}\n" " */\n", - "fielddef", FieldDefinition(options, field), "keytype", key_type, + "fielddef", FieldDefinition(options, field), + "keytype", key_type, "valuetype", value_type); + + // Function start + if (options.import_style == GeneratorOptions::kImportEs6) { + printer->Print( + "$gettername$(opt_noLazyCreate) {\n", + "gettername", "get" + JSGetterName(options, field)); + } else { + printer->Print( + "$class$.prototype.$gettername$ = function(opt_noLazyCreate) {\n", + "class", GetMessagePath(options, field->containing_type()), + "gettername", "get" + JSGetterName(options, field)); + } + + // Begin function body contents. printer->Print( - "$class$.prototype.$gettername$ = function(opt_noLazyCreate) {\n" " return /** @type {!jspb.Map<$keytype$,$valuetype$>} */ (\n", - "class", GetMessagePath(options, field->containing_type()), - "gettername", "get" + JSGetterName(options, field), "keytype", key_type, + "keytype", key_type, "valuetype", value_type); + printer->Annotate("gettername", field); printer->Print( " jspb.Message.getMapField(this, $index$, opt_noLazyCreate", @@ -2594,9 +2696,13 @@ void Generator::GenerateClassField(const GeneratorOptions& options, printer->Print("));\n"); printer->Print( - "};\n" + "$endbrace$\n" "\n" - "\n"); + "\n", + "endbrace", endBrace); + + // End function body contents. + } else if (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) { // Message field: special handling in order to wrap the underlying data // array with a message object. @@ -2613,24 +2719,36 @@ void Generator::GenerateClassField(const GeneratorOptions& options, /* is_setter_argument = */ false, /* force_present = */ false, /* singular_if_not_packed = */ false)); + + + // Function definition begin.. depends on ES6 style or not + if (options.import_style == GeneratorOptions::kImportEs6) { + printer->Print( + "$gettername$() {\n", + "gettername", "get" + JSGetterName(options, field)); + } else { + 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" " return /** @type{$type$} */ (\n" " jspb.Message.get$rpt$WrapperField(this, $wrapperclass$, " "$index$$required$));\n" - "};\n" + "$endbrace$\n" "\n" "\n", - "class", GetMessagePath(options, field->containing_type()), - "gettername", "get" + JSGetterName(options, field), "type", - JSFieldTypeAnnotation(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" : "")); + (field->label() == FieldDescriptor::LABEL_REQUIRED ? ", 1" : ""), + "endbrace", endBrace); printer->Annotate("gettername", field); printer->Print( "/**\n" @@ -2652,11 +2770,12 @@ void Generator::GenerateClassField(const GeneratorOptions& options, printer->Print( "this, $index$$oneofgroup$, value);\n" - "};\n" + "$endbrace$\n" "\n" "\n", "index", JSFieldIndex(field), "oneofgroup", - (InRealOneof(field) ? (", " + JSOneofArray(options, field)) : "")); + (InRealOneof(field) ? (", " + JSOneofArray(options, field)) : ""), + "endbrace", endBrace); if (field->is_repeated()) { GenerateRepeatedMessageHelperMethods(options, printer, field); @@ -2696,9 +2815,18 @@ void Generator::GenerateClassField(const GeneratorOptions& options, 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)); + // Function definition begin.. depends on ES6 style or not + if (options.import_style == GeneratorOptions::kImportEs6) { + printer->Print( + "$gettername$() {\n", + "gettername", "get" + JSGetterName(options, field)); + } else { + printer->Print( + "$class$.prototype.$gettername$ = function() {\n", + "class", GetMessagePath(options, field->containing_type()), + "gettername", "get" + JSGetterName(options, field)); + } + // TODO delete this annotate call? printer->Annotate("gettername", field); if (untyped) { @@ -2726,15 +2854,17 @@ void Generator::GenerateClassField(const GeneratorOptions& options, if (untyped) { printer->Print( ";\n" - "};\n" + "$endbrace$\n" "\n" - "\n"); + "\n", + "endbrace", endBrace); } else { printer->Print( ");\n" - "};\n" + "$endbrace$\n" "\n" - "\n"); + "\n", + "endbrace", endBrace); } if (field->type() == FieldDescriptor::TYPE_BYTES && !untyped) { @@ -3018,17 +3148,27 @@ void Generator::GenerateClassDeserializeBinary(const GeneratorOptions& options, // TODO(cfallin): Handle lazy decoding when requested by field option and/or // by default for 'bytes' fields and packed repeated fields. + const std::string classSymbol = GetMessagePath(options, desc); + printer->Print( "/**\n" " * Deserializes binary data (in protobuf wire format).\n" " * @param {jspb.ByteSource} bytes The bytes to deserialize.\n" " * @return {!$class$}\n" - " */\n" - "$class$.deserializeBinary = function(bytes) {\n" + " */\n", + "class", classSymbol); + GenerateMethodStart(options, printer, classSymbol.c_str(), "deserializeBinary"); + printer->Print( + "(bytes) {\n" " var reader = new jspb.BinaryReader(bytes);\n" " var msg = new $class$;\n" - " return $class$.deserializeBinaryFromReader(msg, reader);\n" - "};\n" + " return $class$.deserializeBinaryFromReader(msg, reader);\n", + "class", classSymbol); + + GenerateMethodEnd(options, printer); + +printer->Print( + "\n" "\n" "\n" "/**\n" @@ -3037,11 +3177,12 @@ void Generator::GenerateClassDeserializeBinary(const GeneratorOptions& options, " * @param {!$class$} msg The message object to deserialize into.\n" " * @param {!jspb.BinaryReader} reader The BinaryReader to use.\n" " * @return {!$class$}\n" - " */\n" - "$class$.deserializeBinaryFromReader = function(msg, reader) {\n" - " while (reader.nextField()) {\n", - "class", GetMessagePath(options, desc)); + " */\n", + "class", classSymbol); + GenerateMethodStart(options, printer, classSymbol.c_str(), "deserializeBinaryFromReader"); printer->Print( + "(msg, reader) {\n" + " while (reader.nextField()) {\n" " if (reader.isEndGroup()) {\n" " break;\n" " }\n" @@ -3074,8 +3215,12 @@ void Generator::GenerateClassDeserializeBinary(const GeneratorOptions& options, printer->Print( " }\n" - " return msg;\n" - "};\n" + " return msg;\n"); + + GenerateMethodEnd(options, printer); + + printer->Print( + "\n" "\n" "\n"); } @@ -3174,16 +3319,25 @@ void Generator::GenerateClassDeserializeBinaryField( void Generator::GenerateClassSerializeBinary(const GeneratorOptions& options, io::Printer* printer, const Descriptor* desc) const { + + const std::string classSymbol = GetMessagePath(options, desc); + printer->Print( "/**\n" " * Serializes the message to binary data (in protobuf wire format).\n" " * @return {!Uint8Array}\n" - " */\n" - "$class$.prototype.serializeBinary = function() {\n" + " */\n"); + GenerateMethodStart(options, printer, classSymbol.c_str(), "serializeBinary"); + printer->Print( + "() {\n" " var writer = new jspb.BinaryWriter();\n" " $class$.serializeBinaryToWriter(this, writer);\n" - " return writer.getResultBuffer();\n" - "};\n" + " return writer.getResultBuffer();\n", + "class", classSymbol); + + GenerateMethodEnd(options, printer); + printer->Print( + "\n" "\n" "\n" "/**\n" @@ -3192,13 +3346,14 @@ void Generator::GenerateClassSerializeBinary(const GeneratorOptions& options, " * @param {!$class$} message\n" " * @param {!jspb.BinaryWriter} writer\n" " * @suppress {unusedLocalVariables} f is only used for nested messages\n" - " */\n" - "$class$.serializeBinaryToWriter = function(message, " - "writer) {\n" - " var f = undefined;\n", - "class", GetMessagePath(options, desc)); + " */\n", + "class", classSymbol); + GenerateMethodStart(options, printer, classSymbol.c_str(), "serializeBinaryToWriter"); + printer->Print( + "(message, writer) {\n" + " var f = undefined;\n"); - for (int i = 0; i < desc->field_count(); i++) { +for (int i = 0; i < desc->field_count(); i++) { if (!IgnoreField(desc->field(i))) { GenerateClassSerializeBinaryField(options, printer, desc->field(i)); } @@ -3212,8 +3367,10 @@ void Generator::GenerateClassSerializeBinary(const GeneratorOptions& options, GetMessagePath(options, desc)); } + GenerateMethodEnd(options, printer); + printer->Print( - "};\n" + "\n" "\n" "\n"); } @@ -3617,7 +3774,15 @@ void Generator::GenerateFile(const GeneratorOptions& options, GenerateHeader(options, file, printer); // Generate "require" statements. - if ((options.import_style == GeneratorOptions::kImportCommonJs || + if (options.import_style == GeneratorOptions::kImportEs6) { + for (int i = 0; i < file->dependency_count(); i++) { + const std::string& name = file->dependency(i)->name(); + printer->Print( + "// TODO: import {used types here} from \"$file$\";\n", + "file", GetRootPath(file->name(), name) + GetJSFilename(options, name)); + } + + } else if ((options.import_style == GeneratorOptions::kImportCommonJs || options.import_style == GeneratorOptions::kImportCommonJsStrict)) { printer->Print("var jspb = require('google-protobuf');\n"); printer->Print("var goog = jspb;\n"); @@ -3677,7 +3842,8 @@ void Generator::GenerateFile(const GeneratorOptions& options, GenerateProvides(options, printer, &provided); std::vector files; files.push_back(file); - if (options.import_style == GeneratorOptions::kImportClosure) { + if (options.import_style == GeneratorOptions::kImportClosure || + options.import_style == GeneratorOptions::kImportEs6) { GenerateRequiresForLibrary(options, printer, files, &provided); } @@ -3939,6 +4105,27 @@ bool Generator::GenerateAll(const std::vector& files, return true; } +// Prints the beginning/end of a method of some class. +void Generator::GenerateMethodStart(const GeneratorOptions& options, + io::Printer* printer, + const char * classSymbol, + const char * methodName) const { + if(options.import_style == GeneratorOptions::kImportEs6) { + printer->Print("$method$", "method", methodName); + } else { + printer->Print("$class$ = function", "class", classSymbol); + } +} + +void Generator::GenerateMethodEnd(const GeneratorOptions& options, + io::Printer* printer) const { + if(options.import_style == GeneratorOptions::kImportEs6) { + printer->Print("}"); + } else { + printer->Print("};"); + } +} + } // namespace js } // namespace compiler } // namespace protobuf diff --git a/generator/js_generator.h b/generator/js_generator.h index c7a942a..0737377 100644 --- a/generator/js_generator.h +++ b/generator/js_generator.h @@ -249,6 +249,8 @@ class PROTOC_EXPORT Generator : public CodeGenerator { // Generate definition for one class. void GenerateClass(const GeneratorOptions& options, io::Printer* printer, const Descriptor* desc) const; + void GenerateClassEs6(const GeneratorOptions& options, io::Printer* printer, + const Descriptor* desc) const; void GenerateClassConstructor(const GeneratorOptions& options, io::Printer* printer, const Descriptor* desc) const; @@ -323,6 +325,14 @@ class PROTOC_EXPORT Generator : public CodeGenerator { io::Printer* printer, const FieldDescriptor* field) const; +// Prints the beginning/end of a method of some class. +void GenerateMethodStart(const GeneratorOptions& options, + io::Printer* printer, + const char * classSymbol, + const char * methodName) const; +void GenerateMethodEnd(const GeneratorOptions& options, + io::Printer* printer) const; + GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(Generator); }; From 611d03df279a67f60fc6fa9e4a1455070d0d2e80 Mon Sep 17 00:00:00 2001 From: Red Daly Date: Sun, 4 Dec 2022 17:04:44 -0800 Subject: [PATCH 02/30] Further WIP towards ES6-style code generation. --- generator/js_generator.cc | 242 +++++++++++++++++++++++--------------- generator/js_generator.h | 21 ++-- 2 files changed, 164 insertions(+), 99 deletions(-) diff --git a/generator/js_generator.cc b/generator/js_generator.cc index 25fab3d..85a64ba 100644 --- a/generator/js_generator.cc +++ b/generator/js_generator.cc @@ -1164,12 +1164,12 @@ bool HasOneofFields(const Descriptor* desc) { return false; } -static const char* kOneofGroupArrayName = ".oneofGroups_"; +static const char* kOneofGroupArrayName = "oneofGroups_"; std::string OneofFieldsArrayName(const GeneratorOptions& options, const Descriptor* desc) { return HasOneofFields(desc) - ? (GetMessagePath(options, desc) + kOneofGroupArrayName) + ? (GetMessagePath(options, desc) + "." + kOneofGroupArrayName) : "null"; } @@ -2174,6 +2174,14 @@ void Generator::GenerateClassFieldInfo(const GeneratorOptions& options, } if (HasOneofFields(desc)) { + const std::string className = GetMessagePath(options, desc); + const std::string assignment = ( + options.import_style == GeneratorOptions::kImportEs6 + ) ? ( + std::string("static ") + kOneofGroupArrayName + " = " + OneofGroupList(desc) + ";" + ) : ( + className + "." + kOneofGroupArrayName + " = " + OneofGroupList(desc) + ";" + ); printer->Print( "/**\n" " * Oneof group definitions for this message. Each group defines the " @@ -2187,10 +2195,10 @@ void Generator::GenerateClassFieldInfo(const GeneratorOptions& options, " * @private {!Array>}\n" " * @const\n" " */\n" - "$classname$$oneofgrouparray$ = $oneofgroups$;\n" + "$assignment$\n" "\n", - "classname", GetMessagePath(options, desc), "oneofgrouparray", - kOneofGroupArrayName, "oneofgroups", OneofGroupList(desc)); + "classname", className, + "assignment", assignment); for (int i = 0; i < desc->oneof_decl_count(); i++) { if (IgnoreOneof(desc->oneof_decl(i))) { @@ -2214,14 +2222,22 @@ void Generator::GenerateClassXid(const GeneratorOptions& options, void Generator::GenerateOneofCaseDefinition( const GeneratorOptions& options, io::Printer* printer, const OneofDescriptor* oneof) const { + + const std::string className = GetMessagePath(options, oneof->containing_type()); + + const std::string oneofCaseName = WantEs6(options) ? ( + JSOneofName(oneof) + "Case" + ) : ( + className + "." + JSOneofName(oneof) + "Case" + ); printer->Print( "/**\n" " * @enum {number}\n" " */\n" - "$classname$.$oneof$Case = {\n" + "$lhs$ = {\n" " $upcase$_NOT_SET: 0", - "classname", GetMessagePath(options, oneof->containing_type()), "oneof", - JSOneofName(oneof), "upcase", ToEnumCase(oneof->name())); + "lhs", oneofCaseName, + "upcase", ToEnumCase(oneof->name())); for (int i = 0; i < oneof->field_count(); i++) { if (IgnoreField(oneof->field(i))) { @@ -2242,14 +2258,23 @@ void Generator::GenerateOneofCaseDefinition( "\n" "/**\n" " * @return {$class$.$oneof$Case}\n" - " */\n" - "$class$.prototype.get$oneof$Case = function() {\n" + " */\n", + "class", className); + + GenerateMethodStart(options, printer, className.c_str(), + (std::string("get") + JSOneofName(oneof) + "Case").c_str()); + + printer->Print( + "() {\n" " return /** @type {$class$.$oneof$Case} */(jspb.Message." - "computeOneofCase(this, $class$.oneofGroups_[$oneofindex$]));\n" - "};\n" - "\n", - "class", GetMessagePath(options, oneof->containing_type()), "oneof", - JSOneofName(oneof), "oneofindex", JSOneofIndex(oneof)); + "computeOneofCase(this, $class$.oneofGroups_[$oneofindex$]));\n", + "class", className, + "oneof", JSOneofName(oneof), + "oneofindex", JSOneofIndex(oneof)); + GenerateMethodEnd(options, printer); + printer->Print( + "\n" + "\n"); } void Generator::GenerateClassToObject(const GeneratorOptions& options, @@ -2628,10 +2653,8 @@ void GenerateBytesWrapper(const GeneratorOptions& options, io::Printer* printer, void Generator::GenerateClassField(const GeneratorOptions& options, io::Printer* printer, const FieldDescriptor* field) const { - -const char * endBrace = - (options.import_style == GeneratorOptions::kImportEs6) ? - "}" : "};"; +const std::string classSymbol = GetMessagePath(options, field->containing_type()); +const char * methodEndBrace = WantEs6(options) ? "}" : "};"; if (field->is_map()) { const FieldDescriptor* key_field = MapFieldKey(field); @@ -2667,7 +2690,7 @@ const char * endBrace = } else { printer->Print( "$class$.prototype.$gettername$ = function(opt_noLazyCreate) {\n", - "class", GetMessagePath(options, field->containing_type()), + "class", classSymbol, "gettername", "get" + JSGetterName(options, field)); } @@ -2699,7 +2722,7 @@ const char * endBrace = "$endbrace$\n" "\n" "\n", - "endbrace", endBrace); + "endbrace", methodEndBrace); // End function body contents. @@ -2729,7 +2752,7 @@ const char * endBrace = } else { printer->Print( "$class$.prototype.$gettername$ = function() {\n", - "class", GetMessagePath(options, field->containing_type()), + "class", classSymbol, "gettername", "get" + JSGetterName(options, field)); } @@ -2748,34 +2771,44 @@ const char * endBrace = JSFieldIndex(field), "wrapperclass", SubmessageTypeRef(options, field), "required", (field->label() == FieldDescriptor::LABEL_REQUIRED ? ", 1" : ""), - "endbrace", endBrace); + "endbrace", methodEndBrace); printer->Annotate("gettername", field); + printer->Print( "/**\n" " * @param {$optionaltype$} value\n" " * @return {!$class$} returns this\n" - "*/\n" - "$class$.prototype.$settername$ = function(value) {\n" + "*/\n", + "optionaltype", + JSFieldTypeAnnotation(options, field, + /* is_setter_argument = */ true, + /* force_present = */ false, + /* singular_if_not_packed = */ false), + "class", classSymbol); + GenerateMethodStart(options, printer, classSymbol.c_str(), + ("set" + JSGetterName(options, field)).c_str()); + printer->Print( + "(value) {\n" " return jspb.Message.set$oneoftag$$repeatedtag$WrapperField(", "optionaltype", JSFieldTypeAnnotation(options, field, /* is_setter_argument = */ true, /* force_present = */ false, /* singular_if_not_packed = */ false), - "class", GetMessagePath(options, field->containing_type()), - "settername", "set" + JSGetterName(options, field), "oneoftag", + "class", classSymbol,"oneoftag", (InRealOneof(field) ? "Oneof" : ""), "repeatedtag", (field->is_repeated() ? "Repeated" : "")); printer->Annotate("settername", field); printer->Print( - "this, $index$$oneofgroup$, value);\n" - "$endbrace$\n" + "this, $index$$oneofgroup$, value);\n", + "index", JSFieldIndex(field), + "oneofgroup", (InRealOneof(field) ? (", " + JSOneofArray(options, field)) : "")); + GenerateMethodEnd(options, printer); + printer->Print( "\n" - "\n", - "index", JSFieldIndex(field), "oneofgroup", - (InRealOneof(field) ? (", " + JSOneofArray(options, field)) : ""), - "endbrace", endBrace); + "\n" + "\n"); if (field->is_repeated()) { GenerateRepeatedMessageHelperMethods(options, printer, field); @@ -2815,20 +2848,13 @@ const char * endBrace = FieldComments(field, bytes_mode), "type", typed_annotation); } - // Function definition begin.. depends on ES6 style or not - if (options.import_style == GeneratorOptions::kImportEs6) { - printer->Print( - "$gettername$() {\n", - "gettername", "get" + JSGetterName(options, field)); - } else { - printer->Print( - "$class$.prototype.$gettername$ = function() {\n", - "class", GetMessagePath(options, field->containing_type()), - "gettername", "get" + JSGetterName(options, field)); - } + GenerateMethodStart(options, printer, classSymbol.c_str(), + ("get" + JSGetterName(options, field)).c_str()); // TODO delete this annotate call? printer->Annotate("gettername", field); + printer->Print("() {\n"); + if (untyped) { printer->Print(" return "); } else { @@ -2850,22 +2876,15 @@ const char * endBrace = } GenerateFieldValueExpression(printer, "this", field, use_default); - if (untyped) { - printer->Print( - ";\n" - "$endbrace$\n" - "\n" - "\n", - "endbrace", endBrace); + printer->Print(";\n"); } else { - printer->Print( - ");\n" - "$endbrace$\n" - "\n" - "\n", - "endbrace", endBrace); + printer->Print(");\n"); } + GenerateMethodEnd(options, printer); + printer->Print( + "\n" + "\n"); if (field->type() == FieldDescriptor::TYPE_BYTES && !untyped) { GenerateBytesWrapper(options, printer, field, BYTES_B64); @@ -2877,7 +2896,7 @@ const char * endBrace = " * @param {$optionaltype$} value\n" " * @return {!$class$} returns this\n" " */\n", - "class", GetMessagePath(options, field->containing_type()), + "class", classSymbol, "optionaltype", untyped ? "*" : JSFieldTypeAnnotation(options, field, @@ -2888,28 +2907,38 @@ const char * endBrace = if (field->file()->syntax() == FileDescriptor::SYNTAX_PROTO3 && !field->is_repeated() && !field->is_map() && !HasFieldPresence(options, field)) { + // Proto3 non-repeated and non-map fields without presence use the // setProto3*Field function. + GenerateMethodStart(options, printer, classSymbol.c_str(), + ("set" + JSGetterName(options, field)).c_str()); + printer->Print( - "$class$.prototype.$settername$ = function(value) {\n" + "(value) {\n" " return jspb.Message.setProto3$typetag$Field(this, $index$, " - "value);" - "\n" - "};\n" + "value);\n", + "typetag", JSTypeTag(field), + "index", JSFieldIndex(field)); + + GenerateMethodEnd(options, printer); + printer->Print( "\n" - "\n", - "class", GetMessagePath(options, field->containing_type()), - "settername", "set" + JSGetterName(options, field), "typetag", - JSTypeTag(field), "index", JSFieldIndex(field)); - printer->Annotate("settername", field); + "\n"); + + // DO NOT SUBMIT: Can the Annotate call be safely removed? + // printer->Annotate("settername", field); + } else { + + GenerateMethodStart(options, printer, classSymbol.c_str(), + ("set" + JSGetterName(options, field)).c_str()); // Otherwise, use the regular setField function. printer->Print( - "$class$.prototype.$settername$ = function(value) {\n" + "(value) {\n" " return jspb.Message.set$oneoftag$Field(this, $index$", - "class", GetMessagePath(options, field->containing_type()), - "settername", "set" + JSGetterName(options, field), "oneoftag", - (InRealOneof(field) ? "Oneof" : ""), "index", JSFieldIndex(field)); + "class", classSymbol, + "oneoftag", (InRealOneof(field) ? "Oneof" : ""), + "index", JSFieldIndex(field)); printer->Annotate("settername", field); printer->Print( "$oneofgroup$, $type$value$rptvalueinit$$typeclose$);\n" @@ -2929,7 +2958,7 @@ const char * endBrace = " * Clears the value.\n" " * @return {!$class$} returns this\n" " */\n", - "class", GetMessagePath(options, field->containing_type())); + "class", classSymbol); } if (field->is_repeated()) { @@ -2937,6 +2966,10 @@ const char * endBrace = } } + const std::string clearerName = "clear" + JSGetterName(options, field); + const std::string clearerMethodStart = MethodStart( + options, classSymbol.c_str(), clearerName.c_str()); + // Generate clearFoo() method for map fields, repeated fields, and other // fields with presence. if (field->is_map()) { @@ -2946,14 +2979,15 @@ const char * endBrace = " * Clears values from the map. The map will be non-null.\n" " * @return {!$class$} returns this\n" " */\n" - "$class$.prototype.$clearername$ = function() {\n" + "$methodstart$() {\n" " this.$gettername$().clear();\n" " return this;\n" - "};\n" + "$methodend$\n" "\n" "\n", - "class", GetMessagePath(options, field->containing_type()), - "clearername", "clear" + JSGetterName(options, field), + "class", classSymbol, + "methodstart", clearerMethodStart, + "methodend", methodEndBrace, "gettername", "get" + JSGetterName(options, field)); // clang-format on printer->Annotate("clearername", field); @@ -2967,7 +3001,7 @@ const char * endBrace = " * $jsdoc$\n" " * @return {!$class$} returns this\n" " */\n" - "$class$.prototype.$clearername$ = function() {\n" + "$methodstart$() {\n" " return this.$settername$($clearedvalue$);\n" "};\n" "\n" @@ -2975,8 +3009,8 @@ const char * endBrace = "jsdoc", field->is_repeated() ? "Clears the list making it empty but non-null." : "Clears the message field making it undefined.", - "class", GetMessagePath(options, field->containing_type()), - "clearername", "clear" + JSGetterName(options, field), + "class", classSymbol, + "methodstart", clearerMethodStart, "settername", "set" + JSGetterName(options, field), "clearedvalue", (field->is_repeated() ? "[]" : "undefined")); // clang-format on @@ -2990,11 +3024,11 @@ const char * endBrace = " * Clears the field making it undefined.\n" " * @return {!$class$} returns this\n" " */\n" - "$class$.prototype.$clearername$ = function() {\n" + "$methodstart$() {\n" " return jspb.Message.set$maybeoneof$Field(this, " "$index$$maybeoneofgroup$, ", - "class", GetMessagePath(options, field->containing_type()), - "clearername", "clear" + JSGetterName(options, field), + "class", classSymbol, + "methodstart", clearerMethodStart, "maybeoneof", (InRealOneof(field) ? "Oneof" : ""), "maybeoneofgroup", (InRealOneof(field) ? (", " + JSOneofArray(options, field)) @@ -3011,18 +3045,24 @@ const char * endBrace = } if (HasFieldPresence(options, field)) { + const std::string haserName = "has" + JSGetterName(options, field); + const std::string haserMethodStart = MethodStart( + options, classSymbol.c_str(), clearerName.c_str()); + printer->Print( "/**\n" " * Returns whether this field is set.\n" " * @return {boolean}\n" " */\n" - "$class$.prototype.$hasername$ = function() {\n" + "$methodstart$() {\n" " return jspb.Message.getField(this, $index$) != null;\n" - "};\n" + "$methodend$\n" "\n" "\n", - "class", GetMessagePath(options, field->containing_type()), "hasername", - "has" + JSGetterName(options, field), "index", JSFieldIndex(field)); + "class", classSymbol, + "methodstart", haserMethodStart, + "methodend", methodEndBrace, + "index", JSFieldIndex(field)); printer->Annotate("hasername", field); } } @@ -3030,6 +3070,7 @@ const char * endBrace = void Generator::GenerateRepeatedPrimitiveHelperMethods( const GeneratorOptions& options, io::Printer* printer, const FieldDescriptor* field, bool untyped) const { + const std::string classSymbol = GetMessagePath(options, field->containing_type()); // clang-format off printer->Print( "/**\n" @@ -3040,7 +3081,7 @@ void Generator::GenerateRepeatedPrimitiveHelperMethods( "$class$.prototype.$addername$ = function(value, opt_index) {\n" " return jspb.Message.addToRepeatedField(this, " "$index$", - "class", GetMessagePath(options, field->containing_type()), "addername", + "class", classSymbol, "addername", "add" + JSGetterName(options, field, BYTES_DEFAULT, /* drop_list = */ true), "optionaltype", @@ -3488,16 +3529,25 @@ void Generator::GenerateClassSerializeBinaryField( printer->Print(" }\n"); } +bool Generator::WantEs6(const GeneratorOptions& options) const { + return options.import_style == GeneratorOptions::kImportEs6; +} + void Generator::GenerateEnum(const GeneratorOptions& options, io::Printer* printer, const EnumDescriptor* enumdesc) const { + const std::string enumNameForDefinition = WantEs6(options) ? ( + enumdesc->name() + ) : ( + GetEnumPathPrefix(options, enumdesc) + enumdesc->name() + ); printer->Print( "/**\n" " * @enum {number}\n" " */\n" - "$enumprefix$$name$ = {\n", - "enumprefix", GetEnumPathPrefix(options, enumdesc), "name", - enumdesc->name()); + "$defname$ = {\n", + "defname", enumNameForDefinition, + "name", enumdesc->name()); printer->Annotate("name", enumdesc); std::set used_name; @@ -4111,9 +4161,17 @@ void Generator::GenerateMethodStart(const GeneratorOptions& options, const char * classSymbol, const char * methodName) const { if(options.import_style == GeneratorOptions::kImportEs6) { - printer->Print("$method$", "method", methodName); + printer->PrintRaw(MethodStart(options, classSymbol, methodName)); + } +} + +const std::string Generator::MethodStart(const GeneratorOptions& options, + const char * classSymbol, + const char * methodName) const { + if (WantEs6(options)) { + return methodName; } else { - printer->Print("$class$ = function", "class", classSymbol); + return std::string(classSymbol) + ".prototype." + methodName + " = function"; } } diff --git a/generator/js_generator.h b/generator/js_generator.h index 0737377..6679619 100644 --- a/generator/js_generator.h +++ b/generator/js_generator.h @@ -325,13 +325,20 @@ class PROTOC_EXPORT Generator : public CodeGenerator { io::Printer* printer, const FieldDescriptor* field) const; -// Prints the beginning/end of a method of some class. -void GenerateMethodStart(const GeneratorOptions& options, - io::Printer* printer, - const char * classSymbol, - const char * methodName) const; -void GenerateMethodEnd(const GeneratorOptions& options, - io::Printer* printer) const; + // Prints the beginning/end of a method of some class. + void GenerateMethodStart(const GeneratorOptions& options, + io::Printer* printer, + const char * classSymbol, + const char * methodName) const; + const std::string MethodStart(const GeneratorOptions& options, + const char * classSymbol, + const char * methodName) const; + void GenerateMethodEnd(const GeneratorOptions& options, + io::Printer* printer) const; + + // True if the implementation code should be es6-style classes. + // This is set to true if the import style is set to "es6". + bool WantEs6(const GeneratorOptions& options) const; GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(Generator); }; From e7e3a4527be4a8bb727b3e2a84c1b84a52539378 Mon Sep 17 00:00:00 2001 From: Red Daly Date: Sun, 4 Dec 2022 17:31:28 -0800 Subject: [PATCH 03/30] Maybe 70% complete ES6 implementation generation --- generator/js_generator.cc | 59 ++++++++++++++++++++++++++++----------- generator/js_generator.h | 12 ++++++-- 2 files changed, 51 insertions(+), 20 deletions(-) diff --git a/generator/js_generator.cc b/generator/js_generator.cc index 85a64ba..392f118 100644 --- a/generator/js_generator.cc +++ b/generator/js_generator.cc @@ -1146,12 +1146,12 @@ bool HasRepeatedFields(const GeneratorOptions& options, return false; } -static const char* kRepeatedFieldArrayName = ".repeatedFields_"; +static const char* kRepeatedFieldArrayName = "repeatedFields_"; std::string RepeatedFieldsArrayName(const GeneratorOptions& options, const Descriptor* desc) { return HasRepeatedFields(options, desc) - ? (GetMessagePath(options, desc) + kRepeatedFieldArrayName) + ? (GetMessagePath(options, desc) + "." + kRepeatedFieldArrayName) : "null"; } @@ -2027,6 +2027,10 @@ void Generator::GenerateClassEs6(const GeneratorOptions& options, return; } + + std::string prefix = (desc->containing_type() == nullptr) ? + "export " : ("static " + desc->name() + " = "); + printer->Print("\n"); printer->Print( "/**\n" @@ -2044,12 +2048,8 @@ void Generator::GenerateClassEs6(const GeneratorOptions& options, " * @extends {jspb.Message}\n" " * @constructor\n" " */\n" - - " // DO NOT SUBMIT: \n" - " // classprefix = $classprefix$\n" - " // classname = $classname$ \n" - - "export class $classname$ extends jspb.Message {\n", + "$prefix$class $classname$ extends jspb.Message {\n", + "prefix", prefix, "classname", desc->name()); printer->Indent(); @@ -2159,6 +2159,7 @@ void Generator::GenerateClassConstructorAndDeclareExtensionFieldInfo( void Generator::GenerateClassFieldInfo(const GeneratorOptions& options, io::Printer* printer, const Descriptor* desc) const { + const std::string className = GetMessagePath(options, desc); if (HasRepeatedFields(options, desc)) { printer->Print( "/**\n" @@ -2166,15 +2167,14 @@ void Generator::GenerateClassFieldInfo(const GeneratorOptions& options, " * @private {!Array}\n" " * @const\n" " */\n" - "$classname$$rptfieldarray$ = $rptfields$;\n" + "$lhs$ = $rptfields$;\n" "\n", - "classname", GetMessagePath(options, desc), "rptfieldarray", - kRepeatedFieldArrayName, "rptfields", - RepeatedFieldNumberList(options, desc)); + "lhs", StaticMemberAssignmentLhs( + options, className.c_str(), kRepeatedFieldArrayName), + "rptfields", RepeatedFieldNumberList(options, desc)); } if (HasOneofFields(desc)) { - const std::string className = GetMessagePath(options, desc); const std::string assignment = ( options.import_style == GeneratorOptions::kImportEs6 ) ? ( @@ -3070,7 +3070,13 @@ const char * methodEndBrace = WantEs6(options) ? "}" : "};"; void Generator::GenerateRepeatedPrimitiveHelperMethods( const GeneratorOptions& options, io::Printer* printer, const FieldDescriptor* field, bool untyped) const { + const std::string classSymbol = GetMessagePath(options, field->containing_type()); + const std::string adderName = JSGetterName(options, field, BYTES_DEFAULT, + /* drop_list = */ true); + const std::string adderMethodStart = MethodStart( + options, classSymbol.c_str(), adderName.c_str()); + // clang-format off printer->Print( "/**\n" @@ -3078,9 +3084,10 @@ void Generator::GenerateRepeatedPrimitiveHelperMethods( " * @param {number=} opt_index\n" " * @return {!$class$} returns this\n" " */\n" - "$class$.prototype.$addername$ = function(value, opt_index) {\n" + "$methodstart$(value, opt_index) {\n" " return jspb.Message.addToRepeatedField(this, " "$index$", + "methodstart", adderMethodStart, "class", classSymbol, "addername", "add" + JSGetterName(options, field, BYTES_DEFAULT, /* drop_list = */ true), @@ -3110,16 +3117,23 @@ void Generator::GenerateRepeatedPrimitiveHelperMethods( void Generator::GenerateRepeatedMessageHelperMethods( const GeneratorOptions& options, io::Printer* printer, const FieldDescriptor* field) const { + + const std::string classSymbol = GetMessagePath(options, field->containing_type()); + const std::string adderName = JSGetterName(options, field, BYTES_DEFAULT, /* drop_list = */ true); + const std::string adderMethodStart = MethodStart( + options, classSymbol.c_str(), adderName.c_str()); + printer->Print( "/**\n" " * @param {!$optionaltype$=} opt_value\n" " * @param {number=} opt_index\n" " * @return {!$optionaltype$}\n" " */\n" - "$class$.prototype.$addername$ = function(opt_value, opt_index) {\n" + "$methodstart$(opt_value, opt_index) {\n" " return jspb.Message.addTo$repeatedtag$WrapperField(", - "optionaltype", JSTypeName(options, field, BYTES_DEFAULT), "class", - GetMessagePath(options, field->containing_type()), "addername", + "optionaltype", JSTypeName(options, field, BYTES_DEFAULT), "class", classSymbol, + "methodstart", adderMethodStart, + "addername", "add" + JSGetterName(options, field, BYTES_DEFAULT, /* drop_list = */ true), "repeatedtag", (field->is_repeated() ? "Repeated" : "")); @@ -4184,6 +4198,17 @@ void Generator::GenerateMethodEnd(const GeneratorOptions& options, } } +const std::string Generator::StaticMemberAssignmentLhs( + const GeneratorOptions& options, + const char * classSymbol, + const char * fieldName) const { + if (WantEs6(options)) { + return std::string("static ") + fieldName; + } else { + return std::string("") + classSymbol + "." + fieldName; + } + } + } // namespace js } // namespace compiler } // namespace protobuf diff --git a/generator/js_generator.h b/generator/js_generator.h index 6679619..9f35977 100644 --- a/generator/js_generator.h +++ b/generator/js_generator.h @@ -330,9 +330,6 @@ class PROTOC_EXPORT Generator : public CodeGenerator { io::Printer* printer, const char * classSymbol, const char * methodName) const; - const std::string MethodStart(const GeneratorOptions& options, - const char * classSymbol, - const char * methodName) const; void GenerateMethodEnd(const GeneratorOptions& options, io::Printer* printer) const; @@ -340,6 +337,15 @@ class PROTOC_EXPORT Generator : public CodeGenerator { // This is set to true if the import style is set to "es6". bool WantEs6(const GeneratorOptions& options) const; + const std::string MethodStart(const GeneratorOptions& options, + const char * classSymbol, + const char * methodName) const; + + const std::string StaticMemberAssignmentLhs( + const GeneratorOptions& options, + const char * classSymbol, + const char * fieldName) const; + GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(Generator); }; From a90de854a059f8eb2b2a577911736ae8a4a5ebb9 Mon Sep 17 00:00:00 2001 From: Red Daly Date: Sun, 4 Dec 2022 18:20:49 -0800 Subject: [PATCH 04/30] Start to attempt to rework how references to imported types work. --- generator/js_generator.cc | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/generator/js_generator.cc b/generator/js_generator.cc index 392f118..6c9f19f 100644 --- a/generator/js_generator.cc +++ b/generator/js_generator.cc @@ -152,6 +152,15 @@ std::string ModuleAlias(const std::string& filename) { // file descriptor's package. std::string GetNamespace(const GeneratorOptions& options, const FileDescriptor* file) { + if (options.import_style == GeneratorOptions::kImportEs6) { + std::string dotSeparated = "proto." + file->package(); + // Use $ because it's not valid in proto package names + // (https://developers.google.com/protocol-buffers/docs/reference/proto3-spec#identifiers). + // If we used _, "foo.a_b" would be equivalent to "foo.a.b". + ReplaceCharacters(&dotSeparated, ".", '$'); + return dotSeparated; + } + if (!options.namespace_prefix.empty()) { return options.namespace_prefix; } else if (!file->package().empty()) { @@ -3839,10 +3848,13 @@ void Generator::GenerateFile(const GeneratorOptions& options, // Generate "require" statements. if (options.import_style == GeneratorOptions::kImportEs6) { + printer->Print("import jspb from \"google-protobuf\";\n"); + for (int i = 0; i < file->dependency_count(); i++) { const std::string& name = file->dependency(i)->name(); printer->Print( - "// TODO: import {used types here} from \"$file$\";\n", + "import * as $alias$ from \"$file$\";\n", + "alias", ModuleAlias(name), "file", GetRootPath(file->name(), name) + GetJSFilename(options, name)); } From b2c7b4614eda92c3f121225718e6f04a9a8ecfc4 Mon Sep 17 00:00:00 2001 From: Red Daly Date: Sun, 4 Dec 2022 23:00:11 -0800 Subject: [PATCH 05/30] Define TypeNames class for referencing message and enum types in generated code. The introduction of this type changes the signatures of many generator methods that need to know how to print a JavaScript expression that refers to a given proto message or enum. --- generator/js_generator.cc | 290 +++++++++++++++++++++++++++++--------- generator/js_generator.h | 113 +++++++++++++-- 2 files changed, 324 insertions(+), 79 deletions(-) diff --git a/generator/js_generator.cc b/generator/js_generator.cc index 6c9f19f..587c309 100644 --- a/generator/js_generator.cc +++ b/generator/js_generator.cc @@ -152,14 +152,14 @@ std::string ModuleAlias(const std::string& filename) { // file descriptor's package. std::string GetNamespace(const GeneratorOptions& options, const FileDescriptor* file) { - if (options.import_style == GeneratorOptions::kImportEs6) { - std::string dotSeparated = "proto." + file->package(); - // Use $ because it's not valid in proto package names - // (https://developers.google.com/protocol-buffers/docs/reference/proto3-spec#identifiers). - // If we used _, "foo.a_b" would be equivalent to "foo.a.b". - ReplaceCharacters(&dotSeparated, ".", '$'); - return dotSeparated; - } + // if (options.import_style == GeneratorOptions::kImportEs6) { + // //std::string dotSeparated = "proto." + file->package(); + // // Use $ because it's not valid in proto package names + // // (https://developers.google.com/protocol-buffers/docs/reference/proto3-spec#identifiers). + // // If we used _, "foo.a_b" would be equivalent to "foo.a.b". + // //ReplaceCharacters(&dotSeparated, ".", '$'); + // //return dotSeparated; + // } if (!options.namespace_prefix.empty()) { return options.namespace_prefix; @@ -246,11 +246,11 @@ std::string MaybeCrossFileRef(const GeneratorOptions& options, } } -std::string SubmessageTypeRef(const GeneratorOptions& options, - const FieldDescriptor* field) { - GOOGLE_CHECK(field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE); - return MaybeCrossFileRef(options, field->file(), field->message_type()); -} +// std::string SubmessageTypeRef(const GeneratorOptions& options, +// const FieldDescriptor* field) { +// GOOGLE_CHECK(field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE); +// return MaybeCrossFileRef(options, field->file(), field->message_type()); +// } // - Object field name: LOWER_UNDERSCORE -> LOWER_CAMEL, except for group fields // (UPPER_CAMEL -> LOWER_CAMEL), with "List" (or "Map") appended if appropriate, @@ -1735,8 +1735,9 @@ void Generator::GenerateProvides(const GeneratorOptions& options, it != provided->end(); ++it) { if (options.import_style == GeneratorOptions::kImportClosure) { printer->Print("goog.provide('$name$');\n", "name", *it); - } else if (options.import_style == GeneratorOptions::kImportEs6) { - printer->Print("// DEBUG: in ES6 mode, no need for provide $name$');\n", "name", *it); + } else if (options.WantEs6()) { + // In ES6 mode, we do not construct the tree of objects + // using goog.exportSymbol. } else { // We aren't using Closure's import system, but we use goog.exportSymbol() // to construct the expected tree of objects, eg. @@ -1863,6 +1864,12 @@ void Generator::GenerateRequiresImpl(const GeneratorOptions& options, std::set* provided, bool require_jspb, bool require_extension, bool require_map) const { + + if (options.WantEs6()) { + // In ES6 mode, imports are handled by GenerateFile and + // goog.* isn't used. + return; + } if (require_jspb) { required->insert("jspb.Message"); required->insert("jspb.BinaryReader"); @@ -1965,6 +1972,7 @@ void Generator::GenerateTestOnly(const GeneratorOptions& options, } void Generator::GenerateClassesAndEnums(const GeneratorOptions& options, + const TypeNames& type_names, io::Printer* printer, const FileDescriptor* file) const { // In ES6 module mode, class constructors are generated within @@ -1976,7 +1984,7 @@ void Generator::GenerateClassesAndEnums(const GeneratorOptions& options, } } for (int i = 0; i < file->message_type_count(); i++) { - GenerateClass(options, printer, file->message_type(i)); + GenerateClass(options, type_names, printer, file->message_type(i)); } for (int i = 0; i < file->enum_type_count(); i++) { GenerateEnum(options, printer, file->enum_type(i)); @@ -1984,10 +1992,11 @@ void Generator::GenerateClassesAndEnums(const GeneratorOptions& options, } void Generator::GenerateClass(const GeneratorOptions& options, + const TypeNames& type_names, io::Printer* printer, const Descriptor* desc) const { if (options.import_style == GeneratorOptions::kImportEs6) { - GenerateClassEs6(options, printer, desc); + GenerateClassEs6(options, type_names, printer, desc); return; } if (IgnoreMessage(desc)) { @@ -1998,13 +2007,13 @@ void Generator::GenerateClass(const GeneratorOptions& options, printer->Print("\n"); GenerateClassFieldInfo(options, printer, desc); - GenerateClassToObject(options, printer, desc); + GenerateClassToObject(options, type_names, 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); + GenerateClassDeserializeBinary(options, type_names, printer, desc); + GenerateClassSerializeBinary(options, type_names, printer, desc); } // Recurse on nested types. These must come *before* the extension-field @@ -2014,24 +2023,25 @@ void Generator::GenerateClass(const GeneratorOptions& options, GenerateEnum(options, printer, desc->enum_type(i)); } for (int i = 0; i < desc->nested_type_count(); i++) { - GenerateClass(options, printer, desc->nested_type(i)); + GenerateClass(options, type_names, printer, desc->nested_type(i)); } if (!NamespaceOnly(desc)) { - GenerateClassRegistration(options, printer, desc); - GenerateClassFields(options, printer, desc); + GenerateClassRegistration(options, type_names, printer, desc); + GenerateClassFields(options, type_names, printer, desc); if (options.import_style != GeneratorOptions::kImportClosure) { for (int i = 0; i < desc->extension_count(); i++) { - GenerateExtension(options, printer, desc->extension(i)); + GenerateExtension(options, type_names, printer, desc->extension(i)); } } } } void Generator::GenerateClassEs6(const GeneratorOptions& options, - io::Printer* printer, - const Descriptor* desc) const { + const TypeNames& type_names, + io::Printer* printer, + const Descriptor* desc) const { if (IgnoreMessage(desc)) { return; } @@ -2071,8 +2081,8 @@ void Generator::GenerateClassEs6(const GeneratorOptions& options, // 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); + GenerateClassDeserializeBinary(options, type_names, printer, desc); + GenerateClassSerializeBinary(options, type_names, printer, desc); // Recurse on nested types. These must come *before* the extension-field // info generation in GenerateClassRegistration so that extensions that @@ -2081,13 +2091,13 @@ void Generator::GenerateClassEs6(const GeneratorOptions& options, GenerateEnum(options, printer, desc->enum_type(i)); } for (int i = 0; i < desc->nested_type_count(); i++) { - GenerateClass(options, printer, desc->nested_type(i)); + GenerateClass(options, type_names, printer, desc->nested_type(i)); } - GenerateClassRegistration(options, printer, desc); - GenerateClassFields(options, printer, desc); + GenerateClassRegistration(options, type_names, printer, desc); + GenerateClassFields(options, type_names, printer, desc); for (int i = 0; i < desc->extension_count(); i++) { - GenerateExtension(options, printer, desc->extension(i)); + GenerateExtension(options, type_names, printer, desc->extension(i)); } printer->Outdent(); @@ -2234,7 +2244,7 @@ void Generator::GenerateOneofCaseDefinition( const std::string className = GetMessagePath(options, oneof->containing_type()); - const std::string oneofCaseName = WantEs6(options) ? ( + const std::string oneofCaseName = options.WantEs6() ? ( JSOneofName(oneof) + "Case" ) : ( className + "." + JSOneofName(oneof) + "Case" @@ -2287,6 +2297,7 @@ void Generator::GenerateOneofCaseDefinition( } void Generator::GenerateClassToObject(const GeneratorOptions& options, + const TypeNames& type_names, io::Printer* printer, const Descriptor* desc) const { printer->Print( @@ -2340,7 +2351,7 @@ void Generator::GenerateClassToObject(const GeneratorOptions& options, first = false; } - GenerateClassFieldToObject(options, printer, field); + GenerateClassFieldToObject(options, type_names, printer, field); } if (!first) { @@ -2420,6 +2431,7 @@ void Generator::GenerateFieldValueExpression(io::Printer* printer, } void Generator::GenerateClassFieldToObject(const GeneratorOptions& options, + const TypeNames& type_names, io::Printer* printer, const FieldDescriptor* field) const { printer->Print("$fieldname$: ", "fieldname", @@ -2448,14 +2460,14 @@ void Generator::GenerateClassFieldToObject(const GeneratorOptions& options, "jspb.Message.toObjectList(msg.get$getter$(),\n" " $type$.toObject, includeInstance)", "getter", JSGetterName(options, field), "type", - SubmessageTypeRef(options, field)); + type_names.SubmessageTypeRef(field)); } } else { printer->Print( "(f = msg.get$getter$()) && " "$type$.toObject(includeInstance, f)", "getter", JSGetterName(options, field), "type", - SubmessageTypeRef(options, field)); + type_names.SubmessageTypeRef(field)); } } else if (field->type() == FieldDescriptor::TYPE_BYTES) { // For bytes fields we want to always return the B64 data. @@ -2523,6 +2535,7 @@ void Generator::GenerateObjectTypedef(const GeneratorOptions& options, } void Generator::GenerateClassFromObject(const GeneratorOptions& options, + const TypeNames& type_names, io::Printer* printer, const Descriptor* desc) const { printer->Print("if (jspb.Message.GENERATE_FROM_OBJECT) {\n\n"); @@ -2543,7 +2556,7 @@ void Generator::GenerateClassFromObject(const GeneratorOptions& options, for (int i = 0; i < desc->field_count(); i++) { const FieldDescriptor* field = desc->field(i); if (!IgnoreField(field)) { - GenerateClassFieldFromObject(options, printer, field); + GenerateClassFieldFromObject(options, type_names, printer, field); } } @@ -2554,7 +2567,9 @@ void Generator::GenerateClassFromObject(const GeneratorOptions& options, } void Generator::GenerateClassFieldFromObject( - const GeneratorOptions& options, io::Printer* printer, + const GeneratorOptions& options, + const TypeNames& type_names, + io::Printer* printer, const FieldDescriptor* field) const { if (field->is_map()) { const FieldDescriptor* value_field = MapFieldValue(field); @@ -2589,14 +2604,15 @@ void Generator::GenerateClassFieldFromObject( " $fieldclass$.fromObject));\n", "name", JSObjectFieldName(options, field), "index", JSFieldIndex(field), "fieldclass", - SubmessageTypeRef(options, field)); + type_names.SubmessageTypeRef(field)); } } else { printer->Print( " obj.$name$ && jspb.Message.setWrapperField(\n" " msg, $index$, $fieldclass$.fromObject(obj.$name$));\n", "name", JSObjectFieldName(options, field), "index", - JSFieldIndex(field), "fieldclass", SubmessageTypeRef(options, field)); + JSFieldIndex(field), "fieldclass", + type_names.SubmessageTypeRef(field)); } } else { // Simple (primitive) field. @@ -2609,23 +2625,25 @@ void Generator::GenerateClassFieldFromObject( } void Generator::GenerateClassRegistration(const GeneratorOptions& options, + const TypeNames& type_names, io::Printer* printer, const Descriptor* desc) const { // Register any extensions defined inside this message type. for (int i = 0; i < desc->extension_count(); i++) { const FieldDescriptor* extension = desc->extension(i); if (ShouldGenerateExtension(extension)) { - GenerateExtension(options, printer, extension); + GenerateExtension(options, type_names, printer, extension); } } } void Generator::GenerateClassFields(const GeneratorOptions& options, + const TypeNames& type_names, io::Printer* printer, const Descriptor* desc) const { for (int i = 0; i < desc->field_count(); i++) { if (!IgnoreField(desc->field(i))) { - GenerateClassField(options, printer, desc->field(i)); + GenerateClassField(options, type_names, printer, desc->field(i)); } } } @@ -2660,10 +2678,11 @@ void GenerateBytesWrapper(const GeneratorOptions& options, io::Printer* printer, } void Generator::GenerateClassField(const GeneratorOptions& options, + const TypeNames& type_names, io::Printer* printer, const FieldDescriptor* field) const { const std::string classSymbol = GetMessagePath(options, field->containing_type()); -const char * methodEndBrace = WantEs6(options) ? "}" : "};"; +const char * methodEndBrace = options.WantEs6() ? "}" : "};"; if (field->is_map()) { const FieldDescriptor* key_field = MapFieldKey(field); @@ -2777,7 +2796,8 @@ const char * methodEndBrace = WantEs6(options) ? "}" : "};"; /* force_present = */ false, /* singular_if_not_packed = */ false), "rpt", (field->is_repeated() ? "Repeated" : ""), "index", - JSFieldIndex(field), "wrapperclass", SubmessageTypeRef(options, field), + JSFieldIndex(field), "wrapperclass", + type_names.SubmessageTypeRef(field), "required", (field->label() == FieldDescriptor::LABEL_REQUIRED ? ", 1" : ""), "endbrace", methodEndBrace); @@ -3207,6 +3227,7 @@ void Generator::GenerateClassExtensionFieldInfo(const GeneratorOptions& options, } void Generator::GenerateClassDeserializeBinary(const GeneratorOptions& options, + const TypeNames& type_names, io::Printer* printer, const Descriptor* desc) const { // TODO(cfallin): Handle lazy decoding when requested by field option and/or @@ -3255,7 +3276,7 @@ printer->Print( for (int i = 0; i < desc->field_count(); i++) { if (!IgnoreField(desc->field(i))) { - GenerateClassDeserializeBinaryField(options, printer, desc->field(i)); + GenerateClassDeserializeBinaryField(options, type_names, printer, desc->field(i)); } } @@ -3290,7 +3311,9 @@ printer->Print( } void Generator::GenerateClassDeserializeBinaryField( - const GeneratorOptions& options, io::Printer* printer, + const GeneratorOptions& options, + const TypeNames& type_names, + io::Printer* printer, const FieldDescriptor* field) const { printer->Print(" case $num$:\n", "num", StrCat(field->number())); @@ -3331,7 +3354,8 @@ void Generator::GenerateClassDeserializeBinaryField( " var value = new $fieldclass$;\n" " reader.read$msgOrGroup$($grpfield$value," "$fieldclass$.deserializeBinaryFromReader);\n", - "fieldclass", SubmessageTypeRef(options, field), "msgOrGroup", + "fieldclass", + type_names.SubmessageTypeRef(field), "msgOrGroup", (field->type() == FieldDescriptor::TYPE_GROUP) ? "Group" : "Message", "grpfield", (field->type() == FieldDescriptor::TYPE_GROUP) @@ -3381,6 +3405,7 @@ void Generator::GenerateClassDeserializeBinaryField( } void Generator::GenerateClassSerializeBinary(const GeneratorOptions& options, + const TypeNames& type_names, io::Printer* printer, const Descriptor* desc) const { @@ -3419,7 +3444,7 @@ void Generator::GenerateClassSerializeBinary(const GeneratorOptions& options, for (int i = 0; i < desc->field_count(); i++) { if (!IgnoreField(desc->field(i))) { - GenerateClassSerializeBinaryField(options, printer, desc->field(i)); + GenerateClassSerializeBinaryField(options, type_names, printer, desc->field(i)); } } @@ -3440,7 +3465,9 @@ for (int i = 0; i < desc->field_count(); i++) { } void Generator::GenerateClassSerializeBinaryField( - const GeneratorOptions& options, io::Printer* printer, + const GeneratorOptions& options, + const TypeNames& type_names, + io::Printer* printer, const FieldDescriptor* field) const { if (HasFieldPresence(options, field) && field->cpp_type() != FieldDescriptor::CPPTYPE_MESSAGE) { @@ -3540,7 +3567,7 @@ void Generator::GenerateClassSerializeBinaryField( printer->Print( ",\n" " $submsg$.serializeBinaryToWriter\n", - "submsg", SubmessageTypeRef(options, field)); + "submsg", type_names.SubmessageTypeRef(field)); } else { printer->Print("\n"); } @@ -3552,14 +3579,10 @@ void Generator::GenerateClassSerializeBinaryField( printer->Print(" }\n"); } -bool Generator::WantEs6(const GeneratorOptions& options) const { - return options.import_style == GeneratorOptions::kImportEs6; -} - void Generator::GenerateEnum(const GeneratorOptions& options, io::Printer* printer, const EnumDescriptor* enumdesc) const { - const std::string enumNameForDefinition = WantEs6(options) ? ( + const std::string enumNameForDefinition = options.WantEs6() ? ( enumdesc->name() ) : ( GetEnumPathPrefix(options, enumdesc) + enumdesc->name() @@ -3596,6 +3619,7 @@ void Generator::GenerateEnum(const GeneratorOptions& options, } void Generator::GenerateExtension(const GeneratorOptions& options, + const TypeNames& type_names, io::Printer* printer, const FieldDescriptor* field) const { std::string extension_scope = @@ -3629,11 +3653,11 @@ void Generator::GenerateExtension(const GeneratorOptions& options, " $repeated$);\n", "index", StrCat(field->number()), "name", extension_object_name, "ctor", (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE - ? SubmessageTypeRef(options, field) + ? type_names.SubmessageTypeRef(field) : std::string("null")), "toObject", (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE - ? (SubmessageTypeRef(options, field) + ".toObject") + ? (type_names.SubmessageTypeRef(field) + ".toObject") : std::string("null")), "repeated", (field->is_repeated() ? "1" : "0")); @@ -3652,11 +3676,11 @@ void Generator::GenerateExtension(const GeneratorOptions& options, JSBinaryReaderMethodName(options, field), "binaryWriterFn", JSBinaryWriterMethodName(options, field), "binaryMessageSerializeFn", (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) - ? (SubmessageTypeRef(options, field) + ".serializeBinaryToWriter") + ? (type_names.SubmessageTypeRef(field) + ".serializeBinaryToWriter") : "undefined", "binaryMessageDeserializeFn", (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) - ? (SubmessageTypeRef(options, field) + ".deserializeBinaryFromReader") + ? (type_names.SubmessageTypeRef(field) + ".deserializeBinaryFromReader") : "undefined"); printer->Print(" $isPacked$);\n", "isPacked", @@ -3774,6 +3798,10 @@ GeneratorOptions::OutputMode GeneratorOptions::output_mode() const { return kOneOutputFilePerSCC; } +bool GeneratorOptions::WantEs6() const { + return this->import_style == GeneratorOptions::kImportEs6; +} + void Generator::GenerateFilesInDepOrder( const GeneratorOptions& options, io::Printer* printer, const std::vector& files) const { @@ -3788,9 +3816,15 @@ void Generator::GenerateFilesInDepOrder( } void Generator::GenerateFileAndDeps( - const GeneratorOptions& options, io::Printer* printer, + const GeneratorOptions& options, + io::Printer* printer, const FileDescriptor* root, std::set* all_files, std::set* generated) const { + // ES6 must use kOneOutputFilePerInputFile. + GOOGLE_CHECK_NE(GeneratorOptions::kOneOutputFilePerInputFile, + options.output_mode()); + TypeNames type_names = TypeNames::NonEs6TypeNames(options); + // Skip if already generated. if (generated->find(root) != generated->end()) { return; @@ -3807,7 +3841,7 @@ void Generator::GenerateFileAndDeps( // original set requested to be generated; i.e., don't take all transitive // deps down to the roots. if (all_files->find(root) != all_files->end()) { - GenerateClassesAndEnums(options, printer, root); + GenerateClassesAndEnums(options, type_names, printer, root); } } @@ -3841,20 +3875,134 @@ bool Generator::GenerateFile(const FileDescriptor* file, return true; } +TypeNames TypeNames::NonEs6TypeNames(const GeneratorOptions& options) { + return TypeNames(options, nullptr, std::map()); +} + +TypeNames TypeNames::Es6TypeNames( + const GeneratorOptions& options, + const FileDescriptor* codegen_file) { + // First, generate a map with values that may be duplicates. Then rename + // ambiguous values from the rhs. + std::map ideal_mapping; + + auto register_types = [&](const FileDescriptor* file) -> void { + for (int j = 0; j < file->message_type_count(); j++) { + auto message_type = file->message_type(j); + ideal_mapping.insert(std::make_pair( + message_type->full_name(), message_type->name())); + } + + for (int j = 0; j < file->enum_type_count(); j++) { + auto enum_type = file->enum_type(j); + ideal_mapping.insert(std::make_pair( + enum_type->full_name(), enum_type->name())); + } + }; + + // Loop through all dependencies and add their types. + for (int i = 0; i < codegen_file->dependency_count(); i++) { + auto dep_file = codegen_file->dependency(i); + register_types(dep_file); + } + register_types(codegen_file); + + // TODO(reddaly): Replace conflicting identifiers. + return TypeNames(options, codegen_file, ideal_mapping); +} + +/** + * Returns the JavaScript expression for referring to the passed message type. + */ +std::string TypeNames::JsExpression(const google::protobuf::Descriptor& desc) const { + if (this->options.WantEs6()) { + return this->JsExpression(desc.full_name()); + } + + return MaybeCrossFileRef(this->options, this->codegen_file, &desc); +} + +/** + * Returns the JavaScript expression for referring to the given enum type. + */ +std::string TypeNames::JsExpression(const google::protobuf::EnumDescriptor& desc) const { + if (this->options.WantEs6()) { + return this->JsExpression(desc.full_name()); + } + return GetEnumPath(this->options, &desc); +} + +std::string TypeNames::SubmessageTypeRef(const FieldDescriptor* field) const { + GOOGLE_CHECK(field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE); + GOOGLE_CHECK(this->codegen_file == nullptr || + this->codegen_file == field->file()); + GOOGLE_CHECK_NOTNULL(field->message_type()); + return JsExpression(*field->message_type()); +} + +std::string TypeNames::JsExpression(const std::string& full_name) const { + GOOGLE_CHECK_OK(this->options.WantEs6()); + + auto iter = this->map_.find(full_name); + if (iter != this->map_.end()) { + return iter->second; + } + // See if the parent full_name is available. + auto parts = google::protobuf::Split(full_name, ".", false); + if (parts.size() > 1) { + // uh oh... not sure how this happend. + std::vector parent_parts = {parts.begin(), parts.end() - 1}; + auto parent_path = google::protobuf::JoinStrings( + parent_parts, + "."); + return this->JsExpression(parent_path); + } + return std::string("INVALID TYPE NAME ") + full_name; +} + +/** + * Returns the import alias for all the top-level messages and enums + * in the given dependency file. +*/ +std::vector ImportAliases( + const TypeNames& type_names, + const FileDescriptor& dep_file) { + std::vector out; + for (int j = 0; j < dep_file.message_type_count(); j++) { + auto message_type = dep_file.message_type(j); + out.push_back(type_names.JsExpression(*message_type)); + } + + for (int j = 0; j < dep_file.enum_type_count(); j++) { + auto enum_type = dep_file.enum_type(j); + out.push_back(type_names.JsExpression(*enum_type)); + } + return out; +} + void Generator::GenerateFile(const GeneratorOptions& options, io::Printer* printer, const FileDescriptor* file) const { GenerateHeader(options, file, printer); + auto type_names = options.WantEs6() ? + TypeNames::Es6TypeNames(options, file) : + TypeNames::NonEs6TypeNames(options); + // Generate "require" statements. if (options.import_style == GeneratorOptions::kImportEs6) { printer->Print("import jspb from \"google-protobuf\";\n"); for (int i = 0; i < file->dependency_count(); i++) { + std::string aliases_comma_delimited = + google::protobuf::JoinStrings( + ImportAliases(type_names, *file->dependency(i)), + ","); const std::string& name = file->dependency(i)->name(); printer->Print( - "import * as $alias$ from \"$file$\";\n", - "alias", ModuleAlias(name), + "import {$aliases$} from \"$file$\";\n", + "aliases", aliases_comma_delimited, + "modulealias", ModuleAlias(name), "file", GetRootPath(file->name(), name) + GetJSFilename(options, name)); } @@ -3923,13 +4071,13 @@ void Generator::GenerateFile(const GeneratorOptions& options, GenerateRequiresForLibrary(options, printer, files, &provided); } - GenerateClassesAndEnums(options, printer, file); + GenerateClassesAndEnums(options, type_names, printer, file); // Generate code for top-level extensions. Extensions nested inside messages // are emitted inside GenerateClassesAndEnums(). for (std::set::const_iterator it = extensions.begin(); it != extensions.end(); ++it) { - GenerateExtension(options, printer, *it); + GenerateExtension(options, type_names, printer, *it); } // if provided is empty, do not export anything @@ -3963,6 +4111,7 @@ bool Generator::GenerateAll(const std::vector& files, } if (options.output_mode() == GeneratorOptions::kEverythingInOneFile) { + auto type_names = TypeNames::NonEs6TypeNames(options); // All output should go in a single file. std::string filename = options.output_dir + "/" + options.library + options.GetFileNameExtension(); @@ -4002,7 +4151,7 @@ bool Generator::GenerateAll(const std::vector& files, for (int i = 0; i < extensions.size(); i++) { if (ShouldGenerateExtension(extensions[i])) { - GenerateExtension(options, &printer, extensions[i]); + GenerateExtension(options, type_names, &printer, extensions[i]); } } @@ -4013,6 +4162,7 @@ bool Generator::GenerateAll(const std::vector& files, EmbedCodeAnnotations(annotations, &printer); } } else if (options.output_mode() == GeneratorOptions::kOneOutputFilePerSCC) { + TypeNames type_names = TypeNames::NonEs6TypeNames(options); std::set have_printed; SCCAnalyzer analyzer; std::map allowed_map; @@ -4071,7 +4221,7 @@ bool Generator::GenerateAll(const std::vector& files, } for (auto one_desc : scc->descriptors) { if (one_desc->containing_type() == nullptr) { - GenerateClass(options, &printer, one_desc); + GenerateClass(options, type_names, &printer, one_desc); } } @@ -4154,7 +4304,7 @@ bool Generator::GenerateAll(const std::vector& files, for (int j = 0; j < files[i]->extension_count(); j++) { if (ShouldGenerateExtension(files[i]->extension(j))) { - GenerateExtension(options, &printer, files[i]->extension(j)); + GenerateExtension(options, type_names, &printer, files[i]->extension(j)); } } if (options.annotate_code) { @@ -4194,7 +4344,7 @@ void Generator::GenerateMethodStart(const GeneratorOptions& options, const std::string Generator::MethodStart(const GeneratorOptions& options, const char * classSymbol, const char * methodName) const { - if (WantEs6(options)) { + if (options.WantEs6()) { return methodName; } else { return std::string(classSymbol) + ".prototype." + methodName + " = function"; @@ -4214,7 +4364,7 @@ const std::string Generator::StaticMemberAssignmentLhs( const GeneratorOptions& options, const char * classSymbol, const char * fieldName) const { - if (WantEs6(options)) { + if (options.WantEs6()) { return std::string("static ") + fieldName; } else { return std::string("") + classSymbol + "." + fieldName; diff --git a/generator/js_generator.h b/generator/js_generator.h index 9f35977..a336fe7 100644 --- a/generator/js_generator.h +++ b/generator/js_generator.h @@ -59,6 +59,8 @@ class Printer; namespace compiler { namespace js { +class TypeNames; + struct GeneratorOptions { // Output path. std::string output_dir; @@ -108,6 +110,11 @@ struct GeneratorOptions { // Indicates how to output the generated code based on the provided options. OutputMode output_mode() const; + // True if the code generator is in ES6 module generation mode. + // + // In this mode, ES6 classes and module-style imports will be used. + bool WantEs6() const; + // The remaining options are only relevant when we are using kImportClosure. // Add a `goog.requires()` call for each enum type used. If not set, a @@ -130,6 +137,78 @@ struct GeneratorOptions { bool annotate_code; }; +/** + * Maps known protobuf type names for enums, messages to a JavaScript + * expresseion used to reference that type. + */ +class TypeNames { +public: + /** + * Returns a TypeNames namer for naming types while generating code + * for the given proto file. Assumes kOneOutputFilePerInputFile. + */ + static TypeNames Es6TypeNames( + const GeneratorOptions& options, + const FileDescriptor* codegen_file); + + /** + * Returns a TypeNames object for naming types while generating + * code in non-es6-mode. Use dot-delmited type names and + * goog.provide/goog.requires. + */ + static TypeNames NonEs6TypeNames(const GeneratorOptions& options); + + /** + * Returns the JavaScript expression for referring to the passed message type. + */ + std::string JsExpression(const google::protobuf::Descriptor& desc) const; + + /** + * Returns the JavaScript expression for referring to the given enum type. + */ + std::string JsExpression(const google::protobuf::EnumDescriptor& desc) const; + + /** + * Returns the JavaScript expression for referring to type of the + * given field, which must be a message field. + */ + std::string SubmessageTypeRef(const FieldDescriptor* field) const; + +private: + TypeNames( + GeneratorOptions options_, + const FileDescriptor* codegen_file_, + const std::map& map) : + options(options_), + codegen_file(codegen_file_), + map_(map) {} + + GeneratorOptions options; + + // The proto file for which code is being generated. + // + // If in ES6 mode, this will always be set. Otherwise, this may + // be null if in kOneOutputFilePerSCC or kEverythingInOneFile mode. + const FileDescriptor* codegen_file; + + // Maps a fully qualified proto type name (as returned from + // Descriptor::full_name()) to a JavaScript expression to use to refer to that + // type within the generated code. + std::map map_; + + // True for non-ES6 mode. Use dot-delimited identifiers to refer + // to protos. e.g., "proto.foo.bar.Baz.Bim" for nested message Bim + // within message Baz within package "foo.bar". + //bool UseDotDelimitedNames(); + + /** + * Returns the JavaScript expression for referring to the Enum + * or Message with the provided full name (as obtained from the type + * descriptor). + */ + std::string JsExpression(const std::string& full_name) const; +}; + // CodeGenerator implementation which generates a JavaScript source file and // header. If you create your own protocol compiler binary and you want it to // support JavaScript output, you can do so by registering an instance of this @@ -238,6 +317,7 @@ class PROTOC_EXPORT Generator : public CodeGenerator { // Generate definitions for all message classes and enums. void GenerateClassesAndEnums(const GeneratorOptions& options, + const TypeNames& type_names, io::Printer* printer, const FileDescriptor* file) const; @@ -247,10 +327,14 @@ class PROTOC_EXPORT Generator : public CodeGenerator { bool use_default) const; // Generate definition for one class. - void GenerateClass(const GeneratorOptions& options, io::Printer* printer, - const Descriptor* desc) const; - void GenerateClassEs6(const GeneratorOptions& options, io::Printer* printer, + void GenerateClass(const GeneratorOptions& options, + const TypeNames& type_names, + io::Printer* printer, const Descriptor* desc) const; + void GenerateClassEs6(const GeneratorOptions& options, + const TypeNames& type_names, + io::Printer* printer, + const Descriptor* desc) const; void GenerateClassConstructor(const GeneratorOptions& options, io::Printer* printer, const Descriptor* desc) const; @@ -269,40 +353,53 @@ class PROTOC_EXPORT Generator : public CodeGenerator { io::Printer* printer, const Descriptor* desc) const; void GenerateClassToObject(const GeneratorOptions& options, + const TypeNames& type_names, io::Printer* printer, const Descriptor* desc) const; void GenerateClassFieldToObject(const GeneratorOptions& options, + const TypeNames& type_names, io::Printer* printer, const FieldDescriptor* field) const; void GenerateClassFromObject(const GeneratorOptions& options, + const TypeNames& type_names, io::Printer* printer, const Descriptor* desc) const; void GenerateClassFieldFromObject(const GeneratorOptions& options, + const TypeNames& type_names, io::Printer* printer, const FieldDescriptor* field) const; void GenerateClassRegistration(const GeneratorOptions& options, + const TypeNames& type_names, io::Printer* printer, const Descriptor* desc) const; void GenerateClassFields(const GeneratorOptions& options, + const TypeNames& type_names, io::Printer* printer, const Descriptor* desc) const; - void GenerateClassField(const GeneratorOptions& options, io::Printer* printer, + void GenerateClassField(const GeneratorOptions& options, + const TypeNames& type_names, + io::Printer* printer, const FieldDescriptor* desc) const; void GenerateClassExtensionFieldInfo(const GeneratorOptions& options, io::Printer* printer, const Descriptor* desc) const; void GenerateClassDeserialize(const GeneratorOptions& options, + const TypeNames& type_names, io::Printer* printer, const Descriptor* desc) const; void GenerateClassDeserializeBinary(const GeneratorOptions& options, + const TypeNames& type_names, io::Printer* printer, const Descriptor* desc) const; void GenerateClassDeserializeBinaryField(const GeneratorOptions& options, + const TypeNames& type_names, io::Printer* printer, const FieldDescriptor* field) const; void GenerateClassSerializeBinary(const GeneratorOptions& options, + const TypeNames& type_names, io::Printer* printer, const Descriptor* desc) const; void GenerateClassSerializeBinaryField(const GeneratorOptions& options, + const TypeNames& type_names, io::Printer* printer, const FieldDescriptor* field) const; @@ -311,7 +408,9 @@ class PROTOC_EXPORT Generator : public CodeGenerator { const EnumDescriptor* enumdesc) const; // Generate an extension definition. - void GenerateExtension(const GeneratorOptions& options, io::Printer* printer, + void GenerateExtension(const GeneratorOptions& options, + const TypeNames& type_names, + io::Printer* printer, const FieldDescriptor* field) const; // Generate addFoo() method for repeated primitive fields. @@ -333,10 +432,6 @@ class PROTOC_EXPORT Generator : public CodeGenerator { void GenerateMethodEnd(const GeneratorOptions& options, io::Printer* printer) const; - // True if the implementation code should be es6-style classes. - // This is set to true if the import style is set to "es6". - bool WantEs6(const GeneratorOptions& options) const; - const std::string MethodStart(const GeneratorOptions& options, const char * classSymbol, const char * methodName) const; From 6eff0cb96be1893a90f39df6ac833b0e7f7f84cc Mon Sep 17 00:00:00 2001 From: Red Daly Date: Tue, 3 Jan 2023 22:07:01 -0800 Subject: [PATCH 06/30] Use "Compile Commands Extractor for Bazel" to get C++ autocomplete. --- .gitignore | 10 ++++++++++ WORKSPACE | 14 ++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/.gitignore b/.gitignore index 99c6e65..feb4c3e 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,13 @@ bazel-* /testproto_libs1.js /testproto_libs2.js /google-protobuf.js + +### Automatically added by Hedron's Bazel Compile Commands Extractor: https://github.com/hedronvision/bazel-compile-commands-extractor +# Ignore the `external` link (that is added by `bazel-compile-commands-extractor`). The link differs between macOS/Linux and Windows, so it shouldn't be checked in. The pattern must not end with a trailing `/` because it's a symlink on macOS/Linux. +/external +# Ignore links to Bazel's output. The pattern needs the `*` because people can change the name of the directory into which your repository is cloned (changing the `bazel-` symlink), and must not end with a trailing `/` because it's a symlink on macOS/Linux. +/bazel-* +# Ignore generated output. Although valuable (after all, the primary purpose of `bazel-compile-commands-extractor` is to produce `compile_commands.json`!), it should not be checked in. +/compile_commands.json +# Ignore the directory in which `clangd` stores its local index. +/.cache/ diff --git a/WORKSPACE b/WORKSPACE index 6902897..163a676 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -14,3 +14,17 @@ protobuf_deps() load("@rules_pkg//:deps.bzl", "rules_pkg_dependencies") rules_pkg_dependencies() + +# Hedron's Compile Commands Extractor for Bazel +# https://github.com/hedronvision/bazel-compile-commands-extractor +http_archive( + name = "hedron_compile_commands", + + # Replace the commit hash in both places (below) with the latest, rather than using the stale one here. + # Even better, set up Renovate and let it do the work for you (see "Suggestion: Updates" in the README). + url = "https://github.com/hedronvision/bazel-compile-commands-extractor/archive/ed994039a951b736091776d677f324b3903ef939.tar.gz", + strip_prefix = "bazel-compile-commands-extractor-ed994039a951b736091776d677f324b3903ef939", + # When you first run this tool, it'll recommend a sha256 hash to put here with a message like: "DEBUG: Rule 'hedron_compile_commands' indicated that a canonical reproducible form can be obtained by modifying arguments sha256 = ..." +) +load("@hedron_compile_commands//:workspace_setup.bzl", "hedron_compile_commands_setup") +hedron_compile_commands_setup() From 5108a8e79f55653f7fc118c9b90abde3d7dfafb4 Mon Sep 17 00:00:00 2001 From: Red Daly Date: Tue, 3 Jan 2023 22:16:26 -0800 Subject: [PATCH 07/30] fixup! Use "Compile Commands Extractor for Bazel" to get C++ autocomplete. --- WORKSPACE | 3 +++ 1 file changed, 3 insertions(+) diff --git a/WORKSPACE b/WORKSPACE index 163a676..944d8eb 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -17,6 +17,9 @@ rules_pkg_dependencies() # Hedron's Compile Commands Extractor for Bazel # https://github.com/hedronvision/bazel-compile-commands-extractor +# +# Run bazel run @hedron_compile_commands//:refresh_all to get autocomplete +# working in VS Code and other editors. http_archive( name = "hedron_compile_commands", From 68ab15fd2002fe58e61e1c71c9d9793c48271c6d Mon Sep 17 00:00:00 2001 From: Red Daly Date: Mon, 9 Jan 2023 22:55:42 -0800 Subject: [PATCH 08/30] Output // proto_import: "path/to/dep.proto" comments to assist post-processors. The import paths for proto imports don't work with our bazel rules, so we post-process the protobuf-javascript generator's output to rewrite the imports. This is easier if each import has a comment indicating the corresponding proto file. --- generator/js_generator.cc | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/generator/js_generator.cc b/generator/js_generator.cc index 587c309..36b017d 100644 --- a/generator/js_generator.cc +++ b/generator/js_generator.cc @@ -4000,10 +4000,11 @@ void Generator::GenerateFile(const GeneratorOptions& options, ","); const std::string& name = file->dependency(i)->name(); printer->Print( - "import {$aliases$} from \"$file$\";\n", + "import {$aliases$} from \"$file$\"; // proto import: \"$proto_filename$\"\n", "aliases", aliases_comma_delimited, "modulealias", ModuleAlias(name), - "file", GetRootPath(file->name(), name) + GetJSFilename(options, name)); + "file", GetRootPath(file->name(), name) + GetJSFilename(options, name), + "proto_filename", name); } } else if ((options.import_style == GeneratorOptions::kImportCommonJs || From e16db874a2f794d249b735c280a570ebeacbf180 Mon Sep 17 00:00:00 2001 From: Red Daly Date: Sat, 14 Jan 2023 13:49:33 -0800 Subject: [PATCH 09/30] Fix generator so it outpus message constructor. --- generator/js_generator.cc | 54 +++++++++++++++++++++++++-------------- 1 file changed, 35 insertions(+), 19 deletions(-) diff --git a/generator/js_generator.cc b/generator/js_generator.cc index 36b017d..0d3c2de 100644 --- a/generator/js_generator.cc +++ b/generator/js_generator.cc @@ -2072,6 +2072,8 @@ void Generator::GenerateClassEs6(const GeneratorOptions& options, "classname", desc->name()); printer->Indent(); + + GenerateClassConstructor(options, printer, desc); GenerateClassFieldInfo(options, printer, desc); @@ -2108,6 +2110,11 @@ void Generator::GenerateClassEs6(const GeneratorOptions& options, void Generator::GenerateClassConstructor(const GeneratorOptions& options, io::Printer* printer, const Descriptor* desc) const { + const std::string classSymbol = GetMessagePath(options, desc); + const std::string methodStart = MethodStart( + options, classSymbol.c_str(), "constructor"); + const char * methodEnd = options.WantEs6() ? "}" : "};"; + printer->Print( "/**\n" " * Generated by JsPbCodeGenerator.\n" @@ -2123,15 +2130,16 @@ void Generator::GenerateClassConstructor(const GeneratorOptions& options, " * @extends {jspb.Message}\n" " * @constructor\n" " */\n" + "$methodstart$(opt_data) {\n", + "methodstart", methodStart); - " // DO NOT SUBMIT: \n" - " // classprefix = $classprefix$\n" - " // classname = $classname$ \n" - - "$classprefix$$classname$ = function(opt_data) {\n", - "classprefix", GetMessagePathPrefix(options, desc), "classname", - desc->name()); printer->Annotate("classname", desc); + +if (options.WantEs6()) { + printer->Print(" super(...arguments);\n"); +} + +// Body of constructor. std::string message_id = GetMessageId(desc); printer->Print( " jspb.Message.initialize(this, opt_data, $messageId$, $pivot$, " @@ -2143,18 +2151,26 @@ printer->Print( RepeatedFieldsArrayName(options, desc), "oneoffields", OneofFieldsArrayName(options, desc)); printer->Print( - "};\n" - "goog.inherits($classname$, jspb.Message);\n" - "if (goog.DEBUG && !COMPILED) {\n" - // displayName overrides Function.prototype.displayName - // http://google3/javascript/externs/es3.js?l=511 - " /**\n" - " * @public\n" - " * @override\n" - " */\n" - " $classname$.displayName = '$classname$';\n" - "}\n", - "classname", GetMessagePath(options, desc)); + "$methodend$\n\n", + "methodend", methodEnd); + + // Additional closure stuff. + if (options.WantEs6()) { + // Skip the displayName for now for ES6. + return; + } + printer->Print( + "goog.inherits($classname$, jspb.Message);\n" + "if (goog.DEBUG && !COMPILED) {\n" + // displayName overrides Function.prototype.displayName + // http://google3/javascript/externs/es3.js?l=511 + " /**\n" + " * @public\n" + " * @override\n" + " */\n" + " $classname$.displayName = '$classname$';\n" + "}\n", + "classname", GetMessagePath(options, desc)); } void Generator::GenerateClassConstructorAndDeclareExtensionFieldInfo( From ed259b332e2543736c17ef9a7effdbd0a44b3d64 Mon Sep 17 00:00:00 2001 From: Red Daly Date: Tue, 17 Jan 2023 21:28:37 -0800 Subject: [PATCH 10/30] add .map file to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index feb4c3e..1db47c8 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ bazel-* /testproto_libs1.js /testproto_libs2.js /google-protobuf.js +/google-protobuf.js.map ### Automatically added by Hedron's Bazel Compile Commands Extractor: https://github.com/hedronvision/bazel-compile-commands-extractor # Ignore the `external` link (that is added by `bazel-compile-commands-extractor`). The link differs between macOS/Linux and Windows, so it shouldn't be checked in. The pattern must not end with a trailing `/` because it's a symlink on macOS/Linux. From 8d2712207756c9969ec5eab617386cb4e16caa17 Mon Sep 17 00:00:00 2001 From: Red Daly Date: Tue, 17 Jan 2023 22:54:45 -0800 Subject: [PATCH 11/30] Fix toObject codegen. --- generator/js_generator.cc | 38 +++++++++++++++++++++++++++++--------- generator/js_generator.h | 3 +++ 2 files changed, 32 insertions(+), 9 deletions(-) diff --git a/generator/js_generator.cc b/generator/js_generator.cc index 0d3c2de..738a901 100644 --- a/generator/js_generator.cc +++ b/generator/js_generator.cc @@ -2077,7 +2077,7 @@ void Generator::GenerateClassEs6(const GeneratorOptions& options, GenerateClassFieldInfo(options, printer, desc); - // DO NOT SUBMIT GenerateClassToObject(options, printer, desc); + GenerateClassToObject(options, type_names, printer, desc); // These must come *before* the extension-field info generation in // GenerateClassRegistration so that references to the binary @@ -2294,7 +2294,8 @@ void Generator::GenerateOneofCaseDefinition( "/**\n" " * @return {$class$.$oneof$Case}\n" " */\n", - "class", className); + "class", className, + "oneof", JSOneofName(oneof)); GenerateMethodStart(options, printer, className.c_str(), (std::string("get") + JSOneofName(oneof) + "Case").c_str()); @@ -2316,10 +2317,15 @@ void Generator::GenerateClassToObject(const GeneratorOptions& options, const TypeNames& type_names, io::Printer* printer, const Descriptor* desc) const { + + const char * if_guard_start = options.WantEs6() ? "" : "if (jspb.Message.GENERATE_TO_OBJECT) {\n"; + const char * if_guard_end = options.WantEs6() ? "" : "}\n"; + const std::string classSymbol = options.WantEs6() ? desc->name() : GetMessagePath(options, desc); + printer->Print( "\n" "\n" - "if (jspb.Message.GENERATE_TO_OBJECT) {\n" + "$if_guard_start$" "/**\n" " * Creates an object representation of this proto.\n" " * Field names that are reserved in JavaScript and will be renamed to " @@ -2334,9 +2340,9 @@ void Generator::GenerateClassToObject(const GeneratorOptions& options, " * http://goto/soy-param-migration\n" " * @return {!Object}\n" " */\n" - "$classname$.prototype.toObject = function(opt_includeInstance) {\n" + "$methodstart$(opt_includeInstance) {\n" " return $classname$.toObject(opt_includeInstance, this);\n" - "};\n" + "}\n" "\n" "\n" "/**\n" @@ -2349,9 +2355,12 @@ void Generator::GenerateClassToObject(const GeneratorOptions& options, " * @return {!Object}\n" " * @suppress {unusedLocalVariables} f is only used for nested messages\n" " */\n" - "$classname$.toObject = function(includeInstance, msg) {\n" + "$classmethodstart$(includeInstance, msg) {\n" " var f, obj = {", - "classname", GetMessagePath(options, desc)); + "if_guard_start", if_guard_start, + "methodstart", MethodStart(options, classSymbol.c_str(), "toObject"), + "classmethodstart", MethodStartStatic(options, classSymbol.c_str(), "toObject"), + "classname", classSymbol); bool first = true; for (int i = 0; i < desc->field_count(); i++) { @@ -2391,11 +2400,12 @@ void Generator::GenerateClassToObject(const GeneratorOptions& options, " obj.$$jspbMessageInstance = msg;\n" " }\n" " return obj;\n" - "};\n" "}\n" + "$if_guard_end$" "\n" "\n", - "classname", GetMessagePath(options, desc)); + "classname", GetMessagePath(options, desc), + "if_guard_end", if_guard_end); } void Generator::GenerateFieldValueExpression(io::Printer* printer, @@ -4368,6 +4378,16 @@ const std::string Generator::MethodStart(const GeneratorOptions& options, } } +const std::string Generator::MethodStartStatic(const GeneratorOptions& options, + const char * classSymbol, + const char * methodName) const { + if (options.WantEs6()) { + return std::string("static ") + methodName; + } else { + return std::string(classSymbol) + "." + methodName + " = function"; + } +} + void Generator::GenerateMethodEnd(const GeneratorOptions& options, io::Printer* printer) const { if(options.import_style == GeneratorOptions::kImportEs6) { diff --git a/generator/js_generator.h b/generator/js_generator.h index a336fe7..8740c17 100644 --- a/generator/js_generator.h +++ b/generator/js_generator.h @@ -435,6 +435,9 @@ class PROTOC_EXPORT Generator : public CodeGenerator { const std::string MethodStart(const GeneratorOptions& options, const char * classSymbol, const char * methodName) const; + const std::string MethodStartStatic(const GeneratorOptions& options, + const char * classSymbol, + const char * methodName) const; const std::string StaticMemberAssignmentLhs( const GeneratorOptions& options, From 1167b4e01dadb0e444e3837b23a812b2535ea9a0 Mon Sep 17 00:00:00 2001 From: Red Daly Date: Sun, 5 Feb 2023 10:20:30 -0800 Subject: [PATCH 12/30] Fix GenerateBytesWrapper and well-known type ES6 codegen. --- generator/js_generator.cc | 57 ++++++++++++++--------------- generator/js_generator.h | 13 +++++++ generator/well_known_types_embed.cc | 28 +++++++------- 3 files changed, 55 insertions(+), 43 deletions(-) diff --git a/generator/js_generator.cc b/generator/js_generator.cc index 738a901..c974c23 100644 --- a/generator/js_generator.cc +++ b/generator/js_generator.cc @@ -76,16 +76,6 @@ static const int kNumKeyword = sizeof(kKeyword) / sizeof(char*); namespace { -// The mode of operation for bytes fields. Historically JSPB always carried -// bytes as JS {string}, containing base64 content by convention. With binary -// and proto3 serialization the new convention is to represent it as binary -// data in Uint8Array. See b/26173701 for background on the migration. -enum BytesMode { - BYTES_DEFAULT, // Default type for getBytesField to return. - BYTES_B64, // Explicitly coerce to base64 string where needed. - BYTES_U8, // Explicitly coerce to Uint8Array where needed. -}; - bool IsReserved(const std::string& ident) { for (int i = 0; i < kNumKeyword; i++) { if (ident == kKeyword[i]) { @@ -2014,6 +2004,14 @@ void Generator::GenerateClass(const GeneratorOptions& options, // objects. GenerateClassDeserializeBinary(options, type_names, printer, desc); GenerateClassSerializeBinary(options, type_names, printer, desc); + + // Emit well-known type methods. + for (FileToc* toc = well_known_types_js; toc->name != NULL; toc++) { + std::string name = std::string("google/protobuf/") + toc->name; + if (name == StripProto(desc->file()->name()) + ".js") { + printer->Print(toc->data); + } + } } // Recurse on nested types. These must come *before* the extension-field @@ -2674,13 +2672,19 @@ void Generator::GenerateClassFields(const GeneratorOptions& options, } } -void GenerateBytesWrapper(const GeneratorOptions& options, io::Printer* printer, - const FieldDescriptor* field, BytesMode bytes_mode) { +void Generator::GenerateBytesWrapper(const GeneratorOptions& options, io::Printer* printer, + const FieldDescriptor* field, BytesMode bytes_mode) const { std::string type = JSFieldTypeAnnotation(options, field, /* is_setter_argument = */ false, /* force_present = */ false, /* singular_if_not_packed = */ false, bytes_mode); + const std::string classSymbol = GetMessagePath(options, field->containing_type()); + const std::string methodStart = MethodStart( + options, classSymbol.c_str(), + (std::string("get") + JSGetterName(options, field, bytes_mode)).c_str()); + const char * methodEnd = options.WantEs6() ? "}" : "};"; + printer->Print( "/**\n" " * $fielddef$\n" @@ -2688,19 +2692,22 @@ void GenerateBytesWrapper(const GeneratorOptions& options, io::Printer* printer, " * This is a type-conversion wrapper around `get$defname$()`\n" " * @return {$type$}\n" " */\n" - "$class$.prototype.get$name$ = function() {\n" + "$methodstart$() {\n" " return /** @type {$type$} */ (jspb.Message.bytes$list$As$suffix$(\n" " this.get$defname$()));\n" - "};\n" + "$methodend$\n" "\n" "\n", - "fielddef", FieldDefinition(options, field), "comment", - FieldComments(field, bytes_mode), "type", type, "class", - GetMessagePath(options, field->containing_type()), "name", - JSGetterName(options, field, bytes_mode), "list", - field->is_repeated() ? "List" : "", "suffix", - JSByteGetterSuffix(bytes_mode), "defname", - JSGetterName(options, field, BYTES_DEFAULT)); + "fielddef", FieldDefinition(options, field), + "comment", FieldComments(field, bytes_mode), + "type", type, + "methodstart", methodStart, + "methodend", methodStart, + "class", GetMessagePath(options, field->containing_type()), + "name", JSGetterName(options, field, bytes_mode), + "list", field->is_repeated() ? "List" : "", + "suffix", JSByteGetterSuffix(bytes_mode), + "defname", JSGetterName(options, field, BYTES_DEFAULT)); } void Generator::GenerateClassField(const GeneratorOptions& options, @@ -4116,14 +4123,6 @@ void Generator::GenerateFile(const GeneratorOptions& options, printer->Print("goog.object.extend(exports, proto);\n", "package", GetNamespace(options, file)); } - - // Emit well-known type methods. - for (FileToc* toc = well_known_types_js; toc->name != NULL; toc++) { - std::string name = std::string("google/protobuf/") + toc->name; - if (name == StripProto(file->name()) + ".js") { - printer->Print(toc->data); - } - } } bool Generator::GenerateAll(const std::vector& files, diff --git a/generator/js_generator.h b/generator/js_generator.h index 8740c17..145ca9c 100644 --- a/generator/js_generator.h +++ b/generator/js_generator.h @@ -61,6 +61,16 @@ namespace js { class TypeNames; +// The mode of operation for bytes fields. Historically JSPB always carried +// bytes as JS {string}, containing base64 content by convention. With binary +// and proto3 serialization the new convention is to represent it as binary +// data in Uint8Array. See b/26173701 for background on the migration. +enum BytesMode { + BYTES_DEFAULT, // Default type for getBytesField to return. + BYTES_B64, // Explicitly coerce to base64 string where needed. + BYTES_U8, // Explicitly coerce to Uint8Array where needed. +}; + struct GeneratorOptions { // Output path. std::string output_dir; @@ -424,6 +434,9 @@ class PROTOC_EXPORT Generator : public CodeGenerator { io::Printer* printer, const FieldDescriptor* field) const; + void GenerateBytesWrapper(const GeneratorOptions& options, io::Printer* printer, + const FieldDescriptor* field, BytesMode bytes_mode) const; + // Prints the beginning/end of a method of some class. void GenerateMethodStart(const GeneratorOptions& options, io::Printer* printer, diff --git a/generator/well_known_types_embed.cc b/generator/well_known_types_embed.cc index ef18854..688ec3b 100644 --- a/generator/well_known_types_embed.cc +++ b/generator/well_known_types_embed.cc @@ -39,7 +39,7 @@ struct FileToc well_known_types_js[] = { " * Returns the type name contained in this instance, if any.\n" " * @return {string|undefined}\n" " */\n" - "proto.google.protobuf.Any.prototype.getTypeName = function() {\n" + "getTypeName() {\n" " return this.getTypeUrl().split('/').pop();\n" "};\n" "\n" @@ -51,7 +51,7 @@ struct FileToc well_known_types_js[] = { " * @param {string} name The type name of this message object.\n" " * @param {string=} opt_typeUrlPrefix the type URL prefix.\n" " */\n" - "proto.google.protobuf.Any.prototype.pack = function(serialized, name,\n" + "pack(serialized, name,\n" " opt_typeUrlPrefix) " "{\n" " if (!opt_typeUrlPrefix) {\n" @@ -65,7 +65,7 @@ struct FileToc well_known_types_js[] = { " }\n" "\n" " this.setValue(serialized);\n" - "};\n" + "}\n" "\n" "\n" "/**\n" @@ -79,14 +79,14 @@ struct FileToc well_known_types_js[] = { "deserialized\n" " * object, otherwise returns null.\n" " */\n" - "proto.google.protobuf.Any.prototype.unpack = function(deserialize, name) " + "unpack(deserialize, name) " "{\n" " if (this.getTypeName() == name) {\n" " return deserialize(this.getValue_asU8());\n" " } else {\n" " return null;\n" " }\n" - "};\n" + "}\n" }, {"timestamp.js", "/* This code will be inserted into generated code for\n" @@ -96,7 +96,7 @@ struct FileToc well_known_types_js[] = { " * Returns a JavaScript 'Date' object corresponding to this Timestamp.\n" " * @return {!Date}\n" " */\n" - "proto.google.protobuf.Timestamp.prototype.toDate = function() {\n" + "toDate() {\n" " var seconds = this.getSeconds();\n" " var nanos = this.getNanos();\n" "\n" @@ -108,7 +108,7 @@ struct FileToc well_known_types_js[] = { " * Sets the value of this Timestamp object to be the given Date.\n" " * @param {!Date} value The value to set.\n" " */\n" - "proto.google.protobuf.Timestamp.prototype.fromDate = function(value) {\n" + "fromDate(value) {\n" " this.setSeconds(Math.floor(value.getTime() / 1000));\n" " this.setNanos(value.getMilliseconds() * 1000000);\n" "};\n" @@ -120,7 +120,7 @@ struct FileToc well_known_types_js[] = { " * @param {!Date} value The value to set.\n" " * @return {!proto.google.protobuf.Timestamp}\n" " */\n" - "proto.google.protobuf.Timestamp.fromDate = function(value) {\n" + "fromDate(value) {\n" " var timestamp = new proto.google.protobuf.Timestamp();\n" " timestamp.fromDate(value);\n" " return timestamp;\n" @@ -142,7 +142,7 @@ struct FileToc well_known_types_js[] = { " * @return {?proto.google.protobuf.JavaScriptValue} a plain JavaScript\n" " * value representing this Struct.\n" " */\n" - "proto.google.protobuf.Value.prototype.toJavaScript = function() {\n" + "toJavaScript() {\n" " var kindCase = proto.google.protobuf.Value.KindCase;\n" " switch (this.getKindCase()) {\n" " case kindCase.NULL_VALUE:\n" @@ -169,7 +169,7 @@ struct FileToc well_known_types_js[] = { " * convert.\n" " * @return {!proto.google.protobuf.Value} The newly constructed value.\n" " */\n" - "proto.google.protobuf.Value.fromJavaScript = function(value) {\n" + "fromJavaScript(value) {\n" " var ret = new proto.google.protobuf.Value();\n" " switch (goog.typeOf(value)) {\n" " case 'string':\n" @@ -204,7 +204,7 @@ struct FileToc well_known_types_js[] = { " * Converts this ListValue object to a plain JavaScript array.\n" " * @return {!Array} a plain JavaScript array representing this List.\n" " */\n" - "proto.google.protobuf.ListValue.prototype.toJavaScript = function() {\n" + "toJavaScript() {\n" " var ret = [];\n" " var values = this.getValuesList();\n" "\n" @@ -221,7 +221,7 @@ struct FileToc well_known_types_js[] = { " * @param {!Array} array a plain JavaScript array\n" " * @return {proto.google.protobuf.ListValue} a new ListValue object\n" " */\n" - "proto.google.protobuf.ListValue.fromJavaScript = function(array) {\n" + "fromJavaScript(array) {\n" " var ret = new proto.google.protobuf.ListValue();\n" "\n" " for (var i = 0; i < array.length; i++) {\n" @@ -239,7 +239,7 @@ struct FileToc well_known_types_js[] = { "plain\n" " * JavaScript object representing this Struct.\n" " */\n" - "proto.google.protobuf.Struct.prototype.toJavaScript = function() {\n" + "toJavaScript() {\n" " var ret = {};\n" "\n" " this.getFieldsMap().forEach(function(value, key) {\n" @@ -255,7 +255,7 @@ struct FileToc well_known_types_js[] = { " * @param {!Object} obj a plain JavaScript object\n" " * @return {proto.google.protobuf.Struct} a new Struct object\n" " */\n" - "proto.google.protobuf.Struct.fromJavaScript = function(obj) {\n" + "fromJavaScript(obj) {\n" " var ret = new proto.google.protobuf.Struct();\n" " var map = ret.getFieldsMap();\n" "\n" From 2df217e00c1c1decbf3bef2db0190918b39d323b Mon Sep 17 00:00:00 2001 From: Red Daly Date: Sun, 5 Feb 2023 11:58:14 -0800 Subject: [PATCH 13/30] Rename imports in ES6 mode if there are possible identifier conflicts. --- generator/js_generator.cc | 114 ++++++++++++++++++++++++++++++++++---- generator/js_generator.h | 24 +++++++- 2 files changed, 126 insertions(+), 12 deletions(-) diff --git a/generator/js_generator.cc b/generator/js_generator.cc index c974c23..1db0447 100644 --- a/generator/js_generator.cc +++ b/generator/js_generator.cc @@ -2702,7 +2702,7 @@ void Generator::GenerateBytesWrapper(const GeneratorOptions& options, io::Printe "comment", FieldComments(field, bytes_mode), "type", type, "methodstart", methodStart, - "methodend", methodStart, + "methodend", methodEnd, "class", GetMessagePath(options, field->containing_type()), "name", JSGetterName(options, field, bytes_mode), "list", field->is_repeated() ? "List" : "", @@ -3912,24 +3912,103 @@ TypeNames TypeNames::NonEs6TypeNames(const GeneratorOptions& options) { return TypeNames(options, nullptr, std::map()); } +std::map TypeNames::ExportedNamesOfDeps( + const FileDescriptor* codegen_file) { + std::map full_name_to_exported_name; + + for (int i = 0; i < codegen_file->dependency_count(); i++) { + auto dep_file = codegen_file->dependency(i); + + for (int j = 0; j < dep_file->message_type_count(); j++) { + auto message_type = dep_file->message_type(j); + full_name_to_exported_name.insert(std::make_pair( + message_type->full_name(), + TypeNames::JsName(message_type->full_name()))); + } + + for (int j = 0; j < dep_file->enum_type_count(); j++) { + auto enum_type = dep_file->enum_type(j); + full_name_to_exported_name.insert(std::make_pair( + enum_type->full_name(), + TypeNames::JsName(enum_type->full_name()))); + } + } + return full_name_to_exported_name; +} + +void ReservedForLocalIdentifiers(const Descriptor* desc, std::set& reserved_identifiers); + +void ReservedForLocalIdentifiers(const EnumDescriptor* desc, std::set& reserved_identifiers); + +void ReservedForLocalIdentifiers(const Descriptor* desc, std::set& reserved_identifiers) { + reserved_identifiers.insert(TypeNames::JsName(desc->full_name())); + for (int j = 0; j < desc->nested_type_count(); j++) { + ReservedForLocalIdentifiers(desc->nested_type(j), reserved_identifiers); + } + for (int j = 0; j < desc->enum_type_count(); j++) { + ReservedForLocalIdentifiers(desc->enum_type(j), reserved_identifiers); + } +} + +void ReservedForLocalIdentifiers(const EnumDescriptor* desc, std::set& reserved_identifiers) { + reserved_identifiers.insert(TypeNames::JsName(desc->full_name())); +} + + +/** + * ReservedForLocalIdentifiers computes the set of symbols that should not be + * used for imports because they might conflict with definitions (class names, + * etc.). + */ +void ReservedForLocalIdentifiers(const FileDescriptor* desc, std::set& reserved_identifiers) { + for (int j = 0; j < desc->message_type_count(); j++) { + ReservedForLocalIdentifiers(desc->message_type(j), reserved_identifiers); + } + for (int j = 0; j < desc->enum_type_count(); j++) { + ReservedForLocalIdentifiers(desc->enum_type(j), reserved_identifiers); + } +} + TypeNames TypeNames::Es6TypeNames( const GeneratorOptions& options, const FileDescriptor* codegen_file) { - // First, generate a map with values that may be duplicates. Then rename - // ambiguous values from the rhs. - std::map ideal_mapping; + + // Full proto name -> local alias in JS codegen file. + std::map full_name_to_alias; + // Local aliases that are already reserved + std::set reserved_aliases; + ReservedForLocalIdentifiers(codegen_file, reserved_aliases); + + auto pick_name = [&](const std::string full_name, const std::string ideal_name) -> void { + std::string base_candidate = ideal_name; + for (int i = -1; i < 10000; i++) { + if (i == 0) { + base_candidate = full_name; + ReplaceCharacters(&base_candidate, ".", '_'); + } + std::string candidate = base_candidate; + if (i > 0) { + candidate += std::to_string(i); + } + + if (reserved_aliases.find(candidate) == reserved_aliases.end()) { + reserved_aliases.insert(candidate); + full_name_to_alias.insert(std::make_pair(full_name, candidate)); + return; + } + } + full_name_to_alias.insert(std::make_pair(full_name, "PICK_NAME_FAILED_INTERNAL_ERROR")); + }; auto register_types = [&](const FileDescriptor* file) -> void { for (int j = 0; j < file->message_type_count(); j++) { auto message_type = file->message_type(j); - ideal_mapping.insert(std::make_pair( - message_type->full_name(), message_type->name())); + pick_name(message_type->full_name(), message_type->name()); } for (int j = 0; j < file->enum_type_count(); j++) { auto enum_type = file->enum_type(j); - ideal_mapping.insert(std::make_pair( - enum_type->full_name(), enum_type->name())); + pick_name(enum_type->full_name(), enum_type->name()); } }; @@ -3941,7 +4020,7 @@ TypeNames TypeNames::Es6TypeNames( register_types(codegen_file); // TODO(reddaly): Replace conflicting identifiers. - return TypeNames(options, codegen_file, ideal_mapping); + return TypeNames(options, codegen_file, full_name_to_alias); } /** @@ -3993,6 +4072,13 @@ std::string TypeNames::JsExpression(const std::string& full_name) const { return std::string("INVALID TYPE NAME ") + full_name; } +std::string TypeNames::JsName(const std::string& full_name) { + // TODO(reddaly): There should probably be some logic to rename messages that + // conflict with reserved names, right? + auto parts = google::protobuf::Split(full_name, ".", false); + return parts[parts.size()-1]; +} + /** * Returns the import alias for all the top-level messages and enums * in the given dependency file. @@ -4003,7 +4089,13 @@ std::vector ImportAliases( std::vector out; for (int j = 0; j < dep_file.message_type_count(); j++) { auto message_type = dep_file.message_type(j); - out.push_back(type_names.JsExpression(*message_type)); + auto alias = type_names.JsExpression(*message_type); + auto exported_name = TypeNames::JsName(message_type->full_name()); + if (alias == exported_name) { + out.push_back(alias); + } else { + out.push_back(exported_name + " as " + alias); + } } for (int j = 0; j < dep_file.enum_type_count(); j++) { @@ -4030,7 +4122,7 @@ void Generator::GenerateFile(const GeneratorOptions& options, std::string aliases_comma_delimited = google::protobuf::JoinStrings( ImportAliases(type_names, *file->dependency(i)), - ","); + ", "); const std::string& name = file->dependency(i)->name(); printer->Print( "import {$aliases$} from \"$file$\"; // proto import: \"$proto_filename$\"\n", diff --git a/generator/js_generator.h b/generator/js_generator.h index 145ca9c..9e19ead 100644 --- a/generator/js_generator.h +++ b/generator/js_generator.h @@ -168,6 +168,14 @@ class TypeNames { */ static TypeNames NonEs6TypeNames(const GeneratorOptions& options); + /** + * Returns the JavaScript expression that is exported by the ES6 module + * that defines the type with the given full name as obtained from the + * type descriptor. If the symbol is not directly exported by the + * ES6 module, the empty string should be returned. + */ + static std::string JsName(const std::string& full_name); + /** * Returns the JavaScript expression for referring to the passed message type. */ @@ -191,7 +199,8 @@ class TypeNames { const std::map& map) : options(options_), codegen_file(codegen_file_), - map_(map) {} + map_(map), + exported_names_(ExportedNamesOfDeps(codegen_file_)) {} GeneratorOptions options; @@ -206,6 +215,11 @@ class TypeNames { // type within the generated code. std::map map_; + // For each top-level messages or enum in each dependency file, there should + // be an entry in this map from full name to the exported name of the + // corresponding class. + std::map exported_names_; + // True for non-ES6 mode. Use dot-delimited identifiers to refer // to protos. e.g., "proto.foo.bar.Baz.Bim" for nested message Bim // within message Baz within package "foo.bar". @@ -217,6 +231,14 @@ class TypeNames { * descriptor). */ std::string JsExpression(const std::string& full_name) const; + + /** + * For each top-level messages or enum in each dependency file, there should + * be an entry in the returned map from full name to the exported name of the + * corresponding class definition. + */ + static std::map ExportedNamesOfDeps( + const FileDescriptor* codegen_file); }; // CodeGenerator implementation which generates a JavaScript source file and From 5802a07e30a82f33a7a1e3017e6a8e2ea9b0c0cd Mon Sep 17 00:00:00 2001 From: Red Daly Date: Sun, 5 Feb 2023 11:59:28 -0800 Subject: [PATCH 14/30] Delete unused ExportedNamesOfDeps. --- generator/js_generator.cc | 24 ------------------------ generator/js_generator.h | 16 +--------------- 2 files changed, 1 insertion(+), 39 deletions(-) diff --git a/generator/js_generator.cc b/generator/js_generator.cc index 1db0447..a12952d 100644 --- a/generator/js_generator.cc +++ b/generator/js_generator.cc @@ -3912,30 +3912,6 @@ TypeNames TypeNames::NonEs6TypeNames(const GeneratorOptions& options) { return TypeNames(options, nullptr, std::map()); } -std::map TypeNames::ExportedNamesOfDeps( - const FileDescriptor* codegen_file) { - std::map full_name_to_exported_name; - - for (int i = 0; i < codegen_file->dependency_count(); i++) { - auto dep_file = codegen_file->dependency(i); - - for (int j = 0; j < dep_file->message_type_count(); j++) { - auto message_type = dep_file->message_type(j); - full_name_to_exported_name.insert(std::make_pair( - message_type->full_name(), - TypeNames::JsName(message_type->full_name()))); - } - - for (int j = 0; j < dep_file->enum_type_count(); j++) { - auto enum_type = dep_file->enum_type(j); - full_name_to_exported_name.insert(std::make_pair( - enum_type->full_name(), - TypeNames::JsName(enum_type->full_name()))); - } - } - return full_name_to_exported_name; -} - void ReservedForLocalIdentifiers(const Descriptor* desc, std::set& reserved_identifiers); void ReservedForLocalIdentifiers(const EnumDescriptor* desc, std::set& reserved_identifiers); diff --git a/generator/js_generator.h b/generator/js_generator.h index 9e19ead..a16346d 100644 --- a/generator/js_generator.h +++ b/generator/js_generator.h @@ -199,8 +199,7 @@ class TypeNames { const std::map& map) : options(options_), codegen_file(codegen_file_), - map_(map), - exported_names_(ExportedNamesOfDeps(codegen_file_)) {} + map_(map) {} GeneratorOptions options; @@ -215,11 +214,6 @@ class TypeNames { // type within the generated code. std::map map_; - // For each top-level messages or enum in each dependency file, there should - // be an entry in this map from full name to the exported name of the - // corresponding class. - std::map exported_names_; - // True for non-ES6 mode. Use dot-delimited identifiers to refer // to protos. e.g., "proto.foo.bar.Baz.Bim" for nested message Bim // within message Baz within package "foo.bar". @@ -231,14 +225,6 @@ class TypeNames { * descriptor). */ std::string JsExpression(const std::string& full_name) const; - - /** - * For each top-level messages or enum in each dependency file, there should - * be an entry in the returned map from full name to the exported name of the - * corresponding class definition. - */ - static std::map ExportedNamesOfDeps( - const FileDescriptor* codegen_file); }; // CodeGenerator implementation which generates a JavaScript source file and From d31e215fd4e4e2c7ced8eae1bd31e71f0eee7b9c Mon Sep 17 00:00:00 2001 From: Red Daly Date: Wed, 8 Mar 2023 19:09:36 -0800 Subject: [PATCH 15/30] Fix generation of top-level enums. --- generator/js_generator.cc | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/generator/js_generator.cc b/generator/js_generator.cc index a12952d..4a7f8e9 100644 --- a/generator/js_generator.cc +++ b/generator/js_generator.cc @@ -3615,8 +3615,14 @@ void Generator::GenerateClassSerializeBinaryField( void Generator::GenerateEnum(const GeneratorOptions& options, io::Printer* printer, const EnumDescriptor* enumdesc) const { + + const bool is_toplevel = enumdesc->containing_type() == nullptr; + const std::string enumNamePrefix = is_toplevel ? "export const " : ""; + + // TODO(reddaly): If the enum is defined at top-level, we need + // 'const = ' instead of ' = ' const std::string enumNameForDefinition = options.WantEs6() ? ( - enumdesc->name() + enumNamePrefix + enumdesc->name() ) : ( GetEnumPathPrefix(options, enumdesc) + enumdesc->name() ); From 36960c27a88db57c2f671eddd277384da406afa1 Mon Sep 17 00:00:00 2001 From: Red Daly Date: Wed, 8 Mar 2023 20:15:31 -0800 Subject: [PATCH 16/30] Fix serialization and deserialization generated code. --- generator/js_generator.cc | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/generator/js_generator.cc b/generator/js_generator.cc index 4a7f8e9..ffa5929 100644 --- a/generator/js_generator.cc +++ b/generator/js_generator.cc @@ -3266,7 +3266,7 @@ void Generator::GenerateClassDeserializeBinary(const GeneratorOptions& options, // TODO(cfallin): Handle lazy decoding when requested by field option and/or // by default for 'bytes' fields and packed repeated fields. - const std::string classSymbol = GetMessagePath(options, desc); + const std::string classSymbol = desc->name(); printer->Print( "/**\n" @@ -3275,12 +3275,12 @@ void Generator::GenerateClassDeserializeBinary(const GeneratorOptions& options, " * @return {!$class$}\n" " */\n", "class", classSymbol); - GenerateMethodStart(options, printer, classSymbol.c_str(), "deserializeBinary"); printer->Print( - "(bytes) {\n" + "$methodstart$(bytes) {\n" " var reader = new jspb.BinaryReader(bytes);\n" " var msg = new $class$;\n" " return $class$.deserializeBinaryFromReader(msg, reader);\n", + "methodstart", this->MethodStartStatic(options, classSymbol.c_str(), "deserializeBinary"), "class", classSymbol); GenerateMethodEnd(options, printer); @@ -3297,15 +3297,15 @@ printer->Print( " * @return {!$class$}\n" " */\n", "class", classSymbol); - GenerateMethodStart(options, printer, classSymbol.c_str(), "deserializeBinaryFromReader"); printer->Print( - "(msg, reader) {\n" + "$methodstart$(msg, reader) {\n" " while (reader.nextField()) {\n" " if (reader.isEndGroup()) {\n" " break;\n" " }\n" " var field = reader.getFieldNumber();\n" - " switch (field) {\n"); + " switch (field) {\n", + "methodstart", MethodStartStatic(options, classSymbol.c_str(), "deserializeBinaryFromReader")); for (int i = 0; i < desc->field_count(); i++) { if (!IgnoreField(desc->field(i))) { @@ -3442,19 +3442,19 @@ void Generator::GenerateClassSerializeBinary(const GeneratorOptions& options, io::Printer* printer, const Descriptor* desc) const { - const std::string classSymbol = GetMessagePath(options, desc); + const std::string classSymbol = desc->name(); printer->Print( "/**\n" " * Serializes the message to binary data (in protobuf wire format).\n" " * @return {!Uint8Array}\n" " */\n"); - GenerateMethodStart(options, printer, classSymbol.c_str(), "serializeBinary"); printer->Print( - "() {\n" + "$methodstart$() {\n" " var writer = new jspb.BinaryWriter();\n" - " $class$.serializeBinaryToWriter(this, writer);\n" + " this.constructor.serializeBinaryToWriter(this, writer);\n" " return writer.getResultBuffer();\n", + "methodstart", MethodStart(options, classSymbol.c_str(), "serializeBinary"), "class", classSymbol); GenerateMethodEnd(options, printer); @@ -3468,12 +3468,11 @@ void Generator::GenerateClassSerializeBinary(const GeneratorOptions& options, " * @param {!$class$} message\n" " * @param {!jspb.BinaryWriter} writer\n" " * @suppress {unusedLocalVariables} f is only used for nested messages\n" - " */\n", + " */\n" + "$methodstart$(message, writer) {\n" + " var f = undefined;\n", + "methodstart", MethodStartStatic(options, classSymbol.c_str(), "serializeBinaryToWriter"), "class", classSymbol); - GenerateMethodStart(options, printer, classSymbol.c_str(), "serializeBinaryToWriter"); - printer->Print( - "(message, writer) {\n" - " var f = undefined;\n"); for (int i = 0; i < desc->field_count(); i++) { if (!IgnoreField(desc->field(i))) { From ea631566403b822c31f56d21985138d650cf1538 Mon Sep 17 00:00:00 2001 From: egorm Date: Thu, 9 Mar 2023 20:40:35 -0800 Subject: [PATCH 17/30] Fix repeated field wrapper class name generation --- generator/js_generator.cc | 12 ++++++------ generator/js_generator.h | 1 + 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/generator/js_generator.cc b/generator/js_generator.cc index ffa5929..0ac547f 100644 --- a/generator/js_generator.cc +++ b/generator/js_generator.cc @@ -1150,7 +1150,7 @@ static const char* kRepeatedFieldArrayName = "repeatedFields_"; std::string RepeatedFieldsArrayName(const GeneratorOptions& options, const Descriptor* desc) { return HasRepeatedFields(options, desc) - ? (GetMessagePath(options, desc) + "." + kRepeatedFieldArrayName) + ? (desc->name() + "." + kRepeatedFieldArrayName) : "null"; } @@ -2873,7 +2873,7 @@ const char * methodEndBrace = options.WantEs6() ? "}" : "};"; "\n"); if (field->is_repeated()) { - GenerateRepeatedMessageHelperMethods(options, printer, field); + GenerateRepeatedMessageHelperMethods(options, type_names, printer, field); } } else { @@ -3177,7 +3177,7 @@ void Generator::GenerateRepeatedPrimitiveHelperMethods( } void Generator::GenerateRepeatedMessageHelperMethods( - const GeneratorOptions& options, io::Printer* printer, + const GeneratorOptions& options, const TypeNames& type_names, io::Printer* printer, const FieldDescriptor* field) const { const std::string classSymbol = GetMessagePath(options, field->containing_type()); @@ -3202,13 +3202,13 @@ void Generator::GenerateRepeatedMessageHelperMethods( printer->Annotate("addername", field); printer->Print( - "this, $index$$oneofgroup$, opt_value, $ctor$, opt_index);\n" + "this, $index$$oneofgroup$, opt_value, $class$, opt_index);\n" "};\n" "\n" "\n", "index", JSFieldIndex(field), "oneofgroup", - (InRealOneof(field) ? (", " + JSOneofArray(options, field)) : ""), "ctor", - GetMessagePath(options, field->message_type())); + (InRealOneof(field) ? (", " + JSOneofArray(options, field)) : ""), "class", + type_names.SubmessageTypeRef(field)); } void Generator::GenerateClassExtensionFieldInfo(const GeneratorOptions& options, diff --git a/generator/js_generator.h b/generator/js_generator.h index a16346d..40198f2 100644 --- a/generator/js_generator.h +++ b/generator/js_generator.h @@ -439,6 +439,7 @@ class PROTOC_EXPORT Generator : public CodeGenerator { // Generate addFoo() method for repeated message fields. void GenerateRepeatedMessageHelperMethods(const GeneratorOptions& options, + const TypeNames& type_names, io::Printer* printer, const FieldDescriptor* field) const; From 2a80405ec88f9c64ff2d9476de67efce0cb799cc Mon Sep 17 00:00:00 2001 From: Red Daly Date: Thu, 9 Mar 2023 20:54:25 -0800 Subject: [PATCH 18/30] Fix repeated field wrapper class name generation - Red. --- generator/js_generator.cc | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/generator/js_generator.cc b/generator/js_generator.cc index 0ac547f..ea9db2e 100644 --- a/generator/js_generator.cc +++ b/generator/js_generator.cc @@ -1168,7 +1168,7 @@ static const char* kOneofGroupArrayName = "oneofGroups_"; std::string OneofFieldsArrayName(const GeneratorOptions& options, const Descriptor* desc) { return HasOneofFields(desc) - ? (GetMessagePath(options, desc) + "." + kOneofGroupArrayName) + ? (desc->name() + "." + kOneofGroupArrayName) : "null"; } @@ -3177,7 +3177,9 @@ void Generator::GenerateRepeatedPrimitiveHelperMethods( } void Generator::GenerateRepeatedMessageHelperMethods( - const GeneratorOptions& options, const TypeNames& type_names, io::Printer* printer, + const GeneratorOptions& options, + const TypeNames& type_names, + io::Printer* printer, const FieldDescriptor* field) const { const std::string classSymbol = GetMessagePath(options, field->containing_type()); From e5011de339a71a7d2cf29a899b90a1081f8edf8d Mon Sep 17 00:00:00 2001 From: Red Daly Date: Thu, 9 Mar 2023 21:28:27 -0800 Subject: [PATCH 19/30] Fix generated code for repeated field 'addFoo' helper methods. --- generator/js_generator.cc | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/generator/js_generator.cc b/generator/js_generator.cc index ea9db2e..711832c 100644 --- a/generator/js_generator.cc +++ b/generator/js_generator.cc @@ -3134,14 +3134,16 @@ void Generator::GenerateRepeatedPrimitiveHelperMethods( const FieldDescriptor* field, bool untyped) const { const std::string classSymbol = GetMessagePath(options, field->containing_type()); - const std::string adderName = JSGetterName(options, field, BYTES_DEFAULT, - /* drop_list = */ true); + const std::string adderName = std::string("add") + + JSGetterName(options, field, BYTES_DEFAULT, /* drop_list = */ true); const std::string adderMethodStart = MethodStart( options, classSymbol.c_str(), adderName.c_str()); // clang-format off printer->Print( "/**\n" + " * Adds a value to the repeated field $field_name$ \n" + " *\n" " * @param {$optionaltype$} value\n" " * @param {number=} opt_index\n" " * @return {!$class$} returns this\n" @@ -3150,9 +3152,8 @@ void Generator::GenerateRepeatedPrimitiveHelperMethods( " return jspb.Message.addToRepeatedField(this, " "$index$", "methodstart", adderMethodStart, - "class", classSymbol, "addername", - "add" + JSGetterName(options, field, BYTES_DEFAULT, - /* drop_list = */ true), + "class", classSymbol, + "addername", adderName, "optionaltype", JSFieldTypeAnnotation( options, field, @@ -3183,23 +3184,26 @@ void Generator::GenerateRepeatedMessageHelperMethods( const FieldDescriptor* field) const { const std::string classSymbol = GetMessagePath(options, field->containing_type()); - const std::string adderName = JSGetterName(options, field, BYTES_DEFAULT, /* drop_list = */ true); + const std::string adderName = std::string("add") + + JSGetterName(options, field, BYTES_DEFAULT, /* drop_list = */ true); const std::string adderMethodStart = MethodStart( options, classSymbol.c_str(), adderName.c_str()); printer->Print( "/**\n" + " * Adds a value to the repeated field $field_name$ \n" + " *\n" " * @param {!$optionaltype$=} opt_value\n" " * @param {number=} opt_index\n" " * @return {!$optionaltype$}\n" " */\n" "$methodstart$(opt_value, opt_index) {\n" " return jspb.Message.addTo$repeatedtag$WrapperField(", - "optionaltype", JSTypeName(options, field, BYTES_DEFAULT), "class", classSymbol, + "optionaltype", JSTypeName(options, field, BYTES_DEFAULT), + "field_name", field->name(), + "class", classSymbol, "methodstart", adderMethodStart, - "addername", - "add" + JSGetterName(options, field, BYTES_DEFAULT, - /* drop_list = */ true), + "addername", adderName, "repeatedtag", (field->is_repeated() ? "Repeated" : "")); printer->Annotate("addername", field); @@ -3209,8 +3213,8 @@ void Generator::GenerateRepeatedMessageHelperMethods( "\n" "\n", "index", JSFieldIndex(field), "oneofgroup", - (InRealOneof(field) ? (", " + JSOneofArray(options, field)) : ""), "class", - type_names.SubmessageTypeRef(field)); + (InRealOneof(field) ? (", " + JSOneofArray(options, field)) : ""), + "class", type_names.SubmessageTypeRef(field)); } void Generator::GenerateClassExtensionFieldInfo(const GeneratorOptions& options, From dfa5a70a44926f183bdf4fd001dcca869a4f4ad6 Mon Sep 17 00:00:00 2001 From: Red Daly Date: Thu, 9 Mar 2023 22:25:05 -0800 Subject: [PATCH 20/30] Fix TypeNames so that fields with types defined in same file work. --- generator/js_generator.cc | 37 +++++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/generator/js_generator.cc b/generator/js_generator.cc index 711832c..cba9aaf 100644 --- a/generator/js_generator.cc +++ b/generator/js_generator.cc @@ -3956,6 +3956,22 @@ void ReservedForLocalIdentifiers(const FileDescriptor* desc, std::set& full_name_to_alias) { + for (int j = 0; j < desc->message_type_count(); j++) { + ReservedForLocalIdentifiers(desc->message_type(j), reserved_identifiers); + } + for (int j = 0; j < desc->enum_type_count(); j++) { + ReservedForLocalIdentifiers(desc->enum_type(j), reserved_identifiers); + } +} + */ + TypeNames TypeNames::Es6TypeNames( const GeneratorOptions& options, const FileDescriptor* codegen_file) { @@ -3965,6 +3981,7 @@ TypeNames TypeNames::Es6TypeNames( // Local aliases that are already reserved std::set reserved_aliases; ReservedForLocalIdentifiers(codegen_file, reserved_aliases); + //RegisterTypesDefinedInGeneratedFile(codegen_file, full_name_to_alias); auto pick_name = [&](const std::string full_name, const std::string ideal_name) -> void { std::string base_candidate = ideal_name; @@ -3990,21 +4007,37 @@ TypeNames TypeNames::Es6TypeNames( auto register_types = [&](const FileDescriptor* file) -> void { for (int j = 0; j < file->message_type_count(); j++) { auto message_type = file->message_type(j); - pick_name(message_type->full_name(), message_type->name()); + if (file == codegen_file) { + // Ensure that messages and enums declared at top level in the .proto + // file corresponding to the currently generaed code get identifiers + // equal to their message identifiers. + // reserved_aliases was already updated. + full_name_to_alias.insert(std::make_pair(message_type->full_name(), message_type->name())); + } else { + pick_name(message_type->full_name(), message_type->name()); + } } for (int j = 0; j < file->enum_type_count(); j++) { auto enum_type = file->enum_type(j); + if (file == codegen_file) { + // Ensure that messages and enums declared at top level in the .proto + // file corresponding to the currently generaed code get identifiers + // equal to their message identifiers. + // reserved_aliases was already updated. + full_name_to_alias.insert(std::make_pair(enum_type->full_name(), enum_type->name())); + } else { pick_name(enum_type->full_name(), enum_type->name()); + } } }; + register_types(codegen_file); // Loop through all dependencies and add their types. for (int i = 0; i < codegen_file->dependency_count(); i++) { auto dep_file = codegen_file->dependency(i); register_types(dep_file); } - register_types(codegen_file); // TODO(reddaly): Replace conflicting identifiers. return TypeNames(options, codegen_file, full_name_to_alias); From 03e6f438613449f4cb07583d6d227d349142da46 Mon Sep 17 00:00:00 2001 From: egorm Date: Fri, 10 Mar 2023 15:12:28 -0800 Subject: [PATCH 21/30] Add 'static' keyword for enums defined inside the class --- generator/js_generator.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/generator/js_generator.cc b/generator/js_generator.cc index cba9aaf..63ecc81 100644 --- a/generator/js_generator.cc +++ b/generator/js_generator.cc @@ -3622,7 +3622,7 @@ void Generator::GenerateEnum(const GeneratorOptions& options, const EnumDescriptor* enumdesc) const { const bool is_toplevel = enumdesc->containing_type() == nullptr; - const std::string enumNamePrefix = is_toplevel ? "export const " : ""; + const std::string enumNamePrefix = is_toplevel ? "export const " : "static "; // TODO(reddaly): If the enum is defined at top-level, we need // 'const = ' instead of ' = ' From 71937034150406241e66d260c7c9532db391bfba Mon Sep 17 00:00:00 2001 From: egorm Date: Fri, 10 Mar 2023 15:34:11 -0800 Subject: [PATCH 22/30] Add 'static' keyword for enums generatef from oneof fields --- generator/js_generator.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/generator/js_generator.cc b/generator/js_generator.cc index 63ecc81..41f4c48 100644 --- a/generator/js_generator.cc +++ b/generator/js_generator.cc @@ -2259,7 +2259,7 @@ void Generator::GenerateOneofCaseDefinition( const std::string className = GetMessagePath(options, oneof->containing_type()); const std::string oneofCaseName = options.WantEs6() ? ( - JSOneofName(oneof) + "Case" + "static " + JSOneofName(oneof) + "Case" ) : ( className + "." + JSOneofName(oneof) + "Case" ); From d3ce92c081bd585dc6079609a868744b5e3033fe Mon Sep 17 00:00:00 2001 From: Red Daly Date: Mon, 13 Mar 2023 13:53:09 -0700 Subject: [PATCH 23/30] Fix treatment of nested messages by TypeNames::JsExpression. Before this, the parent type would be returned. Now, . is returned. --- generator/js_generator.cc | 39 +++++++++++++++++++-------------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/generator/js_generator.cc b/generator/js_generator.cc index 41f4c48..3e790c7 100644 --- a/generator/js_generator.cc +++ b/generator/js_generator.cc @@ -1854,7 +1854,7 @@ void Generator::GenerateRequiresImpl(const GeneratorOptions& options, std::set* provided, bool require_jspb, bool require_extension, bool require_map) const { - + if (options.WantEs6()) { // In ES6 mode, imports are handled by GenerateFile and // goog.* isn't used. @@ -2044,7 +2044,7 @@ void Generator::GenerateClassEs6(const GeneratorOptions& options, return; } - + std::string prefix = (desc->containing_type() == nullptr) ? "export " : ("static " + desc->name() + " = "); @@ -2068,11 +2068,11 @@ void Generator::GenerateClassEs6(const GeneratorOptions& options, "$prefix$class $classname$ extends jspb.Message {\n", "prefix", prefix, "classname", desc->name()); - + printer->Indent(); GenerateClassConstructor(options, printer, desc); - + GenerateClassFieldInfo(options, printer, desc); GenerateClassToObject(options, type_names, printer, desc); @@ -2255,7 +2255,7 @@ void Generator::GenerateClassXid(const GeneratorOptions& options, void Generator::GenerateOneofCaseDefinition( const GeneratorOptions& options, io::Printer* printer, const OneofDescriptor* oneof) const { - + const std::string className = GetMessagePath(options, oneof->containing_type()); const std::string oneofCaseName = options.WantEs6() ? ( @@ -2294,7 +2294,7 @@ void Generator::GenerateOneofCaseDefinition( " */\n", "class", className, "oneof", JSOneofName(oneof)); - + GenerateMethodStart(options, printer, className.c_str(), (std::string("get") + JSOneofName(oneof) + "Case").c_str()); @@ -2319,7 +2319,7 @@ void Generator::GenerateClassToObject(const GeneratorOptions& options, const char * if_guard_start = options.WantEs6() ? "" : "if (jspb.Message.GENERATE_TO_OBJECT) {\n"; const char * if_guard_end = options.WantEs6() ? "" : "}\n"; const std::string classSymbol = options.WantEs6() ? desc->name() : GetMessagePath(options, desc); - + printer->Print( "\n" "\n" @@ -2742,7 +2742,7 @@ const char * methodEndBrace = options.WantEs6() ? "}" : "};"; "fielddef", FieldDefinition(options, field), "keytype", key_type, "valuetype", value_type); - + // Function start if (options.import_style == GeneratorOptions::kImportEs6) { printer->Print( @@ -2981,12 +2981,12 @@ const char * methodEndBrace = options.WantEs6() ? "}" : "};"; "value);\n", "typetag", JSTypeTag(field), "index", JSFieldIndex(field)); - + GenerateMethodEnd(options, printer); printer->Print( "\n" "\n"); - + // DO NOT SUBMIT: Can the Annotate call be safely removed? // printer->Annotate("settername", field); @@ -3132,9 +3132,9 @@ const char * methodEndBrace = options.WantEs6() ? "}" : "};"; void Generator::GenerateRepeatedPrimitiveHelperMethods( const GeneratorOptions& options, io::Printer* printer, const FieldDescriptor* field, bool untyped) const { - + const std::string classSymbol = GetMessagePath(options, field->containing_type()); - const std::string adderName = std::string("add") + + const std::string adderName = std::string("add") + JSGetterName(options, field, BYTES_DEFAULT, /* drop_list = */ true); const std::string adderMethodStart = MethodStart( options, classSymbol.c_str(), adderName.c_str()); @@ -3184,7 +3184,7 @@ void Generator::GenerateRepeatedMessageHelperMethods( const FieldDescriptor* field) const { const std::string classSymbol = GetMessagePath(options, field->containing_type()); - const std::string adderName = std::string("add") + + const std::string adderName = std::string("add") + JSGetterName(options, field, BYTES_DEFAULT, /* drop_list = */ true); const std::string adderMethodStart = MethodStart( options, classSymbol.c_str(), adderName.c_str()); @@ -3975,7 +3975,7 @@ void RegisterTypesDefinedInGeneratedFile( TypeNames TypeNames::Es6TypeNames( const GeneratorOptions& options, const FileDescriptor* codegen_file) { - + // Full proto name -> local alias in JS codegen file. std::map full_name_to_alias; // Local aliases that are already reserved @@ -4017,7 +4017,7 @@ TypeNames TypeNames::Es6TypeNames( pick_name(message_type->full_name(), message_type->name()); } } - + for (int j = 0; j < file->enum_type_count(); j++) { auto enum_type = file->enum_type(j); if (file == codegen_file) { @@ -4069,7 +4069,7 @@ std::string TypeNames::SubmessageTypeRef(const FieldDescriptor* field) const { GOOGLE_CHECK(this->codegen_file == nullptr || this->codegen_file == field->file()); GOOGLE_CHECK_NOTNULL(field->message_type()); - return JsExpression(*field->message_type()); + return JsExpression(*field->message_type()) + "/* message_type = " + field->message_type()->DebugString() +" */"; } std::string TypeNames::JsExpression(const std::string& full_name) const { @@ -4079,15 +4079,14 @@ std::string TypeNames::JsExpression(const std::string& full_name) const { if (iter != this->map_.end()) { return iter->second; } - // See if the parent full_name is available. + // See if the parent full_name is available. If it is, use it as the prefix. auto parts = google::protobuf::Split(full_name, ".", false); if (parts.size() > 1) { - // uh oh... not sure how this happend. std::vector parent_parts = {parts.begin(), parts.end() - 1}; auto parent_path = google::protobuf::JoinStrings( parent_parts, "."); - return this->JsExpression(parent_path); + return this->JsExpression(parent_path) + "." + parts[parts.size() - 1]; } return std::string("INVALID TYPE NAME ") + full_name; } @@ -4117,7 +4116,7 @@ std::vector ImportAliases( out.push_back(exported_name + " as " + alias); } } - + for (int j = 0; j < dep_file.enum_type_count(); j++) { auto enum_type = dep_file.enum_type(j); out.push_back(type_names.JsExpression(*enum_type)); From dc42c5ab74c7b216fca3cdffcd4cad36fab4d3de Mon Sep 17 00:00:00 2001 From: Red Daly Date: Mon, 13 Mar 2023 14:15:03 -0700 Subject: [PATCH 24/30] Delete leftover debug comment in generated code. --- generator/js_generator.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/generator/js_generator.cc b/generator/js_generator.cc index 3e790c7..d73b805 100644 --- a/generator/js_generator.cc +++ b/generator/js_generator.cc @@ -4069,7 +4069,7 @@ std::string TypeNames::SubmessageTypeRef(const FieldDescriptor* field) const { GOOGLE_CHECK(this->codegen_file == nullptr || this->codegen_file == field->file()); GOOGLE_CHECK_NOTNULL(field->message_type()); - return JsExpression(*field->message_type()) + "/* message_type = " + field->message_type()->DebugString() +" */"; + return JsExpression(*field->message_type()); } std::string TypeNames::JsExpression(const std::string& full_name) const { From 885b9cdad5acc46d67f3850155d34226e2b4ee8b Mon Sep 17 00:00:00 2001 From: Egor Modin <106995910+egormodin@users.noreply.github.com> Date: Thu, 16 Mar 2023 20:29:43 -0700 Subject: [PATCH 25/30] Fix has and clear methods (#6) --- generator/js_generator.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/generator/js_generator.cc b/generator/js_generator.cc index d73b805..ef1988a 100644 --- a/generator/js_generator.cc +++ b/generator/js_generator.cc @@ -2302,7 +2302,7 @@ void Generator::GenerateOneofCaseDefinition( "() {\n" " return /** @type {$class$.$oneof$Case} */(jspb.Message." "computeOneofCase(this, $class$.oneofGroups_[$oneofindex$]));\n", - "class", className, + "class", oneof->containing_type()->name(), "oneof", JSOneofName(oneof), "oneofindex", JSOneofIndex(oneof)); GenerateMethodEnd(options, printer); @@ -3109,7 +3109,7 @@ const char * methodEndBrace = options.WantEs6() ? "}" : "};"; if (HasFieldPresence(options, field)) { const std::string haserName = "has" + JSGetterName(options, field); const std::string haserMethodStart = MethodStart( - options, classSymbol.c_str(), clearerName.c_str()); + options, classSymbol.c_str(), haserName.c_str()); printer->Print( "/**\n" From 978cfa4421f4a48fd604eb23443efccda1ff471a Mon Sep 17 00:00:00 2001 From: Red Daly Date: Sun, 19 Mar 2023 09:39:00 -0700 Subject: [PATCH 26/30] Add github action to run bazel build //generator/... (#7) --- .github/workflows/ci.bazelrc | 13 ++++++ .github/workflows/ci.yaml | 88 ++++++++++++++++++++++++++++++++++++ 2 files changed, 101 insertions(+) create mode 100644 .github/workflows/ci.bazelrc create mode 100644 .github/workflows/ci.yaml diff --git a/.github/workflows/ci.bazelrc b/.github/workflows/ci.bazelrc new file mode 100644 index 0000000..a21ece4 --- /dev/null +++ b/.github/workflows/ci.bazelrc @@ -0,0 +1,13 @@ +# This file contains Bazel settings to apply on CI only. +# It is referenced with a --bazelrc option in the call to bazel in ci.yaml + +# Debug where options came from +build --announce_rc +# This directory is configured in GitHub actions to be persisted between runs. +build --disk_cache=~/.cache/bazel +build --repository_cache=~/.cache/bazel-repo +# Don't rely on test logs being easily accessible from the test runner, +# though it makes the log noisier. +test --test_output=errors +# Allows tests to run bazelisk-in-bazel, since this is the cache folder used +test --test_env=XDG_CACHE_HOME diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..2cce537 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,88 @@ +name: CI + +# Controls when the action will run. +on: + # Triggers the workflow on push or pull request events but only for the main branch + push: + branches: [main, es6] + pull_request: + branches: [main, es6] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +concurrency: + # Cancel previous actions from the same PR: https://stackoverflow.com/a/72408109 + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + # matrix-prep-* steps generate JSON used to create a dynamic actions matrix. + # Insanely complex for how simple this requirement is inspired from + # https://stackoverflow.com/questions/65384420/how-to-make-a-github-action-matrix-element-conditional + + matrix-prep-bazelversion: + # Prepares the 'bazelversion' axis of the test matrix + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - id: bazel_from_bazelversion + run: echo "bazelversion=$(head -n 1 .bazelversion)" >> $GITHUB_OUTPUT + # bazel 5 testing disabled for now due to + # https://github.com/aspect-build/bazel-lib/issues/392 + # - id: bazel_5 + # run: echo "bazelversion=5.3.2" >> $GITHUB_OUTPUT + outputs: + # Will look like [""] + bazelversions: ${{ toJSON(steps.*.outputs.bazelversion) }} + + bazel-build: + # The type of runner that the job will run on + runs-on: ubuntu-latest + + needs: + - matrix-prep-bazelversion + + # Run bazel test in each workspace with each version of Bazel supported + strategy: + fail-fast: false + matrix: + bazelversion: ${{ fromJSON(needs.matrix-prep-bazelversion.outputs.bazelversions) }} + folder: + - "." + + # Steps represent a sequence of tasks that will be executed as part of the job + steps: + # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it + - uses: actions/checkout@v3 + + # Cache build and external artifacts so that the next ci build is incremental. + # Because github action caches cannot be updated after a build, we need to + # store the contents of each build in a unique cache key, then fall back to loading + # it on the next ci run. We use hashFiles(...) in the key and restore-keys- with + # the prefix to load the most recent cache for the branch on a cache miss. You + # should customize the contents of hashFiles to capture any bazel input sources, + # although this doesn't need to be perfect. If none of the input sources change + # then a cache hit will load an existing cache and bazel won't have to do any work. + # In the case of a cache miss, you want the fallback cache to contain most of the + # previously built artifacts to minimize build time. The more precise you are with + # hashFiles sources the less work bazel will have to do. + - name: Mount bazel caches + uses: actions/cache@v3 + with: + path: | + ~/.cache/bazel + ~/.cache/bazel-repo + key: bazel-cache-${{ hashFiles('**/BUILD.bazel', '**/*.bzl', 'WORKSPACE') }} + restore-keys: bazel-cache- + + - name: Configure Bazel version + working-directory: ${{ matrix.folder }} + run: echo "${{ matrix.bazelversion }}" > .bazelversion + + - name: bazel build //generator/... + env: + # Bazelisk will download bazel to here, ensure it is cached between runs. + XDG_CACHE_HOME: ~/.cache/bazel-repo + working-directory: ${{ matrix.folder }} + run: bazel --bazelrc=$GITHUB_WORKSPACE/.github/workflows/ci.bazelrc --bazelrc=.github/workflows/ci.bazelrc build //generator/... From 7f11c3ec881adcad71c1c53b185fba9b3c55b9d5 Mon Sep 17 00:00:00 2001 From: Dave Masselink Date: Thu, 6 Jul 2023 06:01:56 -0700 Subject: [PATCH 27/30] Support protobuf `extend` keyword when in `kImportEs6` mode (#8) Enable compilation of protos which use the `extend` keyword. A gRPC-web service broke es6 compilation due to importing [`longrunning/operations.proto`](https://github.com/googleapis/googleapis/blob/master/google/longrunning/operations.proto), which uses annotations, which use extensions. Testing of this is being done in `rules_ts_proto` repo: [PR#9](https://github.com/gonzojive/rules_ts_proto/pull/9/files) --- generator/js_generator.cc | 58 +++++++++++++++++---------------------- 1 file changed, 25 insertions(+), 33 deletions(-) diff --git a/generator/js_generator.cc b/generator/js_generator.cc index ef1988a..7fb731e 100644 --- a/generator/js_generator.cc +++ b/generator/js_generator.cc @@ -1240,7 +1240,9 @@ std::string RelativeTypeName(const FieldDescriptor* field) { std::string JSExtensionsObjectName(const GeneratorOptions& options, const FileDescriptor* from_file, const Descriptor* desc) { - if (desc->full_name() == "google.protobuf.bridge.MessageSet") { + if (options.WantEs6()) { + return TypeNames::JsName(desc->name()) + ".extensions"; + } else if (desc->full_name() == "google.protobuf.bridge.MessageSet") { // TODO(haberman): fix this for the kImportCommonJs case. return "jspb.Message.messageSetExtensions"; } else { @@ -2071,7 +2073,7 @@ void Generator::GenerateClassEs6(const GeneratorOptions& options, printer->Indent(); - GenerateClassConstructor(options, printer, desc); + GenerateClassConstructorAndDeclareExtensionFieldInfo(options, printer, desc); GenerateClassFieldInfo(options, printer, desc); @@ -2181,12 +2183,6 @@ void Generator::GenerateClassConstructorAndDeclareExtensionFieldInfo( GenerateClassExtensionFieldInfo(options, printer, desc); } } - for (int i = 0; i < desc->nested_type_count(); i++) { - if (!IgnoreMessage(desc->nested_type(i))) { - GenerateClassConstructorAndDeclareExtensionFieldInfo( - options, printer, desc->nested_type(i)); - } - } } void Generator::GenerateClassFieldInfo(const GeneratorOptions& options, @@ -3238,9 +3234,8 @@ void Generator::GenerateClassExtensionFieldInfo(const GeneratorOptions& options, " *\n" " * @type {!Object}\n" " */\n" - "$class$.extensions = {};\n" - "\n", - "class", GetMessagePath(options, desc)); + "static extensions = {};\n" + "\n"); printer->Print( "\n" @@ -3259,9 +3254,8 @@ void Generator::GenerateClassExtensionFieldInfo(const GeneratorOptions& options, " *\n" " * @type {!Object}\n" " */\n" - "$class$.extensionsBinary = {};\n" - "\n", - "class", GetMessagePath(options, desc)); + "static extensionsBinary = {};\n" + "\n"); } } @@ -3666,22 +3660,23 @@ void Generator::GenerateExtension(const GeneratorOptions& options, const TypeNames& type_names, io::Printer* printer, const FieldDescriptor* field) const { - std::string extension_scope = - (field->extension_scope() - ? GetMessagePath(options, field->extension_scope()) - : GetNamespace(options, field->file())); + std::string extension_scope_name = + (field->containing_type() + ? TypeNames::JsName(field->containing_type()->name()) + : GetNamespace(options, field->file())); + std::string extension_object_name = + JSExtensionsObjectName(options, field->file(), field->containing_type()); + std::string extension_object_field_name = JSObjectFieldName(options, field); - const std::string extension_object_name = JSObjectFieldName(options, field); printer->Print( "\n" "/**\n" " * A tuple of {field number, class constructor} for the extension\n" - " * field named `$nameInComment$`.\n" + " * field named `$name$`.\n" " * @type {!jspb.ExtensionFieldInfo<$extensionType$>}\n" " */\n" "$class$.$name$ = new jspb.ExtensionFieldInfo(\n", - "nameInComment", extension_object_name, "name", extension_object_name, - "class", extension_scope, "extensionType", + "class", extension_scope_name, "name", extension_object_field_name, "extensionType", JSFieldTypeAnnotation(options, field, /* is_setter_argument = */ false, /* force_present = */ true, @@ -3695,7 +3690,7 @@ void Generator::GenerateExtension(const GeneratorOptions& options, "!Object} */ (\n" " $toObject$),\n" " $repeated$);\n", - "index", StrCat(field->number()), "name", extension_object_name, "ctor", + "index", StrCat(field->number()), "name", extension_object_field_name, "ctor", (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE ? type_names.SubmessageTypeRef(field) : std::string("null")), @@ -3713,12 +3708,11 @@ void Generator::GenerateExtension(const GeneratorOptions& options, " $binaryWriterFn$,\n" " $binaryMessageSerializeFn$,\n" " $binaryMessageDeserializeFn$,\n", - "extendName", - JSExtensionsObjectName(options, field->file(), field->containing_type()), - "index", StrCat(field->number()), "class", extension_scope, "name", - extension_object_name, "binaryReaderFn", - JSBinaryReaderMethodName(options, field), "binaryWriterFn", - JSBinaryWriterMethodName(options, field), "binaryMessageSerializeFn", + "extendName", extension_object_name, "index", StrCat(field->number()), + "class", extension_scope_name, "name", extension_object_field_name, + "binaryReaderFn", JSBinaryReaderMethodName(options, field), + "binaryWriterFn", JSBinaryWriterMethodName(options, field), + "binaryMessageSerializeFn", (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) ? (type_names.SubmessageTypeRef(field) + ".serializeBinaryToWriter") : "undefined", @@ -3735,10 +3729,8 @@ void Generator::GenerateExtension(const GeneratorOptions& options, "// toObject() will function correctly.\n" "$extendName$[$index$] = $class$.$name$;\n" "\n", - "extendName", - JSExtensionsObjectName(options, field->file(), field->containing_type()), - "index", StrCat(field->number()), "class", extension_scope, "name", - extension_object_name); + "extendName", extension_object_name, "index", StrCat(field->number()), + "class", extension_scope_name, "name", extension_object_field_name); } bool GeneratorOptions::ParseFromOptions( From 54910de079892fb09da8ffe7acb2592767787163 Mon Sep 17 00:00:00 2001 From: Red Daly Date: Sat, 3 Aug 2024 10:54:01 -0700 Subject: [PATCH 28/30] bzlmod support (#11) --- .bazelrc | 3 +++ MODULE.bazel | 4 ++++ WORKSPACE.bzlmod | 2 ++ 3 files changed, 9 insertions(+) create mode 100644 .bazelrc create mode 100644 MODULE.bazel create mode 100644 WORKSPACE.bzlmod diff --git a/.bazelrc b/.bazelrc new file mode 100644 index 0000000..c8c6018 --- /dev/null +++ b/.bazelrc @@ -0,0 +1,3 @@ +# TODO: migrate all dependencies from WORKSPACE to MODULE.bazel +# https://github.com/protocolbuffers/protobuf/issues/14313 +common --noenable_bzlmod diff --git a/MODULE.bazel b/MODULE.bazel new file mode 100644 index 0000000..5ab7967 --- /dev/null +++ b/MODULE.bazel @@ -0,0 +1,4 @@ +module(name = "protobuf_javascript_gonzojive", version = "3.21.5") + +bazel_dep(name = "protobuf", version = "27.1", repo_name = "com_google_protobuf") +bazel_dep(name = "rules_pkg", version = "0.7.0") diff --git a/WORKSPACE.bzlmod b/WORKSPACE.bzlmod new file mode 100644 index 0000000..b31eece --- /dev/null +++ b/WORKSPACE.bzlmod @@ -0,0 +1,2 @@ +# When Bzlmod is enabled, this file replaces the content of the original WORKSPACE and +# makes sure no WORKSPACE prefix or suffix are added when Bzlmod is enabled. \ No newline at end of file From 9e2eee17de78176243a2075675f152c0555c3104 Mon Sep 17 00:00:00 2001 From: Red Daly Date: Sat, 3 Aug 2024 12:58:40 -0700 Subject: [PATCH 29/30] Enable bzlmod by default (#12) Fixes compilation using bzlmod and makes bzlmod the default. --- .bazelrc | 4 +- BUILD.bazel | 4 +- MODULE.bazel | 21 +++++++- MODULE.bazel.lock | 110 ++++++++++++++++++++++++++++++++++++++ generator/BUILD.bazel | 2 +- generator/js_generator.cc | 12 ++--- 6 files changed, 139 insertions(+), 14 deletions(-) create mode 100644 MODULE.bazel.lock diff --git a/.bazelrc b/.bazelrc index c8c6018..3ce91d2 100644 --- a/.bazelrc +++ b/.bazelrc @@ -1,3 +1 @@ -# TODO: migrate all dependencies from WORKSPACE to MODULE.bazel -# https://github.com/protocolbuffers/protobuf/issues/14313 -common --noenable_bzlmod +common --enable_bzlmod diff --git a/BUILD.bazel b/BUILD.bazel index 5483303..9837c9b 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -21,7 +21,7 @@ pkg_files( name = "dist_files", srcs = glob([ "google/protobuf/*.js", - "google/protobuf/compiler/*.js" + "google/protobuf/compiler/*.js", ]) + [ "google-protobuf.js", "package.json", @@ -58,5 +58,5 @@ filegroup( srcs = [ ":dist_tar", ":dist_zip", - ] + ], ) diff --git a/MODULE.bazel b/MODULE.bazel index 5ab7967..2e352ca 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -1,4 +1,21 @@ -module(name = "protobuf_javascript_gonzojive", version = "3.21.5") +module( + name = "protobuf_javascript_gonzojive", + version = "3.21.5", +) -bazel_dep(name = "protobuf", version = "27.1", repo_name = "com_google_protobuf") +bazel_dep(name = "protobuf", version = "21.7", repo_name = "com_google_protobuf") +bazel_dep(name = "abseil-cpp", version = "20211102.0") bazel_dep(name = "rules_pkg", version = "0.7.0") + +# For VS Code autocompletion: +# https://github.com/hedronvision/bazel-compile-commands-extractor#usage +# +# Not currently working due to https://github.com/hedronvision/bazel-compile-commands-extractor/issues/199 +bazel_dep(name = "hedron_compile_commands", dev_dependency = True) +git_override( + module_name = "hedron_compile_commands", + commit = "0e990032f3c5a866e72615cf67e5ce22186dcb97", + remote = "https://github.com/hedronvision/bazel-compile-commands-extractor.git", + # Replace the commit hash (above) with the latest (https://github.com/hedronvision/bazel-compile-commands-extractor/commits/main). + # Even better, set up Renovate and let it do the work for you (see "Suggestion: Updates" in the README). +) diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock new file mode 100644 index 0000000..f5cd531 --- /dev/null +++ b/MODULE.bazel.lock @@ -0,0 +1,110 @@ +{ + "lockFileVersion": 11, + "registryFileHashes": { + "https://bcr.bazel.build/bazel_registry.json": "8a28e4aff06ee60aed2a8c281907fb8bcbf3b753c91fb5a5c57da3215d5b3497", + "https://bcr.bazel.build/modules/abseil-cpp/20210324.2/MODULE.bazel": "7cd0312e064fde87c8d1cd79ba06c876bd23630c83466e9500321be55c96ace2", + "https://bcr.bazel.build/modules/abseil-cpp/20211102.0/MODULE.bazel": "70390338f7a5106231d20620712f7cccb659cd0e9d073d1991c038eb9fc57589", + "https://bcr.bazel.build/modules/abseil-cpp/20211102.0/source.json": "7e3a9adf473e9af076ae485ed649d5641ad50ec5c11718103f34de03170d94ad", + "https://bcr.bazel.build/modules/apple_support/1.5.0/MODULE.bazel": "50341a62efbc483e8a2a6aec30994a58749bd7b885e18dd96aa8c33031e558ef", + "https://bcr.bazel.build/modules/apple_support/1.5.0/source.json": "eb98a7627c0bc486b57f598ad8da50f6625d974c8f723e9ea71bd39f709c9862", + "https://bcr.bazel.build/modules/bazel_features/1.11.0/MODULE.bazel": "f9382337dd5a474c3b7d334c2f83e50b6eaedc284253334cf823044a26de03e8", + "https://bcr.bazel.build/modules/bazel_features/1.11.0/source.json": "c9320aa53cd1c441d24bd6b716da087ad7e4ff0d9742a9884587596edfe53015", + "https://bcr.bazel.build/modules/bazel_skylib/1.0.3/MODULE.bazel": "bcb0fd896384802d1ad283b4e4eb4d718eebd8cb820b0a2c3a347fb971afd9d8", + "https://bcr.bazel.build/modules/bazel_skylib/1.2.1/MODULE.bazel": "f35baf9da0efe45fa3da1696ae906eea3d615ad41e2e3def4aeb4e8bc0ef9a7a", + "https://bcr.bazel.build/modules/bazel_skylib/1.3.0/MODULE.bazel": "20228b92868bf5cfc41bda7afc8a8ba2a543201851de39d990ec957b513579c5", + "https://bcr.bazel.build/modules/bazel_skylib/1.6.1/MODULE.bazel": "8fdee2dbaace6c252131c00e1de4b165dc65af02ea278476187765e1a617b917", + "https://bcr.bazel.build/modules/bazel_skylib/1.6.1/source.json": "082ed5f9837901fada8c68c2f3ddc958bb22b6d654f71dd73f3df30d45d4b749", + "https://bcr.bazel.build/modules/buildozer/7.1.2/MODULE.bazel": "2e8dd40ede9c454042645fd8d8d0cd1527966aa5c919de86661e62953cd73d84", + "https://bcr.bazel.build/modules/buildozer/7.1.2/source.json": "c9028a501d2db85793a6996205c8de120944f50a0d570438fcae0457a5f9d1f8", + "https://bcr.bazel.build/modules/googletest/1.11.0/MODULE.bazel": "3a83f095183f66345ca86aa13c58b59f9f94a2f81999c093d4eeaa2d262d12f4", + "https://bcr.bazel.build/modules/googletest/1.11.0/source.json": "c73d9ef4268c91bd0c1cd88f1f9dfa08e814b1dbe89b5f594a9f08ba0244d206", + "https://bcr.bazel.build/modules/platforms/0.0.4/MODULE.bazel": "9b328e31ee156f53f3c416a64f8491f7eb731742655a47c9eec4703a71644aee", + "https://bcr.bazel.build/modules/platforms/0.0.5/MODULE.bazel": "5733b54ea419d5eaf7997054bb55f6a1d0b5ff8aedf0176fef9eea44f3acda37", + "https://bcr.bazel.build/modules/platforms/0.0.6/MODULE.bazel": "ad6eeef431dc52aefd2d77ed20a4b353f8ebf0f4ecdd26a807d2da5aa8cd0615", + "https://bcr.bazel.build/modules/platforms/0.0.7/MODULE.bazel": "72fd4a0ede9ee5c021f6a8dd92b503e089f46c227ba2813ff183b71616034814", + "https://bcr.bazel.build/modules/platforms/0.0.9/MODULE.bazel": "4a87a60c927b56ddd67db50c89acaa62f4ce2a1d2149ccb63ffd871d5ce29ebc", + "https://bcr.bazel.build/modules/platforms/0.0.9/source.json": "cd74d854bf16a9e002fb2ca7b1a421f4403cda29f824a765acd3a8c56f8d43e6", + "https://bcr.bazel.build/modules/protobuf/21.7/MODULE.bazel": "a5a29bb89544f9b97edce05642fac225a808b5b7be74038ea3640fae2f8e66a7", + "https://bcr.bazel.build/modules/protobuf/21.7/source.json": "bbe500720421e582ff2d18b0802464205138c06056f443184de39fbb8187b09b", + "https://bcr.bazel.build/modules/protobuf/3.19.0/MODULE.bazel": "6b5fbb433f760a99a22b18b6850ed5784ef0e9928a72668b66e4d7ccd47db9b0", + "https://bcr.bazel.build/modules/protobuf/3.19.6/MODULE.bazel": "9233edc5e1f2ee276a60de3eaa47ac4132302ef9643238f23128fea53ea12858", + "https://bcr.bazel.build/modules/rules_cc/0.0.1/MODULE.bazel": "cb2aa0747f84c6c3a78dad4e2049c154f08ab9d166b1273835a8174940365647", + "https://bcr.bazel.build/modules/rules_cc/0.0.2/MODULE.bazel": "6915987c90970493ab97393024c156ea8fb9f3bea953b2f3ec05c34f19b5695c", + "https://bcr.bazel.build/modules/rules_cc/0.0.8/MODULE.bazel": "964c85c82cfeb6f3855e6a07054fdb159aced38e99a5eecf7bce9d53990afa3e", + "https://bcr.bazel.build/modules/rules_cc/0.0.9/MODULE.bazel": "836e76439f354b89afe6a911a7adf59a6b2518fafb174483ad78a2a2fde7b1c5", + "https://bcr.bazel.build/modules/rules_cc/0.0.9/source.json": "1f1ba6fea244b616de4a554a0f4983c91a9301640c8fe0dd1d410254115c8430", + "https://bcr.bazel.build/modules/rules_java/4.0.0/MODULE.bazel": "5a78a7ae82cd1a33cef56dc578c7d2a46ed0dca12643ee45edbb8417899e6f74", + "https://bcr.bazel.build/modules/rules_java/7.6.1/MODULE.bazel": "2f14b7e8a1aa2f67ae92bc69d1ec0fa8d9f827c4e17ff5e5f02e91caa3b2d0fe", + "https://bcr.bazel.build/modules/rules_java/7.6.1/source.json": "8f3f3076554e1558e8e468b2232991c510ecbcbed9e6f8c06ac31c93bcf38362", + "https://bcr.bazel.build/modules/rules_jvm_external/4.4.2/MODULE.bazel": "a56b85e418c83eb1839819f0b515c431010160383306d13ec21959ac412d2fe7", + "https://bcr.bazel.build/modules/rules_jvm_external/4.4.2/source.json": "a075731e1b46bc8425098512d038d416e966ab19684a10a34f4741295642fc35", + "https://bcr.bazel.build/modules/rules_license/0.0.3/MODULE.bazel": "627e9ab0247f7d1e05736b59dbb1b6871373de5ad31c3011880b4133cafd4bd0", + "https://bcr.bazel.build/modules/rules_license/0.0.7/MODULE.bazel": "088fbeb0b6a419005b89cf93fe62d9517c0a2b8bb56af3244af65ecfe37e7d5d", + "https://bcr.bazel.build/modules/rules_license/0.0.7/source.json": "355cc5737a0f294e560d52b1b7a6492d4fff2caf0bef1a315df5a298fca2d34a", + "https://bcr.bazel.build/modules/rules_pkg/0.7.0/MODULE.bazel": "df99f03fc7934a4737122518bb87e667e62d780b610910f0447665a7e2be62dc", + "https://bcr.bazel.build/modules/rules_pkg/0.7.0/source.json": "c2557066e0c0342223ba592510ad3d812d4963b9024831f7f66fd0584dd8c66c", + "https://bcr.bazel.build/modules/rules_proto/4.0.0/MODULE.bazel": "a7a7b6ce9bee418c1a760b3d84f83a299ad6952f9903c67f19e4edd964894e06", + "https://bcr.bazel.build/modules/rules_proto/5.3.0-21.7/MODULE.bazel": "e8dff86b0971688790ae75528fe1813f71809b5afd57facb44dad9e8eca631b7", + "https://bcr.bazel.build/modules/rules_proto/5.3.0-21.7/source.json": "d57902c052424dfda0e71646cb12668d39c4620ee0544294d9d941e7d12bc3a9", + "https://bcr.bazel.build/modules/rules_python/0.10.2/MODULE.bazel": "cc82bc96f2997baa545ab3ce73f196d040ffb8756fd2d66125a530031cd90e5f", + "https://bcr.bazel.build/modules/rules_python/0.22.1/MODULE.bazel": "26114f0c0b5e93018c0c066d6673f1a2c3737c7e90af95eff30cfee38d0bbac7", + "https://bcr.bazel.build/modules/rules_python/0.22.1/source.json": "57226905e783bae7c37c2dd662be078728e48fa28ee4324a7eabcafb5a43d014", + "https://bcr.bazel.build/modules/rules_python/0.4.0/MODULE.bazel": "9208ee05fd48bf09ac60ed269791cf17fb343db56c8226a720fbb1cdf467166c", + "https://bcr.bazel.build/modules/stardoc/0.5.1/MODULE.bazel": "1a05d92974d0c122f5ccf09291442580317cdd859f07a8655f1db9a60374f9f8", + "https://bcr.bazel.build/modules/stardoc/0.5.1/source.json": "a96f95e02123320aa015b956f29c00cb818fa891ef823d55148e1a362caacf29", + "https://bcr.bazel.build/modules/upb/0.0.0-20220923-a547704/MODULE.bazel": "7298990c00040a0e2f121f6c32544bab27d4452f80d9ce51349b1a28f3005c43", + "https://bcr.bazel.build/modules/upb/0.0.0-20220923-a547704/source.json": "f1ef7d3f9e0e26d4b23d1c39b5f5de71f584dd7d1b4ef83d9bbba6ec7a6a6459", + "https://bcr.bazel.build/modules/zlib/1.2.11/MODULE.bazel": "07b389abc85fdbca459b69e2ec656ae5622873af3f845e1c9d80fe179f3effa0", + "https://bcr.bazel.build/modules/zlib/1.2.12/MODULE.bazel": "3b1a8834ada2a883674be8cbd36ede1b6ec481477ada359cd2d3ddc562340b27", + "https://bcr.bazel.build/modules/zlib/1.3/MODULE.bazel": "6a9c02f19a24dcedb05572b2381446e27c272cd383aed11d41d99da9e3167a72", + "https://bcr.bazel.build/modules/zlib/1.3/source.json": "b6b43d0737af846022636e6e255fd4a96fee0d34f08f3830e6e0bac51465c37c" + }, + "selectedYankedVersions": {}, + "moduleExtensions": { + "@@apple_support~//crosstool:setup.bzl%apple_cc_configure_extension": { + "general": { + "bzlTransitiveDigest": "PjIds3feoYE8SGbbIq2SFTZy3zmxeO2tQevJZNDo7iY=", + "usagesDigest": "aLmqbvowmHkkBPve05yyDNGN7oh7QE9kBADr3QIZTZs=", + "recordedFileInputs": {}, + "recordedDirentsInputs": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "local_config_apple_cc": { + "bzlFile": "@@apple_support~//crosstool:setup.bzl", + "ruleClassName": "_apple_cc_autoconf", + "attributes": {} + }, + "local_config_apple_cc_toolchains": { + "bzlFile": "@@apple_support~//crosstool:setup.bzl", + "ruleClassName": "_apple_cc_autoconf_toolchains", + "attributes": {} + } + }, + "recordedRepoMappingEntries": [ + [ + "apple_support~", + "bazel_tools", + "bazel_tools" + ] + ] + } + }, + "@@platforms//host:extension.bzl%host_platform": { + "general": { + "bzlTransitiveDigest": "xelQcPZH8+tmuOHVjL9vDxMnnQNMlwj0SlvgoqBkm4U=", + "usagesDigest": "meSzxn3DUCcYEhq4HQwExWkWtU4EjriRBQLsZN+Q0SU=", + "recordedFileInputs": {}, + "recordedDirentsInputs": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "host_platform": { + "bzlFile": "@@platforms//host:extension.bzl", + "ruleClassName": "host_platform_repo", + "attributes": {} + } + }, + "recordedRepoMappingEntries": [] + } + } + } +} diff --git a/generator/BUILD.bazel b/generator/BUILD.bazel index b865ca4..cfdd909 100644 --- a/generator/BUILD.bazel +++ b/generator/BUILD.bazel @@ -9,8 +9,8 @@ cc_binary( ], visibility = ["//visibility:public"], deps = [ + "@abseil-cpp//absl/strings:str_format", "@com_google_protobuf//:protobuf", "@com_google_protobuf//:protoc_lib", ], ) - diff --git a/generator/js_generator.cc b/generator/js_generator.cc index 7fb731e..86891fd 100644 --- a/generator/js_generator.cc +++ b/generator/js_generator.cc @@ -37,7 +37,7 @@ #include #include #include -#include +#include "absl/strings/str_format.h" #include #include @@ -679,9 +679,9 @@ bool EscapeJSString(const std::string& in, std::string* out) { if (codepoint >= 0x20 && codepoint <= 0x7e) { *out += static_cast(codepoint); } else if (codepoint >= 0x100) { - *out += StringPrintf("\\u%04x", codepoint); + *out += absl::StrFormat("\\u%04x", codepoint); } else { - *out += StringPrintf("\\x%02x", codepoint); + *out += absl::StrFormat("\\x%02x", codepoint); } break; } @@ -1276,7 +1276,7 @@ std::string FieldDefinition(const GeneratorOptions& options, } else { value_type = ProtoTypeName(options, value_field); } - return StringPrintf("map<%s, %s> %s = %d;", key_type.c_str(), + return absl::StrFormat("map<%s, %s> %s = %d;", key_type.c_str(), value_type.c_str(), field->name().c_str(), field->number()); } else { @@ -1295,7 +1295,7 @@ std::string FieldDefinition(const GeneratorOptions& options, type = ProtoTypeName(options, field); name = field->name(); } - return StringPrintf("%s %s %s = %d;", qualifier.c_str(), type.c_str(), + return absl::StrFormat("%s %s %s = %d;", qualifier.c_str(), type.c_str(), name.c_str(), field->number()); } } @@ -3676,7 +3676,7 @@ void Generator::GenerateExtension(const GeneratorOptions& options, " * @type {!jspb.ExtensionFieldInfo<$extensionType$>}\n" " */\n" "$class$.$name$ = new jspb.ExtensionFieldInfo(\n", - "class", extension_scope_name, "name", extension_object_field_name, "extensionType", + "class", extension_scope_name, "name", extension_object_field_name, "extensionType", JSFieldTypeAnnotation(options, field, /* is_setter_argument = */ false, /* force_present = */ true, From 56d0a9fe55bc43d54d6733653cff16fcff3008c7 Mon Sep 17 00:00:00 2001 From: Red Daly Date: Sun, 4 Aug 2024 21:45:00 -0700 Subject: [PATCH 30/30] Remove WORKSPACE file in favor of bzlmod (#13) --- MODULE.bazel | 2 ++ WORKSPACE | 33 --------------------------------- WORKSPACE.bzlmod | 2 -- 3 files changed, 2 insertions(+), 35 deletions(-) delete mode 100644 WORKSPACE delete mode 100644 WORKSPACE.bzlmod diff --git a/MODULE.bazel b/MODULE.bazel index 2e352ca..7048166 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -10,6 +10,8 @@ bazel_dep(name = "rules_pkg", version = "0.7.0") # For VS Code autocompletion: # https://github.com/hedronvision/bazel-compile-commands-extractor#usage # +# Run bazel run @hedron_compile_commands//:refresh_all to get autocomplete +# working in VS Code and other editors. # Not currently working due to https://github.com/hedronvision/bazel-compile-commands-extractor/issues/199 bazel_dep(name = "hedron_compile_commands", dev_dependency = True) git_override( diff --git a/WORKSPACE b/WORKSPACE deleted file mode 100644 index 944d8eb..0000000 --- a/WORKSPACE +++ /dev/null @@ -1,33 +0,0 @@ -workspace(name = "com_google_protobuf_javascript") - -load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") - -http_archive( - name = "com_google_protobuf", - strip_prefix = "protobuf-21.3", - urls = ["https://github.com/protocolbuffers/protobuf/archive/refs/tags/v21.3.zip"], -) - -load("@com_google_protobuf//:protobuf_deps.bzl", "protobuf_deps") - -protobuf_deps() - -load("@rules_pkg//:deps.bzl", "rules_pkg_dependencies") -rules_pkg_dependencies() - -# Hedron's Compile Commands Extractor for Bazel -# https://github.com/hedronvision/bazel-compile-commands-extractor -# -# Run bazel run @hedron_compile_commands//:refresh_all to get autocomplete -# working in VS Code and other editors. -http_archive( - name = "hedron_compile_commands", - - # Replace the commit hash in both places (below) with the latest, rather than using the stale one here. - # Even better, set up Renovate and let it do the work for you (see "Suggestion: Updates" in the README). - url = "https://github.com/hedronvision/bazel-compile-commands-extractor/archive/ed994039a951b736091776d677f324b3903ef939.tar.gz", - strip_prefix = "bazel-compile-commands-extractor-ed994039a951b736091776d677f324b3903ef939", - # When you first run this tool, it'll recommend a sha256 hash to put here with a message like: "DEBUG: Rule 'hedron_compile_commands' indicated that a canonical reproducible form can be obtained by modifying arguments sha256 = ..." -) -load("@hedron_compile_commands//:workspace_setup.bzl", "hedron_compile_commands_setup") -hedron_compile_commands_setup() diff --git a/WORKSPACE.bzlmod b/WORKSPACE.bzlmod deleted file mode 100644 index b31eece..0000000 --- a/WORKSPACE.bzlmod +++ /dev/null @@ -1,2 +0,0 @@ -# When Bzlmod is enabled, this file replaces the content of the original WORKSPACE and -# makes sure no WORKSPACE prefix or suffix are added when Bzlmod is enabled. \ No newline at end of file