blob: 351c39660d424a4ffef3615c45b9b1bc5e8c21a6 [file] [log] [blame]
// 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 <google/protobuf/compiler/js/js_generator.h>
#include <assert.h>
#include <algorithm>
#include <limits>
#include <map>
#include <memory>
#ifndef _SHARED_PTR_H
#include <google/protobuf/stubs/shared_ptr.h>
#endif
#include <string>
#include <utility>
#include <vector>
#include <google/protobuf/stubs/logging.h>
#include <google/protobuf/stubs/common.h>
#include <google/protobuf/stubs/stringprintf.h>
#include <google/protobuf/io/printer.h>
#include <google/protobuf/io/zero_copy_stream.h>
#include <google/protobuf/descriptor.pb.h>
#include <google/protobuf/descriptor.h>
#include <google/protobuf/stubs/strutil.h>
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 {
bool IsReserved(const string& ident) {
for (int i = 0; i < kNumKeyword; i++) {
if (ident == kKeyword[i]) {
return true;
}
}
return false;
}
// Returns a copy of |filename| with any trailing ".protodevel" or ".proto
// suffix stripped.
// TODO(robinson): Unify with copy in compiler/cpp/internal/helpers.cc.
string StripProto(const string& filename) {
const char* suffix = HasSuffixString(filename, ".protodevel")
? ".protodevel" : ".proto";
return StripSuffixString(filename, suffix);
}
// Given a filename like foo/bar/baz.proto, returns the correspoding JavaScript
// file foo/bar/baz.js.
string GetJSFilename(const string& filename) {
const char* suffix = HasSuffixString(filename, ".protodevel")
? ".protodevel" : ".proto";
return StripSuffixString(filename, suffix) + "_pb.js";
}
// Returns the alias we assign to the module of the given .proto filename
// when importing.
string ModuleAlias(const 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.
string basename = StripProto(filename);
StripString(&basename, "-", '$');
StripString(&basename, "/", '_');
return basename + "_pb";
}
// Returns the fully normalized JavaScript path for the given
// file descriptor's package.
string GetPath(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";
}
}
// Forward declare, so that GetPrefix can call this method,
// which in turn, calls GetPrefix.
string GetPath(const GeneratorOptions& options,
const Descriptor* descriptor);
// Returns the path prefix for a message or enumeration that
// lives under the given file and containing type.
string GetPrefix(const GeneratorOptions& options,
const FileDescriptor* file_descriptor,
const Descriptor* containing_type) {
string prefix = "";
if (containing_type == NULL) {
prefix = GetPath(options, file_descriptor);
} else {
prefix = GetPath(options, containing_type);
}
if (!prefix.empty()) {
prefix += ".";
}
return prefix;
}
// Returns the fully normalized JavaScript path for the given
// message descriptor.
string GetPath(const GeneratorOptions& options,
const Descriptor* descriptor) {
return GetPrefix(
options, descriptor->file(),
descriptor->containing_type()) + descriptor->name();
}
// Returns the fully normalized JavaScript path for the given
// field's containing message descriptor.
string GetPath(const GeneratorOptions& options,
const FieldDescriptor* descriptor) {
return GetPath(options, descriptor->containing_type());
}
// Returns the fully normalized JavaScript path for the given
// enumeration descriptor.
string GetPath(const GeneratorOptions& options,
const EnumDescriptor* enum_descriptor) {
return GetPrefix(
options, enum_descriptor->file(),
enum_descriptor->containing_type()) + enum_descriptor->name();
}
// Returns the fully normalized JavaScript path for the given
// enumeration value descriptor.
string GetPath(const GeneratorOptions& options,
const EnumValueDescriptor* value_descriptor) {
return GetPath(
options,
value_descriptor->type()) + "." + value_descriptor->name();
}
string MaybeCrossFileRef(const GeneratorOptions& options,
const FileDescriptor* from_file,
const Descriptor* to_message) {
if (options.import_style == GeneratorOptions::IMPORT_COMMONJS &&
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()) + "." + to_message->name();
} else {
// Within a single file we use a full name.
return GetPath(options, to_message);
}
}
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;
}
}
vector<string> ParseLowerUnderscore(const string& input) {
vector<string> words;
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;
}
vector<string> ParseUpperCamel(const string& input) {
vector<string> words;
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;
}
string ToLowerCamel(const vector<string>& words) {
string result;
for (int i = 0; i < words.size(); i++) {
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;
}
string ToUpperCamel(const vector<string>& words) {
string result;
for (int i = 0; i < words.size(); i++) {
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.
string ToEnumCase(const string& input) {
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;
}
string ToFileName(const string& input) {
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;
}
// Returns the message/response ID, if set.
string GetMessageId(const Descriptor* desc) {
return string();
}
// Used inside Google only -- do not remove.
bool IsResponse(const Descriptor* desc) { return false; }
bool IgnoreField(const FieldDescriptor* field) { return false; }
// Does JSPB ignore this entire oneof? True only if all fields are ignored.
bool IgnoreOneof(const OneofDescriptor* oneof) {
for (int i = 0; i < oneof->field_count(); i++) {
if (!IgnoreField(oneof->field(i))) {
return false;
}
}
return true;
}
string JSIdent(const FieldDescriptor* field,
bool is_upper_camel,
bool is_map) {
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) {
result += "Map";
} else if (field->is_repeated()) {
result += "List";
}
return result;
}
string JSObjectFieldName(const FieldDescriptor* field) {
string name = JSIdent(
field,
/* is_upper_camel = */ false,
/* is_map = */ false);
if (IsReserved(name)) {
name = "pb_" + name;
}
return name;
}
// Returns the field name as a capitalized portion of a getter/setter method
// name, e.g. MyField for .getMyField().
string JSGetterName(const FieldDescriptor* field) {
string name = JSIdent(field,
/* is_upper_camel = */ true,
/* is_map = */ false);
if (name == "Extension" || name == "JsPbMessageId") {
// Avoid conflicts with base-class names.
name += "$";
}
return name;
}
string JSMapGetterName(const FieldDescriptor* field) {
return JSIdent(field,
/* is_upper_camel = */ true,
/* is_map = */ true);
}
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).
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 SimpleItoa(field->number() - parent_type->field(i)->number());
}
}
}
return SimpleItoa(field->number());
}
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 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 SimpleItoa(index);
}
// Decodes a codepoint in \x0000 -- \xFFFF. Since JS strings are UTF-16, we only
// need to handle the BMP (16-bit range) here.
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. |is_utf8| determines whether the input data (in a C++ string of
// chars) is UTF-8 encoded (in which case codepoints become JavaScript string
// characters, escaped with 16-bit hex escapes where necessary) or raw binary
// (in which case bytes become JavaScript string characters 0 -- 255).
string EscapeJSString(const string& in, bool is_utf8) {
string result;
size_t decoded = 0;
for (size_t i = 0; i < in.size(); i += decoded) {
uint16 codepoint = 0;
if (is_utf8) {
// Decode the next UTF-8 codepoint.
size_t have_bytes = in.size() - i;
uint8 bytes[3] = {
static_cast<uint8>(in[i]),
static_cast<uint8>(((i + 1) < in.size()) ? in[i + 1] : 0),
static_cast<uint8>(((i + 2) < in.size()) ? in[i + 2] : 0),
};
codepoint = DecodeUTF8Codepoint(bytes, &have_bytes);
if (have_bytes == 0) {
break;
}
decoded = have_bytes;
} else {
codepoint = static_cast<uint16>(static_cast<uint8>(in[i]));
decoded = 1;
}
// Next byte -- used for minimal octal escapes below.
char next_byte = (i + decoded) < in.size() ?
in[i + decoded] : 0;
bool pad_octal = (next_byte >= '0' && next_byte <= '7');
switch (codepoint) {
case '\0': result += pad_octal ? "\\000" : "\\0"; break;
case '\b': result += "\\\b"; break;
case '\t': result += "\\\t"; break;
case '\n': result += "\\\n"; break;
case '\r': result += "\\\r"; break;
case '\f': result += "\\\f"; break;
case '\\': result += "\\\\"; break;
case '"': result += pad_octal ? "\\042" : "\\42"; break;
case '&': result += pad_octal ? "\\046" : "\\46"; break;
case '\'': result += pad_octal ? "\\047" : "\\47"; break;
case '<': result += pad_octal ? "\\074" : "\\74"; break;
case '=': result += pad_octal ? "\\075" : "\\75"; break;
case '>': result += pad_octal ? "\\076" : "\\76"; break;
default:
// All other non-ASCII codepoints are escaped.
// Original codegen uses hex for >= 0x100 and octal for others.
if (codepoint >= 0x20 && codepoint <= 0x7e) {
result += static_cast<char>(codepoint);
} else {
if (codepoint >= 0x100) {
result += StringPrintf("\\u%04x", codepoint);
} else {
if (pad_octal || codepoint >= 0100) {
result += "\\";
result += ('0' + ((codepoint >> 6) & 07));
result += ('0' + ((codepoint >> 3) & 07));
result += ('0' + ((codepoint >> 0) & 07));
} else if (codepoint >= 010) {
result += "\\";
result += ('0' + ((codepoint >> 3) & 07));
result += ('0' + ((codepoint >> 0) & 07));
} else {
result += "\\";
result += ('0' + ((codepoint >> 0) & 07));
}
}
}
break;
}
}
return result;
}
string EscapeBase64(const string& in) {
static const char* kAlphabet =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
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).
string PostProcessFloat(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.
string::size_type exp_pos = result.find('e');
if (exp_pos != string::npos) {
string mantissa = result.substr(0, exp_pos);
string exponent = result.substr(exp_pos + 1);
// Add ".0" to mantissa if no fractional part exists.
if (mantissa.find('.') == 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" + 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('.') == string::npos) {
result += ".0";
}
return result;
}
string FloatToString(float value) {
string result = SimpleFtoa(value);
return PostProcessFloat(result);
}
string DoubleToString(double value) {
string result = SimpleDtoa(value);
return PostProcessFloat(result);
}
string MaybeNumberString(const FieldDescriptor* field, const string& orig) {
return orig;
}
string JSFieldDefault(const FieldDescriptor* field) {
assert(field->has_default_value());
switch (field->cpp_type()) {
case FieldDescriptor::CPPTYPE_INT32:
return MaybeNumberString(
field, SimpleItoa(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, SimpleItoa(static_cast<int32>(field->default_value_uint32())));
case FieldDescriptor::CPPTYPE_INT64:
return MaybeNumberString(
field, SimpleItoa(field->default_value_int64()));
case FieldDescriptor::CPPTYPE_UINT64:
// See above note for uint32 -- reinterpreting as signed.
return MaybeNumberString(
field, SimpleItoa(static_cast<int64>(field->default_value_uint64())));
case FieldDescriptor::CPPTYPE_ENUM:
return SimpleItoa(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) {
return "\"" + EscapeJSString(field->default_value_string(), true) +
"\"";
} else {
return "\"" + EscapeBase64(field->default_value_string()) +
"\"";
}
case FieldDescriptor::CPPTYPE_MESSAGE:
return "null";
}
GOOGLE_LOG(FATAL) << "Shouldn't reach here.";
return "";
}
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 GetPath(options, field->message_type());
case FieldDescriptor::TYPE_ENUM:
return GetPath(options, field->enum_type());
case FieldDescriptor::TYPE_MESSAGE:
return GetPath(options, field->message_type());
default:
return "";
}
}
string JSIntegerTypeName(const FieldDescriptor* field) {
return "number";
}
string JSTypeName(const GeneratorOptions& options,
const FieldDescriptor* field) {
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 "string";
case FieldDescriptor::CPPTYPE_ENUM:
return GetPath(options, field->enum_type());
case FieldDescriptor::CPPTYPE_MESSAGE:
return GetPath(options, field->message_type());
default:
return "";
}
}
bool HasFieldPresence(const FieldDescriptor* field);
string JSFieldTypeAnnotation(const GeneratorOptions& options,
const FieldDescriptor* field,
bool force_optional,
bool force_present,
bool singular_if_not_packed,
bool always_singular) {
bool is_primitive =
(field->cpp_type() != FieldDescriptor::CPPTYPE_ENUM &&
field->cpp_type() != FieldDescriptor::CPPTYPE_MESSAGE);
string jstype = JSTypeName(options, field);
if (field->is_repeated() &&
!always_singular &&
(field->is_packed() || !singular_if_not_packed)) {
if (!is_primitive) {
jstype = "!" + jstype;
}
jstype = "Array.<" + jstype + ">";
if (!force_optional) {
jstype = "!" + jstype;
}
}
if (field->is_optional() && is_primitive &&
(!field->has_default_value() || force_optional) && !force_present) {
jstype += "?";
} else if (field->is_required() && !is_primitive && !force_optional) {
jstype = "!" + jstype;
}
if (force_optional && HasFieldPresence(field)) {
jstype += "|undefined";
}
if (force_present && jstype[0] != '!' && !is_primitive) {
jstype = "!" + jstype;
}
return jstype;
}
string JSBinaryReaderMethodType(const FieldDescriptor* field) {
string name = field->type_name();
if (name[0] >= 'a' && name[0] <= 'z') {
name[0] = (name[0] - 'a') + 'A';
}
return name;
}
string JSBinaryReadWriteMethodName(const FieldDescriptor* field,
bool is_writer) {
string name = JSBinaryReaderMethodType(field);
if (is_writer && field->type() == FieldDescriptor::TYPE_BYTES) {
// Override for `bytes` fields: treat string as raw bytes, not base64.
name = "BytesRawString";
}
if (field->is_packed()) {
name = "Packed" + name;
} else if (is_writer && field->is_repeated()) {
name = "Repeated" + name;
}
return name;
}
string JSBinaryReaderMethodName(const FieldDescriptor* field) {
return "read" + JSBinaryReadWriteMethodName(field, /* is_writer = */ false);
}
string JSBinaryWriterMethodName(const FieldDescriptor* field) {
return "write" + JSBinaryReadWriteMethodName(field, /* is_writer = */ true);
}
string JSReturnClause(const FieldDescriptor* desc) {
return "";
}
string JSReturnDoc(const GeneratorOptions& options,
const FieldDescriptor* desc) {
return "";
}
bool HasRepeatedFields(const Descriptor* desc) {
for (int i = 0; i < desc->field_count(); i++) {
if (desc->field(i)->is_repeated()) {
return true;
}
}
return false;
}
static const char* kRepeatedFieldArrayName = ".repeatedFields_";
string RepeatedFieldsArrayName(const GeneratorOptions& options,
const Descriptor* desc) {
return HasRepeatedFields(desc) ?
(GetPath(options, desc) + kRepeatedFieldArrayName) : "null";
}
bool HasOneofFields(const Descriptor* desc) {
for (int i = 0; i < desc->field_count(); i++) {
if (desc->field(i)->containing_oneof()) {
return true;
}
}
return false;
}
static const char* kOneofGroupArrayName = ".oneofGroups_";
string OneofFieldsArrayName(const GeneratorOptions& options,
const Descriptor* desc) {
return HasOneofFields(desc) ?
(GetPath(options, desc) + kOneofGroupArrayName) : "null";
}
string RepeatedFieldNumberList(const Descriptor* desc) {
std::vector<string> numbers;
for (int i = 0; i < desc->field_count(); i++) {
if (desc->field(i)->is_repeated()) {
numbers.push_back(JSFieldIndex(desc->field(i)));
}
}
return "[" + Join(numbers, ",") + "]";
}
string OneofGroupList(const Descriptor* desc) {
// List of arrays (one per oneof), each of which is a list of field indices
std::vector<string> 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<string> 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, ",") + "]";
}
string JSOneofArray(const GeneratorOptions& options,
const FieldDescriptor* field) {
return OneofFieldsArrayName(options, field->containing_type()) + "[" +
JSOneofIndex(field->containing_oneof()) + "]";
}
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.
string package = field->file()->package();
string containing_type = field->containing_type()->full_name() + ".";
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);
}
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 IMPORT_COMMONJS case.
return "jspb.Message.messageSetExtensions";
} else {
return MaybeCrossFileRef(options, from_file, desc) + ".extensions";
}
}
string FieldDefinition(const GeneratorOptions& options,
const FieldDescriptor* field) {
string qualifier = field->is_repeated() ? "repeated" :
(field->is_optional() ? "optional" : "required");
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());
}
string FieldComments(const FieldDescriptor* field) {
string comments;
if (field->cpp_type() == FieldDescriptor::CPPTYPE_BOOL) {
comments +=
" * Note that Boolean fields may be set to 0/1 when serialized from "
"a Java server.\n"
" * You should avoid comparisons like {@code val === true/false} in "
"those cases.\n";
}
if (field->is_repeated()) {
comments +=
" * If you change this array by adding, removing or replacing "
"elements, or if you\n"
" * replace the array itself, then you must call the setter to "
"update it.\n";
}
return comments;
}
bool ShouldGenerateExtension(const FieldDescriptor* field) {
return
field->is_extension() &&
!IgnoreField(field);
}
bool HasExtensions(const Descriptor* desc) {
if (desc->extension_count() > 0) {
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 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.
string GetPivot(const Descriptor* desc) {
static const int kDefaultPivot = (1 << 29); // max field number (29 bits)
// 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)) {
pivot = ((max_field_number + 1) < kDefaultPivot) ?
(max_field_number + 1) : kDefaultPivot;
}
return SimpleItoa(pivot);
}
// Returns true for fields that represent "null" as distinct from the default
// value. See https://go/proto3#heading=h.kozewqqcqhuz for more information.
bool HasFieldPresence(const FieldDescriptor* field) {
return
(field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) ||
(field->containing_oneof() != NULL) ||
(field->file()->syntax() != FileDescriptor::SYNTAX_PROTO3);
}
// For proto3 fields without presence, returns a string representing the default
// value in JavaScript. See https://go/proto3#heading=h.kozewqqcqhuz for more
// information.
string Proto3PrimitiveFieldDefault(const FieldDescriptor* field) {
switch (field->cpp_type()) {
case FieldDescriptor::CPPTYPE_INT32:
case FieldDescriptor::CPPTYPE_INT64:
case FieldDescriptor::CPPTYPE_UINT32:
case FieldDescriptor::CPPTYPE_UINT64: {
return "0";
}
case FieldDescriptor::CPPTYPE_ENUM:
case FieldDescriptor::CPPTYPE_FLOAT:
case FieldDescriptor::CPPTYPE_DOUBLE:
return "0";
case FieldDescriptor::CPPTYPE_BOOL:
return "false";
case FieldDescriptor::CPPTYPE_STRING:
return "\"\"";
default:
// BYTES and MESSAGE are handled separately.
assert(false);
return "";
}
}
} // anonymous namespace
void Generator::GenerateHeader(const GeneratorOptions& options,
io::Printer* printer) const {
printer->Print("/**\n"
" * @fileoverview\n"
" * @enhanceable\n"
" * @public\n"
" */\n"
"// GENERATED CODE -- DO NOT EDIT!\n"
"\n");
}
void Generator::FindProvidesForFile(const GeneratorOptions& options,
io::Printer* printer,
const FileDescriptor* file,
std::set<string>* 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 vector<const FileDescriptor*>& files,
std::set<string>* provided) const {
for (int i = 0; i < files.size(); i++) {
FindProvidesForFile(options, printer, files[i], provided);
}
printer->Print("\n");
}
void Generator::FindProvidesForMessage(
const GeneratorOptions& options,
io::Printer* printer,
const Descriptor* desc,
std::set<string>* provided) const {
string name = GetPath(options, desc);
provided->insert(name);
for (int i = 0; i < desc->enum_type_count(); i++) {
FindProvidesForEnum(options, printer, desc->enum_type(i),
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<string>* provided) const {
string name = GetPath(options, enumdesc);
provided->insert(name);
}
void Generator::FindProvidesForFields(
const GeneratorOptions& options,
io::Printer* printer,
const vector<const FieldDescriptor*>& fields,
std::set<string>* provided) const {
for (int i = 0; i < fields.size(); i++) {
const FieldDescriptor* field = fields[i];
if (IgnoreField(field)) {
continue;
}
string name =
GetPath(options, field->file()) + "." + JSObjectFieldName(field);
provided->insert(name);
}
}
void Generator::GenerateProvides(const GeneratorOptions& options,
io::Printer* printer,
std::set<string>* provided) const {
for (std::set<string>::iterator it = provided->begin();
it != provided->end(); ++it) {
printer->Print("goog.provide('$name$');\n",
"name", *it);
}
}
void Generator::GenerateRequires(const GeneratorOptions& options,
io::Printer* printer,
const Descriptor* desc,
std::set<string>* provided) const {
std::set<string> required;
std::set<string> forwards;
bool have_message = false;
FindRequiresForMessage(options, desc,
&required, &forwards, &have_message);
GenerateRequiresImpl(options, printer, &required, &forwards, provided,
/* require_jspb = */ have_message,
/* require_extension = */ HasExtensions(desc));
}
void Generator::GenerateRequires(const GeneratorOptions& options,
io::Printer* printer,
const vector<const FileDescriptor*>& files,
std::set<string>* provided) const {
if (options.import_style == GeneratorOptions::IMPORT_BROWSER) {
return;
} else if (options.import_style == GeneratorOptions::IMPORT_CLOSURE) {
// For Closure imports we need to import every message type individually.
std::set<string> required;
std::set<string> forwards;
bool have_extensions = false;
bool have_message = false;
for (int i = 0; i < files.size(); i++) {
for (int j = 0; j < files[i]->message_type_count(); j++) {
FindRequiresForMessage(options,
files[i]->message_type(j),
&required, &forwards, &have_message);
}
if (!have_extensions && HasExtensions(files[i])) {
have_extensions = 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(GetPath(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);
} else if (options.import_style == GeneratorOptions::IMPORT_COMMONJS) {
// CommonJS imports are based on files
}
}
void Generator::GenerateRequires(const GeneratorOptions& options,
io::Printer* printer,
const vector<const FieldDescriptor*>& fields,
std::set<string>* provided) const {
std::set<string> required;
std::set<string> 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);
}
void Generator::GenerateRequiresImpl(const GeneratorOptions& options,
io::Printer* printer,
std::set<string>* required,
std::set<string>* forwards,
std::set<string>* provided,
bool require_jspb,
bool require_extension) const {
if (require_jspb) {
printer->Print(
"goog.require('jspb.Message');\n");
if (options.binary) {
printer->Print(
"goog.require('jspb.BinaryReader');\n"
"goog.require('jspb.BinaryWriter');\n");
}
}
if (require_extension) {
printer->Print(
"goog.require('jspb.ExtensionFieldInfo');\n");
}
std::set<string>::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<string>* required,
std::set<string>* 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<string>* required,
std::set<string>* 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() == NULL)) {
if (options.add_require_for_enums) {
required->insert(GetPath(options, field->enum_type()));
} else {
forwards->insert(GetPath(options, field->enum_type()));
}
} else if (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) {
required->insert(GetPath(options, field->message_type()));
}
}
void Generator::FindRequiresForExtension(const GeneratorOptions& options,
const FieldDescriptor* field,
std::set<string>* required,
std::set<string>* forwards) const {
if (field->containing_type()->full_name() != "google.protobuf.bridge.MessageSet") {
required->insert(GetPath(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++) {
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 (!NamespaceOnly(desc)) {
printer->Print("\n");
GenerateClassConstructor(options, printer, desc);
GenerateClassFieldInfo(options, printer, desc);
GenerateClassToObject(options, printer, desc);
if (options.binary) {
// 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);
}
GenerateClassClone(options, printer, desc);
GenerateClassRegistration(options, printer, desc);
GenerateClassFields(options, printer, desc);
if (IsExtendable(desc) && desc->full_name() != "google.protobuf.bridge.MessageSet") {
GenerateClassExtensionFieldInfo(options, printer, desc);
}
if (options.import_style != GeneratorOptions:: IMPORT_CLOSURE) {
for (int i = 0; i < desc->extension_count(); i++) {
GenerateExtension(options, printer, desc->extension(i));
}
}
}
// Recurse on 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));
}
}
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"
"$classname$ = function(opt_data) {\n",
"classname", GetPath(options, desc));
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"
" $classname$.displayName = '$classname$';\n"
"}\n",
"classname", GetPath(options, desc));
}
void Generator::GenerateClassFieldInfo(const GeneratorOptions& options,
io::Printer* printer,
const Descriptor* desc) const {
if (HasRepeatedFields(desc)) {
printer->Print(
"/**\n"
" * List of repeated fields within this message type.\n"
" * @private {!Array<number>}\n"
" * @const\n"
" */\n"
"$classname$$rptfieldarray$ = $rptfields$;\n"
"\n",
"classname", GetPath(options, desc),
"rptfieldarray", kRepeatedFieldArrayName,
"rptfields", RepeatedFieldNumberList(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<!Array<number>>}\n"
" * @const\n"
" */\n"
"$classname$$oneofgrouparray$ = $oneofgroups$;\n"
"\n",
"classname", GetPath(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", GetPath(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", GetPath(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->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", GetPath(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 suitable for use in "
"Soy templates.\n"
" * Field names that are reserved in JavaScript and will be renamed to "
"pb_name.\n"
" * To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.\n"
" * For the list of reserved names please see:\n"
" * com.google.apps.jspb.JsClassTemplate.JS_RESERVED_WORDS.\n"
" * @param {boolean=} opt_includeInstance Whether to include the JSPB "
"instance\n"
" * for transitional soy proto support: 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 Whether to include the "
"JSPB\n"
" * 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"
" */\n"
"$classname$.toObject = function(includeInstance, msg) {\n"
" var f, obj = {",
"classname", GetPath(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", GetPath(options, desc));
}
printer->Print(
" if (includeInstance) {\n"
" obj.$$jspbMessageInstance = msg\n"
" }\n"
" return obj;\n"
"};\n"
"}\n"
"\n"
"\n",
"classname", GetPath(options, desc));
}
void Generator::GenerateClassFieldToObject(const GeneratorOptions& options,
io::Printer* printer,
const FieldDescriptor* field) const {
printer->Print("$fieldname$: ",
"fieldname", JSObjectFieldName(field));
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(field),
"type", SubmessageTypeRef(options, field));
}
} else {
printer->Print("(f = msg.get$getter$()) && "
"$type$.toObject(includeInstance, f)",
"getter", JSGetterName(field),
"type", SubmessageTypeRef(options, field));
}
} else {
// Simple field (singular or repeated).
if (!HasFieldPresence(field) && !field->is_repeated()) {
// Delegate to the generated get<field>() method in order not to duplicate
// the proto3-field-default-value logic here.
printer->Print("msg.get$getter$()",
"getter", JSGetterName(field));
} else {
if (field->has_default_value()) {
printer->Print("jspb.Message.getField(msg, $index$) != null ? "
"jspb.Message.getField(msg, $index$) : $defaultValue$",
"index", JSFieldIndex(field),
"defaultValue", JSFieldDefault(field));
} else {
printer->Print("jspb.Message.getField(msg, $index$)",
"index", JSFieldIndex(field));
}
}
}
}
void Generator::GenerateClassFromObject(const GeneratorOptions& options,
io::Printer* printer,
const Descriptor* desc) const {
printer->Print(
"if (jspb.Message.GENERATE_FROM_OBJECT) {\n"
"/**\n"
" * Loads data from an object into a new instance of this proto.\n"
" * @param {!Object} obj The object representation of this proto to\n"
" * load the data from.\n"
" * @return {!$classname$}\n"
" */\n"
"$classname$.fromObject = function(obj) {\n"
" var f, msg = new $classname$();\n",
"classname", GetPath(options, desc));
for (int i = 0; i < desc->field_count(); i++) {
const FieldDescriptor* field = desc->field(i);
GenerateClassFieldFromObject(options, printer, field);
}
printer->Print(
" return msg;\n"
"};\n"
"}\n");
}
void Generator::GenerateClassFieldFromObject(
const GeneratorOptions& options,
io::Printer* printer,
const FieldDescriptor* field) const {
if (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) {
// Message field (singular or repeated)
if (field->is_repeated()) {
{
printer->Print(
" goog.isDef(obj.$name$) && "
"jspb.Message.setRepeatedWrapperField(\n"
" msg, $index$, goog.array.map(obj.$name$, function(i) {\n"
" return $fieldclass$.fromObject(i);\n"
" }));\n",
"name", JSObjectFieldName(field),
"index", JSFieldIndex(field),
"fieldclass", SubmessageTypeRef(options, field));
}
} else {
printer->Print(
" goog.isDef(obj.$name$) && jspb.Message.setWrapperField(\n"
" msg, $index$, $fieldclass$.fromObject(obj.$name$));\n",
"name", JSObjectFieldName(field),
"index", JSFieldIndex(field),
"fieldclass", SubmessageTypeRef(options, field));
}
} else {
// Simple (primitive) field.
printer->Print(
" goog.isDef(obj.$name$) && jspb.Message.setField(msg, $index$, "
"obj.$name$);\n",
"name", JSObjectFieldName(field),
"index", JSFieldIndex(field));
}
}
void Generator::GenerateClassClone(const GeneratorOptions& options,
io::Printer* printer,
const Descriptor* desc) const {
printer->Print(
"/**\n"
" * Creates a deep clone of this proto. No data is shared with the "
"original.\n"
" * @return {!$name$} The clone.\n"
" */\n"
"$name$.prototype.cloneMessage = function() {\n"
" return /** @type {!$name$} */ (jspb.Message.cloneMessage(this));\n"
"};\n\n\n",
"name", GetPath(options, desc));
}
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 Generator::GenerateClassField(const GeneratorOptions& options,
io::Printer* printer,
const FieldDescriptor* field) const {
if (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) {
printer->Print(
"/**\n"
" * $fielddef$\n"
"$comment$"
" * @return {$type$}\n"
" */\n",
"fielddef", FieldDefinition(options, field),
"comment", FieldComments(field),
"type", JSFieldTypeAnnotation(options, field,
/* force_optional = */ false,
/* force_present = */ false,
/* singular_if_not_packed = */ false,
/* always_singular = */ false));
printer->Print(
"$class$.prototype.get$name$ = function() {\n"
" return /** @type{$type$} */ (\n"
" jspb.Message.get$rpt$WrapperField(this, $wrapperclass$, "
"$index$$required$));\n"
"};\n"
"\n"
"\n",
"class", GetPath(options, field->containing_type()),
"name", JSGetterName(field),
"type", JSFieldTypeAnnotation(options, field,
/* force_optional = */ false,
/* force_present = */ false,
/* singular_if_not_packed = */ false,
/* always_singular = */ false),
"rpt", (field->is_repeated() ? "Repeated" : ""),
"index", JSFieldIndex(field),
"wrapperclass", SubmessageTypeRef(options, field),
"required", (field->label() == FieldDescriptor::LABEL_REQUIRED ?
", 1" : ""));
printer->Print(
"/** @param {$optionaltype$} value $returndoc$ */\n"
"$class$.prototype.set$name$ = function(value) {\n"
" jspb.Message.set$oneoftag$$repeatedtag$WrapperField(",
"optionaltype",
JSFieldTypeAnnotation(options, field,
/* force_optional = */ true,
/* force_present = */ false,
/* singular_if_not_packed = */ false,
/* always_singular = */ false),
"returndoc", JSReturnDoc(options, field),
"class", GetPath(options, field->containing_type()),
"name", JSGetterName(field),
"oneoftag", (field->containing_oneof() ? "Oneof" : ""),
"repeatedtag", (field->is_repeated() ? "Repeated" : ""));
printer->Print(
"this, $index$$oneofgroup$, value);$returnvalue$\n"
"};\n"
"\n"
"\n",
"index", JSFieldIndex(field),
"oneofgroup", (field->containing_oneof() ?
(", " + JSOneofArray(options, field)) : ""),
"returnvalue", JSReturnClause(field));
printer->Print(
"$class$.prototype.clear$name$ = function() {\n"
" this.set$name$($clearedvalue$);$returnvalue$\n"
"};\n"
"\n"
"\n",
"class", GetPath(options, field->containing_type()),
"name", JSGetterName(field),
"clearedvalue", (field->is_repeated() ? "[]" : "undefined"),
"returnvalue", JSReturnClause(field));
} else {
string typed_annotation;
// Simple (primitive) field, either singular or repeated.
{
typed_annotation = JSFieldTypeAnnotation(options, field,
/* force_optional = */ false,
/* force_present = */ !HasFieldPresence(field),
/* singular_if_not_packed = */ false,
/* always_singular = */ false),
printer->Print(
"/**\n"
" * $fielddef$\n"
"$comment$"
" * @return {$type$}\n"
" */\n",
"fielddef", FieldDefinition(options, field),
"comment", FieldComments(field),
"type", typed_annotation);
}
printer->Print(
"$class$.prototype.get$name$ = function() {\n",
"class", GetPath(options, field->containing_type()),
"name", JSGetterName(field));
{
printer->Print(
" return /** @type {$type$} */ (",
"type", typed_annotation);
}
// For proto3 fields without presence, use special getters that will return
// defaults when the field is unset, possibly constructing a value if
// required.
if (!HasFieldPresence(field) && !field->is_repeated()) {
printer->Print("jspb.Message.getFieldProto3(this, $index$, $default$)",
"index", JSFieldIndex(field),
"default", Proto3PrimitiveFieldDefault(field));
} else {
if (field->has_default_value()) {
printer->Print("jspb.Message.getField(this, $index$) != null ? "
"jspb.Message.getField(this, $index$) : $defaultValue$",
"index", JSFieldIndex(field),
"defaultValue", JSFieldDefault(field));
} else {
printer->Print("jspb.Message.getField(this, $index$)",
"index", JSFieldIndex(field));
}
}
{
printer->Print(
");\n"
"};\n"
"\n"
"\n");
}
{
printer->Print(
"/** @param {$optionaltype$} value $returndoc$ */\n",
"optionaltype",
JSFieldTypeAnnotation(options, field,
/* force_optional = */ true,
/* force_present = */ !HasFieldPresence(field),
/* singular_if_not_packed = */ false,
/* always_singular = */ false),
"returndoc", JSReturnDoc(options, field));
}
printer->Print(
"$class$.prototype.set$name$ = function(value) {\n"
" jspb.Message.set$oneoftag$Field(this, $index$",
"class", GetPath(options, field->containing_type()),
"name", JSGetterName(field),
"oneoftag", (field->containing_oneof() ? "Oneof" : ""),
"index", JSFieldIndex(field));
printer->Print(
"$oneofgroup$, $type$value$rptvalueinit$$typeclose$);$returnvalue$\n"
"};\n"
"\n"
"\n",
"type", "",
"typeclose", "",
"oneofgroup",
(field->containing_oneof() ? (", " + JSOneofArray(options, field))
: ""),
"returnvalue", JSReturnClause(field), "rptvalueinit",
(field->is_repeated() ? " || []" : ""));
if (HasFieldPresence(field)) {
printer->Print(
"$class$.prototype.clear$name$ = function() {\n"
" jspb.Message.set$oneoftag$Field(this, $index$$oneofgroup$, ",
"class", GetPath(options, field->containing_type()),
"name", JSGetterName(field),
"oneoftag", (field->containing_oneof() ? "Oneof" : ""),
"oneofgroup", (field->containing_oneof() ?
(", " + JSOneofArray(options, field)) : ""),
"index", JSFieldIndex(field));
printer->Print(
"$clearedvalue$);$returnvalue$\n"
"};\n"
"\n"
"\n",
"clearedvalue", (field->is_repeated() ? "[]" : "undefined"),
"returnvalue", JSReturnClause(field));
}
}
}
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.<number, jspb.ExtensionFieldInfo>}\n"
" */\n"
"$class$.extensions = {};\n"
"\n",
"class", GetPath(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"
" if (reader.isEndGroup()) {\n"
" break;\n"
" }\n"
" var field = reader.getFieldNumber();\n"
" switch (field) {\n",
"class", GetPath(options, desc));
for (int i = 0; i < desc->field_count(); i++) {
GenerateClassDeserializeBinaryField(options, printer, desc->field(i));
}
printer->Print(
" default:\n");
if (IsExtendable(desc)) {
printer->Print(
" jspb.Message.readBinaryExtension(msg, reader, $extobj$,\n"
" $class$.prototype.getExtension,\n"
" $class$.prototype.setExtension);\n"
" break;\n",
"extobj", JSExtensionsObjectName(options, desc->file(), desc),
"class", GetPath(options, desc));
} else {
printer->Print(
" reader.skipField();\n"
" break;\n");
}
printer->Print(
" }\n"
" }\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", SimpleItoa(field->number()));
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) ?
(SimpleItoa(field->number()) + ", ") : "");
} else {
printer->Print(
" var value = /** @type {$fieldtype$} */ (reader.$reader$());\n",
"fieldtype", JSFieldTypeAnnotation(options, field, false, true,
/* singular_if_not_packed = */ true,
/* always_singular = */ false),
"reader", JSBinaryReaderMethodName(field));
}
if (field->is_repeated() && !field->is_packed()) {
// Repeated fields receive a |value| one at at a time; append to array
// returned by get$name$().
printer->Print(
" msg.get$name$().push(value);\n",
"name", JSGetterName(field));
} 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(field));
}
printer->Print(" break;\n");
}
void Generator::GenerateClassSerializeBinary(const GeneratorOptions& options,
io::Printer* printer,
const Descriptor* desc) const {
printer->Print(
"/**\n"
" * Class method variant: serializes the given message to binary data\n"
" * (in protobuf wire format), writing to the given BinaryWriter.\n"
" * @param {!$class$} message\n"
" * @param {!jspb.BinaryWriter} writer\n"
" */\n"
"$class$.serializeBinaryToWriter = function(message, "
"writer) {\n"
" message.serializeBinaryToWriter(writer);\n"
"};\n"
"\n"
"\n"
"/**\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"
" this.serializeBinaryToWriter(writer);\n"
" return writer.getResultBuffer();\n"
"};\n"
"\n"
"\n"
"/**\n"
" * Serializes the message to binary data (in protobuf wire format),\n"
" * writing to the given BinaryWriter.\n"
" * @param {!jspb.BinaryWriter} writer\n"
" */\n"
"$class$.prototype.serializeBinaryToWriter = function (writer) {\n"
" var f = undefined;\n",
"class", GetPath(options, desc));
for (int i = 0; i < desc->field_count(); i++) {
GenerateClassSerializeBinaryField(options, printer, desc->field(i));
}
if (IsExtendable(desc)) {
printer->Print(
" jspb.Message.serializeBinaryExtensions(this, writer, $extobj$,\n"
" $class$.prototype.getExtension);\n",
"extobj", JSExtensionsObjectName(options, desc->file(), desc),
"class", GetPath(options, desc));
}
printer->Print(
"};\n"
"\n"
"\n");
}
void Generator::GenerateClassSerializeBinaryField(
const GeneratorOptions& options,
io::Printer* printer,
const FieldDescriptor* field) const {
printer->Print(
" f = this.get$name$();\n",
"name", JSGetterName(field));
if (field->is_repeated()) {
printer->Print(
" if (f.length > 0) {\n");
} else {
if (HasFieldPresence(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: {
{
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;
}
}
}
printer->Print(
" writer.$writer$(\n"
" $index$,\n"
" f",
"writer", JSBinaryWriterMethodName(field),
"name", JSGetterName(field),
"index", SimpleItoa(field->number()));
if (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) {
printer->Print(
",\n"
" $submsg$.serializeBinaryToWriter\n",
"submsg", SubmessageTypeRef(options, field));
} else {
printer->Print("\n");
}
printer->Print(
" );\n"
" }\n");
}
void Generator::GenerateEnum(const GeneratorOptions& options,
io::Printer* printer,
const EnumDescriptor* enumdesc) const {
printer->Print(
"/**\n"
" * @enum {number}\n"
" */\n"
"$name$ = {\n",
"name", GetPath(options, enumdesc));
for (int i = 0; i < enumdesc->value_count(); i++) {
const EnumValueDescriptor* value = enumdesc->value(i);
printer->Print(
" $name$: $value$$comma$\n",
"name", ToEnumCase(value->name()),
"value", SimpleItoa(value->number()),
"comma", (i == enumdesc->value_count() - 1) ? "" : ",");
}
printer->Print(
"};\n"
"\n");
}
void Generator::GenerateExtension(const GeneratorOptions& options,
io::Printer* printer,
const FieldDescriptor* field) const {
string extension_scope =
(field->extension_scope() ?
GetPath(options, field->extension_scope()) :
GetPath(options, field->file()));
printer->Print(
"\n"
"/**\n"
" * A tuple of {field number, class constructor} for the extension\n"
" * field named `$name$`.\n"
" * @type {!jspb.ExtensionFieldInfo.<$extensionType$>}\n"
" */\n"
"$class$.$name$ = new jspb.ExtensionFieldInfo(\n",
"name", JSObjectFieldName(field),
"class", extension_scope,
"extensionType", JSFieldTypeAnnotation(
options, field,
/* force_optional = */ false,
/* force_present = */ true,
/* singular_if_not_packed = */ false,
/* always_singular = */ false));
printer->Print(
" $index$,\n"
" {$name$: 0},\n"
" $ctor$,\n"
" /** @type {?function((boolean|undefined),!jspb.Message=): "
"!Object} */ (\n"
" $toObject$),\n"
" $repeated$",
"index", SimpleItoa(field->number()),
"name", JSObjectFieldName(field),
"ctor", (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE ?
SubmessageTypeRef(options, field) : string("null")),
"toObject", (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE ?
(SubmessageTypeRef(options, field) + ".toObject") :
string("null")),
"repeated", (field->is_repeated() ? "1" : "0"));
if (options.binary) {
printer->Print(
",\n"
" jspb.BinaryReader.prototype.$binaryReaderFn$,\n"
" jspb.BinaryWriter.prototype.$binaryWriterFn$,\n"
" $binaryMessageSerializeFn$,\n"
" $binaryMessageDeserializeFn$,\n"
" $isPacked$);\n",
"binaryReaderFn", JSBinaryReaderMethodName(field),
"binaryWriterFn", JSBinaryWriterMethodName(field),
"binaryMessageSerializeFn",
(field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) ?
(SubmessageTypeRef(options, field) +
".serializeBinaryToWriter") : "null",
"binaryMessageDeserializeFn",
(field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) ?
(SubmessageTypeRef(options, field) +
".deserializeBinaryFromReader") : "null",
"isPacked", (field->is_packed() ? "true" : "false"));
} else {
printer->Print(");\n");
}
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", SimpleItoa(field->number()),
"class", extension_scope,
"name", JSObjectFieldName(field));
}
bool GeneratorOptions::ParseFromOptions(
const vector< pair< string, string > >& options,
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") {
if (options[i].second != "") {
*error = "Unexpected option value for error_on_name_conflict";
return false;
}
error_on_name_conflict = true;
} 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 = IMPORT_CLOSURE;
} else if (options[i].second == "commonjs") {
import_style = IMPORT_COMMONJS;
} else if (options[i].second == "browser") {
import_style = IMPORT_BROWSER;
} else if (options[i].second == "es6") {
import_style = IMPORT_ES6;
} else {
*error = "Unknown import style " + options[i].second + ", expected " +
"one of: closure, commonjs, browser, es6.";
}
} 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 (!library.empty() && import_style != IMPORT_CLOSURE) {
*error = "The library option should only be used for "
"import_style=closure";
}
return true;
}
void Generator::GenerateFilesInDepOrder(
const GeneratorOptions& options,
io::Printer* printer,
const vector<const FileDescriptor*>& 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<const FileDescriptor*> all_files(files.begin(), files.end());
// Track the in-progress set of files that have been generated already.
std::set<const FileDescriptor*> 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<const FileDescriptor*>* all_files,
std::set<const FileDescriptor*>* 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);
}
}
void Generator::GenerateFile(const GeneratorOptions& options,
io::Printer* printer,
const FileDescriptor* file) const {
GenerateHeader(options, printer);
// Generate "require" statements.
if (options.import_style == GeneratorOptions::IMPORT_COMMONJS) {
printer->Print("var jspb = require('google-protobuf');\n");
printer->Print("var goog = jspb;\n");
printer->Print("var global = Function('return this')();\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",
"alias", ModuleAlias(name),
"file", GetJSFilename(name));
}
}
// 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() { /* ... */ }
std::set<std::string> provided;
// Cover the case where this file declares extensions but no messages.
// This will ensure that the file-level object will be declared to hold
// the extensions.
for (int i = 0; i < file->extension_count(); i++) {
provided.insert(file->extension(i)->full_name());
}
FindProvidesForFile(options, printer, file, &provided);
for (std::set<string>::iterator it = provided.begin();
it != provided.end(); ++it) {
printer->Print("goog.exportSymbol('$name$', null, global);\n",
"name", *it);
}
GenerateClassesAndEnums(options, printer, file);
// Extensions nested inside messages are emitted inside
// GenerateClassesAndEnums().
for (int i = 0; i < file->extension_count(); i++) {
GenerateExtension(options, printer, file->extension(i));
}
if (options.import_style == GeneratorOptions::IMPORT_COMMONJS) {
printer->Print("goog.object.extend(exports, $package$);\n",
"package", GetPath(options, file));
}
}
bool Generator::GenerateAll(const vector<const FileDescriptor*>& files,
const string& parameter,
GeneratorContext* context,
string* error) const {
vector< pair< string, string > > option_pairs;
ParseGeneratorParameter(parameter, &option_pairs);
GeneratorOptions options;
if (!options.ParseFromOptions(option_pairs, error)) {
return false;
}
// There are three schemes for where output files go:
//
// - import_style = IMPORT_CLOSURE, library non-empty: all output in one file
// - import_style = IMPORT_CLOSURE, library empty: one output file per type
// - import_style != IMPORT_CLOSURE: one output file per .proto file
if (options.import_style == GeneratorOptions::IMPORT_CLOSURE &&
options.library != "") {
// All output should go in a single file.
string filename = options.output_dir + "/" + options.library + ".js";
google::protobuf::scoped_ptr<io::ZeroCopyOutputStream> output(context->Open(filename));
GOOGLE_CHECK(output.get());
io::Printer printer(output.get(), '$');
// Pull out all extensions -- we need these to generate all
// provides/requires.
vector<const FieldDescriptor*> 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);
}
}
GenerateHeader(options, &printer);
std::set<string> provided;
FindProvides(options, &printer, files, &provided);
FindProvidesForFields(options, &printer, extensions, &provided);
GenerateProvides(options, &printer, &provided);
GenerateTestOnly(options, &printer);
GenerateRequires(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;
}
} else if (options.import_style == GeneratorOptions::IMPORT_CLOSURE) {
// Collect all types, and print each type to a separate file. Pull out
// free-floating extensions while we make this pass.
map< string, vector<const FieldDescriptor*> > extensions_by_namespace;
// 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.
// Current descriptor that will generate each filename, indexed by filename.
map<string, const void*> desc_by_filename;
// Set of descriptors allowed to generate files.
set<const void*> allowed_descs;
for (int i = 0; i < files.size(); i++) {
// Collect all (descriptor, filename) pairs.
map<const void*, string> descs_in_file;
for (int j = 0; j < files[i]->message_type_count(); j++) {
const Descriptor* desc = files[i]->message_type(j);
string filename =
options.output_dir + "/" + ToFileName(desc->name()) + ".js";
descs_in_file[desc] = filename;
}
for (int j = 0; j < files[i]->enum_type_count(); j++) {
const EnumDescriptor* desc = files[i]->enum_type(j);
string filename =
options.output_dir + "/" + ToFileName(desc->name()) + ".js";
descs_in_file[desc] = filename;
}
// For each (descriptor, filename) pair, update the
// descriptors-by-filename map, and if a previous descriptor was already
// writing the filename, remove it from the allowed-descriptors set.
map<const void*, string>::iterator it;
for (it = descs_in_file.begin(); it != descs_in_file.end(); ++it) {
const void* desc = it->first;
const string& filename = it->second;
if (desc_by_filename.find(filename) != desc_by_filename.end()) {
if (options.error_on_name_conflict) {
*error = "Name conflict: file name " + filename +
" would be generated by two descriptors";
return false;
}
allowed_descs.erase(desc_by_filename[filename]);
}
desc_by_filename[filename] = desc;
allowed_descs.insert(desc);
}
}
// Generate code.
for (int i = 0; i < files.size(); i++) {
const FileDescriptor* file = files[i];
for (int j = 0; j < file->message_type_count(); j++) {
const Descriptor* desc = file->message_type(j);
if (allowed_descs.find(desc) == allowed_descs.end()) {
continue;
}
string filename = options.output_dir + "/" +
ToFileName(desc->name()) + ".js";
google::protobuf::scoped_ptr<io::ZeroCopyOutputStream> output(
context->Open(filename));
GOOGLE_CHECK(output.get());
io::Printer printer(output.get(), '$');
GenerateHeader(options, &printer);
std::set<string> provided;
FindProvidesForMessage(options, &printer, desc, &provided);
GenerateProvides(options, &printer, &provided);
GenerateTestOnly(options, &printer);
GenerateRequires(options, &printer, desc, &provided);
GenerateClass(options, &printer, desc);
if (printer.failed()) {
return false;
}
}
for (int j = 0; j < file->enum_type_count(); j++) {
const EnumDescriptor* enumdesc = file->enum_type(j);
if (allowed_descs.find(enumdesc) == allowed_descs.end()) {
continue;
}
string filename = options.output_dir + "/" +
ToFileName(enumdesc->name()) + ".js";
google::protobuf::scoped_ptr<io::ZeroCopyOutputStream> output(
context->Open(filename));
GOOGLE_CHECK(output.get());
io::Printer printer(output.get(), '$');
GenerateHeader(options, &printer);
std::set<string> provided;
FindProvidesForEnum(options, &printer, enumdesc, &provided);
GenerateProvides(options, &printer, &provided);
GenerateTestOnly(options, &printer);
GenerateEnum(options, &printer, enumdesc);
if (printer.failed()) {
return false;
}
}
// Pull out all free-floating extensions and generate files for those too.
for (int j = 0; j < file->extension_count(); j++) {
const FieldDescriptor* extension = file->extension(j);
extensions_by_namespace[GetPath(options, files[i])]
.push_back(extension);
}
}
// Generate extensions in separate files.
map< string, vector<const FieldDescriptor*> >::iterator it;
for (it = extensions_by_namespace.begin();
it != extensions_by_namespace.end();
++it) {
string filename = options.output_dir + "/" +
ToFileName(it->first) + ".js";
google::protobuf::scoped_ptr<io::ZeroCopyOutputStream> output(
context->Open(filename));
GOOGLE_CHECK(output.get());
io::Printer printer(output.get(), '$');
GenerateHeader(options, &printer);
std::set<string> provided;
FindProvidesForFields(options, &printer, it->second, &provided);
GenerateProvides(options, &printer, &provided);
GenerateTestOnly(options, &printer);
GenerateRequires(options, &printer, it->second, &provided);
for (int j = 0; j < it->second.size(); j++) {
if (ShouldGenerateExtension(it->second[j])) {
GenerateExtension(options, &printer, it->second[j]);
}
}
}
} else {
// Generate one output file per input (.proto) file.
for (int i = 0; i < files.size(); i++) {
const google::protobuf::FileDescriptor* file = files[i];
string filename = options.output_dir + "/" + GetJSFilename(file->name());
google::protobuf::scoped_ptr<io::ZeroCopyOutputStream> output(
context->Open(filename));
GOOGLE_CHECK(output.get());
io::Printer printer(output.get(), '$');
GenerateFile(options, &printer, file);
if (printer.failed()) {
return false;
}
}
}
return true;
}
} // namespace js
} // namespace compiler
} // namespace protobuf
} // namespace google