aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/google/protobuf/compiler/js/js_generator.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/google/protobuf/compiler/js/js_generator.cc')
-rwxr-xr-xsrc/google/protobuf/compiler/js/js_generator.cc1577
1 files changed, 970 insertions, 607 deletions
diff --git a/src/google/protobuf/compiler/js/js_generator.cc b/src/google/protobuf/compiler/js/js_generator.cc
index 58c77d00..d8282e40 100755
--- a/src/google/protobuf/compiler/js/js_generator.cc
+++ b/src/google/protobuf/compiler/js/js_generator.cc
@@ -28,16 +28,13 @@
// (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 <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>
@@ -45,12 +42,14 @@
#include <google/protobuf/stubs/logging.h>
#include <google/protobuf/stubs/common.h>
#include <google/protobuf/stubs/stringprintf.h>
+#include <google/protobuf/compiler/js/well_known_types_embed.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 {
@@ -142,19 +141,23 @@ bool IsReserved(const string& ident) {
return false;
}
+bool StrEndsWith(StringPiece sp, StringPiece x) {
+ return sp.size() >= x.size() && sp.substr(sp.size() - x.size()) == x;
+}
+
// Returns a copy of |filename| with any trailing ".protodevel" or ".proto
// suffix stripped.
// TODO(haberman): Unify with copy in compiler/cpp/internal/helpers.cc.
string StripProto(const string& filename) {
- const char* suffix = HasSuffixString(filename, ".protodevel")
- ? ".protodevel" : ".proto";
+ const char* suffix =
+ StrEndsWith(filename, ".protodevel") ? ".protodevel" : ".proto";
return StripSuffixString(filename, suffix);
}
// Given a filename like foo/bar/baz.proto, returns the corresponding JavaScript
// file foo/bar/baz.js.
-string GetJSFilename(const string& filename) {
- return StripProto(filename) + "_pb.js";
+string GetJSFilename(const GeneratorOptions& options, const string& filename) {
+ return StripProto(filename) + options.GetFileNameExtension();
}
// Given a filename like foo/bar/baz.proto, returns the root directory
@@ -190,15 +193,16 @@ string ModuleAlias(const string& filename) {
// 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, "/", '_');
+ ReplaceCharacters(&basename, "-", '$');
+ ReplaceCharacters(&basename, "/", '_');
+ ReplaceCharacters(&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) {
+string GetFilePath(const GeneratorOptions& options,
+ const FileDescriptor* file) {
if (!options.namespace_prefix.empty()) {
return options.namespace_prefix;
} else if (!file->package().empty()) {
@@ -208,79 +212,80 @@ string GetPath(const GeneratorOptions& options,
}
}
-// Forward declare, so that GetPrefix can call this method,
-// which in turn, calls GetPrefix.
-string GetPath(const GeneratorOptions& options,
- const Descriptor* descriptor);
+// 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.
+string GetNestedMessageName(const Descriptor* descriptor) {
+ if (descriptor == NULL) {
+ return "";
+ }
+ 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.
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);
- }
-
+ string prefix = GetFilePath(options, file_descriptor) +
+ GetNestedMessageName(containing_type);
if (!prefix.empty()) {
prefix += ".";
}
-
return prefix;
}
-
-// Returns the fully normalized JavaScript path for the given
+// Returns the fully normalized JavaScript path prefix for the given
// message descriptor.
-string GetPath(const GeneratorOptions& options,
- const Descriptor* descriptor) {
+string GetMessagePathPrefix(const GeneratorOptions& options,
+ const Descriptor* descriptor) {
return GetPrefix(
options, descriptor->file(),
- descriptor->containing_type()) + descriptor->name();
+ descriptor->containing_type());
}
-
// 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());
+// message descriptor.
+string GetMessagePath(const GeneratorOptions& options,
+ const Descriptor* descriptor) {
+ return GetMessagePathPrefix(options, descriptor) + descriptor->name();
}
-// Returns the fully normalized JavaScript path for the given
+// Returns the fully normalized JavaScript path prefix 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();
+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 value descriptor.
-string GetPath(const GeneratorOptions& options,
- const EnumValueDescriptor* value_descriptor) {
- return GetPath(
- options,
- value_descriptor->type()) + "." + value_descriptor->name();
+// enumeration descriptor.
+string GetEnumPath(const GeneratorOptions& options,
+ const EnumDescriptor* enum_descriptor) {
+ return GetEnumPathPrefix(options, enum_descriptor) + enum_descriptor->name();
}
string MaybeCrossFileRef(const GeneratorOptions& options,
const FileDescriptor* from_file,
const Descriptor* to_message) {
- if (options.import_style == GeneratorOptions::IMPORT_COMMONJS &&
+ if (options.import_style == GeneratorOptions::kImportCommonJs &&
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();
+ 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 GetPath(options, to_message);
+ return GetMessagePath(options, to_message);
}
}
@@ -307,8 +312,8 @@ char ToLowerASCII(char c) {
}
}
-vector<string> ParseLowerUnderscore(const string& input) {
- vector<string> words;
+std::vector<string> ParseLowerUnderscore(const string& input) {
+ std::vector<string> words;
string running = "";
for (int i = 0; i < input.size(); i++) {
if (input[i] == '_') {
@@ -326,8 +331,8 @@ vector<string> ParseLowerUnderscore(const string& input) {
return words;
}
-vector<string> ParseUpperCamel(const string& input) {
- vector<string> words;
+std::vector<string> ParseUpperCamel(const string& input) {
+ std::vector<string> words;
string running = "";
for (int i = 0; i < input.size(); i++) {
if (input[i] >= 'A' && input[i] <= 'Z' && !running.empty()) {
@@ -342,7 +347,7 @@ vector<string> ParseUpperCamel(const string& input) {
return words;
}
-string ToLowerCamel(const vector<string>& words) {
+string ToLowerCamel(const std::vector<string>& words) {
string result;
for (int i = 0; i < words.size(); i++) {
string word = words[i];
@@ -356,7 +361,7 @@ string ToLowerCamel(const vector<string>& words) {
return result;
}
-string ToUpperCamel(const vector<string>& words) {
+string ToUpperCamel(const std::vector<string>& words) {
string result;
for (int i = 0; i < words.size(); i++) {
string word = words[i];
@@ -405,21 +410,24 @@ string ToFileName(const string& input) {
// that top-level extensions should go in.
string GetExtensionFileName(const GeneratorOptions& options,
const FileDescriptor* file) {
- return options.output_dir + "/" + ToFileName(GetPath(options, file)) + ".js";
+ return options.output_dir + "/" + ToFileName(GetFilePath(options, file)) +
+ options.GetFileNameExtension();
}
// When we're generating one output file per type name, this is the filename
// that a top-level message should go in.
string GetMessageFileName(const GeneratorOptions& options,
const Descriptor* desc) {
- return options.output_dir + "/" + ToFileName(desc->name()) + ".js";
+ return options.output_dir + "/" + ToFileName(desc->name()) +
+ options.GetFileNameExtension();
}
// When we're generating one output file per type name, this is the filename
// that a top-level message should go in.
string GetEnumFileName(const GeneratorOptions& options,
const EnumDescriptor* desc) {
- return options.output_dir + "/" + ToFileName(desc->name()) + ".js";
+ return options.output_dir + "/" + ToFileName(desc->name()) +
+ options.GetFileNameExtension();
}
// Returns the message/response ID, if set.
@@ -444,9 +452,19 @@ bool IgnoreField(const FieldDescriptor* field) {
}
+// Used inside Google only -- do not remove.
+bool ShouldTreatMapsAsRepeatedFields(const FileDescriptor& descriptor) {
+ return false;
+}
+
// Do we ignore this message type?
bool IgnoreMessage(const GeneratorOptions& options, const Descriptor* d) {
- return d->options().map_entry();
+ return d->options().map_entry() &&
+ !ShouldTreatMapsAsRepeatedFields(*d->file());
+}
+
+bool IsMap(const GeneratorOptions& options, const FieldDescriptor* field) {
+ return field->is_map() && !ShouldTreatMapsAsRepeatedFields(*field->file());
}
// Does JSPB ignore this entire oneof? True only if all fields are ignored.
@@ -459,10 +477,8 @@ bool IgnoreOneof(const OneofDescriptor* oneof) {
return true;
}
-string JSIdent(const GeneratorOptions& options,
- const FieldDescriptor* field,
- bool is_upper_camel,
- bool is_map) {
+string JSIdent(const GeneratorOptions& options, const FieldDescriptor* field,
+ bool is_upper_camel, bool is_map, bool drop_list) {
string result;
if (field->type() == FieldDescriptor::TYPE_GROUP) {
result = is_upper_camel ?
@@ -473,10 +489,10 @@ string JSIdent(const GeneratorOptions& options,
ToUpperCamel(ParseLowerUnderscore(field->name())) :
ToLowerCamel(ParseLowerUnderscore(field->name()));
}
- if (is_map || (field->is_map())) {
+ if (is_map || IsMap(options, field)) {
// JSPB-style or proto3-style map.
result += "Map";
- } else if (field->is_repeated()) {
+ } else if (!drop_list && field->is_repeated()) {
// Repeated field.
result += "List";
}
@@ -485,11 +501,10 @@ string JSIdent(const GeneratorOptions& options,
string JSObjectFieldName(const GeneratorOptions& options,
const FieldDescriptor* field) {
- string name = JSIdent(
- options,
- field,
- /* is_upper_camel = */ false,
- /* is_map = */ false);
+ string name = JSIdent(options, field,
+ /* is_upper_camel = */ false,
+ /* is_map = */ false,
+ /* drop_list = */ false);
if (IsReserved(name)) {
name = "pb_" + name;
}
@@ -514,10 +529,11 @@ string JSByteGetterSuffix(BytesMode bytes_mode) {
// name, e.g. MyField for .getMyField().
string JSGetterName(const GeneratorOptions& options,
const FieldDescriptor* field,
- BytesMode bytes_mode = BYTES_DEFAULT) {
+ BytesMode bytes_mode = BYTES_DEFAULT,
+ bool drop_list = false) {
string name = JSIdent(options, field,
/* is_upper_camel = */ true,
- /* is_map = */ false);
+ /* is_map = */ false, drop_list);
if (field->type() == FieldDescriptor::TYPE_BYTES) {
string suffix = JSByteGetterSuffix(bytes_mode);
if (!suffix.empty()) {
@@ -531,13 +547,6 @@ string JSGetterName(const GeneratorOptions& options,
return name;
}
-string JSMapGetterName(const GeneratorOptions& options,
- const FieldDescriptor* field) {
- return JSIdent(options, field,
- /* is_upper_camel = */ true,
- /* is_map = */ true);
-}
-
string JSOneofName(const OneofDescriptor* oneof) {
@@ -763,11 +772,29 @@ string DoubleToString(double value) {
return PostProcessFloat(result);
}
+// 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() == google::protobuf::FieldOptions::JS_STRING;
+ default:
+ return false;
+ }
+}
+
string MaybeNumberString(const FieldDescriptor* field, const string& orig) {
- return orig;
+ return IsIntegralFieldWithStringJSType(field) ? ("\"" + orig + "\"") : orig;
}
string JSFieldDefault(const FieldDescriptor* field) {
+ if (field->is_repeated()) {
+ return "[]";
+ }
+
switch (field->cpp_type()) {
case FieldDescriptor::CPPTYPE_INT32:
return MaybeNumberString(
@@ -848,18 +875,18 @@ string ProtoTypeName(const GeneratorOptions& options,
case FieldDescriptor::TYPE_BYTES:
return "bytes";
case FieldDescriptor::TYPE_GROUP:
- return GetPath(options, field->message_type());
+ return GetMessagePath(options, field->message_type());
case FieldDescriptor::TYPE_ENUM:
- return GetPath(options, field->enum_type());
+ return GetEnumPath(options, field->enum_type());
case FieldDescriptor::TYPE_MESSAGE:
- return GetPath(options, field->message_type());
+ return GetMessagePath(options, field->message_type());
default:
return "";
}
}
string JSIntegerTypeName(const FieldDescriptor* field) {
- return "number";
+ return IsIntegralFieldWithStringJSType(field) ? "string" : "number";
}
string JSStringTypeName(const GeneratorOptions& options,
@@ -901,28 +928,93 @@ string JSTypeName(const GeneratorOptions& options,
case FieldDescriptor::CPPTYPE_STRING:
return JSStringTypeName(options, field, bytes_mode);
case FieldDescriptor::CPPTYPE_ENUM:
- return GetPath(options, field->enum_type());
+ return GetEnumPath(options, field->enum_type());
case FieldDescriptor::CPPTYPE_MESSAGE:
- return GetPath(options, field->message_type());
+ return GetMessagePath(options, field->message_type());
default:
return "";
}
}
-bool HasFieldPresence(const FieldDescriptor* field);
+// 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 string& type) {
+ return type == "undefined" || type == "string" || type == "number" ||
+ type == "boolean";
+}
string JSFieldTypeAnnotation(const GeneratorOptions& options,
const FieldDescriptor* field,
- bool force_optional,
+ bool is_setter_argument,
bool force_present,
bool singular_if_not_packed,
BytesMode bytes_mode = BYTES_DEFAULT) {
- bool is_primitive =
- (field->cpp_type() != FieldDescriptor::CPPTYPE_ENUM &&
- field->cpp_type() != FieldDescriptor::CPPTYPE_MESSAGE &&
- (field->type() != FieldDescriptor::TYPE_BYTES ||
- bytes_mode == BYTES_B64));
-
+ GOOGLE_CHECK(!(is_setter_argument && force_present));
string jstype = JSTypeName(options, field, bytes_mode);
if (field->is_repeated() &&
@@ -931,27 +1023,35 @@ string JSFieldTypeAnnotation(const GeneratorOptions& options,
bytes_mode == BYTES_DEFAULT) {
jstype = "(Array<!Uint8Array>|Array<string>)";
} else {
- if (!is_primitive) {
+ if (!IsPrimitive(jstype)) {
jstype = "!" + jstype;
}
- jstype = "Array.<" + jstype + ">";
- }
- if (!force_optional) {
- jstype = "!" + jstype;
+ jstype = "Array<" + jstype + ">";
}
}
- if (field->is_optional() && is_primitive &&
- force_optional && !force_present) {
- jstype += "?";
- } else if (field->is_required() && !is_primitive && !force_optional) {
- jstype = "!" + jstype;
- }
+ bool is_null_or_undefined = false;
- if (force_optional && HasFieldPresence(field)) {
- jstype += "|undefined";
+ 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 (force_present && jstype[0] != '!' && !is_primitive) {
+
+ if (!is_null_or_undefined && !IsPrimitive(jstype)) {
jstype = "!" + jstype;
}
@@ -963,8 +1063,7 @@ string JSBinaryReaderMethodType(const FieldDescriptor* field) {
if (name[0] >= 'a' && name[0] <= 'z') {
name[0] = (name[0] - 'a') + 'A';
}
-
- return name;
+ return IsIntegralFieldWithStringJSType(field) ? (name + "String") : name;
}
string JSBinaryReadWriteMethodName(const FieldDescriptor* field,
@@ -980,36 +1079,63 @@ string JSBinaryReadWriteMethodName(const FieldDescriptor* field,
string JSBinaryReaderMethodName(const GeneratorOptions& options,
const FieldDescriptor* field) {
- if (options.binary) {
- return "jspb.BinaryReader.prototype.read" +
- JSBinaryReadWriteMethodName(field, /* is_writer = */ false);
- } else {
- return "null";
- }
+ return "jspb.BinaryReader.prototype.read" +
+ JSBinaryReadWriteMethodName(field, /* is_writer = */ false);
}
string JSBinaryWriterMethodName(const GeneratorOptions& options,
const FieldDescriptor* field) {
- if (options.binary) {
- return "jspb.BinaryWriter.prototype.write" +
- JSBinaryReadWriteMethodName(field, /* is_writer = */ true);
- } else {
- return "null";
- }
+ return "jspb.BinaryWriter.prototype.write" +
+ JSBinaryReadWriteMethodName(field, /* is_writer = */ true);
}
string JSReturnClause(const FieldDescriptor* desc) {
return "";
}
+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 "";
+}
+
string JSReturnDoc(const GeneratorOptions& options,
const FieldDescriptor* desc) {
return "";
}
-bool HasRepeatedFields(const Descriptor* desc) {
+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()) {
+ if (desc->field(i)->is_repeated() && !IsMap(options, desc->field(i))) {
return true;
}
}
@@ -1020,8 +1146,9 @@ static const char* kRepeatedFieldArrayName = ".repeatedFields_";
string RepeatedFieldsArrayName(const GeneratorOptions& options,
const Descriptor* desc) {
- return HasRepeatedFields(desc) ?
- (GetPath(options, desc) + kRepeatedFieldArrayName) : "null";
+ return HasRepeatedFields(options, desc)
+ ? (GetMessagePath(options, desc) + kRepeatedFieldArrayName)
+ : "null";
}
bool HasOneofFields(const Descriptor* desc) {
@@ -1037,14 +1164,16 @@ static const char* kOneofGroupArrayName = ".oneofGroups_";
string OneofFieldsArrayName(const GeneratorOptions& options,
const Descriptor* desc) {
- return HasOneofFields(desc) ?
- (GetPath(options, desc) + kOneofGroupArrayName) : "null";
+ return HasOneofFields(desc)
+ ? (GetMessagePath(options, desc) + kOneofGroupArrayName)
+ : "null";
}
-string RepeatedFieldNumberList(const Descriptor* desc) {
+string RepeatedFieldNumberList(const GeneratorOptions& options,
+ const Descriptor* desc) {
std::vector<string> numbers;
for (int i = 0; i < desc->field_count(); i++) {
- if (desc->field(i)->is_repeated() && !desc->field(i)->is_map()) {
+ if (desc->field(i)->is_repeated() && !IsMap(options, desc->field(i))) {
numbers.push_back(JSFieldIndex(desc->field(i)));
}
}
@@ -1108,7 +1237,7 @@ 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.
+ // TODO(haberman): fix this for the kImportCommonJs case.
return "jspb.Message.messageSetExtensions";
} else {
return MaybeCrossFileRef(options, from_file, desc) + ".extensions";
@@ -1130,7 +1259,7 @@ const FieldDescriptor* MapFieldValue(const FieldDescriptor* field) {
string FieldDefinition(const GeneratorOptions& options,
const FieldDescriptor* field) {
- if (field->is_map()) {
+ if (IsMap(options, field)) {
const FieldDescriptor* key_field = MapFieldKey(field);
const FieldDescriptor* value_field = MapFieldValue(field);
string key_type = ProtoTypeName(options, key_field);
@@ -1178,13 +1307,6 @@ string FieldComments(const FieldDescriptor* field, BytesMode bytes_mode) {
" * 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";
- }
if (field->type() == FieldDescriptor::TYPE_BYTES && bytes_mode == BYTES_U8) {
comments +=
" * Note that Uint8Array is not supported on all browsers.\n"
@@ -1227,6 +1349,29 @@ bool HasExtensions(const FileDescriptor* file) {
return false;
}
+bool HasMap(const GeneratorOptions& options, const Descriptor* desc) {
+ for (int i = 0; i < desc->field_count(); i++) {
+ if (IsMap(options, desc->field(i))) {
+ 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;
}
@@ -1234,7 +1379,7 @@ bool IsExtendable(const Descriptor* desc) {
// 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)
+ static const int kDefaultPivot = 500;
// Find the max field number
int max_field_number = 0;
@@ -1246,7 +1391,7 @@ string GetPivot(const Descriptor* desc) {
}
int pivot = -1;
- if (IsExtendable(desc)) {
+ if (IsExtendable(desc) || (max_field_number >= kDefaultPivot)) {
pivot = ((max_field_number + 1) < kDefaultPivot) ?
(max_field_number + 1) : kDefaultPivot;
}
@@ -1254,47 +1399,24 @@ string GetPivot(const Descriptor* desc) {
return SimpleItoa(pivot);
}
-// Returns true for fields that represent "null" as distinct from the default
-// value. See http://go/proto3#heading=h.kozewqqcqhuz for more information.
-bool HasFieldPresence(const FieldDescriptor* field) {
- if (field->is_repeated()) {
+// 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) {
+ if (field->is_repeated() || field->is_map()) {
+ // We say repeated fields and maps don't have presence, but we still do
+ // generate clearFoo() methods for them through a special case elsewhere.
return false;
}
- 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 http://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: // includes BYTES
- return "\"\"";
-
- default:
- // MESSAGE is handled separately.
- assert(false);
- return "";
+ if (UseBrokenPresenceSemantics(options, field)) {
+ // Proto3 files with broken presence semantics have field presence.
+ return true;
}
+
+ return field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE ||
+ field->containing_oneof() != NULL ||
+ field->file()->syntax() == FileDescriptor::SYNTAX_PROTO2;
}
// We use this to implement the semantics that same file can be generated
@@ -1321,19 +1443,19 @@ class FileDeduplicator {
return true;
}
- void GetAllowedSet(set<const void*>* allowed_set) {
+ void GetAllowedSet(std::set<const void*>* allowed_set) {
*allowed_set = allowed_descs_;
}
private:
bool error_on_conflict_;
- map<string, const void*> descs_by_filename_;
- set<const void*> allowed_descs_;
+ std::map<string, const void*> descs_by_filename_;
+ std::set<const void*> allowed_descs_;
};
void DepthFirstSearch(const FileDescriptor* file,
- vector<const FileDescriptor*>* list,
- set<const FileDescriptor*>* seen) {
+ std::vector<const FileDescriptor*>* list,
+ std::set<const FileDescriptor*>* seen) {
if (!seen->insert(file).second) {
return;
}
@@ -1351,7 +1473,7 @@ void DepthFirstSearch(const FileDescriptor* file,
// FileDescriptor is not in the given set.
class NotInSet {
public:
- explicit NotInSet(const set<const FileDescriptor*>& file_set)
+ explicit NotInSet(const std::set<const FileDescriptor*>& file_set)
: file_set_(file_set) {}
bool operator()(const FileDescriptor* file) {
@@ -1359,21 +1481,21 @@ class NotInSet {
}
private:
- const set<const FileDescriptor*>& file_set_;
+ const std::set<const FileDescriptor*>& 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 vector<const FileDescriptor*>& input,
- vector<const FileDescriptor*>* ordered) {
+void GenerateJspbFileOrder(const std::vector<const FileDescriptor*>& input,
+ std::vector<const FileDescriptor*>* 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();
- set<const FileDescriptor*> seen;
- set<const FileDescriptor*> input_set;
+ std::set<const FileDescriptor*> seen;
+ std::set<const FileDescriptor*> input_set;
for (int i = 0; i < input.size(); i++) {
DepthFirstSearch(input[i], ordered, &seen);
input_set.insert(input[i]);
@@ -1390,10 +1512,10 @@ void GenerateJspbFileOrder(const vector<const FileDescriptor*>& input,
// only those to generate code.
bool GenerateJspbAllowedSet(const GeneratorOptions& options,
- const vector<const FileDescriptor*>& files,
- set<const void*>* allowed_set,
+ const std::vector<const FileDescriptor*>& files,
+ std::set<const void*>* allowed_set,
string* error) {
- vector<const FileDescriptor*> files_ordered;
+ std::vector<const FileDescriptor*> files_ordered;
GenerateJspbFileOrder(files, &files_ordered);
// Choose the last descriptor for each filename.
@@ -1434,6 +1556,22 @@ bool GenerateJspbAllowedSet(const GeneratorOptions& options,
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.
+ string meta_content;
+ annotations.SerializeToString(&meta_content);
+ 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);
+}
+
} // anonymous namespace
void Generator::GenerateHeader(const GeneratorOptions& options,
@@ -1441,6 +1579,10 @@ void Generator::GenerateHeader(const GeneratorOptions& options,
printer->Print("/**\n"
" * @fileoverview\n"
" * @enhanceable\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"
@@ -1461,7 +1603,7 @@ void Generator::FindProvidesForFile(const GeneratorOptions& options,
void Generator::FindProvides(const GeneratorOptions& options,
io::Printer* printer,
- const vector<const FileDescriptor*>& files,
+ const std::vector<const FileDescriptor*>& files,
std::set<string>* provided) const {
for (int i = 0; i < files.size(); i++) {
FindProvidesForFile(options, printer, files[i], provided);
@@ -1479,7 +1621,7 @@ void Generator::FindProvidesForMessage(
return;
}
- string name = GetPath(options, desc);
+ string name = GetMessagePath(options, desc);
provided->insert(name);
for (int i = 0; i < desc->enum_type_count(); i++) {
@@ -1496,14 +1638,14 @@ void Generator::FindProvidesForEnum(const GeneratorOptions& options,
io::Printer* printer,
const EnumDescriptor* enumdesc,
std::set<string>* provided) const {
- string name = GetPath(options, enumdesc);
+ string name = GetEnumPath(options, enumdesc);
provided->insert(name);
}
void Generator::FindProvidesForFields(
const GeneratorOptions& options,
io::Printer* printer,
- const vector<const FieldDescriptor*>& fields,
+ const std::vector<const FieldDescriptor*>& fields,
std::set<string>* provided) const {
for (int i = 0; i < fields.size(); i++) {
const FieldDescriptor* field = fields[i];
@@ -1512,9 +1654,8 @@ void Generator::FindProvidesForFields(
continue;
}
- string name =
- GetPath(options, field->file()) + "." +
- JSObjectFieldName(options, field);
+ string name = GetFilePath(options, field->file()) + "." +
+ JSObjectFieldName(options, field);
provided->insert(name);
}
}
@@ -1524,8 +1665,19 @@ void Generator::GenerateProvides(const GeneratorOptions& options,
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);
+ 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() { /* ... */ }
+ printer->Print("goog.exportSymbol('$name$', null, global);\n", "name",
+ *it);
+ }
}
}
@@ -1541,18 +1693,20 @@ void Generator::GenerateRequiresForMessage(const GeneratorOptions& options,
GenerateRequiresImpl(options, printer, &required, &forwards, provided,
/* require_jspb = */ have_message,
- /* require_extension = */ HasExtensions(desc));
+ /* require_extension = */ HasExtensions(desc),
+ /* require_map = */ HasMap(options, desc));
}
void Generator::GenerateRequiresForLibrary(
const GeneratorOptions& options, io::Printer* printer,
- const vector<const FileDescriptor*>& files,
+ const std::vector<const FileDescriptor*>& files,
std::set<string>* provided) const {
- GOOGLE_CHECK_EQ(options.import_style, GeneratorOptions::IMPORT_CLOSURE);
+ GOOGLE_CHECK_EQ(options.import_style, GeneratorOptions::kImportClosure);
// 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_map = false;
bool have_message = false;
for (int i = 0; i < files.size(); i++) {
@@ -1568,6 +1722,10 @@ void Generator::GenerateRequiresForLibrary(
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)) {
@@ -1575,7 +1733,7 @@ void Generator::GenerateRequiresForLibrary(
}
if (extension->containing_type()->full_name() !=
"google.protobuf.bridge.MessageSet") {
- required.insert(GetPath(options, extension->containing_type()));
+ required.insert(GetMessagePath(options, extension->containing_type()));
}
FindRequiresForField(options, extension, &required, &forwards);
have_extensions = true;
@@ -1584,12 +1742,13 @@ void Generator::GenerateRequiresForLibrary(
GenerateRequiresImpl(options, printer, &required, &forwards, provided,
/* require_jspb = */ have_message,
- /* require_extension = */ have_extensions);
+ /* require_extension = */ have_extensions,
+ /* require_map = */ have_map);
}
void Generator::GenerateRequiresForExtensions(
const GeneratorOptions& options, io::Printer* printer,
- const vector<const FieldDescriptor*>& fields,
+ const std::vector<const FieldDescriptor*>& fields,
std::set<string>* provided) const {
std::set<string> required;
std::set<string> forwards;
@@ -1603,7 +1762,8 @@ void Generator::GenerateRequiresForExtensions(
GenerateRequiresImpl(options, printer, &required, &forwards, provided,
/* require_jspb = */ false,
- /* require_extension = */ fields.size() > 0);
+ /* require_extension = */ fields.size() > 0,
+ /* require_map = */ false);
}
void Generator::GenerateRequiresImpl(const GeneratorOptions& options,
@@ -1611,20 +1771,19 @@ void Generator::GenerateRequiresImpl(const GeneratorOptions& options,
std::set<string>* required,
std::set<string>* forwards,
std::set<string>* provided,
- bool require_jspb,
- bool require_extension) const {
+ bool require_jspb, bool require_extension,
+ bool require_map) 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");
- }
+ required->insert("jspb.Message");
+ required->insert("jspb.BinaryReader");
+ required->insert("jspb.BinaryWriter");
}
if (require_extension) {
- printer->Print(
- "goog.require('jspb.ExtensionFieldInfo');\n");
+ required->insert("jspb.ExtensionFieldBinaryInfo");
+ required->insert("jspb.ExtensionFieldInfo");
+ }
+ if (require_map) {
+ required->insert("jspb.Map");
}
std::set<string>::iterator it;
@@ -1693,13 +1852,13 @@ void Generator::FindRequiresForField(const GeneratorOptions& options,
// 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()));
+ required->insert(GetEnumPath(options, field->enum_type()));
} else {
- forwards->insert(GetPath(options, field->enum_type()));
+ forwards->insert(GetEnumPath(options, field->enum_type()));
}
} else if (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) {
if (!IgnoreMessage(options, field->message_type())) {
- required->insert(GetPath(options, field->message_type()));
+ required->insert(GetMessagePath(options, field->message_type()));
}
}
}
@@ -1709,7 +1868,7 @@ void Generator::FindRequiresForExtension(const GeneratorOptions& options,
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()));
+ required->insert(GetMessagePath(options, field->containing_type()));
}
FindRequiresForField(options, field, required, forwards);
}
@@ -1747,35 +1906,37 @@ void Generator::GenerateClass(const GeneratorOptions& options,
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);
+ // 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 (IsExtendable(desc) && desc->full_name() != "google.protobuf.bridge.MessageSet") {
GenerateClassExtensionFieldInfo(options, printer, desc);
}
- if (options.import_style != GeneratorOptions:: IMPORT_CLOSURE) {
+ if (options.import_style != GeneratorOptions::kImportClosure) {
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,
@@ -1796,8 +1957,10 @@ void Generator::GenerateClassConstructor(const GeneratorOptions& options,
" * @extends {jspb.Message}\n"
" * @constructor\n"
" */\n"
- "$classname$ = function(opt_data) {\n",
- "classname", GetPath(options, desc));
+ "$classprefix$$classname$ = function(opt_data) {\n",
+ "classprefix", GetMessagePathPrefix(options, desc),
+ "classname", desc->name());
+ printer->Annotate("classname", desc);
string message_id = GetMessageId(desc);
printer->Print(
" jspb.Message.initialize(this, opt_data, $messageId$, $pivot$, "
@@ -1814,13 +1977,13 @@ void Generator::GenerateClassConstructor(const GeneratorOptions& options,
"if (goog.DEBUG && !COMPILED) {\n"
" $classname$.displayName = '$classname$';\n"
"}\n",
- "classname", GetPath(options, desc));
+ "classname", GetMessagePath(options, desc));
}
void Generator::GenerateClassFieldInfo(const GeneratorOptions& options,
io::Printer* printer,
const Descriptor* desc) const {
- if (HasRepeatedFields(desc)) {
+ if (HasRepeatedFields(options, desc)) {
printer->Print(
"/**\n"
" * List of repeated fields within this message type.\n"
@@ -1829,9 +1992,9 @@ void Generator::GenerateClassFieldInfo(const GeneratorOptions& options,
" */\n"
"$classname$$rptfieldarray$ = $rptfields$;\n"
"\n",
- "classname", GetPath(options, desc),
+ "classname", GetMessagePath(options, desc),
"rptfieldarray", kRepeatedFieldArrayName,
- "rptfields", RepeatedFieldNumberList(desc));
+ "rptfields", RepeatedFieldNumberList(options, desc));
}
if (HasOneofFields(desc)) {
@@ -1850,7 +2013,7 @@ void Generator::GenerateClassFieldInfo(const GeneratorOptions& options,
" */\n"
"$classname$$oneofgrouparray$ = $oneofgroups$;\n"
"\n",
- "classname", GetPath(options, desc),
+ "classname", GetMessagePath(options, desc),
"oneofgrouparray", kOneofGroupArrayName,
"oneofgroups", OneofGroupList(desc));
@@ -1870,7 +2033,7 @@ void Generator::GenerateClassXid(const GeneratorOptions& options,
"\n"
"\n"
"$class$.prototype.messageXid = xid('$class$');\n",
- "class", GetPath(options, desc));
+ "class", GetMessagePath(options, desc));
}
void Generator::GenerateOneofCaseDefinition(
@@ -1883,7 +2046,7 @@ void Generator::GenerateOneofCaseDefinition(
" */\n"
"$classname$.$oneof$Case = {\n"
" $upcase$_NOT_SET: 0",
- "classname", GetPath(options, oneof->containing_type()),
+ "classname", GetMessagePath(options, oneof->containing_type()),
"oneof", JSOneofName(oneof),
"upcase", ToEnumCase(oneof->name()));
@@ -1897,6 +2060,7 @@ void Generator::GenerateOneofCaseDefinition(
" $upcase$: $number$",
"upcase", ToEnumCase(oneof->field(i)->name()),
"number", JSFieldIndex(oneof->field(i)));
+ printer->Annotate("upcase", oneof->field(i));
}
printer->Print(
@@ -1911,7 +2075,7 @@ void Generator::GenerateOneofCaseDefinition(
"computeOneofCase(this, $class$.oneofGroups_[$oneofindex$]));\n"
"};\n"
"\n",
- "class", GetPath(options, oneof->containing_type()),
+ "class", GetMessagePath(options, oneof->containing_type()),
"oneof", JSOneofName(oneof),
"oneofindex", JSOneofIndex(oneof));
}
@@ -1950,10 +2114,11 @@ void Generator::GenerateClassToObject(const GeneratorOptions& options,
" * 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", GetPath(options, desc));
+ "classname", GetMessagePath(options, desc));
bool first = true;
for (int i = 0; i < desc->field_count(); i++) {
@@ -1985,7 +2150,7 @@ void Generator::GenerateClassToObject(const GeneratorOptions& options,
" $extObject$, $class$.prototype.getExtension,\n"
" includeInstance);\n",
"extObject", JSExtensionsObjectName(options, desc->file(), desc),
- "class", GetPath(options, desc));
+ "class", GetMessagePath(options, desc));
}
printer->Print(
@@ -1997,7 +2162,53 @@ void Generator::GenerateClassToObject(const GeneratorOptions& options,
"}\n"
"\n"
"\n",
- "classname", GetPath(options, desc));
+ "classname", GetMessagePath(options, desc));
+}
+
+void Generator::GenerateFieldValueExpression(io::Printer* printer,
+ const char *obj_reference,
+ const FieldDescriptor* field,
+ bool use_default) const {
+ bool is_float_or_double =
+ field->cpp_type() == FieldDescriptor::CPPTYPE_FLOAT ||
+ field->cpp_type() == FieldDescriptor::CPPTYPE_DOUBLE;
+ if (use_default) {
+ if (is_float_or_double) {
+ // Coerce "Nan" and "Infinity" to actual float values.
+ //
+ // This will change null to 0, but that doesn't matter since we're getting
+ // with a default.
+ printer->Print("+");
+ }
+
+ printer->Print(
+ "jspb.Message.getFieldWithDefault($obj$, $index$, $default$)",
+ "obj", obj_reference,
+ "index", JSFieldIndex(field),
+ "default", JSFieldDefault(field));
+ } else {
+ if (is_float_or_double) {
+ if (field->is_required()) {
+ // Use "+" to convert all fields to numeric (including null).
+ printer->Print(
+ "+jspb.Message.getField($obj$, $index$)",
+ "index", JSFieldIndex(field),
+ "obj", obj_reference);
+ } else {
+ // Converts "NaN" and "Infinity" while preserving null.
+ printer->Print(
+ "jspb.Message.get$cardinality$FloatingPointField($obj$, $index$)",
+ "cardinality", field->is_repeated() ? "Repeated" : "Optional",
+ "index", JSFieldIndex(field),
+ "obj", obj_reference);
+ }
+ } else {
+ printer->Print("jspb.Message.get$cardinality$Field($obj$, $index$)",
+ "cardinality", field->is_repeated() ? "Repeated" : "",
+ "index", JSFieldIndex(field),
+ "obj", obj_reference);
+ }
+ }
}
void Generator::GenerateClassFieldToObject(const GeneratorOptions& options,
@@ -2006,9 +2217,21 @@ void Generator::GenerateClassFieldToObject(const GeneratorOptions& options,
printer->Print("$fieldname$: ",
"fieldname", JSObjectFieldName(options, field));
- if (field->is_map()) {
- printer->Print("(f = msg.get$name$(true)) ? f.toArray() : []",
- "name", JSGetterName(options, field));
+ if (IsMap(options, field)) {
+ 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.
+ 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()) {
@@ -2024,40 +2247,29 @@ void Generator::GenerateClassFieldToObject(const GeneratorOptions& options,
"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 {
- // Simple field (singular or repeated).
- if ((!HasFieldPresence(field) && !field->is_repeated()) ||
- field->type() == FieldDescriptor::TYPE_BYTES) {
- // Delegate to the generated get<field>() method in order not to duplicate
- // the proto3-field-default-value or byte-coercion logic here.
- printer->Print("msg.get$getter$()",
- "getter", JSGetterName(options, field, BYTES_B64));
- } else {
- if (field->has_default_value()) {
- printer->Print("!msg.has$name$() ? $defaultValue$ : ",
- "name", JSGetterName(options, field),
- "defaultValue", JSFieldDefault(field));
- }
- if (field->cpp_type() == FieldDescriptor::CPPTYPE_FLOAT ||
- field->cpp_type() == FieldDescriptor::CPPTYPE_DOUBLE) {
- if (field->is_repeated()) {
- printer->Print("jspb.Message.getRepeatedFloatingPointField("
- "msg, $index$)",
- "index", JSFieldIndex(field));
- } else if (field->is_optional() && !field->has_default_value()) {
- printer->Print("jspb.Message.getOptionalFloatingPointField("
- "msg, $index$)",
- "index", JSFieldIndex(field));
- } else {
- // Convert "NaN" to NaN.
- printer->Print("+jspb.Message.getField(msg, $index$)",
- "index", JSFieldIndex(field));
- }
- } else {
- printer->Print("jspb.Message.getField(msg, $index$)",
- "index", JSFieldIndex(field));
- }
+ 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() && !UseBrokenPresenceSemantics(options, field)) {
+ // 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.
+ GenerateFieldValueExpression(printer, "msg", field, use_default);
}
}
@@ -2074,7 +2286,7 @@ void Generator::GenerateClassFromObject(const GeneratorOptions& options,
" */\n"
"$classname$.fromObject = function(obj) {\n"
" var f, msg = new $classname$();\n",
- "classname", GetPath(options, desc));
+ "classname", GetMessagePath(options, desc));
for (int i = 0; i < desc->field_count(); i++) {
const FieldDescriptor* field = desc->field(i);
@@ -2091,16 +2303,28 @@ void Generator::GenerateClassFieldFromObject(
const GeneratorOptions& options,
io::Printer* printer,
const FieldDescriptor* field) const {
-
- if (field->is_map()) {
- // `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(
- " goog.isDef(obj.$name$) && "
- "jspb.Message.setField(msg, $index$, obj.$name$);\n",
- "name", JSObjectFieldName(options, field),
- "index", JSFieldIndex(field));
+ if (IsMap(options, field)) {
+ 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(
+ " goog.isDef(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(
+ " goog.isDef(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()) {
@@ -2133,21 +2357,6 @@ void Generator::GenerateClassFieldFromObject(
}
}
-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 {
@@ -2175,12 +2384,11 @@ void GenerateBytesWrapper(const GeneratorOptions& options,
io::Printer* printer,
const FieldDescriptor* field,
BytesMode bytes_mode) {
- string type =
- JSFieldTypeAnnotation(options, field,
- /* force_optional = */ false,
- /* force_present = */ !HasFieldPresence(field),
- /* singular_if_not_packed = */ false,
- bytes_mode);
+ string type = JSFieldTypeAnnotation(
+ options, field,
+ /* is_setter_argument = */ false,
+ /* force_present = */ false,
+ /* singular_if_not_packed = */ false, bytes_mode);
printer->Print(
"/**\n"
" * $fielddef$\n"
@@ -2197,31 +2405,30 @@ void GenerateBytesWrapper(const GeneratorOptions& options,
"fielddef", FieldDefinition(options, field),
"comment", FieldComments(field, bytes_mode),
"type", type,
- "class", GetPath(options, field->containing_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()) {
+ if (IsMap(options, field)) {
const FieldDescriptor* key_field = MapFieldKey(field);
const FieldDescriptor* value_field = MapFieldValue(field);
// Map field: special handling to instantiate the map object on demand.
string key_type =
JSFieldTypeAnnotation(
options, key_field,
- /* force_optional = */ false,
+ /* is_setter_argument = */ false,
/* force_present = */ true,
/* singular_if_not_packed = */ false);
string value_type =
JSFieldTypeAnnotation(
options, value_field,
- /* force_optional = */ false,
+ /* is_setter_argument = */ false,
/* force_present = */ true,
/* singular_if_not_packed = */ false);
@@ -2236,21 +2443,23 @@ void Generator::GenerateClassField(const GeneratorOptions& options,
"keytype", key_type,
"valuetype", value_type);
printer->Print(
- "$class$.prototype.get$name$ = function(opt_noLazyCreate) {\n"
+ "$class$.prototype.$gettername$ = function(opt_noLazyCreate) {\n"
" return /** @type {!jspb.Map<$keytype$,$valuetype$>} */ (\n",
- "class", GetPath(options, field->containing_type()),
- "name", JSGetterName(options, field),
+ "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"
+ printer->Print(
+ ",\n"
" $messageType$",
- "messageType", GetPath(options, value_field->message_type()));
- } else if (options.binary) {
+ "messageType", GetMessagePath(options, value_field->message_type()));
+ } else {
printer->Print(",\n"
" null");
}
@@ -2275,21 +2484,21 @@ void Generator::GenerateClassField(const GeneratorOptions& options,
"fielddef", FieldDefinition(options, field),
"comment", FieldComments(field, BYTES_DEFAULT),
"type", JSFieldTypeAnnotation(options, field,
- /* force_optional = */ false,
+ /* is_setter_argument = */ false,
/* force_present = */ false,
/* singular_if_not_packed = */ false));
printer->Print(
- "$class$.prototype.get$name$ = function() {\n"
+ "$class$.prototype.$gettername$ = 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(options, field),
+ "class", GetMessagePath(options, field->containing_type()),
+ "gettername", "get" + JSGetterName(options, field),
"type", JSFieldTypeAnnotation(options, field,
- /* force_optional = */ false,
+ /* is_setter_argument = */ false,
/* force_present = */ false,
/* singular_if_not_packed = */ false),
"rpt", (field->is_repeated() ? "Repeated" : ""),
@@ -2297,20 +2506,22 @@ void Generator::GenerateClassField(const GeneratorOptions& options,
"wrapperclass", SubmessageTypeRef(options, field),
"required", (field->label() == FieldDescriptor::LABEL_REQUIRED ?
", 1" : ""));
+ printer->Annotate("gettername", field);
printer->Print(
- "/** @param {$optionaltype$} value $returndoc$ */\n"
- "$class$.prototype.set$name$ = function(value) {\n"
+ "/** @param {$optionaltype$} value$returndoc$ */\n"
+ "$class$.prototype.$settername$ = function(value) {\n"
" jspb.Message.set$oneoftag$$repeatedtag$WrapperField(",
"optionaltype",
JSFieldTypeAnnotation(options, field,
- /* force_optional = */ true,
+ /* is_setter_argument = */ true,
/* force_present = */ false,
/* singular_if_not_packed = */ false),
"returndoc", JSReturnDoc(options, field),
- "class", GetPath(options, field->containing_type()),
- "name", JSGetterName(options, field),
+ "class", GetMessagePath(options, field->containing_type()),
+ "settername", "set" + JSGetterName(options, field),
"oneoftag", (field->containing_oneof() ? "Oneof" : ""),
"repeatedtag", (field->is_repeated() ? "Repeated" : ""));
+ printer->Annotate("settername", field);
printer->Print(
"this, $index$$oneofgroup$, value);$returnvalue$\n"
@@ -2322,16 +2533,9 @@ void Generator::GenerateClassField(const GeneratorOptions& options,
(", " + 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(options, field),
- "clearedvalue", (field->is_repeated() ? "[]" : "undefined"),
- "returnvalue", JSReturnClause(field));
+ if (field->is_repeated()) {
+ GenerateRepeatedMessageHelperMethods(options, printer, field);
+ }
} else {
bool untyped =
@@ -2340,17 +2544,17 @@ void Generator::GenerateClassField(const GeneratorOptions& options,
// 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 the return
+ // 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;
- string typed_annotation =
- JSFieldTypeAnnotation(options, field,
- /* force_optional = */ false,
- /* force_present = */ !HasFieldPresence(field),
- /* singular_if_not_packed = */ false,
- /* bytes_mode = */ bytes_mode);
+ 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"
@@ -2369,9 +2573,10 @@ void Generator::GenerateClassField(const GeneratorOptions& options,
}
printer->Print(
- "$class$.prototype.get$name$ = function() {\n",
- "class", GetPath(options, field->containing_type()),
- "name", JSGetterName(options, field));
+ "$class$.prototype.$gettername$ = function() {\n",
+ "class", GetMessagePath(options, field->containing_type()),
+ "gettername", "get" + JSGetterName(options, field));
+ printer->Annotate("gettername", field);
if (untyped) {
printer->Print(
@@ -2382,36 +2587,21 @@ void Generator::GenerateClassField(const GeneratorOptions& options,
"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->is_repeated()) {
- printer->Print("!this.has$name$() ? $defaultValue$ : ",
- "name", JSGetterName(options, field),
- "defaultValue", JSFieldDefault(field));
- }
- if (field->cpp_type() == FieldDescriptor::CPPTYPE_FLOAT ||
- field->cpp_type() == FieldDescriptor::CPPTYPE_DOUBLE) {
- if (field->is_repeated()) {
- printer->Print("jspb.Message.getRepeatedFloatingPointField("
- "this, $index$)",
- "index", JSFieldIndex(field));
- } else {
- // Convert "NaN" to NaN.
- printer->Print("+jspb.Message.getField(this, $index$)",
- "index", JSFieldIndex(field));
- }
- } else {
- printer->Print("jspb.Message.getField(this, $index$)",
- "index", JSFieldIndex(field));
- }
+ 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"
@@ -2434,85 +2624,205 @@ void Generator::GenerateClassField(const GeneratorOptions& options,
if (untyped) {
printer->Print(
"/**\n"
- " * @param {*} value $returndoc$\n"
+ " * @param {*} value$returndoc$\n"
" */\n",
"returndoc", JSReturnDoc(options, field));
} else {
printer->Print(
- "/** @param {$optionaltype$} value $returndoc$ */\n",
- "optionaltype",
- JSFieldTypeAnnotation(options, field,
- /* force_optional = */ true,
- /* force_present = */ !HasFieldPresence(field),
- /* singular_if_not_packed = */ false),
+ "/** @param {$optionaltype$} value$returndoc$ */\n", "optionaltype",
+ JSFieldTypeAnnotation(
+ options, field,
+ /* is_setter_argument = */ true,
+ /* force_present = */ false,
+ /* singular_if_not_packed = */ 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(options, field),
- "oneoftag", (field->containing_oneof() ? "Oneof" : ""),
- "index", JSFieldIndex(field));
- printer->Print(
- "$oneofgroup$, $type$value$rptvalueinit$$typeclose$);$returnvalue$\n"
- "};\n"
- "\n"
- "\n",
- "type",
- untyped ? "/** @type{string|number|boolean|Array|undefined} */(" : "",
- "typeclose", untyped ? ")" : "",
- "oneofgroup",
- (field->containing_oneof() ? (", " + JSOneofArray(options, field))
- : ""),
- "returnvalue", JSReturnClause(field), "rptvalueinit",
- (field->is_repeated() ? " || []" : ""));
+
+ 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"
+ " jspb.Message.setProto3$typetag$Field(this, $index$, "
+ "value);$returnvalue$\n"
+ "};\n"
+ "\n"
+ "\n",
+ "class", GetMessagePath(options, field->containing_type()),
+ "settername", "set" + JSGetterName(options, field), "typetag",
+ JSTypeTag(field), "index", JSFieldIndex(field), "returnvalue",
+ JSReturnClause(field));
+ printer->Annotate("settername", field);
+ } else {
+ // Otherwise, use the regular setField function.
+ printer->Print(
+ "$class$.prototype.$settername$ = function(value) {\n"
+ " jspb.Message.set$oneoftag$Field(this, $index$",
+ "class", GetMessagePath(options, field->containing_type()),
+ "settername", "set" + JSGetterName(options, field), "oneoftag",
+ (field->containing_oneof() ? "Oneof" : ""), "index",
+ JSFieldIndex(field));
+ printer->Annotate("settername", field);
+ printer->Print(
+ "$oneofgroup$, $type$value$rptvalueinit$$typeclose$);$returnvalue$\n"
+ "};\n"
+ "\n"
+ "\n",
+ "type",
+ untyped ? "/** @type{string|number|boolean|Array|undefined} */(" : "",
+ "typeclose", untyped ? ")" : "", "oneofgroup",
+ (field->containing_oneof() ? (", " + JSOneofArray(options, field))
+ : ""),
+ "returnvalue", JSReturnClause(field), "rptvalueinit",
+ (field->is_repeated() ? " || []" : ""));
+ }
if (untyped) {
printer->Print(
"/**\n"
- " * Clears the value. $returndoc$\n"
+ " * Clears the value.$returndoc$\n"
" */\n",
"returndoc", JSReturnDoc(options, field));
}
- if (HasFieldPresence(field) || field->is_repeated()) {
- printer->Print(
- "$class$.prototype.clear$name$ = function() {\n"
- " jspb.Message.set$oneoftag$Field(this, $index$$oneofgroup$, ",
- "class", GetPath(options, field->containing_type()),
- "name", JSGetterName(options, 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));
+
+ if (field->is_repeated()) {
+ GenerateRepeatedPrimitiveHelperMethods(options, printer, field, untyped);
}
}
- if (HasFieldPresence(field)) {
+ // Generate clearFoo() method for map fields, repeated fields, and other
+ // fields with presence.
+ if (IsMap(options, field)) {
+ printer->Print(
+ "$class$.prototype.$clearername$ = function() {\n"
+ " this.$gettername$().clear();$returnvalue$\n"
+ "};\n"
+ "\n"
+ "\n",
+ "class", GetMessagePath(options, field->containing_type()),
+ "clearername", "clear" + JSGetterName(options, field),
+ "gettername", "get" + JSGetterName(options, field),
+ "returnvalue", JSReturnClause(field));
+ 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.
+ printer->Print(
+ "$class$.prototype.$clearername$ = function() {\n"
+ " this.$settername$($clearedvalue$);$returnvalue$\n"
+ "};\n"
+ "\n"
+ "\n",
+ "class", GetMessagePath(options, field->containing_type()),
+ "clearername", "clear" + JSGetterName(options, field),
+ "settername", "set" + JSGetterName(options, field),
+ "clearedvalue", (field->is_repeated() ? "[]" : "undefined"),
+ "returnvalue", JSReturnClause(field));
+ 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.
+ printer->Print(
+ "$class$.prototype.$clearername$ = function() {\n"
+ " jspb.Message.set$maybeoneof$Field(this, "
+ "$index$$maybeoneofgroup$, ",
+ "class", GetMessagePath(options, field->containing_type()),
+ "clearername", "clear" + JSGetterName(options, field),
+ "maybeoneof", (field->containing_oneof() ? "Oneof" : ""),
+ "maybeoneofgroup", (field->containing_oneof() ?
+ (", " + JSOneofArray(options, field)) : ""),
+ "index", JSFieldIndex(field));
+ printer->Annotate("clearername", field);
+ printer->Print(
+ "$clearedvalue$);$returnvalue$\n"
+ "};\n"
+ "\n"
+ "\n",
+ "clearedvalue", (field->is_repeated() ? "[]" : "undefined"),
+ "returnvalue", JSReturnClause(field));
+ }
+
+ if (HasFieldPresence(options, field)) {
printer->Print(
"/**\n"
" * Returns whether this field is set.\n"
- " * @return{!boolean}\n"
+ " * @return {!boolean}\n"
" */\n"
- "$class$.prototype.has$name$ = function() {\n"
+ "$class$.prototype.$hasername$ = function() {\n"
" return jspb.Message.getField(this, $index$) != null;\n"
"};\n"
"\n"
"\n",
- "class", GetPath(options, field->containing_type()),
- "name", JSGetterName(options, field),
+ "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$returndoc$\n"
+ " */\n"
+ "$class$.prototype.$addername$ = function(value, opt_index) {\n"
+ " jspb.Message.addToRepeatedField(this, $index$",
+ "class", GetMessagePath(options, field->containing_type()), "addername",
+ "add" + JSGetterName(options, field, BYTES_DEFAULT,
+ /* drop_list = */ true),
+ "optionaltype", JSTypeName(options, field, BYTES_DEFAULT),
+ "index", JSFieldIndex(field),
+ "returndoc", JSReturnDoc(options, field));
+ printer->Annotate("addername", field);
+ printer->Print(
+ "$oneofgroup$, $type$value$rptvalueinit$$typeclose$, "
+ "opt_index);$returnvalue$\n"
+ "};\n"
+ "\n"
+ "\n",
+ "type", untyped ? "/** @type{string|number|boolean|!Uint8Array} */(" : "",
+ "typeclose", untyped ? ")" : "", "oneofgroup",
+ (field->containing_oneof() ? (", " + JSOneofArray(options, field)) : ""),
+ "rptvalueinit", "",
+ "returnvalue", JSReturnClause(field));
+ // 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.add$name$ = function(opt_value, opt_index) {\n"
+ " return jspb.Message.addTo$repeatedtag$WrapperField(",
+ "optionaltype", JSTypeName(options, field, BYTES_DEFAULT),
+ "class", GetMessagePath(options, field->containing_type()),
+ "name", JSGetterName(options, field, BYTES_DEFAULT,
+ /* drop_list = */ true),
+ "repeatedtag", (field->is_repeated() ? "Repeated" : ""));
+
+ printer->Print(
+ "this, $index$$oneofgroup$, opt_value, $ctor$, opt_index);\n"
+ "};\n"
+ "\n"
+ "\n",
+ "index", JSFieldIndex(field), "oneofgroup",
+ (field->containing_oneof() ? (", " + JSOneofArray(options, field)) : ""),
+ "ctor", GetMessagePath(options, field->message_type()));
+}
+
void Generator::GenerateClassExtensionFieldInfo(const GeneratorOptions& options,
io::Printer* printer,
const Descriptor* desc) const {
@@ -2532,34 +2842,32 @@ void Generator::GenerateClassExtensionFieldInfo(const GeneratorOptions& options,
"so that it\n"
" * works in OPTIMIZED mode.\n"
" *\n"
- " * @type {!Object.<number, jspb.ExtensionFieldInfo>}\n"
+ " * @type {!Object<number, jspb.ExtensionFieldInfo>}\n"
" */\n"
"$class$.extensions = {};\n"
"\n",
- "class", GetPath(options, desc));
+ "class", GetMessagePath(options, desc));
- if (options.binary) {
- 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$.extensionsBinary = {};\n"
- "\n",
- "class", GetPath(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<number, jspb.ExtensionFieldBinaryInfo>}\n"
+ " */\n"
+ "$class$.extensionsBinary = {};\n"
+ "\n",
+ "class", GetMessagePath(options, desc));
}
}
@@ -2597,10 +2905,12 @@ void Generator::GenerateClassDeserializeBinary(const GeneratorOptions& options,
" }\n"
" var field = reader.getFieldNumber();\n"
" switch (field) {\n",
- "class", GetPath(options, desc));
+ "class", GetMessagePath(options, desc));
for (int i = 0; i < desc->field_count(); i++) {
- GenerateClassDeserializeBinaryField(options, printer, desc->field(i));
+ if (!IgnoreField(desc->field(i))) {
+ GenerateClassDeserializeBinaryField(options, printer, desc->field(i));
+ }
}
printer->Print(
@@ -2612,7 +2922,7 @@ void Generator::GenerateClassDeserializeBinary(const GeneratorOptions& options,
" $class$.prototype.setExtension);\n"
" break;\n",
"extobj", JSExtensionsObjectName(options, desc->file(), desc),
- "class", GetPath(options, desc));
+ "class", GetMessagePath(options, desc));
} else {
printer->Print(
" reader.skipField();\n"
@@ -2636,7 +2946,7 @@ void Generator::GenerateClassDeserializeBinaryField(
printer->Print(" case $num$:\n",
"num", SimpleItoa(field->number()));
- if (field->is_map()) {
+ if (IsMap(options, field)) {
const FieldDescriptor* key_field = MapFieldKey(field);
const FieldDescriptor* value_field = MapFieldValue(field);
printer->Print(
@@ -2651,7 +2961,7 @@ void Generator::GenerateClassDeserializeBinaryField(
if (value_field->type() == FieldDescriptor::TYPE_MESSAGE) {
printer->Print(", $messageType$.deserializeBinaryFromReader",
- "messageType", GetPath(options, value_field->message_type()));
+ "messageType", GetMessagePath(options, value_field->message_type()));
}
printer->Print(");\n");
@@ -2679,13 +2989,9 @@ void Generator::GenerateClassDeserializeBinaryField(
}
if (field->is_repeated() && !field->is_packed()) {
- // Repeated fields receive a |value| one at at a time; append to array
- // returned by get$name$(). Annoyingly, we have to call 'set' after
- // changing the array.
- printer->Print(" msg.get$name$().push(value);\n", "name",
- JSGetterName(options, field));
- printer->Print(" msg.set$name$(msg.get$name$());\n", "name",
- JSGetterName(options, field));
+ 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
@@ -2704,47 +3010,40 @@ void Generator::GenerateClassSerializeBinary(const GeneratorOptions& options,
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"
+ " $class$.serializeBinaryToWriter(this, 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"
+ " * 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$.prototype.serializeBinaryToWriter = function (writer) {\n"
+ "$class$.serializeBinaryToWriter = function(message, "
+ "writer) {\n"
" var f = undefined;\n",
- "class", GetPath(options, desc));
+ "class", GetMessagePath(options, desc));
for (int i = 0; i < desc->field_count(); i++) {
- GenerateClassSerializeBinaryField(options, printer, desc->field(i));
+ if (!IgnoreField(desc->field(i))) {
+ GenerateClassSerializeBinaryField(options, printer, desc->field(i));
+ }
}
if (IsExtendable(desc)) {
printer->Print(
- " jspb.Message.serializeBinaryExtensions(this, writer,\n"
+ " jspb.Message.serializeBinaryExtensions(message, writer,\n"
" $extobj$Binary, $class$.prototype.getExtension);\n",
"extobj", JSExtensionsObjectName(options, desc->file(), desc),
- "class", GetPath(options, desc));
+ "class", GetMessagePath(options, desc));
}
printer->Print(
@@ -2757,30 +3056,37 @@ void Generator::GenerateClassSerializeBinaryField(
const GeneratorOptions& options,
io::Printer* printer,
const FieldDescriptor* field) const {
- if (HasFieldPresence(field) &&
+ if (HasFieldPresence(options, field) &&
field->cpp_type() != FieldDescriptor::CPPTYPE_MESSAGE) {
+ 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 = jspb.Message.getField(this, $index$);\n",
- "index", JSFieldIndex(field));
+ " f = /** @type {$type$} */ "
+ "(jspb.Message.getField(message, $index$));\n",
+ "index", JSFieldIndex(field),
+ "type", typed_annotation);
} else {
printer->Print(
- " f = this.get$name$($nolazy$);\n",
+ " 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" : "");
+ "nolazy", IsMap(options, field) ? "true" : "");
}
-
// Print an `if (condition)` statement that evaluates to true if the field
// goes on the wire.
- if (field->is_map()) {
+ if (IsMap(options, field)) {
printer->Print(
" if (f && f.getLength() > 0) {\n");
} else if (field->is_repeated()) {
printer->Print(
" if (f.length > 0) {\n");
} else {
- if (HasFieldPresence(field)) {
+ if (HasFieldPresence(options, field)) {
printer->Print(
" if (f != null) {\n");
} else {
@@ -2792,7 +3098,13 @@ void Generator::GenerateClassSerializeBinaryField(
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;
@@ -2820,7 +3132,7 @@ void Generator::GenerateClassSerializeBinaryField(
}
// Write the field on the wire.
- if (field->is_map()) {
+ if (IsMap(options, field)) {
const FieldDescriptor* key_field = MapFieldKey(field);
const FieldDescriptor* value_field = MapFieldValue(field);
printer->Print(
@@ -2832,7 +3144,7 @@ void Generator::GenerateClassSerializeBinaryField(
if (value_field->type() == FieldDescriptor::TYPE_MESSAGE) {
printer->Print(", $messageType$.serializeBinaryToWriter",
- "messageType", GetPath(options, value_field->message_type()));
+ "messageType", GetMessagePath(options, value_field->message_type()));
}
printer->Print(");\n");
@@ -2845,7 +3157,7 @@ void Generator::GenerateClassSerializeBinaryField(
"index", SimpleItoa(field->number()));
if (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE &&
- !(field->is_map())) {
+ !IsMap(options, field)) {
printer->Print(
",\n"
" $submsg$.serializeBinaryToWriter\n",
@@ -2870,8 +3182,10 @@ void Generator::GenerateEnum(const GeneratorOptions& options,
"/**\n"
" * @enum {number}\n"
" */\n"
- "$name$ = {\n",
- "name", GetPath(options, enumdesc));
+ "$enumprefix$$name$ = {\n",
+ "enumprefix", GetEnumPathPrefix(options, enumdesc),
+ "name", enumdesc->name());
+ printer->Annotate("name", enumdesc);
for (int i = 0; i < enumdesc->value_count(); i++) {
const EnumValueDescriptor* value = enumdesc->value(i);
@@ -2880,6 +3194,7 @@ void Generator::GenerateEnum(const GeneratorOptions& options,
"name", ToEnumCase(value->name()),
"value", SimpleItoa(value->number()),
"comma", (i == enumdesc->value_count() - 1) ? "" : ",");
+ printer->Annotate("name", value);
}
printer->Print(
@@ -2891,25 +3206,28 @@ 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()));
+ (field->extension_scope()
+ ? GetMessagePath(options, field->extension_scope())
+ : GetFilePath(options, field->file()));
+ const string extension_object_name = JSObjectFieldName(options, field);
printer->Print(
"\n"
"/**\n"
" * A tuple of {field number, class constructor} for the extension\n"
- " * field named `$name$`.\n"
- " * @type {!jspb.ExtensionFieldInfo.<$extensionType$>}\n"
+ " * field named `$nameInComment$`.\n"
+ " * @type {!jspb.ExtensionFieldInfo<$extensionType$>}\n"
" */\n"
"$class$.$name$ = new jspb.ExtensionFieldInfo(\n",
- "name", JSObjectFieldName(options, field),
+ "nameInComment", extension_object_name,
+ "name", extension_object_name,
"class", extension_scope,
"extensionType", JSFieldTypeAnnotation(
options, field,
- /* force_optional = */ false,
+ /* is_setter_argument = */ false,
/* force_present = */ true,
/* singular_if_not_packed = */ false));
+ printer->Annotate("name", field);
printer->Print(
" $index$,\n"
" {$name$: 0},\n"
@@ -2919,7 +3237,7 @@ void Generator::GenerateExtension(const GeneratorOptions& options,
" $toObject$),\n"
" $repeated$);\n",
"index", SimpleItoa(field->number()),
- "name", JSObjectFieldName(options, field),
+ "name", extension_object_name,
"ctor", (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE ?
SubmessageTypeRef(options, field) : string("null")),
"toObject", (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE ?
@@ -2927,35 +3245,30 @@ void Generator::GenerateExtension(const GeneratorOptions& options,
string("null")),
"repeated", (field->is_repeated() ? "1" : "0"));
- if (options.binary) {
- 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", SimpleItoa(field->number()),
- "class", extension_scope,
- "name", JSObjectFieldName(options, field),
- "binaryReaderFn", JSBinaryReaderMethodName(options, field),
- "binaryWriterFn", JSBinaryWriterMethodName(options, 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");
-
- printer->Print(
- " $isPacked$);\n",
- "isPacked", (field->is_packed() ? "true" : "false"));
- }
+ 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", SimpleItoa(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"
@@ -2966,11 +3279,11 @@ void Generator::GenerateExtension(const GeneratorOptions& options,
field->containing_type()),
"index", SimpleItoa(field->number()),
"class", extension_scope,
- "name", JSObjectFieldName(options, field));
+ "name", extension_object_name);
}
bool GeneratorOptions::ParseFromOptions(
- const vector< pair< string, string > >& options,
+ const std::vector< std::pair< string, string > >& options,
string* error) {
for (int i = 0; i < options.size(); i++) {
if (options[i].first == "add_require_for_enums") {
@@ -3005,17 +3318,31 @@ bool GeneratorOptions::ParseFromOptions(
library = options[i].second;
} else if (options[i].first == "import_style") {
if (options[i].second == "closure") {
- import_style = IMPORT_CLOSURE;
+ import_style = kImportClosure;
} else if (options[i].second == "commonjs") {
- import_style = IMPORT_COMMONJS;
+ import_style = kImportCommonJs;
} else if (options[i].second == "browser") {
- import_style = IMPORT_BROWSER;
+ import_style = kImportBrowser;
} else if (options[i].second == "es6") {
- import_style = IMPORT_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.
@@ -3027,18 +3354,40 @@ bool GeneratorOptions::ParseFromOptions(
}
}
- if (!library.empty() && import_style != IMPORT_CLOSURE) {
- *error = "The library option should only be used for "
- "import_style=closure";
+ if (import_style != kImportClosure &&
+ (add_require_for_enums || testonly || !library.empty() ||
+ error_on_name_conflict || extension != ".js" ||
+ one_output_file_per_input_file)) {
+ *error =
+ "The add_require_for_enums, testonly, library, error_on_name_conflict, "
+ "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 type.
+ return kOneOutputFilePerType;
+}
+
void Generator::GenerateFilesInDepOrder(
const GeneratorOptions& options,
io::Printer* printer,
- const vector<const FileDescriptor*>& files) const {
+ const std::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());
@@ -3081,7 +3430,7 @@ void Generator::GenerateFile(const GeneratorOptions& options,
GenerateHeader(options, printer);
// Generate "require" statements.
- if (options.import_style == GeneratorOptions::IMPORT_COMMONJS) {
+ if (options.import_style == GeneratorOptions::kImportCommonJs) {
printer->Print("var jspb = require('google-protobuf');\n");
printer->Print("var goog = jspb;\n");
printer->Print("var global = Function('return this')();\n\n");
@@ -3091,52 +3440,62 @@ void Generator::GenerateFile(const GeneratorOptions& options,
printer->Print(
"var $alias$ = require('$file$');\n",
"alias", ModuleAlias(name),
- "file", GetRootPath(file->name(), name) + GetJSFilename(name));
+ "file",
+ GetRootPath(file->name(), name) + GetJSFilename(options, 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() { /* ... */ }
- set<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.
+ std::set<string> provided;
+ std::set<const FieldDescriptor*> extensions;
for (int i = 0; i < file->extension_count(); i++) {
- provided.insert(file->extension(i)->full_name());
+ // 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(GetFilePath(options, file) + "." +
+ JSObjectFieldName(options, file->extension(i)));
+ extensions.insert(file->extension(i));
}
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);
+ GenerateProvides(options, printer, &provided);
+ std::vector<const FileDescriptor*> files;
+ files.push_back(file);
+ if (options.import_style == GeneratorOptions::kImportClosure) {
+ GenerateRequiresForLibrary(options, printer, files, &provided);
}
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));
+ // Generate code for top-level extensions. Extensions nested inside messages
+ // are emitted inside GenerateClassesAndEnums().
+ for (std::set<const FieldDescriptor*>::const_iterator it = extensions.begin();
+ it != extensions.end(); ++it) {
+ GenerateExtension(options, printer, *it);
}
- if (options.import_style == GeneratorOptions::IMPORT_COMMONJS) {
+ if (options.import_style == GeneratorOptions::kImportCommonJs) {
printer->Print("goog.object.extend(exports, $package$);\n",
- "package", GetPath(options, file));
+ "package", GetFilePath(options, file));
+ }
+
+ // Emit well-known type methods.
+ for (FileToc* toc = well_known_types_js; toc->name != NULL; toc++) {
+ string name = string("google/protobuf/") + toc->name;
+ if (name == StripProto(file->name()) + ".js") {
+ printer->Print(toc->data);
+ }
}
}
-bool Generator::GenerateAll(const vector<const FileDescriptor*>& files,
+bool Generator::GenerateAll(const std::vector<const FileDescriptor*>& files,
const string& parameter,
GeneratorContext* context,
string* error) const {
- vector< pair< string, string > > option_pairs;
+ std::vector< std::pair< string, string > > option_pairs;
ParseGeneratorParameter(parameter, &option_pairs);
GeneratorOptions options;
if (!options.ParseFromOptions(option_pairs, error)) {
@@ -3144,22 +3503,17 @@ bool Generator::GenerateAll(const vector<const FileDescriptor*>& files,
}
- // 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 != "") {
+ if (options.output_mode() == GeneratorOptions::kEverythingInOneFile) {
// 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));
+ string filename = options.output_dir + "/" + options.library +
+ options.GetFileNameExtension();
+ std::unique_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;
+ std::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);
@@ -3187,8 +3541,8 @@ bool Generator::GenerateAll(const vector<const FileDescriptor*>& files,
if (printer.failed()) {
return false;
}
- } else if (options.import_style == GeneratorOptions::IMPORT_CLOSURE) {
- set<const void*> allowed_set;
+ } else if (options.output_mode() == GeneratorOptions::kOneOutputFilePerType) {
+ std::set<const void*> allowed_set;
if (!GenerateJspbAllowedSet(options, files, &allowed_set, error)) {
return false;
}
@@ -3202,7 +3556,7 @@ bool Generator::GenerateAll(const vector<const FileDescriptor*>& files,
}
string filename = GetMessageFileName(options, desc);
- google::protobuf::scoped_ptr<io::ZeroCopyOutputStream> output(
+ std::unique_ptr<io::ZeroCopyOutputStream> output(
context->Open(filename));
GOOGLE_CHECK(output.get());
io::Printer printer(output.get(), '$');
@@ -3228,7 +3582,7 @@ bool Generator::GenerateAll(const vector<const FileDescriptor*>& files,
}
string filename = GetEnumFileName(options, enumdesc);
- google::protobuf::scoped_ptr<io::ZeroCopyOutputStream> output(
+ std::unique_ptr<io::ZeroCopyOutputStream> output(
context->Open(filename));
GOOGLE_CHECK(output.get());
io::Printer printer(output.get(), '$');
@@ -3251,7 +3605,7 @@ bool Generator::GenerateAll(const vector<const FileDescriptor*>& files,
if (allowed_set.count(file) == 1) {
string filename = GetExtensionFileName(options, file);
- google::protobuf::scoped_ptr<io::ZeroCopyOutputStream> output(
+ std::unique_ptr<io::ZeroCopyOutputStream> output(
context->Open(filename));
GOOGLE_CHECK(output.get());
io::Printer printer(output.get(), '$');
@@ -3259,7 +3613,7 @@ bool Generator::GenerateAll(const vector<const FileDescriptor*>& files,
GenerateHeader(options, &printer);
std::set<string> provided;
- vector<const FieldDescriptor*> fields;
+ std::vector<const FieldDescriptor*> fields;
for (int j = 0; j < files[i]->extension_count(); j++) {
if (ShouldGenerateExtension(files[i]->extension(j))) {
@@ -3279,25 +3633,34 @@ bool Generator::GenerateAll(const vector<const FileDescriptor*>& files,
}
}
}
- } else {
+ } else /* options.output_mode() == kOneOutputFilePerInputFile */ {
// 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));
+ string filename =
+ options.output_dir + "/" + GetJSFilename(options, file->name());
+ std::unique_ptr<io::ZeroCopyOutputStream> output(context->Open(filename));
GOOGLE_CHECK(output.get());
- io::Printer printer(output.get(), '$');
+ GeneratedCodeInfo annotations;
+ io::AnnotationProtoCollector<GeneratedCodeInfo> annotation_collector(
+ &annotations);
+ io::Printer printer(output.get(), '$',
+ options.annotate_code ? &annotation_collector : NULL);
+
GenerateFile(options, &printer, file);
if (printer.failed()) {
return false;
}
+
+ if (options.annotate_code) {
+ EmbedCodeAnnotations(annotations, &printer);
+ }
}
}
-
return true;
}