// Protocol Buffers - Google's data interchange format // Copyright 2008 Google Inc. All rights reserved. // https://developers.google.com/protocol-buffers/ // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace google { namespace protobuf { namespace compiler { namespace js { // Sorted list of JavaScript keywords. These cannot be used as names. If they // appear, we prefix them with "pb_". const char* kKeyword[] = { "abstract", "boolean", "break", "byte", "case", "catch", "char", "class", "const", "continue", "debugger", "default", "delete", "do", "double", "else", "enum", "export", "extends", "false", "final", "finally", "float", "for", "function", "goto", "if", "implements", "import", "in", "instanceof", "int", "interface", "long", "native", "new", "null", "package", "private", "protected", "public", "return", "short", "static", "super", "switch", "synchronized", "this", "throw", "throws", "transient", "try", "typeof", "var", "void", "volatile", "while", "with", }; 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]) { return true; } } return false; } std::string GetSnakeFilename(const std::string& filename) { std::string snake_name = filename; ReplaceCharacters(&snake_name, "/", '_'); return snake_name; } // Given a filename like foo/bar/baz.proto, returns the corresponding JavaScript // file foo/bar/baz.js. std::string GetJSFilename(const GeneratorOptions& options, const std::string& filename) { return StripProto(filename) + options.GetFileNameExtension(); } // Given a filename like foo/bar/baz.proto, returns the root directory // path ../../ std::string GetRootPath(const std::string& from_filename, const std::string& to_filename) { if (to_filename.find("google/protobuf") == 0) { // Well-known types (.proto files in the google/protobuf directory) are // assumed to come from the 'google-protobuf' npm package. We may want to // generalize this exception later by letting others put generated code in // their own npm packages. return "google-protobuf/"; } size_t slashes = std::count(from_filename.begin(), from_filename.end(), '/'); if (slashes == 0) { return "./"; } std::string result = ""; for (size_t i = 0; i < slashes; i++) { result += "../"; } return result; } // Returns the alias we assign to the module of the given .proto filename // when importing. std::string ModuleAlias(const std::string& filename) { // This scheme could technically cause problems if a file includes any 2 of: // foo/bar_baz.proto // foo_bar_baz.proto // foo_bar/baz.proto // // We'll worry about this problem if/when we actually see it. This name isn't // exposed to users so we can change it later if we need to. std::string basename = StripProto(filename); ReplaceCharacters(&basename, "-", '$'); ReplaceCharacters(&basename, "/", '_'); ReplaceCharacters(&basename, ".", '_'); return basename + "_pb"; } // Returns the fully normalized JavaScript namespace for the given // file descriptor's package. std::string GetNamespace(const GeneratorOptions& options, const FileDescriptor* file) { if (!options.namespace_prefix.empty()) { return options.namespace_prefix; } else if (!file->package().empty()) { return "proto." + file->package(); } else { return "proto"; } } // Returns the name of the message with a leading dot and taking into account // nesting, for example ".OuterMessage.InnerMessage", or returns empty if // descriptor is null. This function does not handle namespacing, only message // nesting. std::string GetNestedMessageName(const Descriptor* descriptor) { if (descriptor == NULL) { return ""; } std::string result = StripPrefixString(descriptor->full_name(), descriptor->file()->package()); // Add a leading dot if one is not already present. if (!result.empty() && result[0] != '.') { result = "." + result; } return result; } // Returns the path prefix for a message or enumeration that // lives under the given file and containing type. std::string GetPrefix(const GeneratorOptions& options, const FileDescriptor* file_descriptor, const Descriptor* containing_type) { std::string prefix = GetNamespace(options, file_descriptor) + GetNestedMessageName(containing_type); if (!prefix.empty()) { prefix += "."; } return prefix; } // Returns the fully normalized JavaScript path prefix for the given // message descriptor. std::string GetMessagePathPrefix(const GeneratorOptions& options, const Descriptor* descriptor) { return GetPrefix(options, descriptor->file(), descriptor->containing_type()); } // Returns the fully normalized JavaScript path for the given // message descriptor. std::string GetMessagePath(const GeneratorOptions& options, const Descriptor* descriptor) { return GetMessagePathPrefix(options, descriptor) + descriptor->name(); } // Returns the fully normalized JavaScript path prefix for the given // enumeration descriptor. std::string GetEnumPathPrefix(const GeneratorOptions& options, const EnumDescriptor* enum_descriptor) { return GetPrefix(options, enum_descriptor->file(), enum_descriptor->containing_type()); } // Returns the fully normalized JavaScript path for the given // enumeration descriptor. std::string GetEnumPath(const GeneratorOptions& options, const EnumDescriptor* enum_descriptor) { return GetEnumPathPrefix(options, enum_descriptor) + enum_descriptor->name(); } std::string MaybeCrossFileRef(const GeneratorOptions& options, const FileDescriptor* from_file, const Descriptor* to_message) { if ((options.import_style == GeneratorOptions::kImportCommonJs || options.import_style == GeneratorOptions::kImportCommonJsStrict) && from_file != to_message->file()) { // Cross-file ref in CommonJS needs to use the module alias instead of // the global name. return ModuleAlias(to_message->file()->name()) + GetNestedMessageName(to_message->containing_type()) + "." + to_message->name(); } else { // Within a single file we use a full name. return GetMessagePath(options, to_message); } } 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, // and with reserved words triggering a "pb_" prefix. // - Getters/setters: LOWER_UNDERSCORE -> UPPER_CAMEL, except for group fields // (use the name directly), then append "List" if appropriate, then append "$" // if resulting name is equal to a reserved word. // - Enums: just uppercase. // Locale-independent version of ToLower that deals only with ASCII A-Z. char ToLowerASCII(char c) { if (c >= 'A' && c <= 'Z') { return (c - 'A') + 'a'; } else { return c; } } std::vector ParseLowerUnderscore(const std::string& input) { std::vector words; std::string running = ""; for (int i = 0; i < input.size(); i++) { if (input[i] == '_') { if (!running.empty()) { words.push_back(running); running.clear(); } } else { running += ToLowerASCII(input[i]); } } if (!running.empty()) { words.push_back(running); } return words; } std::vector ParseUpperCamel(const std::string& input) { std::vector words; std::string running = ""; for (int i = 0; i < input.size(); i++) { if (input[i] >= 'A' && input[i] <= 'Z' && !running.empty()) { words.push_back(running); running.clear(); } running += ToLowerASCII(input[i]); } if (!running.empty()) { words.push_back(running); } return words; } std::string ToLowerCamel(const std::vector& words) { std::string result; for (int i = 0; i < words.size(); i++) { std::string word = words[i]; if (i == 0 && (word[0] >= 'A' && word[0] <= 'Z')) { word[0] = (word[0] - 'A') + 'a'; } else if (i != 0 && (word[0] >= 'a' && word[0] <= 'z')) { word[0] = (word[0] - 'a') + 'A'; } result += word; } return result; } std::string ToUpperCamel(const std::vector& words) { std::string result; for (int i = 0; i < words.size(); i++) { std::string word = words[i]; if (word[0] >= 'a' && word[0] <= 'z') { word[0] = (word[0] - 'a') + 'A'; } result += word; } return result; } // Based on code from descriptor.cc (Thanks Kenton!) // Uppercases the entire string, turning ValueName into // VALUENAME. std::string ToEnumCase(const std::string& input) { std::string result; result.reserve(input.size()); for (int i = 0; i < input.size(); i++) { if ('a' <= input[i] && input[i] <= 'z') { result.push_back(input[i] - 'a' + 'A'); } else { result.push_back(input[i]); } } return result; } std::string ToLower(const std::string& input) { std::string result; result.reserve(input.size()); for (int i = 0; i < input.size(); i++) { if ('A' <= input[i] && input[i] <= 'Z') { result.push_back(input[i] - 'A' + 'a'); } else { result.push_back(input[i]); } } return result; } // When we're generating one output file per SCC, this is the filename // that top-level extensions should go in. // e.g. one proto file (test.proto): // package a; // extends Foo { // ... // } // If "with_filename" equals true, the extension filename will be // "proto.a_test_extensions.js", otherwise will be "proto.a.js" std::string GetExtensionFileName(const GeneratorOptions& options, const FileDescriptor* file, bool with_filename) { std::string snake_name = StripProto(GetSnakeFilename(file->name())); return options.output_dir + "/" + ToLower(GetNamespace(options, file)) + (with_filename ? ("_" + snake_name + "_extensions") : "") + options.GetFileNameExtension(); } // When we're generating one output file per SCC, this is the filename // that all messages in the SCC should go in. // If with_package equals true, filename will have package prefix, // If the filename length is longer than 200, the filename will be the // SCC's proto filename with suffix "_long_sccs_(index)" (if with_package equals // true it still has package prefix) std::string GetMessagesFileName(const GeneratorOptions& options, const SCC* scc, bool with_package) { static std::map* long_name_dict = new std::map(); std::string package_base = with_package ? ToLower(GetNamespace(options, scc->GetRepresentative()->file()) + "_") : ""; std::string filename_base = ""; std::vector all_message_names; for (auto one_desc : scc->descriptors) { if (one_desc->containing_type() == nullptr) { all_message_names.push_back(ToLower(one_desc->name())); } } sort(all_message_names.begin(), all_message_names.end()); for (auto one_message : all_message_names) { if (!filename_base.empty()) { filename_base += "_"; } filename_base += one_message; } if (filename_base.size() + package_base.size() > 200) { if ((*long_name_dict).find(scc->GetRepresentative()) == (*long_name_dict).end()) { std::string snake_name = StripProto( GetSnakeFilename(scc->GetRepresentative()->file()->name())); (*long_name_dict)[scc->GetRepresentative()] = StrCat(snake_name, "_long_sccs_", static_cast((*long_name_dict).size())); } filename_base = (*long_name_dict)[scc->GetRepresentative()]; } return options.output_dir + "/" + package_base + filename_base + options.GetFileNameExtension(); } // When we're generating one output file per type name, this is the filename // that a top-level enum should go in. // If with_package equals true, filename will have package prefix. std::string GetEnumFileName(const GeneratorOptions& options, const EnumDescriptor* desc, bool with_package) { return options.output_dir + "/" + (with_package ? ToLower(GetNamespace(options, desc->file()) + "_") : "") + ToLower(desc->name()) + options.GetFileNameExtension(); } // Returns the message/response ID, if set. std::string GetMessageId(const Descriptor* desc) { return std::string(); } bool IgnoreExtensionField(const FieldDescriptor* field) { // Exclude descriptor extensions from output "to avoid clutter" (from original // codegen). if (!field->is_extension()) return false; const FileDescriptor* file = field->containing_type()->file(); return file->name() == "net/proto2/proto/descriptor.proto" || file->name() == "google/protobuf/descriptor.proto"; } // Used inside Google only -- do not remove. bool IsResponse(const Descriptor* desc) { return false; } bool IgnoreField(const FieldDescriptor* field) { return IgnoreExtensionField(field); } // Do we ignore this message type? bool IgnoreMessage(const Descriptor* d) { return d->options().map_entry(); } // Does JSPB ignore this entire oneof? True only if all fields are ignored. bool IgnoreOneof(const OneofDescriptor* oneof) { if (oneof->is_synthetic()) return true; for (int i = 0; i < oneof->field_count(); i++) { if (!IgnoreField(oneof->field(i))) { return false; } } return true; } std::string JSIdent(const GeneratorOptions& options, const FieldDescriptor* field, bool is_upper_camel, bool is_map, bool drop_list) { std::string result; if (field->type() == FieldDescriptor::TYPE_GROUP) { result = is_upper_camel ? ToUpperCamel(ParseUpperCamel(field->message_type()->name())) : ToLowerCamel(ParseUpperCamel(field->message_type()->name())); } else { result = is_upper_camel ? ToUpperCamel(ParseLowerUnderscore(field->name())) : ToLowerCamel(ParseLowerUnderscore(field->name())); } if (is_map || field->is_map()) { // JSPB-style or proto3-style map. result += "Map"; } else if (!drop_list && field->is_repeated()) { // Repeated field. result += "List"; } return result; } std::string JSObjectFieldName(const GeneratorOptions& options, const FieldDescriptor* field) { std::string name = JSIdent(options, field, /* is_upper_camel = */ false, /* is_map = */ false, /* drop_list = */ false); if (IsReserved(name)) { name = "pb_" + name; } return name; } std::string JSByteGetterSuffix(BytesMode bytes_mode) { switch (bytes_mode) { case BYTES_DEFAULT: return ""; case BYTES_B64: return "B64"; case BYTES_U8: return "U8"; default: assert(false); } return ""; } // Returns the field name as a capitalized portion of a getter/setter method // name, e.g. MyField for .getMyField(). std::string JSGetterName(const GeneratorOptions& options, const FieldDescriptor* field, BytesMode bytes_mode = BYTES_DEFAULT, bool drop_list = false) { std::string name = JSIdent(options, field, /* is_upper_camel = */ true, /* is_map = */ false, drop_list); if (field->type() == FieldDescriptor::TYPE_BYTES) { std::string suffix = JSByteGetterSuffix(bytes_mode); if (!suffix.empty()) { name += "_as" + suffix; } } if (name == "Extension" || name == "JsPbMessageId") { // Avoid conflicts with base-class names. name += "$"; } return name; } std::string JSOneofName(const OneofDescriptor* oneof) { return ToUpperCamel(ParseLowerUnderscore(oneof->name())); } // Returns the index corresponding to this field in the JSPB array (underlying // data storage array). std::string JSFieldIndex(const FieldDescriptor* field) { // Determine whether this field is a member of a group. Group fields are a bit // wonky: their "containing type" is a message type created just for the // group, and that type's parent type has a field with the group-message type // as its message type and TYPE_GROUP as its field type. For such fields, the // index we use is relative to the field number of the group submessage field. // For all other fields, we just use the field number. const Descriptor* containing_type = field->containing_type(); const Descriptor* parent_type = containing_type->containing_type(); if (parent_type != NULL) { for (int i = 0; i < parent_type->field_count(); i++) { if (parent_type->field(i)->type() == FieldDescriptor::TYPE_GROUP && parent_type->field(i)->message_type() == containing_type) { return StrCat(field->number() - parent_type->field(i)->number()); } } } return StrCat(field->number()); } std::string JSOneofIndex(const OneofDescriptor* oneof) { int index = -1; for (int i = 0; i < oneof->containing_type()->oneof_decl_count(); i++) { const OneofDescriptor* o = oneof->containing_type()->oneof_decl(i); if (o->is_synthetic()) continue; // If at least one field in this oneof is not JSPB-ignored, count the oneof. for (int j = 0; j < o->field_count(); j++) { const FieldDescriptor* f = o->field(j); if (!IgnoreField(f)) { index++; break; // inner loop } } if (o == oneof) { break; } } return StrCat(index); } // Decodes a codepoint in \x0000 -- \xFFFF. uint16 DecodeUTF8Codepoint(uint8* bytes, size_t* length) { if (*length == 0) { return 0; } size_t expected = 0; if ((*bytes & 0x80) == 0) { expected = 1; } else if ((*bytes & 0xe0) == 0xc0) { expected = 2; } else if ((*bytes & 0xf0) == 0xe0) { expected = 3; } else { // Too long -- don't accept. *length = 0; return 0; } if (*length < expected) { // Not enough bytes -- don't accept. *length = 0; return 0; } *length = expected; switch (expected) { case 1: return bytes[0]; case 2: return ((bytes[0] & 0x1F) << 6) | ((bytes[1] & 0x3F) << 0); case 3: return ((bytes[0] & 0x0F) << 12) | ((bytes[1] & 0x3F) << 6) | ((bytes[2] & 0x3F) << 0); default: return 0; } } // Escapes the contents of a string to be included within double-quotes ("") in // JavaScript. The input data should be a UTF-8 encoded C++ string of chars. // Returns false if |out| was truncated because |in| contained invalid UTF-8 or // codepoints outside the BMP. // TODO(b/115551870): Support codepoints outside the BMP. bool EscapeJSString(const std::string& in, std::string* out) { size_t decoded = 0; for (size_t i = 0; i < in.size(); i += decoded) { uint16 codepoint = 0; // Decode the next UTF-8 codepoint. size_t have_bytes = in.size() - i; uint8 bytes[3] = { static_cast(in[i]), static_cast(((i + 1) < in.size()) ? in[i + 1] : 0), static_cast(((i + 2) < in.size()) ? in[i + 2] : 0), }; codepoint = DecodeUTF8Codepoint(bytes, &have_bytes); if (have_bytes == 0) { return false; } decoded = have_bytes; switch (codepoint) { case '\'': *out += "\\x27"; break; case '"': *out += "\\x22"; break; case '<': *out += "\\x3c"; break; case '=': *out += "\\x3d"; break; case '>': *out += "\\x3e"; break; case '&': *out += "\\x26"; break; case '\b': *out += "\\b"; break; case '\t': *out += "\\t"; break; case '\n': *out += "\\n"; break; case '\f': *out += "\\f"; break; case '\r': *out += "\\r"; break; case '\\': *out += "\\\\"; break; default: // TODO(b/115551870): Once we're supporting codepoints outside the BMP, // use a single Unicode codepoint escape if the output language is // ECMAScript 2015 or above. Otherwise, use a surrogate pair. // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#String_literals if (codepoint >= 0x20 && codepoint <= 0x7e) { *out += static_cast(codepoint); } else if (codepoint >= 0x100) { *out += StringPrintf("\\u%04x", codepoint); } else { *out += StringPrintf("\\x%02x", codepoint); } break; } } return true; } std::string EscapeBase64(const std::string& in) { static const char* kAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; std::string result; for (size_t i = 0; i < in.size(); i += 3) { int value = (in[i] << 16) | (((i + 1) < in.size()) ? (in[i + 1] << 8) : 0) | (((i + 2) < in.size()) ? (in[i + 2] << 0) : 0); result += kAlphabet[(value >> 18) & 0x3f]; result += kAlphabet[(value >> 12) & 0x3f]; if ((i + 1) < in.size()) { result += kAlphabet[(value >> 6) & 0x3f]; } else { result += '='; } if ((i + 2) < in.size()) { result += kAlphabet[(value >> 0) & 0x3f]; } else { result += '='; } } return result; } // Post-process the result of SimpleFtoa/SimpleDtoa to *exactly* match the // original codegen's formatting (which is just .toString() on java.lang.Double // or java.lang.Float). std::string PostProcessFloat(std::string result) { // If inf, -inf or nan, replace with +Infinity, -Infinity or NaN. if (result == "inf") { return "Infinity"; } else if (result == "-inf") { return "-Infinity"; } else if (result == "nan") { return "NaN"; } // If scientific notation (e.g., "1e10"), (i) capitalize the "e", (ii) // ensure that the mantissa (portion prior to the "e") has at least one // fractional digit (after the decimal point), and (iii) strip any unnecessary // leading zeroes and/or '+' signs from the exponent. std::string::size_type exp_pos = result.find('e'); if (exp_pos != std::string::npos) { std::string mantissa = result.substr(0, exp_pos); std::string exponent = result.substr(exp_pos + 1); // Add ".0" to mantissa if no fractional part exists. if (mantissa.find('.') == std::string::npos) { mantissa += ".0"; } // Strip the sign off the exponent and store as |exp_neg|. bool exp_neg = false; if (!exponent.empty() && exponent[0] == '+') { exponent = exponent.substr(1); } else if (!exponent.empty() && exponent[0] == '-') { exp_neg = true; exponent = exponent.substr(1); } // Strip any leading zeroes off the exponent. while (exponent.size() > 1 && exponent[0] == '0') { exponent = exponent.substr(1); } return mantissa + "E" + std::string(exp_neg ? "-" : "") + exponent; } // Otherwise, this is an ordinary decimal number. Append ".0" if result has no // decimal/fractional part in order to match output of original codegen. if (result.find('.') == std::string::npos) { result += ".0"; } return result; } std::string FloatToString(float value) { std::string result = SimpleFtoa(value); return PostProcessFloat(result); } std::string DoubleToString(double value) { std::string result = SimpleDtoa(value); return PostProcessFloat(result); } bool InRealOneof(const FieldDescriptor* field) { return field->containing_oneof() && !field->containing_oneof()->is_synthetic(); } // Return true if this is an integral field that should be represented as string // in JS. bool IsIntegralFieldWithStringJSType(const FieldDescriptor* field) { switch (field->cpp_type()) { case FieldDescriptor::CPPTYPE_INT64: case FieldDescriptor::CPPTYPE_UINT64: // The default value of JSType is JS_NORMAL, which behaves the same as // JS_NUMBER. return field->options().jstype() == FieldOptions::JS_STRING; default: return false; } } std::string MaybeNumberString(const FieldDescriptor* field, const std::string& orig) { return IsIntegralFieldWithStringJSType(field) ? ("\"" + orig + "\"") : orig; } std::string JSFieldDefault(const FieldDescriptor* field) { if (field->is_repeated()) { return "[]"; } switch (field->cpp_type()) { case FieldDescriptor::CPPTYPE_INT32: return MaybeNumberString(field, StrCat(field->default_value_int32())); case FieldDescriptor::CPPTYPE_UINT32: // The original codegen is in Java, and Java protobufs store unsigned // integer values as signed integer values. In order to exactly match the // output, we need to reinterpret as base-2 signed. Ugh. return MaybeNumberString( field, StrCat(static_cast(field->default_value_uint32()))); case FieldDescriptor::CPPTYPE_INT64: return MaybeNumberString(field, StrCat(field->default_value_int64())); case FieldDescriptor::CPPTYPE_UINT64: // See above note for uint32 -- reinterpreting as signed. return MaybeNumberString( field, StrCat(static_cast(field->default_value_uint64()))); case FieldDescriptor::CPPTYPE_ENUM: return StrCat(field->default_value_enum()->number()); case FieldDescriptor::CPPTYPE_BOOL: return field->default_value_bool() ? "true" : "false"; case FieldDescriptor::CPPTYPE_FLOAT: return FloatToString(field->default_value_float()); case FieldDescriptor::CPPTYPE_DOUBLE: return DoubleToString(field->default_value_double()); case FieldDescriptor::CPPTYPE_STRING: if (field->type() == FieldDescriptor::TYPE_STRING) { std::string out; bool is_valid = EscapeJSString(field->default_value_string(), &out); if (!is_valid) { // TODO(b/115551870): Decide whether this should be a hard error. GOOGLE_LOG(WARNING) << "The default value for field " << field->full_name() << " was truncated since it contained invalid UTF-8 or" " codepoints outside the basic multilingual plane."; } return "\"" + out + "\""; } else { // Bytes return "\"" + EscapeBase64(field->default_value_string()) + "\""; } case FieldDescriptor::CPPTYPE_MESSAGE: return "null"; } GOOGLE_LOG(FATAL) << "Shouldn't reach here."; return ""; } std::string ProtoTypeName(const GeneratorOptions& options, const FieldDescriptor* field) { switch (field->type()) { case FieldDescriptor::TYPE_BOOL: return "bool"; case FieldDescriptor::TYPE_INT32: return "int32"; case FieldDescriptor::TYPE_UINT32: return "uint32"; case FieldDescriptor::TYPE_SINT32: return "sint32"; case FieldDescriptor::TYPE_FIXED32: return "fixed32"; case FieldDescriptor::TYPE_SFIXED32: return "sfixed32"; case FieldDescriptor::TYPE_INT64: return "int64"; case FieldDescriptor::TYPE_UINT64: return "uint64"; case FieldDescriptor::TYPE_SINT64: return "sint64"; case FieldDescriptor::TYPE_FIXED64: return "fixed64"; case FieldDescriptor::TYPE_SFIXED64: return "sfixed64"; case FieldDescriptor::TYPE_FLOAT: return "float"; case FieldDescriptor::TYPE_DOUBLE: return "double"; case FieldDescriptor::TYPE_STRING: return "string"; case FieldDescriptor::TYPE_BYTES: return "bytes"; case FieldDescriptor::TYPE_GROUP: return GetMessagePath(options, field->message_type()); case FieldDescriptor::TYPE_ENUM: return GetEnumPath(options, field->enum_type()); case FieldDescriptor::TYPE_MESSAGE: return GetMessagePath(options, field->message_type()); default: return ""; } } std::string JSIntegerTypeName(const FieldDescriptor* field) { return IsIntegralFieldWithStringJSType(field) ? "string" : "number"; } std::string JSStringTypeName(const GeneratorOptions& options, const FieldDescriptor* field, BytesMode bytes_mode) { if (field->type() == FieldDescriptor::TYPE_BYTES) { switch (bytes_mode) { case BYTES_DEFAULT: return "(string|Uint8Array)"; case BYTES_B64: return "string"; case BYTES_U8: return "Uint8Array"; default: assert(false); } } return "string"; } std::string JSTypeName(const GeneratorOptions& options, const FieldDescriptor* field, BytesMode bytes_mode) { switch (field->cpp_type()) { case FieldDescriptor::CPPTYPE_BOOL: return "boolean"; case FieldDescriptor::CPPTYPE_INT32: return JSIntegerTypeName(field); case FieldDescriptor::CPPTYPE_INT64: return JSIntegerTypeName(field); case FieldDescriptor::CPPTYPE_UINT32: return JSIntegerTypeName(field); case FieldDescriptor::CPPTYPE_UINT64: return JSIntegerTypeName(field); case FieldDescriptor::CPPTYPE_FLOAT: return "number"; case FieldDescriptor::CPPTYPE_DOUBLE: return "number"; case FieldDescriptor::CPPTYPE_STRING: return JSStringTypeName(options, field, bytes_mode); case FieldDescriptor::CPPTYPE_ENUM: return GetEnumPath(options, field->enum_type()); case FieldDescriptor::CPPTYPE_MESSAGE: return GetMessagePath(options, field->message_type()); default: return ""; } } // Used inside Google only -- do not remove. bool UseBrokenPresenceSemantics(const GeneratorOptions& options, const FieldDescriptor* field) { return false; } // Returns true for fields that return "null" from accessors when they are // unset. This should normally only be true for non-repeated submessages, but we // have legacy users who relied on old behavior where accessors behaved this // way. bool ReturnsNullWhenUnset(const GeneratorOptions& options, const FieldDescriptor* field) { if (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE && field->is_optional()) { return true; } // TODO(haberman): remove this case and unconditionally return false. return UseBrokenPresenceSemantics(options, field) && !field->is_repeated() && !field->has_default_value(); } // In a sane world, this would be the same as ReturnsNullWhenUnset(). But in // the status quo, some fields declare that they never return null/undefined // even though they actually do: // * required fields // * optional enum fields // * proto3 primitive fields. bool DeclaredReturnTypeIsNullable(const GeneratorOptions& options, const FieldDescriptor* field) { if (field->is_required() || field->type() == FieldDescriptor::TYPE_ENUM) { return false; } if (field->file()->syntax() == FileDescriptor::SYNTAX_PROTO3 && field->cpp_type() != FieldDescriptor::CPPTYPE_MESSAGE) { return false; } return ReturnsNullWhenUnset(options, field); } bool SetterAcceptsUndefined(const GeneratorOptions& options, const FieldDescriptor* field) { if (ReturnsNullWhenUnset(options, field)) { return true; } // Broken presence semantics always accepts undefined for setters. return UseBrokenPresenceSemantics(options, field); } bool SetterAcceptsNull(const GeneratorOptions& options, const FieldDescriptor* field) { if (ReturnsNullWhenUnset(options, field)) { return true; } // With broken presence semantics, fields with defaults accept "null" for // setters, but other fields do not. This is a strange quirk of the old // codegen. return UseBrokenPresenceSemantics(options, field) && field->has_default_value(); } // Returns types which are known to by non-nullable by default. // The style guide requires that we omit "!" in this case. bool IsPrimitive(const std::string& type) { return type == "undefined" || type == "string" || type == "number" || type == "boolean"; } std::string JSFieldTypeAnnotation(const GeneratorOptions& options, const FieldDescriptor* field, bool is_setter_argument, bool force_present, bool singular_if_not_packed, BytesMode bytes_mode = BYTES_DEFAULT, bool force_singular = false) { std::string jstype = JSTypeName(options, field, bytes_mode); if (!force_singular && field->is_repeated() && (field->is_packed() || !singular_if_not_packed)) { if (field->type() == FieldDescriptor::TYPE_BYTES && bytes_mode == BYTES_DEFAULT) { jstype = "(Array|Array)"; } else { if (!IsPrimitive(jstype)) { jstype = "!" + jstype; } jstype = "Array<" + jstype + ">"; } } bool is_null_or_undefined = false; if (is_setter_argument) { if (SetterAcceptsNull(options, field)) { jstype = "?" + jstype; is_null_or_undefined = true; } if (SetterAcceptsUndefined(options, field)) { jstype += "|undefined"; is_null_or_undefined = true; } } else if (force_present) { // Don't add null or undefined. } else { if (DeclaredReturnTypeIsNullable(options, field)) { jstype = "?" + jstype; is_null_or_undefined = true; } } if (!is_null_or_undefined && !IsPrimitive(jstype)) { jstype = "!" + jstype; } return jstype; } std::string JSBinaryReaderMethodType(const FieldDescriptor* field) { std::string name = field->type_name(); if (name[0] >= 'a' && name[0] <= 'z') { name[0] = (name[0] - 'a') + 'A'; } return IsIntegralFieldWithStringJSType(field) ? (name + "String") : name; } std::string JSBinaryReadWriteMethodName(const FieldDescriptor* field, bool is_writer) { std::string name = JSBinaryReaderMethodType(field); if (field->is_packed()) { name = "Packed" + name; } else if (is_writer && field->is_repeated()) { name = "Repeated" + name; } return name; } std::string JSBinaryReaderMethodName(const GeneratorOptions& options, const FieldDescriptor* field) { return "jspb.BinaryReader.prototype.read" + JSBinaryReadWriteMethodName(field, /* is_writer = */ false); } std::string JSBinaryWriterMethodName(const GeneratorOptions& options, const FieldDescriptor* field) { if (field->containing_type() && field->containing_type()->options().message_set_wire_format()) { return "jspb.BinaryWriter.prototype.writeMessageSet"; } return "jspb.BinaryWriter.prototype.write" + JSBinaryReadWriteMethodName(field, /* is_writer = */ true); } std::string JSTypeTag(const FieldDescriptor* desc) { switch (desc->type()) { case FieldDescriptor::TYPE_DOUBLE: case FieldDescriptor::TYPE_FLOAT: return "Float"; case FieldDescriptor::TYPE_INT32: case FieldDescriptor::TYPE_UINT32: case FieldDescriptor::TYPE_INT64: case FieldDescriptor::TYPE_UINT64: case FieldDescriptor::TYPE_FIXED32: case FieldDescriptor::TYPE_FIXED64: case FieldDescriptor::TYPE_SINT32: case FieldDescriptor::TYPE_SINT64: case FieldDescriptor::TYPE_SFIXED32: case FieldDescriptor::TYPE_SFIXED64: if (IsIntegralFieldWithStringJSType(desc)) { return "StringInt"; } else { return "Int"; } case FieldDescriptor::TYPE_BOOL: return "Boolean"; case FieldDescriptor::TYPE_STRING: return "String"; case FieldDescriptor::TYPE_BYTES: return "Bytes"; case FieldDescriptor::TYPE_ENUM: return "Enum"; default: assert(false); } return ""; } bool HasRepeatedFields(const GeneratorOptions& options, const Descriptor* desc) { for (int i = 0; i < desc->field_count(); i++) { if (desc->field(i)->is_repeated() && !desc->field(i)->is_map()) { return true; } } return false; } static const char* kRepeatedFieldArrayName = ".repeatedFields_"; std::string RepeatedFieldsArrayName(const GeneratorOptions& options, const Descriptor* desc) { return HasRepeatedFields(options, desc) ? (GetMessagePath(options, desc) + kRepeatedFieldArrayName) : "null"; } bool HasOneofFields(const Descriptor* desc) { for (int i = 0; i < desc->field_count(); i++) { if (InRealOneof(desc->field(i))) { return true; } } return false; } static const char* kOneofGroupArrayName = ".oneofGroups_"; std::string OneofFieldsArrayName(const GeneratorOptions& options, const Descriptor* desc) { return HasOneofFields(desc) ? (GetMessagePath(options, desc) + kOneofGroupArrayName) : "null"; } std::string RepeatedFieldNumberList(const GeneratorOptions& options, const Descriptor* desc) { std::vector numbers; for (int i = 0; i < desc->field_count(); i++) { if (desc->field(i)->is_repeated() && !desc->field(i)->is_map()) { numbers.push_back(JSFieldIndex(desc->field(i))); } } return "[" + Join(numbers, ",") + "]"; } std::string OneofGroupList(const Descriptor* desc) { // List of arrays (one per oneof), each of which is a list of field indices std::vector oneof_entries; for (int i = 0; i < desc->oneof_decl_count(); i++) { const OneofDescriptor* oneof = desc->oneof_decl(i); if (IgnoreOneof(oneof)) { continue; } std::vector oneof_fields; for (int j = 0; j < oneof->field_count(); j++) { if (IgnoreField(oneof->field(j))) { continue; } oneof_fields.push_back(JSFieldIndex(oneof->field(j))); } oneof_entries.push_back("[" + Join(oneof_fields, ",") + "]"); } return "[" + Join(oneof_entries, ",") + "]"; } std::string JSOneofArray(const GeneratorOptions& options, const FieldDescriptor* field) { return OneofFieldsArrayName(options, field->containing_type()) + "[" + JSOneofIndex(field->containing_oneof()) + "]"; } std::string RelativeTypeName(const FieldDescriptor* field) { assert(field->cpp_type() == FieldDescriptor::CPPTYPE_ENUM || field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE); // For a field with an enum or message type, compute a name relative to the // path name of the message type containing this field. std::string package = field->file()->package(); std::string containing_type = field->containing_type()->full_name() + "."; std::string type = (field->cpp_type() == FieldDescriptor::CPPTYPE_ENUM) ? field->enum_type()->full_name() : field->message_type()->full_name(); // |prefix| is advanced as we find separators '.' past the common package // prefix that yield common prefixes in the containing type's name and this // type's name. int prefix = 0; for (int i = 0; i < type.size() && i < containing_type.size(); i++) { if (type[i] != containing_type[i]) { break; } if (type[i] == '.' && i >= package.size()) { prefix = i + 1; } } return type.substr(prefix); } std::string JSExtensionsObjectName(const GeneratorOptions& options, const FileDescriptor* from_file, const Descriptor* desc) { if (desc->full_name() == "google.protobuf.bridge.MessageSet") { // TODO(haberman): fix this for the kImportCommonJs case. return "jspb.Message.messageSetExtensions"; } else { return MaybeCrossFileRef(options, from_file, desc) + ".extensions"; } } static const int kMapKeyField = 1; static const int kMapValueField = 2; const FieldDescriptor* MapFieldKey(const FieldDescriptor* field) { assert(field->is_map()); return field->message_type()->FindFieldByNumber(kMapKeyField); } const FieldDescriptor* MapFieldValue(const FieldDescriptor* field) { assert(field->is_map()); return field->message_type()->FindFieldByNumber(kMapValueField); } std::string FieldDefinition(const GeneratorOptions& options, const FieldDescriptor* field) { if (field->is_map()) { const FieldDescriptor* key_field = MapFieldKey(field); const FieldDescriptor* value_field = MapFieldValue(field); std::string key_type = ProtoTypeName(options, key_field); std::string value_type; if (value_field->type() == FieldDescriptor::TYPE_ENUM || value_field->type() == FieldDescriptor::TYPE_MESSAGE) { value_type = RelativeTypeName(value_field); } else { value_type = ProtoTypeName(options, value_field); } return StringPrintf("map<%s, %s> %s = %d;", key_type.c_str(), value_type.c_str(), field->name().c_str(), field->number()); } else { std::string qualifier = field->is_repeated() ? "repeated" : (field->is_optional() ? "optional" : "required"); std::string type, name; if (field->type() == FieldDescriptor::TYPE_ENUM || field->type() == FieldDescriptor::TYPE_MESSAGE) { type = RelativeTypeName(field); name = field->name(); } else if (field->type() == FieldDescriptor::TYPE_GROUP) { type = "group"; name = field->message_type()->name(); } else { type = ProtoTypeName(options, field); name = field->name(); } return StringPrintf("%s %s %s = %d;", qualifier.c_str(), type.c_str(), name.c_str(), field->number()); } } std::string FieldComments(const FieldDescriptor* field, BytesMode bytes_mode) { std::string comments; if (field->type() == FieldDescriptor::TYPE_BYTES && bytes_mode == BYTES_U8) { comments += " * Note that Uint8Array is not supported on all browsers.\n" " * @see http://caniuse.com/Uint8Array\n"; } return comments; } bool ShouldGenerateExtension(const FieldDescriptor* field) { return field->is_extension() && !IgnoreField(field); } bool HasExtensions(const Descriptor* desc) { for (int i = 0; i < desc->extension_count(); i++) { if (ShouldGenerateExtension(desc->extension(i))) { return true; } } for (int i = 0; i < desc->nested_type_count(); i++) { if (HasExtensions(desc->nested_type(i))) { return true; } } return false; } bool HasExtensions(const FileDescriptor* file) { for (int i = 0; i < file->extension_count(); i++) { if (ShouldGenerateExtension(file->extension(i))) { return true; } } for (int i = 0; i < file->message_type_count(); i++) { if (HasExtensions(file->message_type(i))) { return true; } } return false; } bool HasMap(const GeneratorOptions& options, const Descriptor* desc) { for (int i = 0; i < desc->field_count(); i++) { if (desc->field(i)->is_map()) { return true; } } for (int i = 0; i < desc->nested_type_count(); i++) { if (HasMap(options, desc->nested_type(i))) { return true; } } return false; } bool FileHasMap(const GeneratorOptions& options, const FileDescriptor* desc) { for (int i = 0; i < desc->message_type_count(); i++) { if (HasMap(options, desc->message_type(i))) { return true; } } return false; } bool IsExtendable(const Descriptor* desc) { return desc->extension_range_count() > 0; } // Returns the max index in the underlying data storage array beyond which the // extension object is used. std::string GetPivot(const Descriptor* desc) { static const int kDefaultPivot = 500; // Find the max field number int max_field_number = 0; for (int i = 0; i < desc->field_count(); i++) { if (!IgnoreField(desc->field(i)) && desc->field(i)->number() > max_field_number) { max_field_number = desc->field(i)->number(); } } int pivot = -1; if (IsExtendable(desc) || (max_field_number >= kDefaultPivot)) { pivot = ((max_field_number + 1) < kDefaultPivot) ? (max_field_number + 1) : kDefaultPivot; } return StrCat(pivot); } // Whether this field represents presence. For fields with presence, we // generate extra methods (clearFoo() and hasFoo()) for this field. bool HasFieldPresence(const GeneratorOptions& options, const FieldDescriptor* field) { // This returns false for repeated fields and maps, but we still do // generate clearFoo() methods for these through a special case elsewhere. return field->has_presence(); } // We use this to implement the semantics that same file can be generated // multiple times, but only the last one keep the short name. Others all use // long name with extra information to distinguish (For message and enum, the // extra information is package name, for file level extension, the extra // information is proto's filename). // We never actually write the files, but we keep a set of which descriptors // were the final one for a given filename. class FileDeduplicator { public: explicit FileDeduplicator(const GeneratorOptions& options) {} // params: // filenames: a pair of {short filename, full filename} // (short filename don't have extra information, full filename // contains extra information) // desc: The Descriptor or SCC pointer or EnumDescriptor. bool AddFile(const std::pair filenames, const void* desc) { if (descs_by_shortname_.find(filenames.first) != descs_by_shortname_.end()) { // Change old pointer's actual name to full name. auto short_name_desc = descs_by_shortname_[filenames.first]; allowed_descs_actual_name_[short_name_desc] = allowed_descs_full_name_[short_name_desc]; } descs_by_shortname_[filenames.first] = desc; allowed_descs_actual_name_[desc] = filenames.first; allowed_descs_full_name_[desc] = filenames.second; return true; } void GetAllowedMap(std::map* allowed_set) { *allowed_set = allowed_descs_actual_name_; } private: // The map that restores all the descs that are using short name as filename. std::map descs_by_shortname_; // The final actual filename map. std::map allowed_descs_actual_name_; // The full name map. std::map allowed_descs_full_name_; }; void DepthFirstSearch(const FileDescriptor* file, std::vector* list, std::set* seen) { if (!seen->insert(file).second) { return; } // Add all dependencies. for (int i = 0; i < file->dependency_count(); i++) { DepthFirstSearch(file->dependency(i), list, seen); } // Add this file. list->push_back(file); } // A functor for the predicate to remove_if() below. Returns true if a given // FileDescriptor is not in the given set. class NotInSet { public: explicit NotInSet(const std::set& file_set) : file_set_(file_set) {} bool operator()(const FileDescriptor* file) { return file_set_.count(file) == 0; } private: const std::set& file_set_; }; // This function generates an ordering of the input FileDescriptors that matches // the logic of the old code generator. The order is significant because two // different input files can generate the same output file, and the last one // needs to win. void GenerateJspbFileOrder(const std::vector& input, std::vector* ordered) { // First generate an ordering of all reachable files (including dependencies) // with depth-first search. This mimics the behavior of --include_imports, // which is what the old codegen used. ordered->clear(); std::set seen; std::set input_set; for (int i = 0; i < input.size(); i++) { DepthFirstSearch(input[i], ordered, &seen); input_set.insert(input[i]); } // Now remove the entries that are not actually in our input list. ordered->erase( std::remove_if(ordered->begin(), ordered->end(), NotInSet(input_set)), ordered->end()); } // If we're generating code in file-per-type mode, avoid overwriting files // by choosing the last descriptor that writes each filename and permitting // only those to generate code. struct DepsGenerator { std::vector operator()(const Descriptor* desc) const { std::vector deps; auto maybe_add = [&](const Descriptor* d) { if (d) deps.push_back(d); }; for (int i = 0; i < desc->field_count(); i++) { if (!IgnoreField(desc->field(i))) { maybe_add(desc->field(i)->message_type()); } } for (int i = 0; i < desc->extension_count(); i++) { maybe_add(desc->extension(i)->message_type()); maybe_add(desc->extension(i)->containing_type()); } for (int i = 0; i < desc->nested_type_count(); i++) { maybe_add(desc->nested_type(i)); } maybe_add(desc->containing_type()); return deps; } }; bool GenerateJspbAllowedMap(const GeneratorOptions& options, const std::vector& files, std::map* allowed_set, SCCAnalyzer* analyzer) { std::vector files_ordered; GenerateJspbFileOrder(files, &files_ordered); // Choose the last descriptor for each filename. FileDeduplicator dedup(options); std::set added; for (int i = 0; i < files_ordered.size(); i++) { for (int j = 0; j < files_ordered[i]->message_type_count(); j++) { const Descriptor* desc = files_ordered[i]->message_type(j); if (added.insert(analyzer->GetSCC(desc)).second && !dedup.AddFile( std::make_pair( GetMessagesFileName(options, analyzer->GetSCC(desc), false), GetMessagesFileName(options, analyzer->GetSCC(desc), true)), analyzer->GetSCC(desc))) { return false; } } for (int j = 0; j < files_ordered[i]->enum_type_count(); j++) { const EnumDescriptor* desc = files_ordered[i]->enum_type(j); if (!dedup.AddFile(std::make_pair(GetEnumFileName(options, desc, false), GetEnumFileName(options, desc, true)), desc)) { return false; } } // Pull out all free-floating extensions and generate files for those too. bool has_extension = false; for (int j = 0; j < files_ordered[i]->extension_count(); j++) { if (ShouldGenerateExtension(files_ordered[i]->extension(j))) { has_extension = true; } } if (has_extension) { if (!dedup.AddFile( std::make_pair( GetExtensionFileName(options, files_ordered[i], false), GetExtensionFileName(options, files_ordered[i], true)), files_ordered[i])) { return false; } } } dedup.GetAllowedMap(allowed_set); return true; } // Embeds base64 encoded GeneratedCodeInfo proto in a comment at the end of // file. void EmbedCodeAnnotations(const GeneratedCodeInfo& annotations, io::Printer* printer) { // Serialize annotations proto into base64 string. std::string meta_content; annotations.SerializeToString(&meta_content); std::string meta_64; Base64Escape(meta_content, &meta_64); // Print base64 encoded annotations at the end of output file in // a comment. printer->Print("\n// Below is base64 encoded GeneratedCodeInfo proto"); printer->Print("\n// $encoded_proto$\n", "encoded_proto", meta_64); } bool IsWellKnownTypeFile(const FileDescriptor* file) { return HasPrefixString(file->name(), "google/protobuf/"); } } // anonymous namespace void Generator::GenerateHeader(const GeneratorOptions& options, const FileDescriptor* file, io::Printer* printer) const { if (file != nullptr) { printer->Print("// source: $filename$\n", "filename", file->name()); } printer->Print( "/**\n" " * @fileoverview\n" " * @enhanceable\n" // TODO(b/152440355): requireType/requires diverged from internal version. " * @suppress {missingRequire} reports error on implicit type usages.\n" " * @suppress {messageConventions} JS Compiler reports an " "error if a variable or\n" " * field starts with 'MSG_' and isn't a translatable " "message.\n" " * @public\n" " */\n" "// GENERATED CODE -- DO NOT EDIT!\n" "/* eslint-disable */\n" "// @ts-nocheck\n" "\n"); } void Generator::FindProvidesForFile(const GeneratorOptions& options, io::Printer* printer, const FileDescriptor* file, std::set* provided) const { for (int i = 0; i < file->message_type_count(); i++) { FindProvidesForMessage(options, printer, file->message_type(i), provided); } for (int i = 0; i < file->enum_type_count(); i++) { FindProvidesForEnum(options, printer, file->enum_type(i), provided); } } void Generator::FindProvides(const GeneratorOptions& options, io::Printer* printer, const std::vector& files, std::set* provided) const { for (int i = 0; i < files.size(); i++) { FindProvidesForFile(options, printer, files[i], provided); } printer->Print("\n"); } void FindProvidesForOneOfEnum(const GeneratorOptions& options, const OneofDescriptor* oneof, std::set* provided) { std::string name = GetMessagePath(options, oneof->containing_type()) + "." + JSOneofName(oneof) + "Case"; provided->insert(name); } void FindProvidesForOneOfEnums(const GeneratorOptions& options, io::Printer* printer, const Descriptor* desc, std::set* provided) { if (HasOneofFields(desc)) { for (int i = 0; i < desc->oneof_decl_count(); i++) { if (IgnoreOneof(desc->oneof_decl(i))) { continue; } FindProvidesForOneOfEnum(options, desc->oneof_decl(i), provided); } } } void Generator::FindProvidesForMessage(const GeneratorOptions& options, io::Printer* printer, const Descriptor* desc, std::set* provided) const { if (IgnoreMessage(desc)) { return; } std::string name = GetMessagePath(options, desc); provided->insert(name); for (int i = 0; i < desc->enum_type_count(); i++) { FindProvidesForEnum(options, printer, desc->enum_type(i), provided); } FindProvidesForOneOfEnums(options, printer, desc, provided); for (int i = 0; i < desc->nested_type_count(); i++) { FindProvidesForMessage(options, printer, desc->nested_type(i), provided); } } void Generator::FindProvidesForEnum(const GeneratorOptions& options, io::Printer* printer, const EnumDescriptor* enumdesc, std::set* provided) const { std::string name = GetEnumPath(options, enumdesc); provided->insert(name); } void Generator::FindProvidesForFields( const GeneratorOptions& options, io::Printer* printer, const std::vector& fields, std::set* provided) const { for (int i = 0; i < fields.size(); i++) { const FieldDescriptor* field = fields[i]; if (IgnoreField(field)) { continue; } std::string name = GetNamespace(options, field->file()) + "." + JSObjectFieldName(options, field); provided->insert(name); } } void Generator::GenerateProvides(const GeneratorOptions& options, io::Printer* printer, std::set* provided) const { for (std::set::iterator it = provided->begin(); it != provided->end(); ++it) { if (options.import_style == GeneratorOptions::kImportClosure) { printer->Print("goog.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. // // goog.exportSymbol('foo.bar.Baz', null, this); // // // Later generated code expects foo.bar = {} to exist: // foo.bar.Baz = function() { /* ... */ } // Do not use global scope in strict mode if (options.import_style == GeneratorOptions::kImportCommonJsStrict) { std::string namespaceObject = *it; // Remove "proto." from the namespace object GOOGLE_CHECK_EQ(0, namespaceObject.compare(0, 6, "proto.")); namespaceObject.erase(0, 6); printer->Print("goog.exportSymbol('$name$', null, proto);\n", "name", namespaceObject); } else { printer->Print("goog.exportSymbol('$name$', null, global);\n", "name", *it); } } } } void Generator::GenerateRequiresForSCC(const GeneratorOptions& options, io::Printer* printer, const SCC* scc, std::set* provided) const { std::set required; std::set forwards; bool have_message = false; bool has_extension = false; bool has_map = false; for (auto desc : scc->descriptors) { if (desc->containing_type() == nullptr) { FindRequiresForMessage(options, desc, &required, &forwards, &have_message); has_extension = (has_extension || HasExtensions(desc)); has_map = (has_map || HasMap(options, desc)); } } GenerateRequiresImpl(options, printer, &required, &forwards, provided, /* require_jspb = */ have_message, /* require_extension = */ has_extension, /* require_map = */ has_map); } 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); // For Closure imports we need to import every message type individually. std::set required; std::set forwards; bool have_extensions = false; bool have_map = false; bool have_message = false; for (int i = 0; i < files.size(); i++) { for (int j = 0; j < files[i]->message_type_count(); j++) { const Descriptor* desc = files[i]->message_type(j); if (!IgnoreMessage(desc)) { FindRequiresForMessage(options, desc, &required, &forwards, &have_message); } } if (!have_extensions && HasExtensions(files[i])) { have_extensions = true; } if (!have_map && FileHasMap(options, files[i])) { have_map = true; } for (int j = 0; j < files[i]->extension_count(); j++) { const FieldDescriptor* extension = files[i]->extension(j); if (IgnoreField(extension)) { continue; } if (extension->containing_type()->full_name() != "google.protobuf.bridge.MessageSet") { required.insert(GetMessagePath(options, extension->containing_type())); } FindRequiresForField(options, extension, &required, &forwards); have_extensions = true; } } GenerateRequiresImpl(options, printer, &required, &forwards, provided, /* require_jspb = */ have_message, /* require_extension = */ have_extensions, /* require_map = */ have_map); } void Generator::GenerateRequiresForExtensions( const GeneratorOptions& options, io::Printer* printer, const std::vector& fields, std::set* provided) const { std::set required; std::set forwards; for (int i = 0; i < fields.size(); i++) { const FieldDescriptor* field = fields[i]; if (IgnoreField(field)) { continue; } FindRequiresForExtension(options, field, &required, &forwards); } GenerateRequiresImpl(options, printer, &required, &forwards, provided, /* require_jspb = */ false, /* require_extension = */ fields.size() > 0, /* require_map = */ false); } void Generator::GenerateRequiresImpl(const GeneratorOptions& options, io::Printer* printer, std::set* required, std::set* forwards, std::set* provided, bool require_jspb, bool require_extension, bool require_map) const { if (require_jspb) { required->insert("jspb.Message"); required->insert("jspb.BinaryReader"); required->insert("jspb.BinaryWriter"); } if (require_extension) { required->insert("jspb.ExtensionFieldBinaryInfo"); required->insert("jspb.ExtensionFieldInfo"); } if (require_map) { required->insert("jspb.Map"); } std::set::iterator it; for (it = required->begin(); it != required->end(); ++it) { if (provided->find(*it) != provided->end()) { continue; } printer->Print("goog.require('$name$');\n", "name", *it); } printer->Print("\n"); for (it = forwards->begin(); it != forwards->end(); ++it) { if (provided->find(*it) != provided->end()) { continue; } printer->Print("goog.forwardDeclare('$name$');\n", "name", *it); } } bool NamespaceOnly(const Descriptor* desc) { return false; } void Generator::FindRequiresForMessage(const GeneratorOptions& options, const Descriptor* desc, std::set* required, std::set* forwards, bool* have_message) const { if (!NamespaceOnly(desc)) { *have_message = true; for (int i = 0; i < desc->field_count(); i++) { const FieldDescriptor* field = desc->field(i); if (IgnoreField(field)) { continue; } FindRequiresForField(options, field, required, forwards); } } for (int i = 0; i < desc->extension_count(); i++) { const FieldDescriptor* field = desc->extension(i); if (IgnoreField(field)) { continue; } FindRequiresForExtension(options, field, required, forwards); } for (int i = 0; i < desc->nested_type_count(); i++) { FindRequiresForMessage(options, desc->nested_type(i), required, forwards, have_message); } } void Generator::FindRequiresForField(const GeneratorOptions& options, const FieldDescriptor* field, std::set* required, std::set* forwards) const { if (field->cpp_type() == FieldDescriptor::CPPTYPE_ENUM && // N.B.: file-level extensions with enum type do *not* create // dependencies, as per original codegen. !(field->is_extension() && field->extension_scope() == nullptr)) { if (options.add_require_for_enums) { required->insert(GetEnumPath(options, field->enum_type())); } else { forwards->insert(GetEnumPath(options, field->enum_type())); } } else if (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) { if (!IgnoreMessage(field->message_type())) { required->insert(GetMessagePath(options, field->message_type())); } } } void Generator::FindRequiresForExtension( const GeneratorOptions& options, const FieldDescriptor* field, std::set* required, std::set* forwards) const { if (field->containing_type()->full_name() != "google.protobuf.bridge.MessageSet") { required->insert(GetMessagePath(options, field->containing_type())); } FindRequiresForField(options, field, required, forwards); } void Generator::GenerateTestOnly(const GeneratorOptions& options, io::Printer* printer) const { if (options.testonly) { printer->Print("goog.setTestOnly();\n\n"); } printer->Print("\n"); } void Generator::GenerateClassesAndEnums(const GeneratorOptions& options, io::Printer* printer, const FileDescriptor* file) const { for (int i = 0; i < file->message_type_count(); i++) { GenerateClassConstructorAndDeclareExtensionFieldInfo(options, printer, file->message_type(i)); } for (int i = 0; i < file->message_type_count(); i++) { GenerateClass(options, printer, file->message_type(i)); } for (int i = 0; i < file->enum_type_count(); i++) { GenerateEnum(options, printer, file->enum_type(i)); } } void Generator::GenerateClass(const GeneratorOptions& options, io::Printer* printer, const Descriptor* desc) const { if (IgnoreMessage(desc)) { return; } if (!NamespaceOnly(desc)) { printer->Print("\n"); GenerateClassFieldInfo(options, printer, desc); GenerateClassToObject(options, printer, desc); // These must come *before* the extension-field info generation in // GenerateClassRegistration so that references to the binary // serialization/deserialization functions may be placed in the extension // objects. GenerateClassDeserializeBinary(options, printer, desc); GenerateClassSerializeBinary(options, printer, desc); } // Recurse on nested types. These must come *before* the extension-field // info generation in GenerateClassRegistration so that extensions that // reference nested types proceed the definitions of the nested types. for (int i = 0; i < desc->enum_type_count(); i++) { GenerateEnum(options, printer, desc->enum_type(i)); } for (int i = 0; i < desc->nested_type_count(); i++) { GenerateClass(options, printer, desc->nested_type(i)); } if (!NamespaceOnly(desc)) { GenerateClassRegistration(options, printer, desc); GenerateClassFields(options, printer, desc); if (options.import_style != GeneratorOptions::kImportClosure) { for (int i = 0; i < desc->extension_count(); i++) { GenerateExtension(options, printer, desc->extension(i)); } } } } void Generator::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)); } void Generator::GenerateClassConstructorAndDeclareExtensionFieldInfo( const GeneratorOptions& options, io::Printer* printer, const Descriptor* desc) const { if (!NamespaceOnly(desc)) { GenerateClassConstructor(options, printer, desc); if (IsExtendable(desc) && desc->full_name() != "google.protobuf.bridge.MessageSet") { 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, io::Printer* printer, const Descriptor* desc) const { if (HasRepeatedFields(options, desc)) { printer->Print( "/**\n" " * List of repeated fields within this message type.\n" " * @private {!Array}\n" " * @const\n" " */\n" "$classname$$rptfieldarray$ = $rptfields$;\n" "\n", "classname", GetMessagePath(options, desc), "rptfieldarray", kRepeatedFieldArrayName, "rptfields", RepeatedFieldNumberList(options, desc)); } if (HasOneofFields(desc)) { printer->Print( "/**\n" " * Oneof group definitions for this message. Each group defines the " "field\n" " * numbers belonging to that group. When of these fields' value is " "set, all\n" " * other fields in the group are cleared. During deserialization, if " "multiple\n" " * fields are encountered for a group, only the last value seen will " "be kept.\n" " * @private {!Array>}\n" " * @const\n" " */\n" "$classname$$oneofgrouparray$ = $oneofgroups$;\n" "\n", "classname", GetMessagePath(options, desc), "oneofgrouparray", kOneofGroupArrayName, "oneofgroups", OneofGroupList(desc)); for (int i = 0; i < desc->oneof_decl_count(); i++) { if (IgnoreOneof(desc->oneof_decl(i))) { continue; } GenerateOneofCaseDefinition(options, printer, desc->oneof_decl(i)); } } } void Generator::GenerateClassXid(const GeneratorOptions& options, io::Printer* printer, const Descriptor* desc) const { printer->Print( "\n" "\n" "$class$.prototype.messageXid = xid('$class$');\n", "class", GetMessagePath(options, desc)); } void Generator::GenerateOneofCaseDefinition( const GeneratorOptions& options, io::Printer* printer, const OneofDescriptor* oneof) const { printer->Print( "/**\n" " * @enum {number}\n" " */\n" "$classname$.$oneof$Case = {\n" " $upcase$_NOT_SET: 0", "classname", GetMessagePath(options, oneof->containing_type()), "oneof", JSOneofName(oneof), "upcase", ToEnumCase(oneof->name())); for (int i = 0; i < oneof->field_count(); i++) { if (IgnoreField(oneof->field(i))) { continue; } printer->Print( ",\n" " $upcase$: $number$", "upcase", ToEnumCase(oneof->field(i)->name()), "number", JSFieldIndex(oneof->field(i))); printer->Annotate("upcase", oneof->field(i)); } printer->Print( "\n" "};\n" "\n" "/**\n" " * @return {$class$.$oneof$Case}\n" " */\n" "$class$.prototype.get$oneof$Case = function() {\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)); } void Generator::GenerateClassToObject(const GeneratorOptions& options, io::Printer* printer, const Descriptor* desc) const { printer->Print( "\n" "\n" "if (jspb.Message.GENERATE_TO_OBJECT) {\n" "/**\n" " * Creates an object representation of this proto.\n" " * Field names that are reserved in JavaScript and will be renamed to " "pb_name.\n" " * Optional fields that are not set will be set to undefined.\n" " * To access a reserved field use, foo.pb_, eg, foo.pb_default.\n" " * For the list of reserved names please see:\n" " * net/proto2/compiler/js/internal/generator.cc#kKeyword.\n" " * @param {boolean=} opt_includeInstance Deprecated. whether to include " "the\n" " * JSPB instance for transitional soy proto support:\n" " * http://goto/soy-param-migration\n" " * @return {!Object}\n" " */\n" "$classname$.prototype.toObject = function(opt_includeInstance) {\n" " return $classname$.toObject(opt_includeInstance, this);\n" "};\n" "\n" "\n" "/**\n" " * Static version of the {@see toObject} method.\n" " * @param {boolean|undefined} includeInstance Deprecated. Whether to " "include\n" " * the JSPB instance for transitional soy proto support:\n" " * http://goto/soy-param-migration\n" " * @param {!$classname$} msg The msg instance to transform.\n" " * @return {!Object}\n" " * @suppress {unusedLocalVariables} f is only used for nested messages\n" " */\n" "$classname$.toObject = function(includeInstance, msg) {\n" " var f, obj = {", "classname", GetMessagePath(options, desc)); bool first = true; for (int i = 0; i < desc->field_count(); i++) { const FieldDescriptor* field = desc->field(i); if (IgnoreField(field)) { continue; } if (!first) { printer->Print(",\n "); } else { printer->Print("\n "); first = false; } GenerateClassFieldToObject(options, printer, field); } if (!first) { printer->Print("\n };\n\n"); } else { printer->Print("\n\n };\n\n"); } if (IsExtendable(desc)) { printer->Print( " jspb.Message.toObjectExtension(/** @type {!jspb.Message} */ (msg), " "obj,\n" " $extObject$, $class$.prototype.getExtension,\n" " includeInstance);\n", "extObject", JSExtensionsObjectName(options, desc->file(), desc), "class", GetMessagePath(options, desc)); } printer->Print( " if (includeInstance) {\n" " obj.$$jspbMessageInstance = msg;\n" " }\n" " return obj;\n" "};\n" "}\n" "\n" "\n", "classname", GetMessagePath(options, desc)); } void Generator::GenerateFieldValueExpression(io::Printer* printer, const char* obj_reference, const FieldDescriptor* field, bool use_default) const { const bool is_float_or_double = field->cpp_type() == FieldDescriptor::CPPTYPE_FLOAT || field->cpp_type() == FieldDescriptor::CPPTYPE_DOUBLE; const bool is_boolean = field->cpp_type() == FieldDescriptor::CPPTYPE_BOOL; const std::string with_default = use_default ? "WithDefault" : ""; const std::string default_arg = use_default ? StrCat(", ", JSFieldDefault(field)) : ""; const std::string cardinality = field->is_repeated() ? "Repeated" : ""; std::string type = ""; if (is_float_or_double) { type = "FloatingPoint"; } if (is_boolean) { type = "Boolean"; } // Prints the appropriate function, among: // - getField // - getBooleanField // - getFloatingPointField => Replaced by getOptionalFloatingPointField to // preserve backward compatibility. // - getFieldWithDefault // - getBooleanFieldWithDefault // - getFloatingPointFieldWithDefault // - getRepeatedField // - getRepeatedBooleanField // - getRepeatedFloatingPointField if (is_float_or_double && !field->is_repeated() && !use_default) { printer->Print( "jspb.Message.getOptionalFloatingPointField($obj$, " "$index$$default$)", "obj", obj_reference, "index", JSFieldIndex(field), "default", default_arg); } else { printer->Print( "jspb.Message.get$cardinality$$type$Field$with_default$($obj$, " "$index$$default$)", "cardinality", cardinality, "type", type, "with_default", with_default, "obj", obj_reference, "index", JSFieldIndex(field), "default", default_arg); } } void Generator::GenerateClassFieldToObject(const GeneratorOptions& options, io::Printer* printer, const FieldDescriptor* field) const { printer->Print("$fieldname$: ", "fieldname", JSObjectFieldName(options, field)); if (field->is_map()) { const FieldDescriptor* value_field = MapFieldValue(field); // If the map values are of a message type, we must provide their static // toObject() method; otherwise we pass undefined for that argument. std::string value_to_object; if (value_field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) { value_to_object = GetMessagePath(options, value_field->message_type()) + ".toObject"; } else { value_to_object = "undefined"; } printer->Print( "(f = msg.get$name$()) ? f.toObject(includeInstance, $valuetoobject$) " ": []", "name", JSGetterName(options, field), "valuetoobject", value_to_object); } else if (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) { // Message field. if (field->is_repeated()) { { printer->Print( "jspb.Message.toObjectList(msg.get$getter$(),\n" " $type$.toObject, includeInstance)", "getter", JSGetterName(options, field), "type", SubmessageTypeRef(options, field)); } } else { printer->Print( "(f = msg.get$getter$()) && " "$type$.toObject(includeInstance, f)", "getter", JSGetterName(options, field), "type", SubmessageTypeRef(options, field)); } } else if (field->type() == FieldDescriptor::TYPE_BYTES) { // For bytes fields we want to always return the B64 data. printer->Print("msg.get$getter$()", "getter", JSGetterName(options, field, BYTES_B64)); } else { bool use_default = field->has_default_value(); if (field->file()->syntax() == FileDescriptor::SYNTAX_PROTO3 && // Repeated fields get initialized to their default in the constructor // (why?), so we emit a plain getField() call for them. !field->is_repeated()) { // Proto3 puts all defaults (including implicit defaults) in toObject(). // But for proto2 we leave the existing semantics unchanged: unset fields // without default are unset. use_default = true; } // We don't implement this by calling the accessors, because the semantics // of the accessors are changing independently of the toObject() semantics. // We are migrating the accessors to return defaults instead of null, but // it may take longer to migrate toObject (or we might not want to do it at // all). So we want to generate independent code. // The accessor for unset optional values without default should return // null. Those are converted to undefined in the generated object. if (!use_default) { printer->Print("(f = "); } GenerateFieldValueExpression(printer, "msg", field, use_default); if (!use_default) { printer->Print(") == null ? undefined : f"); } } } void Generator::GenerateObjectTypedef(const GeneratorOptions& options, io::Printer* printer, const Descriptor* desc) const { // TODO(b/122687752): Consider renaming nested messages called ObjectFormat // to prevent collisions. const std::string type_name = GetMessagePath(options, desc) + ".ObjectFormat"; printer->Print( "/**\n" " * The raw object form of $messageName$ as accepted by the `fromObject` " "method.\n" " * @record\n" " */\n" "$typeName$ = function() {\n", "messageName", desc->name(), "typeName", type_name); for (int i = 0; i < desc->field_count(); i++) { if (i > 0) { printer->Print("\n"); } printer->Print( " /** @type {$fieldType$|undefined} */\n" " this.$fieldName$;\n", "fieldName", JSObjectFieldName(options, desc->field(i)), // TODO(b/121097361): Add type checking for field values. "fieldType", "?"); } printer->Print("};\n\n"); } void Generator::GenerateClassFromObject(const GeneratorOptions& options, io::Printer* printer, const Descriptor* desc) const { printer->Print("if (jspb.Message.GENERATE_FROM_OBJECT) {\n\n"); GenerateObjectTypedef(options, printer, desc); printer->Print( "/**\n" " * Loads data from an object into a new instance of this proto.\n" " * @param {!$classname$.ObjectFormat} obj\n" " * The object representation of this proto to load the data from.\n" " * @return {!$classname$}\n" " */\n" "$classname$.fromObject = function(obj) {\n" " var msg = new $classname$();\n", "classname", GetMessagePath(options, desc)); for (int i = 0; i < desc->field_count(); i++) { const FieldDescriptor* field = desc->field(i); if (!IgnoreField(field)) { GenerateClassFieldFromObject(options, printer, field); } } printer->Print( " return msg;\n" "};\n" "}\n\n"); } void Generator::GenerateClassFieldFromObject( const GeneratorOptions& options, io::Printer* printer, const FieldDescriptor* field) const { if (field->is_map()) { const FieldDescriptor* value_field = MapFieldValue(field); if (value_field->type() == FieldDescriptor::TYPE_MESSAGE) { // Since the map values are of message type, we have to do some extra work // to recursively call fromObject() on them before setting the map field. printer->Print( " obj.$name$ && jspb.Message.setWrapperField(\n" " msg, $index$, jspb.Map.fromObject(obj.$name$, $fieldclass$, " "$fieldclass$.fromObject));\n", "name", JSObjectFieldName(options, field), "index", JSFieldIndex(field), "fieldclass", GetMessagePath(options, value_field->message_type())); } else { // `msg` is a newly-constructed message object that has not yet built any // map containers wrapping underlying arrays, so we can simply directly // set the array here without fear of a stale wrapper. printer->Print( " obj.$name$ && " "jspb.Message.setField(msg, $index$, obj.$name$);\n", "name", JSObjectFieldName(options, field), "index", JSFieldIndex(field)); } } else if (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) { // Message field (singular or repeated) if (field->is_repeated()) { { printer->Print( " obj.$name$ && " "jspb.Message.setRepeatedWrapperField(\n" " msg, $index$, obj.$name$.map(\n" " $fieldclass$.fromObject));\n", "name", JSObjectFieldName(options, field), "index", JSFieldIndex(field), "fieldclass", SubmessageTypeRef(options, 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)); } } else { // Simple (primitive) field. printer->Print( " obj.$name$ != null && jspb.Message.setField(msg, $index$, " "obj.$name$);\n", "name", JSObjectFieldName(options, field), "index", JSFieldIndex(field)); } } void Generator::GenerateClassRegistration(const GeneratorOptions& options, 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); } } } void Generator::GenerateClassFields(const GeneratorOptions& options, 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)); } } } void GenerateBytesWrapper(const GeneratorOptions& options, io::Printer* printer, const FieldDescriptor* field, BytesMode bytes_mode) { std::string type = JSFieldTypeAnnotation(options, field, /* is_setter_argument = */ false, /* force_present = */ false, /* singular_if_not_packed = */ false, bytes_mode); printer->Print( "/**\n" " * $fielddef$\n" "$comment$" " * This is a type-conversion wrapper around `get$defname$()`\n" " * @return {$type$}\n" " */\n" "$class$.prototype.get$name$ = function() {\n" " return /** @type {$type$} */ (jspb.Message.bytes$list$As$suffix$(\n" " this.get$defname$()));\n" "};\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)); } void Generator::GenerateClassField(const GeneratorOptions& options, io::Printer* printer, const FieldDescriptor* field) const { if (field->is_map()) { const FieldDescriptor* key_field = MapFieldKey(field); const FieldDescriptor* value_field = MapFieldValue(field); // Map field: special handling to instantiate the map object on demand. std::string key_type = JSFieldTypeAnnotation(options, key_field, /* is_setter_argument = */ false, /* force_present = */ true, /* singular_if_not_packed = */ false); std::string value_type = JSFieldTypeAnnotation(options, value_field, /* is_setter_argument = */ false, /* force_present = */ true, /* singular_if_not_packed = */ false); printer->Print( "/**\n" " * $fielddef$\n" " * @param {boolean=} opt_noLazyCreate Do not create the map if\n" " * empty, instead returning `undefined`\n" " * @return {!jspb.Map<$keytype$,$valuetype$>}\n" " */\n", "fielddef", FieldDefinition(options, field), "keytype", key_type, "valuetype", value_type); 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, "valuetype", value_type); printer->Annotate("gettername", field); printer->Print( " jspb.Message.getMapField(this, $index$, opt_noLazyCreate", "index", JSFieldIndex(field)); if (value_field->type() == FieldDescriptor::TYPE_MESSAGE) { printer->Print( ",\n" " $messageType$", "messageType", GetMessagePath(options, value_field->message_type())); } else { printer->Print( ",\n" " null"); } printer->Print("));\n"); printer->Print( "};\n" "\n" "\n"); } else if (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) { // Message field: special handling in order to wrap the underlying data // array with a message object. printer->Print( "/**\n" " * $fielddef$\n" "$comment$" " * @return {$type$}\n" " */\n", "fielddef", FieldDefinition(options, field), "comment", FieldComments(field, BYTES_DEFAULT), "type", JSFieldTypeAnnotation(options, field, /* is_setter_argument = */ false, /* force_present = */ false, /* singular_if_not_packed = */ false)); printer->Print( "$class$.prototype.$gettername$ = function() {\n" " return /** @type{$type$} */ (\n" " jspb.Message.get$rpt$WrapperField(this, $wrapperclass$, " "$index$$required$));\n" "};\n" "\n" "\n", "class", GetMessagePath(options, field->containing_type()), "gettername", "get" + JSGetterName(options, field), "type", JSFieldTypeAnnotation(options, field, /* is_setter_argument = */ false, /* force_present = */ false, /* singular_if_not_packed = */ false), "rpt", (field->is_repeated() ? "Repeated" : ""), "index", JSFieldIndex(field), "wrapperclass", SubmessageTypeRef(options, field), "required", (field->label() == FieldDescriptor::LABEL_REQUIRED ? ", 1" : "")); printer->Annotate("gettername", field); printer->Print( "/**\n" " * @param {$optionaltype$} value\n" " * @return {!$class$} returns this\n" "*/\n" "$class$.prototype.$settername$ = function(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", (InRealOneof(field) ? "Oneof" : ""), "repeatedtag", (field->is_repeated() ? "Repeated" : "")); printer->Annotate("settername", field); printer->Print( "this, $index$$oneofgroup$, value);\n" "};\n" "\n" "\n", "index", JSFieldIndex(field), "oneofgroup", (InRealOneof(field) ? (", " + JSOneofArray(options, field)) : "")); if (field->is_repeated()) { GenerateRepeatedMessageHelperMethods(options, printer, field); } } else { bool untyped = false; // Simple (primitive) field, either singular or repeated. // TODO(b/26173701): Always use BYTES_DEFAULT for the getter return type; // at this point we "lie" to non-binary users and tell the return // type is always base64 string, pending a LSC to migrate to typed getters. BytesMode bytes_mode = field->type() == FieldDescriptor::TYPE_BYTES && !options.binary ? BYTES_B64 : BYTES_DEFAULT; std::string typed_annotation = JSFieldTypeAnnotation(options, field, /* is_setter_argument = */ false, /* force_present = */ false, /* singular_if_not_packed = */ false, /* bytes_mode = */ bytes_mode); if (untyped) { printer->Print( "/**\n" " * @return {?} Raw field, untyped.\n" " */\n"); } else { printer->Print( "/**\n" " * $fielddef$\n" "$comment$" " * @return {$type$}\n" " */\n", "fielddef", FieldDefinition(options, field), "comment", FieldComments(field, bytes_mode), "type", typed_annotation); } printer->Print("$class$.prototype.$gettername$ = function() {\n", "class", GetMessagePath(options, field->containing_type()), "gettername", "get" + JSGetterName(options, field)); printer->Annotate("gettername", field); if (untyped) { printer->Print(" return "); } else { printer->Print(" return /** @type {$type$} */ (", "type", typed_annotation); } bool use_default = !ReturnsNullWhenUnset(options, field); // Raw fields with no default set should just return undefined. if (untyped && !field->has_default_value()) { use_default = false; } // Repeated fields get initialized to their default in the constructor // (why?), so we emit a plain getField() call for them. if (field->is_repeated()) { use_default = false; } GenerateFieldValueExpression(printer, "this", field, use_default); if (untyped) { printer->Print( ";\n" "};\n" "\n" "\n"); } else { printer->Print( ");\n" "};\n" "\n" "\n"); } if (field->type() == FieldDescriptor::TYPE_BYTES && !untyped) { GenerateBytesWrapper(options, printer, field, BYTES_B64); GenerateBytesWrapper(options, printer, field, BYTES_U8); } printer->Print( "/**\n" " * @param {$optionaltype$} value\n" " * @return {!$class$} returns this\n" " */\n", "class", GetMessagePath(options, field->containing_type()), "optionaltype", untyped ? "*" : JSFieldTypeAnnotation(options, field, /* is_setter_argument = */ true, /* force_present = */ false, /* singular_if_not_packed = */ false)); 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. printer->Print( "$class$.prototype.$settername$ = function(value) {\n" " return jspb.Message.setProto3$typetag$Field(this, $index$, " "value);" "\n" "};\n" "\n" "\n", "class", GetMessagePath(options, field->containing_type()), "settername", "set" + JSGetterName(options, field), "typetag", JSTypeTag(field), "index", JSFieldIndex(field)); printer->Annotate("settername", field); } else { // Otherwise, use the regular setField function. printer->Print( "$class$.prototype.$settername$ = function(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)); printer->Annotate("settername", field); printer->Print( "$oneofgroup$, $type$value$rptvalueinit$$typeclose$);\n" "};\n" "\n" "\n", "type", untyped ? "/** @type{string|number|boolean|Array|undefined} */(" : "", "typeclose", untyped ? ")" : "", "oneofgroup", (InRealOneof(field) ? (", " + JSOneofArray(options, field)) : ""), "rptvalueinit", (field->is_repeated() ? " || []" : "")); } if (untyped) { printer->Print( "/**\n" " * Clears the value.\n" " * @return {!$class$} returns this\n" " */\n", "class", GetMessagePath(options, field->containing_type())); } if (field->is_repeated()) { GenerateRepeatedPrimitiveHelperMethods(options, printer, field, untyped); } } // Generate clearFoo() method for map fields, repeated fields, and other // fields with presence. if (field->is_map()) { // clang-format off printer->Print( "/**\n" " * Clears values from the map. The map will be non-null.\n" " * @return {!$class$} returns this\n" " */\n" "$class$.prototype.$clearername$ = function() {\n" " this.$gettername$().clear();\n" " return this;" "};\n" "\n" "\n", "class", GetMessagePath(options, field->containing_type()), "clearername", "clear" + JSGetterName(options, field), "gettername", "get" + JSGetterName(options, field)); // clang-format on printer->Annotate("clearername", field); } else if (field->is_repeated() || (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE && !field->is_required())) { // Fields where we can delegate to the regular setter. // clang-format off printer->Print( "/**\n" " * $jsdoc$\n" " * @return {!$class$} returns this\n" " */\n" "$class$.prototype.$clearername$ = function() {\n" " return this.$settername$($clearedvalue$);\n" "};\n" "\n" "\n", "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), "settername", "set" + JSGetterName(options, field), "clearedvalue", (field->is_repeated() ? "[]" : "undefined")); // clang-format on printer->Annotate("clearername", field); } else if (HasFieldPresence(options, field)) { // Fields where we can't delegate to the regular setter because it doesn't // accept "undefined" as an argument. // clang-format off printer->Print( "/**\n" " * Clears the field making it undefined.\n" " * @return {!$class$} returns this\n" " */\n" "$class$.prototype.$clearername$ = function() {\n" " return jspb.Message.set$maybeoneof$Field(this, " "$index$$maybeoneofgroup$, ", "class", GetMessagePath(options, field->containing_type()), "clearername", "clear" + JSGetterName(options, field), "maybeoneof", (InRealOneof(field) ? "Oneof" : ""), "maybeoneofgroup", (InRealOneof(field) ? (", " + JSOneofArray(options, field)) : ""), "index", JSFieldIndex(field)); // clang-format on printer->Annotate("clearername", field); printer->Print( "$clearedvalue$);\n" "};\n" "\n" "\n", "clearedvalue", (field->is_repeated() ? "[]" : "undefined")); } if (HasFieldPresence(options, field)) { printer->Print( "/**\n" " * Returns whether this field is set.\n" " * @return {boolean}\n" " */\n" "$class$.prototype.$hasername$ = function() {\n" " return jspb.Message.getField(this, $index$) != null;\n" "};\n" "\n" "\n", "class", GetMessagePath(options, field->containing_type()), "hasername", "has" + JSGetterName(options, field), "index", JSFieldIndex(field)); printer->Annotate("hasername", field); } } void Generator::GenerateRepeatedPrimitiveHelperMethods( const GeneratorOptions& options, io::Printer* printer, const FieldDescriptor* field, bool untyped) const { // clang-format off printer->Print( "/**\n" " * @param {$optionaltype$} value\n" " * @param {number=} opt_index\n" " * @return {!$class$} returns this\n" " */\n" "$class$.prototype.$addername$ = function(value, opt_index) {\n" " return jspb.Message.addToRepeatedField(this, " "$index$", "class", GetMessagePath(options, field->containing_type()), "addername", "add" + JSGetterName(options, field, BYTES_DEFAULT, /* drop_list = */ true), "optionaltype", JSFieldTypeAnnotation( options, field, /* is_setter_argument = */ false, /* force_present = */ true, /* singular_if_not_packed = */ false, BYTES_DEFAULT, /* force_singular = */ true), "index", JSFieldIndex(field)); printer->Annotate("addername", field); printer->Print( "$oneofgroup$, $type$value$rptvalueinit$$typeclose$, " "opt_index);\n" "};\n" "\n" "\n", "type", untyped ? "/** @type{string|number|boolean|!Uint8Array} */(" : "", "typeclose", untyped ? ")" : "", "oneofgroup", (InRealOneof(field) ? (", " + JSOneofArray(options, field)) : ""), "rptvalueinit", ""); // clang-format on } void Generator::GenerateRepeatedMessageHelperMethods( const GeneratorOptions& options, io::Printer* printer, const FieldDescriptor* field) const { 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" " return jspb.Message.addTo$repeatedtag$WrapperField(", "optionaltype", JSTypeName(options, field, BYTES_DEFAULT), "class", GetMessagePath(options, field->containing_type()), "addername", "add" + JSGetterName(options, field, BYTES_DEFAULT, /* drop_list = */ true), "repeatedtag", (field->is_repeated() ? "Repeated" : "")); printer->Annotate("addername", field); printer->Print( "this, $index$$oneofgroup$, opt_value, $ctor$, opt_index);\n" "};\n" "\n" "\n", "index", JSFieldIndex(field), "oneofgroup", (InRealOneof(field) ? (", " + JSOneofArray(options, field)) : ""), "ctor", GetMessagePath(options, field->message_type())); } void Generator::GenerateClassExtensionFieldInfo(const GeneratorOptions& options, io::Printer* printer, const Descriptor* desc) const { if (IsExtendable(desc)) { printer->Print( "\n" "/**\n" " * The extensions registered with this message class. This is a " "map of\n" " * extension field number to fieldInfo object.\n" " *\n" " * For example:\n" " * { 123: {fieldIndex: 123, fieldName: {my_field_name: 0}, " "ctor: proto.example.MyMessage} }\n" " *\n" " * fieldName contains the JsCompiler renamed field name property " "so that it\n" " * works in OPTIMIZED mode.\n" " *\n" " * @type {!Object}\n" " */\n" "$class$.extensions = {};\n" "\n", "class", GetMessagePath(options, desc)); printer->Print( "\n" "/**\n" " * The extensions registered with this message class. This is a " "map of\n" " * extension field number to fieldInfo object.\n" " *\n" " * For example:\n" " * { 123: {fieldIndex: 123, fieldName: {my_field_name: 0}, " "ctor: proto.example.MyMessage} }\n" " *\n" " * fieldName contains the JsCompiler renamed field name property " "so that it\n" " * works in OPTIMIZED mode.\n" " *\n" " * @type {!Object}\n" " */\n" "$class$.extensionsBinary = {};\n" "\n", "class", GetMessagePath(options, desc)); } } void Generator::GenerateClassDeserializeBinary(const GeneratorOptions& options, io::Printer* printer, const Descriptor* desc) const { // TODO(cfallin): Handle lazy decoding when requested by field option and/or // by default for 'bytes' fields and packed repeated fields. 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" " var reader = new jspb.BinaryReader(bytes);\n" " var msg = new $class$;\n" " return $class$.deserializeBinaryFromReader(msg, reader);\n" "};\n" "\n" "\n" "/**\n" " * Deserializes binary data (in protobuf wire format) from the\n" " * given reader into the given message object.\n" " * @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)); printer->Print( " if (reader.isEndGroup()) {\n" " break;\n" " }\n" " var field = reader.getFieldNumber();\n" " switch (field) {\n"); for (int i = 0; i < desc->field_count(); i++) { if (!IgnoreField(desc->field(i))) { GenerateClassDeserializeBinaryField(options, printer, desc->field(i)); } } printer->Print(" default:\n"); if (IsExtendable(desc)) { printer->Print( " jspb.Message.readBinaryExtension(msg, reader,\n" " $extobj$Binary,\n" " $class$.prototype.getExtension,\n" " $class$.prototype.setExtension);\n" " break;\n" " }\n", "extobj", JSExtensionsObjectName(options, desc->file(), desc), "class", GetMessagePath(options, desc)); } else { printer->Print( " reader.skipField();\n" " break;\n" " }\n"); } printer->Print( " }\n" " return msg;\n" "};\n" "\n" "\n"); } void Generator::GenerateClassDeserializeBinaryField( const GeneratorOptions& options, io::Printer* printer, const FieldDescriptor* field) const { printer->Print(" case $num$:\n", "num", StrCat(field->number())); if (field->is_map()) { const FieldDescriptor* key_field = MapFieldKey(field); const FieldDescriptor* value_field = MapFieldValue(field); printer->Print( " var value = msg.get$name$();\n" " reader.readMessage(value, function(message, reader) {\n", "name", JSGetterName(options, field)); printer->Print( " jspb.Map.deserializeBinary(message, reader, " "$keyReaderFn$, $valueReaderFn$", "keyReaderFn", JSBinaryReaderMethodName(options, key_field), "valueReaderFn", JSBinaryReaderMethodName(options, value_field)); if (value_field->type() == FieldDescriptor::TYPE_MESSAGE) { printer->Print(", $messageType$.deserializeBinaryFromReader", "messageType", GetMessagePath(options, value_field->message_type())); } else { printer->Print(", null"); } printer->Print(", $defaultKey$", "defaultKey", JSFieldDefault(key_field)); if (value_field->type() == FieldDescriptor::TYPE_MESSAGE) { printer->Print(", new $messageType$()", "messageType", GetMessagePath(options, value_field->message_type())); } else { printer->Print(", $defaultValue$", "defaultValue", JSFieldDefault(value_field)); } printer->Print(");\n"); printer->Print(" });\n"); } else { if (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) { printer->Print( " var value = new $fieldclass$;\n" " reader.read$msgOrGroup$($grpfield$value," "$fieldclass$.deserializeBinaryFromReader);\n", "fieldclass", SubmessageTypeRef(options, field), "msgOrGroup", (field->type() == FieldDescriptor::TYPE_GROUP) ? "Group" : "Message", "grpfield", (field->type() == FieldDescriptor::TYPE_GROUP) ? (StrCat(field->number()) + ", ") : ""); } else if (field->is_packable()) { printer->Print( " var values = /** @type {$fieldtype$} */ " "(reader.isDelimited() " "? reader.readPacked$reader$() : [reader.read$reader$()]);\n", "fieldtype", JSFieldTypeAnnotation(options, field, false, true, /* singular_if_not_packed */ false, BYTES_U8), "reader", JSBinaryReaderMethodType(field)); } else { printer->Print( " var value = /** @type {$fieldtype$} */ " "(reader.read$reader$());\n", "fieldtype", JSFieldTypeAnnotation(options, field, false, true, /* singular_if_not_packed */ true, BYTES_U8), "reader", JSBinaryReadWriteMethodName(field, /* is_writer = */ false)); } if (field->is_packable()) { printer->Print( " for (var i = 0; i < values.length; i++) {\n" " msg.add$name$(values[i]);\n" " }\n", "name", JSGetterName(options, field, BYTES_DEFAULT, /* drop_list = */ true)); } else if (field->is_repeated()) { printer->Print( " msg.add$name$(value);\n", "name", JSGetterName(options, field, BYTES_DEFAULT, /* drop_list = */ true)); } else { // Singular fields, and packed repeated fields, receive a |value| either // as the field's value or as the array of all the field's values; set // this as the field's value directly. printer->Print(" msg.set$name$(value);\n", "name", JSGetterName(options, field)); } } printer->Print(" break;\n"); } void Generator::GenerateClassSerializeBinary(const GeneratorOptions& options, io::Printer* printer, const Descriptor* desc) const { printer->Print( "/**\n" " * Serializes the message to binary data (in protobuf wire format).\n" " * @return {!Uint8Array}\n" " */\n" "$class$.prototype.serializeBinary = function() {\n" " var writer = new jspb.BinaryWriter();\n" " $class$.serializeBinaryToWriter(this, writer);\n" " return writer.getResultBuffer();\n" "};\n" "\n" "\n" "/**\n" " * Serializes the given message to binary data (in protobuf wire\n" " * format), writing to the given BinaryWriter.\n" " * @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)); for (int i = 0; i < desc->field_count(); i++) { if (!IgnoreField(desc->field(i))) { GenerateClassSerializeBinaryField(options, printer, desc->field(i)); } } if (IsExtendable(desc)) { printer->Print( " jspb.Message.serializeBinaryExtensions(message, writer,\n" " $extobj$Binary, $class$.prototype.getExtension);\n", "extobj", JSExtensionsObjectName(options, desc->file(), desc), "class", GetMessagePath(options, desc)); } printer->Print( "};\n" "\n" "\n"); } void Generator::GenerateClassSerializeBinaryField( const GeneratorOptions& options, io::Printer* printer, const FieldDescriptor* field) const { if (HasFieldPresence(options, field) && field->cpp_type() != FieldDescriptor::CPPTYPE_MESSAGE) { std::string typed_annotation = JSFieldTypeAnnotation(options, field, /* is_setter_argument = */ false, /* force_present = */ false, /* singular_if_not_packed = */ false, /* bytes_mode = */ BYTES_DEFAULT); printer->Print( " f = /** @type {$type$} */ " "(jspb.Message.getField(message, $index$));\n", "index", JSFieldIndex(field), "type", typed_annotation); } else { printer->Print( " f = message.get$name$($nolazy$);\n", "name", JSGetterName(options, field, BYTES_U8), // No lazy creation for maps containers -- fastpath the empty case. "nolazy", field->is_map() ? "true" : ""); } // Print an `if (condition)` statement that evaluates to true if the field // goes on the wire. if (field->is_map()) { printer->Print(" if (f && f.getLength() > 0) {\n"); } else if (field->is_repeated()) { printer->Print(" if (f.length > 0) {\n"); } else { if (HasFieldPresence(options, field)) { printer->Print(" if (f != null) {\n"); } else { // No field presence: serialize onto the wire only if value is // non-default. Defaults are documented here: // https://goto.google.com/lhdfm switch (field->cpp_type()) { case FieldDescriptor::CPPTYPE_INT32: case FieldDescriptor::CPPTYPE_INT64: case FieldDescriptor::CPPTYPE_UINT32: case FieldDescriptor::CPPTYPE_UINT64: { if (IsIntegralFieldWithStringJSType(field)) { // We can use `parseInt` here even though it will not be precise for // 64-bit quantities because we are only testing for zero/nonzero, // and JS numbers (64-bit floating point values, i.e., doubles) are // integer-precise in the range that includes zero. printer->Print(" if (parseInt(f, 10) !== 0) {\n"); } else { printer->Print(" if (f !== 0) {\n"); } break; } case FieldDescriptor::CPPTYPE_ENUM: case FieldDescriptor::CPPTYPE_FLOAT: case FieldDescriptor::CPPTYPE_DOUBLE: printer->Print(" if (f !== 0.0) {\n"); break; case FieldDescriptor::CPPTYPE_BOOL: printer->Print(" if (f) {\n"); break; case FieldDescriptor::CPPTYPE_STRING: printer->Print(" if (f.length > 0) {\n"); break; default: assert(false); break; } } } // Write the field on the wire. if (field->is_map()) { const FieldDescriptor* key_field = MapFieldKey(field); const FieldDescriptor* value_field = MapFieldValue(field); printer->Print( " f.serializeBinary($index$, writer, " "$keyWriterFn$, $valueWriterFn$", "index", StrCat(field->number()), "keyWriterFn", JSBinaryWriterMethodName(options, key_field), "valueWriterFn", JSBinaryWriterMethodName(options, value_field)); if (value_field->type() == FieldDescriptor::TYPE_MESSAGE) { printer->Print(", $messageType$.serializeBinaryToWriter", "messageType", GetMessagePath(options, value_field->message_type())); } printer->Print(");\n"); } else { printer->Print( " writer.write$method$(\n" " $index$,\n" " f", "method", JSBinaryReadWriteMethodName(field, /* is_writer = */ true), "index", StrCat(field->number())); if (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE && !field->is_map()) { printer->Print( ",\n" " $submsg$.serializeBinaryToWriter\n", "submsg", SubmessageTypeRef(options, field)); } else { printer->Print("\n"); } printer->Print(" );\n"); } // Close the `if`. printer->Print(" }\n"); } void Generator::GenerateEnum(const GeneratorOptions& options, io::Printer* printer, const EnumDescriptor* enumdesc) const { printer->Print( "/**\n" " * @enum {number}\n" " */\n" "$enumprefix$$name$ = {\n", "enumprefix", GetEnumPathPrefix(options, enumdesc), "name", enumdesc->name()); printer->Annotate("name", enumdesc); std::set used_name; std::vector valid_index; for (int i = 0; i < enumdesc->value_count(); i++) { if (enumdesc->options().allow_alias() && !used_name.insert(ToEnumCase(enumdesc->value(i)->name())).second) { continue; } valid_index.push_back(i); } for (auto i : valid_index) { const EnumValueDescriptor* value = enumdesc->value(i); printer->Print(" $name$: $value$$comma$\n", "name", ToEnumCase(value->name()), "value", StrCat(value->number()), "comma", (i == valid_index.back()) ? "" : ","); printer->Annotate("name", value); } printer->Print( "};\n" "\n"); } void Generator::GenerateExtension(const GeneratorOptions& options, io::Printer* printer, const FieldDescriptor* field) const { std::string extension_scope = (field->extension_scope() ? GetMessagePath(options, field->extension_scope()) : GetNamespace(options, field->file())); 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" " * @type {!jspb.ExtensionFieldInfo<$extensionType$>}\n" " */\n" "$class$.$name$ = new jspb.ExtensionFieldInfo(\n", "nameInComment", extension_object_name, "name", extension_object_name, "class", extension_scope, "extensionType", JSFieldTypeAnnotation(options, field, /* is_setter_argument = */ false, /* force_present = */ true, /* singular_if_not_packed = */ false)); printer->Annotate("name", field); printer->Print( " $index$,\n" " {$name$: 0},\n" " $ctor$,\n" " /** @type {?function((boolean|undefined),!jspb.Message=): " "!Object} */ (\n" " $toObject$),\n" " $repeated$);\n", "index", StrCat(field->number()), "name", extension_object_name, "ctor", (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE ? SubmessageTypeRef(options, field) : std::string("null")), "toObject", (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE ? (SubmessageTypeRef(options, field) + ".toObject") : std::string("null")), "repeated", (field->is_repeated() ? "1" : "0")); printer->Print( "\n" "$extendName$Binary[$index$] = new jspb.ExtensionFieldBinaryInfo(\n" " $class$.$name$,\n" " $binaryReaderFn$,\n" " $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", (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) ? (SubmessageTypeRef(options, field) + ".serializeBinaryToWriter") : "undefined", "binaryMessageDeserializeFn", (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) ? (SubmessageTypeRef(options, field) + ".deserializeBinaryFromReader") : "undefined"); printer->Print(" $isPacked$);\n", "isPacked", (field->is_packed() ? "true" : "false")); printer->Print( "// This registers the extension field with the extended class, so that\n" "// 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); } bool GeneratorOptions::ParseFromOptions( const std::vector >& options, std::string* error) { for (int i = 0; i < options.size(); i++) { if (options[i].first == "add_require_for_enums") { if (options[i].second != "") { *error = "Unexpected option value for add_require_for_enums"; return false; } add_require_for_enums = true; } else if (options[i].first == "binary") { if (options[i].second != "") { *error = "Unexpected option value for binary"; return false; } binary = true; } else if (options[i].first == "testonly") { if (options[i].second != "") { *error = "Unexpected option value for testonly"; return false; } testonly = true; } else if (options[i].first == "error_on_name_conflict") { GOOGLE_LOG(WARNING) << "Ignoring error_on_name_conflict option, this " "will be removed in a future release"; } else if (options[i].first == "output_dir") { output_dir = options[i].second; } else if (options[i].first == "namespace_prefix") { namespace_prefix = options[i].second; } else if (options[i].first == "library") { library = options[i].second; } else if (options[i].first == "import_style") { if (options[i].second == "closure") { import_style = kImportClosure; } else if (options[i].second == "commonjs") { import_style = kImportCommonJs; } else if (options[i].second == "commonjs_strict") { import_style = kImportCommonJsStrict; } else if (options[i].second == "browser") { import_style = kImportBrowser; } else if (options[i].second == "es6") { import_style = kImportEs6; } else { *error = "Unknown import style " + options[i].second + ", expected " + "one of: closure, commonjs, browser, es6."; } } else if (options[i].first == "extension") { extension = options[i].second; } else if (options[i].first == "one_output_file_per_input_file") { if (!options[i].second.empty()) { *error = "Unexpected option value for one_output_file_per_input_file"; return false; } one_output_file_per_input_file = true; } else if (options[i].first == "annotate_code") { if (!options[i].second.empty()) { *error = "Unexpected option value for annotate_code"; return false; } annotate_code = true; } else { // Assume any other option is an output directory, as long as it is a bare // `key` rather than a `key=value` option. if (options[i].second != "") { *error = "Unknown option: " + options[i].first; return false; } output_dir = options[i].first; } } if (import_style != kImportClosure && (add_require_for_enums || testonly || !library.empty() || extension != ".js" || one_output_file_per_input_file)) { *error = "The add_require_for_enums, testonly, library, extension, and " "one_output_file_per_input_file options should only be " "used for import_style=closure"; return false; } return true; } GeneratorOptions::OutputMode GeneratorOptions::output_mode() const { // We use one output file per input file if we are not using Closure or if // this is explicitly requested. if (import_style != kImportClosure || one_output_file_per_input_file) { return kOneOutputFilePerInputFile; } // If a library name is provided, we put everything in that one file. if (!library.empty()) { return kEverythingInOneFile; } // Otherwise, we create one output file per SCC. return kOneOutputFilePerSCC; } void Generator::GenerateFilesInDepOrder( const GeneratorOptions& options, io::Printer* printer, const std::vector& files) const { // Build a std::set over all files so that the DFS can detect when it recurses // into a dep not specified in the user's command line. std::set all_files(files.begin(), files.end()); // Track the in-progress set of files that have been generated already. std::set generated; for (int i = 0; i < files.size(); i++) { GenerateFileAndDeps(options, printer, files[i], &all_files, &generated); } } void Generator::GenerateFileAndDeps( const GeneratorOptions& options, io::Printer* printer, const FileDescriptor* root, std::set* all_files, std::set* generated) const { // Skip if already generated. if (generated->find(root) != generated->end()) { return; } generated->insert(root); // Generate all dependencies before this file's content. for (int i = 0; i < root->dependency_count(); i++) { const FileDescriptor* dep = root->dependency(i); GenerateFileAndDeps(options, printer, dep, all_files, generated); } // Generate this file's content. Only generate if the file is part of the // 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); } } bool Generator::GenerateFile(const FileDescriptor* file, const GeneratorOptions& options, GeneratorContext* context, bool use_short_name) const { std::string filename = options.output_dir + "/" + GetJSFilename(options, use_short_name ? file->name().substr(file->name().rfind('/')) : file->name()); std::unique_ptr output(context->Open(filename)); GOOGLE_CHECK(output); GeneratedCodeInfo annotations; io::AnnotationProtoCollector annotation_collector( &annotations); io::Printer printer(output.get(), '$', options.annotate_code ? &annotation_collector : nullptr); GenerateFile(options, &printer, file); if (printer.failed()) { return false; } if (options.annotate_code) { EmbedCodeAnnotations(annotations, &printer); } return true; } void Generator::GenerateFile(const GeneratorOptions& options, io::Printer* printer, const FileDescriptor* file) const { GenerateHeader(options, file, printer); // Generate "require" statements. 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"); // Do not use global scope in strict mode if (options.import_style == GeneratorOptions::kImportCommonJsStrict) { printer->Print("var proto = {};\n\n"); } else { // To get the global object we call a function with .call(null), this will set "this" inside the // function to the global object. // This does not work if we are running in strict mode ("use strict"), // so we fallback to the following things (in order from first to last): // - window: defined in browsers // - global: defined in most server side environments like NodeJS // - self: defined inside Web Workers (WorkerGlobalScope) // - Function('return this')(): this will work on most platforms, but it may be blocked by things like CSP. // Function('') is almost the same as eval('') printer->Print( "var global = (function() {\n" " if (this) { return this; }\n" " if (typeof window !== 'undefined') { return window; }\n" " if (typeof global !== 'undefined') { return global; }\n" " if (typeof self !== 'undefined') { return self; }\n" " return Function('return this')();\n" "}.call(null));\n\n"); } for (int i = 0; i < file->dependency_count(); i++) { const std::string& name = file->dependency(i)->name(); printer->Print( "var $alias$ = require('$file$');\n" "goog.object.extend(proto, $alias$);\n", "alias", ModuleAlias(name), "file", GetRootPath(file->name(), name) + GetJSFilename(options, name)); } } std::set provided; std::set extensions; for (int i = 0; i < file->extension_count(); i++) { // We honor the jspb::ignore option here only when working with // Closure-style imports. Use of this option is discouraged and so we want // to avoid adding new support for it. if (options.import_style == GeneratorOptions::kImportClosure && IgnoreField(file->extension(i))) { continue; } provided.insert(GetNamespace(options, file) + "." + JSObjectFieldName(options, file->extension(i))); extensions.insert(file->extension(i)); } FindProvidesForFile(options, printer, file, &provided); GenerateProvides(options, printer, &provided); std::vector files; files.push_back(file); if (options.import_style == GeneratorOptions::kImportClosure) { GenerateRequiresForLibrary(options, printer, files, &provided); } GenerateClassesAndEnums(options, 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); } // if provided is empty, do not export anything if (options.import_style == GeneratorOptions::kImportCommonJs && !provided.empty()) { printer->Print("goog.object.extend(exports, $package$);\n", "package", GetNamespace(options, file)); } else if (options.import_style == GeneratorOptions::kImportCommonJsStrict) { 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, const std::string& parameter, GeneratorContext* context, std::string* error) const { std::vector > option_pairs; ParseGeneratorParameter(parameter, &option_pairs); GeneratorOptions options; if (!options.ParseFromOptions(option_pairs, error)) { return false; } if (options.output_mode() == GeneratorOptions::kEverythingInOneFile) { // All output should go in a single file. std::string filename = options.output_dir + "/" + options.library + options.GetFileNameExtension(); std::unique_ptr output(context->Open(filename)); GOOGLE_CHECK(output.get()); GeneratedCodeInfo annotations; io::AnnotationProtoCollector annotation_collector( &annotations); io::Printer printer( output.get(), '$', options.annotate_code ? &annotation_collector : nullptr); // Pull out all extensions -- we need these to generate all // provides/requires. std::vector extensions; for (int i = 0; i < files.size(); i++) { for (int j = 0; j < files[i]->extension_count(); j++) { const FieldDescriptor* extension = files[i]->extension(j); extensions.push_back(extension); } } if (files.size() == 1) { GenerateHeader(options, files[0], &printer); } else { GenerateHeader(options, nullptr, &printer); } std::set provided; FindProvides(options, &printer, files, &provided); FindProvidesForFields(options, &printer, extensions, &provided); GenerateProvides(options, &printer, &provided); GenerateTestOnly(options, &printer); GenerateRequiresForLibrary(options, &printer, files, &provided); GenerateFilesInDepOrder(options, &printer, files); for (int i = 0; i < extensions.size(); i++) { if (ShouldGenerateExtension(extensions[i])) { GenerateExtension(options, &printer, extensions[i]); } } if (printer.failed()) { return false; } if (options.annotate_code) { EmbedCodeAnnotations(annotations, &printer); } } else if (options.output_mode() == GeneratorOptions::kOneOutputFilePerSCC) { std::set have_printed; SCCAnalyzer analyzer; std::map allowed_map; if (!GenerateJspbAllowedMap(options, files, &allowed_map, &analyzer)) { return false; } bool generated = false; for (int i = 0; i < files.size(); i++) { const FileDescriptor* file = files[i]; // Force well known type to generate in a whole file. if (IsWellKnownTypeFile(file)) { if (!GenerateFile(file, options, context, true)) { return false; } generated = true; continue; } for (int j = 0; j < file->message_type_count(); j++) { const Descriptor* desc = file->message_type(j); if (have_printed.count(desc) || allowed_map.count(analyzer.GetSCC(desc)) == 0) { continue; } generated = true; const SCC* scc = analyzer.GetSCC(desc); const std::string& filename = allowed_map[scc]; std::unique_ptr output( context->Open(filename)); GOOGLE_CHECK(output.get()); GeneratedCodeInfo annotations; io::AnnotationProtoCollector annotation_collector( &annotations); io::Printer printer( output.get(), '$', options.annotate_code ? &annotation_collector : nullptr); GenerateHeader(options, file, &printer); std::set provided; for (auto one_desc : scc->descriptors) { if (one_desc->containing_type() == nullptr) { FindProvidesForMessage(options, &printer, one_desc, &provided); } } GenerateProvides(options, &printer, &provided); GenerateTestOnly(options, &printer); GenerateRequiresForSCC(options, &printer, scc, &provided); for (auto one_desc : scc->descriptors) { if (one_desc->containing_type() == nullptr) { GenerateClassConstructorAndDeclareExtensionFieldInfo( options, &printer, one_desc); } } for (auto one_desc : scc->descriptors) { if (one_desc->containing_type() == nullptr) { GenerateClass(options, &printer, one_desc); } } for (auto one_desc : scc->descriptors) { have_printed.insert(one_desc); } if (printer.failed()) { return false; } if (options.annotate_code) { EmbedCodeAnnotations(annotations, &printer); } } for (int j = 0; j < file->enum_type_count(); j++) { const EnumDescriptor* enumdesc = file->enum_type(j); if (allowed_map.count(enumdesc) == 0) { continue; } generated = true; const std::string& filename = allowed_map[enumdesc]; std::unique_ptr output( context->Open(filename)); GOOGLE_CHECK(output.get()); GeneratedCodeInfo annotations; io::AnnotationProtoCollector annotation_collector( &annotations); io::Printer printer( output.get(), '$', options.annotate_code ? &annotation_collector : nullptr); GenerateHeader(options, file, &printer); std::set provided; FindProvidesForEnum(options, &printer, enumdesc, &provided); GenerateProvides(options, &printer, &provided); GenerateTestOnly(options, &printer); GenerateEnum(options, &printer, enumdesc); if (printer.failed()) { return false; } if (options.annotate_code) { EmbedCodeAnnotations(annotations, &printer); } } // File-level extensions (message-level extensions are generated under // the enclosing message). if (allowed_map.count(file) == 1) { generated = true; const std::string& filename = allowed_map[file]; std::unique_ptr output( context->Open(filename)); GOOGLE_CHECK(output.get()); GeneratedCodeInfo annotations; io::AnnotationProtoCollector annotation_collector( &annotations); io::Printer printer( output.get(), '$', options.annotate_code ? &annotation_collector : nullptr); GenerateHeader(options, file, &printer); std::set provided; std::vector fields; for (int j = 0; j < files[i]->extension_count(); j++) { if (ShouldGenerateExtension(files[i]->extension(j))) { fields.push_back(files[i]->extension(j)); } } FindProvidesForFields(options, &printer, fields, &provided); GenerateProvides(options, &printer, &provided); GenerateTestOnly(options, &printer); GenerateRequiresForExtensions(options, &printer, fields, &provided); for (int j = 0; j < files[i]->extension_count(); j++) { if (ShouldGenerateExtension(files[i]->extension(j))) { GenerateExtension(options, &printer, files[i]->extension(j)); } } if (options.annotate_code) { EmbedCodeAnnotations(annotations, &printer); } } } if (!generated) { std::string filename = options.output_dir + "/" + "empty_no_content_void_file" + options.GetFileNameExtension(); std::unique_ptr output(context->Open(filename)); } } else /* options.output_mode() == kOneOutputFilePerInputFile */ { // Generate one output file per input (.proto) file. for (int i = 0; i < files.size(); i++) { const FileDescriptor* file = files[i]; if (!GenerateFile(file, options, context, false)) { return false; } } } return true; } } // namespace js } // namespace compiler } // namespace protobuf } // namespace google