| Index: third_party/protobuf/src/google/protobuf/compiler/js/js_generator.cc
|
| diff --git a/third_party/protobuf/src/google/protobuf/compiler/js/js_generator.cc b/third_party/protobuf/src/google/protobuf/compiler/js/js_generator.cc
|
| index e6c3b36aab9f8097cd4b359292c53a7173aef98c..a66d28efe58465ef941d0aefe484643b869acc33 100755
|
| --- a/third_party/protobuf/src/google/protobuf/compiler/js/js_generator.cc
|
| +++ b/third_party/protobuf/src/google/protobuf/compiler/js/js_generator.cc
|
| @@ -123,6 +123,16 @@ static const int kNumKeyword = sizeof(kKeyword) / sizeof(char*);
|
|
|
| namespace {
|
|
|
| +// The mode of operation for bytes fields. Historically JSPB always carried
|
| +// bytes as JS {string}, containing base64 content by convention. With binary
|
| +// and proto3 serialization the new convention is to represent it as binary
|
| +// data in Uint8Array. See b/26173701 for background on the migration.
|
| +enum BytesMode {
|
| + BYTES_DEFAULT, // Default type for getBytesField to return.
|
| + BYTES_B64, // Explicitly coerce to base64 string where needed.
|
| + BYTES_U8, // Explicitly coerce to Uint8Array where needed.
|
| +};
|
| +
|
| bool IsReserved(const string& ident) {
|
| for (int i = 0; i < kNumKeyword; i++) {
|
| if (ident == kKeyword[i]) {
|
| @@ -134,12 +144,49 @@ bool IsReserved(const string& ident) {
|
|
|
| // 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";
|
| return StripSuffixString(filename, suffix);
|
| }
|
|
|
| +// Given a filename like foo/bar/baz.proto, returns the correspoding JavaScript
|
| +// file foo/bar/baz.js.
|
| +string GetJSFilename(const string& filename) {
|
| + return StripProto(filename) + "_pb.js";
|
| +}
|
| +
|
| +// Given a filename like foo/bar/baz.proto, returns the root directory
|
| +// path ../../
|
| +string GetRootPath(const string& filename) {
|
| + size_t slashes = std::count(filename.begin(), filename.end(), '/');
|
| + if (slashes == 0) {
|
| + return "./";
|
| + }
|
| + string result = "";
|
| + for (size_t i = 0; i < slashes; i++) {
|
| + result += "../";
|
| + }
|
| + return result;
|
| +}
|
| +
|
| +// Returns the alias we assign to the module of the given .proto filename
|
| +// when importing.
|
| +string ModuleAlias(const string& filename) {
|
| + // This scheme could technically cause problems if a file includes any 2 of:
|
| + // foo/bar_baz.proto
|
| + // foo_bar_baz.proto
|
| + // foo_bar/baz.proto
|
| + //
|
| + // We'll worry about this problem if/when we actually see it. This name isn't
|
| + // exposed to users so we can change it later if we need to.
|
| + string basename = StripProto(filename);
|
| + StripString(&basename, "-", '$');
|
| + StripString(&basename, "/", '_');
|
| + return basename + "_pb";
|
| +}
|
| +
|
| // Returns the fully normalized JavaScript path for the given
|
| // file descriptor's package.
|
| string GetPath(const GeneratorOptions& options,
|
| @@ -215,6 +262,26 @@ string GetPath(const GeneratorOptions& options,
|
| value_descriptor->type()) + "." + value_descriptor->name();
|
| }
|
|
|
| +string MaybeCrossFileRef(const GeneratorOptions& options,
|
| + const FileDescriptor* from_file,
|
| + const Descriptor* to_message) {
|
| + if (options.import_style == GeneratorOptions::IMPORT_COMMONJS &&
|
| + from_file != to_message->file()) {
|
| + // Cross-file ref in CommonJS needs to use the module alias instead of
|
| + // the global name.
|
| + return ModuleAlias(to_message->file()->name()) + "." + to_message->name();
|
| + } else {
|
| + // Within a single file we use a full name.
|
| + return GetPath(options, to_message);
|
| + }
|
| +}
|
| +
|
| +string SubmessageTypeRef(const GeneratorOptions& options,
|
| + const FieldDescriptor* field) {
|
| + GOOGLE_CHECK(field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE);
|
| + return MaybeCrossFileRef(options, field->file(), field->message_type());
|
| +}
|
| +
|
| // - Object field name: LOWER_UNDERSCORE -> LOWER_CAMEL, except for group fields
|
| // (UPPER_CAMEL -> LOWER_CAMEL), with "List" (or "Map") appended if appropriate,
|
| // and with reserved words triggering a "pb_" prefix.
|
| @@ -326,15 +393,47 @@ string ToFileName(const string& input) {
|
| return result;
|
| }
|
|
|
| +// When we're generating one output file per type name, this is the filename
|
| +// that top-level extensions should go in.
|
| +string GetExtensionFileName(const GeneratorOptions& options,
|
| + const FileDescriptor* file) {
|
| + return options.output_dir + "/" + ToFileName(GetPath(options, file)) + ".js";
|
| +}
|
| +
|
| +// 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";
|
| +}
|
| +
|
| +// 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";
|
| +}
|
| +
|
| // Returns the message/response ID, if set.
|
| string GetMessageId(const Descriptor* desc) {
|
| return string();
|
| }
|
|
|
| +bool IgnoreExtensionField(const FieldDescriptor* field) {
|
| + // Exclude descriptor extensions from output "to avoid clutter" (from original
|
| + // codegen).
|
| + return field->is_extension() &&
|
| + field->containing_type()->file()->name() ==
|
| + "google/protobuf/descriptor.proto";
|
| +}
|
| +
|
|
|
| // Used inside Google only -- do not remove.
|
| bool IsResponse(const Descriptor* desc) { return false; }
|
| -bool IgnoreField(const FieldDescriptor* field) { return false; }
|
| +
|
| +bool IgnoreField(const FieldDescriptor* field) {
|
| + return IgnoreExtensionField(field);
|
| +}
|
|
|
|
|
| // Does JSPB ignore this entire oneof? True only if all fields are ignored.
|
| @@ -379,12 +478,32 @@ string JSObjectFieldName(const FieldDescriptor* field) {
|
| return name;
|
| }
|
|
|
| +string JSByteGetterSuffix(BytesMode bytes_mode) {
|
| + switch (bytes_mode) {
|
| + case BYTES_DEFAULT:
|
| + return "";
|
| + case BYTES_B64:
|
| + return "B64";
|
| + case BYTES_U8:
|
| + return "U8";
|
| + default:
|
| + assert(false);
|
| + }
|
| +}
|
| +
|
| // Returns the field name as a capitalized portion of a getter/setter method
|
| // name, e.g. MyField for .getMyField().
|
| -string JSGetterName(const FieldDescriptor* field) {
|
| +string JSGetterName(const FieldDescriptor* field,
|
| + BytesMode bytes_mode = BYTES_DEFAULT) {
|
| string name = JSIdent(field,
|
| /* is_upper_camel = */ true,
|
| /* is_map = */ false);
|
| + if (field->type() == FieldDescriptor::TYPE_BYTES) {
|
| + string suffix = JSByteGetterSuffix(bytes_mode);
|
| + if (!suffix.empty()) {
|
| + name += "_as" + suffix;
|
| + }
|
| + }
|
| if (name == "Extension" || name == "JsPbMessageId") {
|
| // Avoid conflicts with base-class names.
|
| name += "$";
|
| @@ -445,8 +564,7 @@ string JSOneofIndex(const OneofDescriptor* oneof) {
|
| return SimpleItoa(index);
|
| }
|
|
|
| -// Decodes a codepoint in \x0000 -- \xFFFF. Since JS strings are UTF-16, we only
|
| -// need to handle the BMP (16-bit range) here.
|
| +// Decodes a codepoint in \x0000 -- \xFFFF.
|
| uint16 DecodeUTF8Codepoint(uint8* bytes, size_t* length) {
|
| if (*length == 0) {
|
| return 0;
|
| @@ -483,80 +601,56 @@ uint16 DecodeUTF8Codepoint(uint8* bytes, size_t* length) {
|
| }
|
|
|
| // Escapes the contents of a string to be included within double-quotes ("") in
|
| -// JavaScript. |is_utf8| determines whether the input data (in a C++ string of
|
| -// chars) is UTF-8 encoded (in which case codepoints become JavaScript string
|
| -// characters, escaped with 16-bit hex escapes where necessary) or raw binary
|
| -// (in which case bytes become JavaScript string characters 0 -- 255).
|
| -string EscapeJSString(const string& in, bool is_utf8) {
|
| - string result;
|
| +// JavaScript. The input data should be a UTF-8 encoded C++ string of chars.
|
| +// Returns false if |out| was truncated because |in| contained invalid UTF-8 or
|
| +// codepoints outside the BMP.
|
| +// TODO(lukestebbing): Support codepoints outside the BMP.
|
| +bool EscapeJSString(const string& in, string* out) {
|
| size_t decoded = 0;
|
| for (size_t i = 0; i < in.size(); i += decoded) {
|
| uint16 codepoint = 0;
|
| - if (is_utf8) {
|
| - // Decode the next UTF-8 codepoint.
|
| - size_t have_bytes = in.size() - i;
|
| - uint8 bytes[3] = {
|
| + // Decode the next UTF-8 codepoint.
|
| + size_t have_bytes = in.size() - i;
|
| + uint8 bytes[3] = {
|
| static_cast<uint8>(in[i]),
|
| static_cast<uint8>(((i + 1) < in.size()) ? in[i + 1] : 0),
|
| static_cast<uint8>(((i + 2) < in.size()) ? in[i + 2] : 0),
|
| - };
|
| - codepoint = DecodeUTF8Codepoint(bytes, &have_bytes);
|
| - if (have_bytes == 0) {
|
| - break;
|
| - }
|
| - decoded = have_bytes;
|
| - } else {
|
| - codepoint = static_cast<uint16>(static_cast<uint8>(in[i]));
|
| - decoded = 1;
|
| + };
|
| + codepoint = DecodeUTF8Codepoint(bytes, &have_bytes);
|
| + if (have_bytes == 0) {
|
| + return false;
|
| }
|
| -
|
| - // Next byte -- used for minimal octal escapes below.
|
| - char next_byte = (i + decoded) < in.size() ?
|
| - in[i + decoded] : 0;
|
| - bool pad_octal = (next_byte >= '0' && next_byte <= '7');
|
| + decoded = have_bytes;
|
|
|
| switch (codepoint) {
|
| - case '\0': result += pad_octal ? "\\000" : "\\0"; break;
|
| - case '\b': result += "\\\b"; break;
|
| - case '\t': result += "\\\t"; break;
|
| - case '\n': result += "\\\n"; break;
|
| - case '\r': result += "\\\r"; break;
|
| - case '\f': result += "\\\f"; break;
|
| - case '\\': result += "\\\\"; break;
|
| - case '"': result += pad_octal ? "\\042" : "\\42"; break;
|
| - case '&': result += pad_octal ? "\\046" : "\\46"; break;
|
| - case '\'': result += pad_octal ? "\\047" : "\\47"; break;
|
| - case '<': result += pad_octal ? "\\074" : "\\74"; break;
|
| - case '=': result += pad_octal ? "\\075" : "\\75"; break;
|
| - case '>': result += pad_octal ? "\\076" : "\\76"; break;
|
| + case '\'': *out += "\\x27"; break;
|
| + case '"': *out += "\\x22"; break;
|
| + case '<': *out += "\\x3c"; break;
|
| + case '=': *out += "\\x3d"; break;
|
| + case '>': *out += "\\x3e"; break;
|
| + case '&': *out += "\\x26"; break;
|
| + case '\b': *out += "\\b"; break;
|
| + case '\t': *out += "\\t"; break;
|
| + case '\n': *out += "\\n"; break;
|
| + case '\f': *out += "\\f"; break;
|
| + case '\r': *out += "\\r"; break;
|
| + case '\\': *out += "\\\\"; break;
|
| default:
|
| - // All other non-ASCII codepoints are escaped.
|
| - // Original codegen uses hex for >= 0x100 and octal for others.
|
| + // TODO(lukestebbing): Once we're supporting codepoints outside the BMP,
|
| + // use a single Unicode codepoint escape if the output language is
|
| + // ECMAScript 2015 or above. Otherwise, use a surrogate pair.
|
| + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#String_literals
|
| if (codepoint >= 0x20 && codepoint <= 0x7e) {
|
| - result += static_cast<char>(codepoint);
|
| + *out += static_cast<char>(codepoint);
|
| + } else if (codepoint >= 0x100) {
|
| + *out += StringPrintf("\\u%04x", codepoint);
|
| } else {
|
| - if (codepoint >= 0x100) {
|
| - result += StringPrintf("\\u%04x", codepoint);
|
| - } else {
|
| - if (pad_octal || codepoint >= 0100) {
|
| - result += "\\";
|
| - result += ('0' + ((codepoint >> 6) & 07));
|
| - result += ('0' + ((codepoint >> 3) & 07));
|
| - result += ('0' + ((codepoint >> 0) & 07));
|
| - } else if (codepoint >= 010) {
|
| - result += "\\";
|
| - result += ('0' + ((codepoint >> 3) & 07));
|
| - result += ('0' + ((codepoint >> 0) & 07));
|
| - } else {
|
| - result += "\\";
|
| - result += ('0' + ((codepoint >> 0) & 07));
|
| - }
|
| - }
|
| + *out += StringPrintf("\\x%02x", codepoint);
|
| }
|
| break;
|
| }
|
| }
|
| - return result;
|
| + return true;
|
| }
|
|
|
| string EscapeBase64(const string& in) {
|
| @@ -681,11 +775,17 @@ string JSFieldDefault(const FieldDescriptor* field) {
|
| return DoubleToString(field->default_value_double());
|
| case FieldDescriptor::CPPTYPE_STRING:
|
| if (field->type() == FieldDescriptor::TYPE_STRING) {
|
| - return "\"" + EscapeJSString(field->default_value_string(), true) +
|
| - "\"";
|
| - } else {
|
| - return "\"" + EscapeBase64(field->default_value_string()) +
|
| - "\"";
|
| + string out;
|
| + bool is_valid = EscapeJSString(field->default_value_string(), &out);
|
| + if (!is_valid) {
|
| + // TODO(lukestebbing): Decide whether this should be a hard error.
|
| + GOOGLE_LOG(WARNING) << "The default value for field " << field->full_name()
|
| + << " was truncated since it contained invalid UTF-8 or"
|
| + " codepoints outside the basic multilingual plane.";
|
| + }
|
| + return "\"" + out + "\"";
|
| + } else { // Bytes
|
| + return "\"" + EscapeBase64(field->default_value_string()) + "\"";
|
| }
|
| case FieldDescriptor::CPPTYPE_MESSAGE:
|
| return "null";
|
| @@ -742,8 +842,27 @@ string JSIntegerTypeName(const FieldDescriptor* field) {
|
| return "number";
|
| }
|
|
|
| +string JSStringTypeName(const GeneratorOptions& options,
|
| + const FieldDescriptor* field,
|
| + BytesMode bytes_mode) {
|
| + if (field->type() == FieldDescriptor::TYPE_BYTES) {
|
| + switch (bytes_mode) {
|
| + case BYTES_DEFAULT:
|
| + return "(string|Uint8Array)";
|
| + case BYTES_B64:
|
| + return "string";
|
| + case BYTES_U8:
|
| + return "Uint8Array";
|
| + default:
|
| + assert(false);
|
| + }
|
| + }
|
| + return "string";
|
| +}
|
| +
|
| string JSTypeName(const GeneratorOptions& options,
|
| - const FieldDescriptor* field) {
|
| + const FieldDescriptor* field,
|
| + BytesMode bytes_mode) {
|
| switch (field->cpp_type()) {
|
| case FieldDescriptor::CPPTYPE_BOOL:
|
| return "boolean";
|
| @@ -760,7 +879,7 @@ string JSTypeName(const GeneratorOptions& options,
|
| case FieldDescriptor::CPPTYPE_DOUBLE:
|
| return "number";
|
| case FieldDescriptor::CPPTYPE_STRING:
|
| - return "string";
|
| + return JSStringTypeName(options, field, bytes_mode);
|
| case FieldDescriptor::CPPTYPE_ENUM:
|
| return GetPath(options, field->enum_type());
|
| case FieldDescriptor::CPPTYPE_MESSAGE:
|
| @@ -777,20 +896,26 @@ string JSFieldTypeAnnotation(const GeneratorOptions& options,
|
| bool force_optional,
|
| bool force_present,
|
| bool singular_if_not_packed,
|
| - bool always_singular) {
|
| + BytesMode bytes_mode = BYTES_DEFAULT) {
|
| bool is_primitive =
|
| (field->cpp_type() != FieldDescriptor::CPPTYPE_ENUM &&
|
| - field->cpp_type() != FieldDescriptor::CPPTYPE_MESSAGE);
|
| + field->cpp_type() != FieldDescriptor::CPPTYPE_MESSAGE &&
|
| + (field->type() != FieldDescriptor::TYPE_BYTES ||
|
| + bytes_mode == BYTES_B64));
|
|
|
| - string jstype = JSTypeName(options, field);
|
| + string jstype = JSTypeName(options, field, bytes_mode);
|
|
|
| if (field->is_repeated() &&
|
| - !always_singular &&
|
| (field->is_packed() || !singular_if_not_packed)) {
|
| - if (!is_primitive) {
|
| - jstype = "!" + jstype;
|
| + if (field->type() == FieldDescriptor::TYPE_BYTES &&
|
| + bytes_mode == BYTES_DEFAULT) {
|
| + jstype = "(Array<!Uint8Array>|Array<string>)";
|
| + } else {
|
| + if (!is_primitive) {
|
| + jstype = "!" + jstype;
|
| + }
|
| + jstype = "Array.<" + jstype + ">";
|
| }
|
| - jstype = "Array.<" + jstype + ">";
|
| if (!force_optional) {
|
| jstype = "!" + jstype;
|
| }
|
| @@ -825,10 +950,6 @@ string JSBinaryReaderMethodType(const FieldDescriptor* field) {
|
| string JSBinaryReadWriteMethodName(const FieldDescriptor* field,
|
| bool is_writer) {
|
| string name = JSBinaryReaderMethodType(field);
|
| - if (is_writer && field->type() == FieldDescriptor::TYPE_BYTES) {
|
| - // Override for `bytes` fields: treat string as raw bytes, not base64.
|
| - name = "BytesRawString";
|
| - }
|
| if (field->is_packed()) {
|
| name = "Packed" + name;
|
| } else if (is_writer && field->is_repeated()) {
|
| @@ -952,11 +1073,13 @@ string RelativeTypeName(const FieldDescriptor* field) {
|
| }
|
|
|
| string JSExtensionsObjectName(const GeneratorOptions& options,
|
| + const FileDescriptor* from_file,
|
| const Descriptor* desc) {
|
| if (desc->full_name() == "google.protobuf.bridge.MessageSet") {
|
| + // TODO(haberman): fix this for the IMPORT_COMMONJS case.
|
| return "jspb.Message.messageSetExtensions";
|
| } else {
|
| - return GetPath(options, desc) + ".extensions";
|
| + return MaybeCrossFileRef(options, from_file, desc) + ".extensions";
|
| }
|
| }
|
|
|
| @@ -983,7 +1106,7 @@ string FieldDefinition(const GeneratorOptions& options,
|
| field->number());
|
| }
|
|
|
| -string FieldComments(const FieldDescriptor* field) {
|
| +string FieldComments(const FieldDescriptor* field, BytesMode bytes_mode) {
|
| string comments;
|
| if (field->cpp_type() == FieldDescriptor::CPPTYPE_BOOL) {
|
| comments +=
|
| @@ -999,6 +1122,11 @@ string FieldComments(const FieldDescriptor* field) {
|
| " * 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"
|
| + " * @see http://caniuse.com/Uint8Array\n";
|
| + }
|
| return comments;
|
| }
|
|
|
| @@ -1009,8 +1137,10 @@ bool ShouldGenerateExtension(const FieldDescriptor* field) {
|
| }
|
|
|
| bool HasExtensions(const Descriptor* desc) {
|
| - if (desc->extension_count() > 0) {
|
| - return true;
|
| + for (int i = 0; i < desc->extension_count(); i++) {
|
| + if (ShouldGenerateExtension(desc->extension(i))) {
|
| + return true;
|
| + }
|
| }
|
| for (int i = 0; i < desc->nested_type_count(); i++) {
|
| if (HasExtensions(desc->nested_type(i))) {
|
| @@ -1062,7 +1192,7 @@ string GetPivot(const Descriptor* desc) {
|
| }
|
|
|
| // Returns true for fields that represent "null" as distinct from the default
|
| -// value. See https://go/proto3#heading=h.kozewqqcqhuz for more information.
|
| +// value. See http://go/proto3#heading=h.kozewqqcqhuz for more information.
|
| bool HasFieldPresence(const FieldDescriptor* field) {
|
| return
|
| (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) ||
|
| @@ -1071,7 +1201,7 @@ bool HasFieldPresence(const FieldDescriptor* field) {
|
| }
|
|
|
| // For proto3 fields without presence, returns a string representing the default
|
| -// value in JavaScript. See https://go/proto3#heading=h.kozewqqcqhuz for more
|
| +// value in JavaScript. See http://go/proto3#heading=h.kozewqqcqhuz for more
|
| // information.
|
| string Proto3PrimitiveFieldDefault(const FieldDescriptor* field) {
|
| switch (field->cpp_type()) {
|
| @@ -1090,16 +1220,153 @@ string Proto3PrimitiveFieldDefault(const FieldDescriptor* field) {
|
| case FieldDescriptor::CPPTYPE_BOOL:
|
| return "false";
|
|
|
| - case FieldDescriptor::CPPTYPE_STRING:
|
| + case FieldDescriptor::CPPTYPE_STRING: // includes BYTES
|
| return "\"\"";
|
|
|
| default:
|
| - // BYTES and MESSAGE are handled separately.
|
| + // MESSAGE is handled separately.
|
| assert(false);
|
| return "";
|
| }
|
| }
|
|
|
| +// We use this to implement the semantics that same file can be generated
|
| +// multiple times, but the last one wins. We never actually write the files,
|
| +// but we keep a set of which descriptors were the final one for a given
|
| +// filename.
|
| +class FileDeduplicator {
|
| + public:
|
| + explicit FileDeduplicator(const GeneratorOptions& options)
|
| + : error_on_conflict_(options.error_on_name_conflict) {}
|
| +
|
| + bool AddFile(const string& filename, const void* desc, string* error) {
|
| + if (descs_by_filename_.find(filename) != descs_by_filename_.end()) {
|
| + if (error_on_conflict_) {
|
| + *error = "Name conflict: file name " + filename +
|
| + " would be generated by two descriptors";
|
| + return false;
|
| + }
|
| + allowed_descs_.erase(descs_by_filename_[filename]);
|
| + }
|
| +
|
| + descs_by_filename_[filename] = desc;
|
| + allowed_descs_.insert(desc);
|
| + return true;
|
| + }
|
| +
|
| + void GetAllowedSet(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_;
|
| +};
|
| +
|
| +void DepthFirstSearch(const FileDescriptor* file,
|
| + vector<const FileDescriptor*>* list,
|
| + set<const FileDescriptor*>* seen) {
|
| + if (!seen->insert(file).second) {
|
| + return;
|
| + }
|
| +
|
| + // Add all dependencies.
|
| + for (int i = 0; i < file->dependency_count(); i++) {
|
| + DepthFirstSearch(file->dependency(i), list, seen);
|
| + }
|
| +
|
| + // Add this file.
|
| + list->push_back(file);
|
| +}
|
| +
|
| +// A functor for the predicate to remove_if() below. Returns true if a given
|
| +// FileDescriptor is not in the given set.
|
| +class NotInSet {
|
| + public:
|
| + explicit NotInSet(const set<const FileDescriptor*>& file_set)
|
| + : file_set_(file_set) {}
|
| +
|
| + bool operator()(const FileDescriptor* file) {
|
| + return file_set_.count(file) == 0;
|
| + }
|
| +
|
| + private:
|
| + const 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) {
|
| + // 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;
|
| + for (int i = 0; i < input.size(); i++) {
|
| + DepthFirstSearch(input[i], ordered, &seen);
|
| + input_set.insert(input[i]);
|
| + }
|
| +
|
| + // Now remove the entries that are not actually in our input list.
|
| + ordered->erase(
|
| + std::remove_if(ordered->begin(), ordered->end(), NotInSet(input_set)),
|
| + ordered->end());
|
| +}
|
| +
|
| +// If we're generating code in file-per-type mode, avoid overwriting files
|
| +// by choosing the last descriptor that writes each filename and permitting
|
| +// only those to generate code.
|
| +
|
| +bool GenerateJspbAllowedSet(const GeneratorOptions& options,
|
| + const vector<const FileDescriptor*>& files,
|
| + set<const void*>* allowed_set,
|
| + string* error) {
|
| + vector<const FileDescriptor*> files_ordered;
|
| + GenerateJspbFileOrder(files, &files_ordered);
|
| +
|
| + // Choose the last descriptor for each filename.
|
| + FileDeduplicator dedup(options);
|
| + for (int i = 0; i < files_ordered.size(); i++) {
|
| + for (int j = 0; j < files_ordered[i]->message_type_count(); j++) {
|
| + const Descriptor* desc = files_ordered[i]->message_type(j);
|
| + if (!dedup.AddFile(GetMessageFileName(options, desc), desc, error)) {
|
| + return false;
|
| + }
|
| + }
|
| + for (int j = 0; j < files_ordered[i]->enum_type_count(); j++) {
|
| + const EnumDescriptor* desc = files_ordered[i]->enum_type(j);
|
| + if (!dedup.AddFile(GetEnumFileName(options, desc), desc, error)) {
|
| + return false;
|
| + }
|
| + }
|
| +
|
| + // Pull out all free-floating extensions and generate files for those too.
|
| + bool has_extension = false;
|
| +
|
| + for (int j = 0; j < files_ordered[i]->extension_count(); j++) {
|
| + if (ShouldGenerateExtension(files_ordered[i]->extension(j))) {
|
| + has_extension = true;
|
| + }
|
| + }
|
| +
|
| + if (has_extension) {
|
| + if (!dedup.AddFile(GetExtensionFileName(options, files_ordered[i]),
|
| + files_ordered[i], error)) {
|
| + return false;
|
| + }
|
| + }
|
| + }
|
| +
|
| + dedup.GetAllowedSet(allowed_set);
|
| +
|
| + return true;
|
| +}
|
| +
|
| } // anonymous namespace
|
|
|
| void Generator::GenerateHeader(const GeneratorOptions& options,
|
| @@ -1113,19 +1380,24 @@ void Generator::GenerateHeader(const GeneratorOptions& options,
|
| "\n");
|
| }
|
|
|
| +void Generator::FindProvidesForFile(const GeneratorOptions& options,
|
| + io::Printer* printer,
|
| + const FileDescriptor* file,
|
| + std::set<string>* provided) const {
|
| + for (int i = 0; i < file->message_type_count(); i++) {
|
| + FindProvidesForMessage(options, printer, file->message_type(i), provided);
|
| + }
|
| + for (int i = 0; i < file->enum_type_count(); i++) {
|
| + FindProvidesForEnum(options, printer, file->enum_type(i), provided);
|
| + }
|
| +}
|
| +
|
| void Generator::FindProvides(const GeneratorOptions& options,
|
| io::Printer* printer,
|
| const vector<const FileDescriptor*>& files,
|
| std::set<string>* provided) const {
|
| for (int i = 0; i < files.size(); i++) {
|
| - for (int j = 0; j < files[i]->message_type_count(); j++) {
|
| - FindProvidesForMessage(options, printer, files[i]->message_type(j),
|
| - provided);
|
| - }
|
| - for (int j = 0; j < files[i]->enum_type_count(); j++) {
|
| - FindProvidesForEnum(options, printer, files[i]->enum_type(j),
|
| - provided);
|
| - }
|
| + FindProvidesForFile(options, printer, files[i], provided);
|
| }
|
|
|
| printer->Print("\n");
|
| @@ -1185,10 +1457,10 @@ void Generator::GenerateProvides(const GeneratorOptions& options,
|
| }
|
| }
|
|
|
| -void Generator::GenerateRequires(const GeneratorOptions& options,
|
| - io::Printer* printer,
|
| - const Descriptor* desc,
|
| - std::set<string>* provided) const {
|
| +void Generator::GenerateRequiresForMessage(const GeneratorOptions& options,
|
| + io::Printer* printer,
|
| + const Descriptor* desc,
|
| + std::set<string>* provided) const {
|
| std::set<string> required;
|
| std::set<string> forwards;
|
| bool have_message = false;
|
| @@ -1200,10 +1472,12 @@ void Generator::GenerateRequires(const GeneratorOptions& options,
|
| /* require_extension = */ HasExtensions(desc));
|
| }
|
|
|
| -void Generator::GenerateRequires(const GeneratorOptions& options,
|
| - io::Printer* printer,
|
| - const vector<const FileDescriptor*>& files,
|
| - std::set<string>* provided) const {
|
| +void Generator::GenerateRequiresForLibrary(
|
| + const GeneratorOptions& options, io::Printer* printer,
|
| + const vector<const FileDescriptor*>& files,
|
| + std::set<string>* provided) const {
|
| + GOOGLE_CHECK_EQ(options.import_style, GeneratorOptions::IMPORT_CLOSURE);
|
| + // For Closure imports we need to import every message type individually.
|
| std::set<string> required;
|
| std::set<string> forwards;
|
| bool have_extensions = false;
|
| @@ -1225,7 +1499,7 @@ void Generator::GenerateRequires(const GeneratorOptions& options,
|
| continue;
|
| }
|
| if (extension->containing_type()->full_name() !=
|
| - "google.protobuf.bridge.MessageSet") {
|
| + "google.protobuf.bridge.MessageSet") {
|
| required.insert(GetPath(options, extension->containing_type()));
|
| }
|
| FindRequiresForField(options, extension, &required, &forwards);
|
| @@ -1238,10 +1512,10 @@ void Generator::GenerateRequires(const GeneratorOptions& options,
|
| /* require_extension = */ have_extensions);
|
| }
|
|
|
| -void Generator::GenerateRequires(const GeneratorOptions& options,
|
| - io::Printer* printer,
|
| - const vector<const FieldDescriptor*>& fields,
|
| - std::set<string>* provided) const {
|
| +void Generator::GenerateRequiresForExtensions(
|
| + const GeneratorOptions& options, io::Printer* printer,
|
| + const vector<const FieldDescriptor*>& fields,
|
| + std::set<string>* provided) const {
|
| std::set<string> required;
|
| std::set<string> forwards;
|
| for (int i = 0; i < fields.size(); i++) {
|
| @@ -1406,6 +1680,12 @@ void Generator::GenerateClass(const GeneratorOptions& options,
|
| if (IsExtendable(desc) && desc->full_name() != "google.protobuf.bridge.MessageSet") {
|
| GenerateClassExtensionFieldInfo(options, printer, desc);
|
| }
|
| +
|
| + if (options.import_style != GeneratorOptions:: IMPORT_CLOSURE) {
|
| + for (int i = 0; i < desc->extension_count(); i++) {
|
| + GenerateExtension(options, printer, desc->extension(i));
|
| + }
|
| + }
|
| }
|
|
|
| // Recurse on nested types.
|
| @@ -1623,13 +1903,13 @@ void Generator::GenerateClassToObject(const GeneratorOptions& options,
|
| "obj,\n"
|
| " $extObject$, $class$.prototype.getExtension,\n"
|
| " includeInstance);\n",
|
| - "extObject", JSExtensionsObjectName(options, desc),
|
| + "extObject", JSExtensionsObjectName(options, desc->file(), desc),
|
| "class", GetPath(options, desc));
|
| }
|
|
|
| printer->Print(
|
| " if (includeInstance) {\n"
|
| - " obj.$$jspbMessageInstance = msg\n"
|
| + " obj.$$jspbMessageInstance = msg;\n"
|
| " }\n"
|
| " return obj;\n"
|
| "};\n"
|
| @@ -1652,27 +1932,44 @@ void Generator::GenerateClassFieldToObject(const GeneratorOptions& options,
|
| printer->Print("jspb.Message.toObjectList(msg.get$getter$(),\n"
|
| " $type$.toObject, includeInstance)",
|
| "getter", JSGetterName(field),
|
| - "type", GetPath(options, field->message_type()));
|
| + "type", SubmessageTypeRef(options, field));
|
| }
|
| } else {
|
| printer->Print("(f = msg.get$getter$()) && "
|
| "$type$.toObject(includeInstance, f)",
|
| "getter", JSGetterName(field),
|
| - "type", GetPath(options, field->message_type()));
|
| + "type", SubmessageTypeRef(options, field));
|
| }
|
| } else {
|
| // Simple field (singular or repeated).
|
| - if (!HasFieldPresence(field) && !field->is_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 logic here.
|
| + // the proto3-field-default-value or byte-coercion logic here.
|
| printer->Print("msg.get$getter$()",
|
| - "getter", JSGetterName(field));
|
| + "getter", JSGetterName(field, BYTES_B64));
|
| } else {
|
| if (field->has_default_value()) {
|
| - printer->Print("jspb.Message.getField(msg, $index$) != null ? "
|
| - "jspb.Message.getField(msg, $index$) : $defaultValue$",
|
| + printer->Print("jspb.Message.getField(msg, $index$) == null ? "
|
| + "$defaultValue$ : ",
|
| "index", JSFieldIndex(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));
|
| @@ -1723,7 +2020,7 @@ void Generator::GenerateClassFieldFromObject(
|
| " }));\n",
|
| "name", JSObjectFieldName(field),
|
| "index", JSFieldIndex(field),
|
| - "fieldclass", GetPath(options, field->message_type()));
|
| + "fieldclass", SubmessageTypeRef(options, field));
|
| }
|
| } else {
|
| printer->Print(
|
| @@ -1731,7 +2028,7 @@ void Generator::GenerateClassFieldFromObject(
|
| " msg, $index$, $fieldclass$.fromObject(obj.$name$));\n",
|
| "name", JSObjectFieldName(field),
|
| "index", JSFieldIndex(field),
|
| - "fieldclass", GetPath(options, field->message_type()));
|
| + "fieldclass", SubmessageTypeRef(options, field));
|
| }
|
| } else {
|
| // Simple (primitive) field.
|
| @@ -1781,6 +2078,40 @@ void Generator::GenerateClassFields(const GeneratorOptions& options,
|
| }
|
| }
|
|
|
| +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);
|
| + printer->Print(
|
| + "/**\n"
|
| + " * $fielddef$\n"
|
| + "$comment$"
|
| + " * This is a type-conversion wrapper around `get$defname$()`\n"
|
| + " * @return {$type$}\n"
|
| + " */\n"
|
| + "$class$.prototype.get$name$ = function() {\n"
|
| + " return /** @type {$type$} */ (jspb.Message.bytes$list$As$suffix$(\n"
|
| + " this.get$defname$()));\n"
|
| + "};\n"
|
| + "\n"
|
| + "\n",
|
| + "fielddef", FieldDefinition(options, field),
|
| + "comment", FieldComments(field, bytes_mode),
|
| + "type", type,
|
| + "class", GetPath(options, field->containing_type()),
|
| + "name", JSGetterName(field, bytes_mode),
|
| + "list", field->is_repeated() ? "List" : "",
|
| + "suffix", JSByteGetterSuffix(bytes_mode),
|
| + "defname", JSGetterName(field, BYTES_DEFAULT));
|
| +}
|
| +
|
| +
|
| void Generator::GenerateClassField(const GeneratorOptions& options,
|
| io::Printer* printer,
|
| const FieldDescriptor* field) const {
|
| @@ -1792,12 +2123,11 @@ void Generator::GenerateClassField(const GeneratorOptions& options,
|
| " * @return {$type$}\n"
|
| " */\n",
|
| "fielddef", FieldDefinition(options, field),
|
| - "comment", FieldComments(field),
|
| + "comment", FieldComments(field, BYTES_DEFAULT),
|
| "type", JSFieldTypeAnnotation(options, field,
|
| /* force_optional = */ false,
|
| /* force_present = */ false,
|
| - /* singular_if_not_packed = */ false,
|
| - /* always_singular = */ false));
|
| + /* singular_if_not_packed = */ false));
|
| printer->Print(
|
| "$class$.prototype.get$name$ = function() {\n"
|
| " return /** @type{$type$} */ (\n"
|
| @@ -1811,11 +2141,10 @@ void Generator::GenerateClassField(const GeneratorOptions& options,
|
| "type", JSFieldTypeAnnotation(options, field,
|
| /* force_optional = */ false,
|
| /* force_present = */ false,
|
| - /* singular_if_not_packed = */ false,
|
| - /* always_singular = */ false),
|
| + /* singular_if_not_packed = */ false),
|
| "rpt", (field->is_repeated() ? "Repeated" : ""),
|
| "index", JSFieldIndex(field),
|
| - "wrapperclass", GetPath(options, field->message_type()),
|
| + "wrapperclass", SubmessageTypeRef(options, field),
|
| "required", (field->label() == FieldDescriptor::LABEL_REQUIRED ?
|
| ", 1" : ""));
|
| printer->Print(
|
| @@ -1826,8 +2155,7 @@ void Generator::GenerateClassField(const GeneratorOptions& options,
|
| JSFieldTypeAnnotation(options, field,
|
| /* force_optional = */ true,
|
| /* force_present = */ false,
|
| - /* singular_if_not_packed = */ false,
|
| - /* always_singular = */ false),
|
| + /* singular_if_not_packed = */ false),
|
| "returndoc", JSReturnDoc(options, field),
|
| "class", GetPath(options, field->containing_type()),
|
| "name", JSGetterName(field),
|
| @@ -1856,15 +2184,29 @@ void Generator::GenerateClassField(const GeneratorOptions& options,
|
| "returnvalue", JSReturnClause(field));
|
|
|
| } else {
|
| - string typed_annotation;
|
| + bool untyped =
|
| + false;
|
|
|
| // Simple (primitive) field, either singular or repeated.
|
| - {
|
| - typed_annotation = JSFieldTypeAnnotation(options, field,
|
| +
|
| + // 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
|
| + // 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,
|
| - /* always_singular = */ false),
|
| + /* bytes_mode = */ bytes_mode);
|
| + if (untyped) {
|
| + printer->Print(
|
| + "/**\n"
|
| + " * @return {?} Raw field, untyped.\n"
|
| + " */\n");
|
| + } else {
|
| printer->Print(
|
| "/**\n"
|
| " * $fielddef$\n"
|
| @@ -1872,7 +2214,7 @@ void Generator::GenerateClassField(const GeneratorOptions& options,
|
| " * @return {$type$}\n"
|
| " */\n",
|
| "fielddef", FieldDefinition(options, field),
|
| - "comment", FieldComments(field),
|
| + "comment", FieldComments(field, bytes_mode),
|
| "type", typed_annotation);
|
| }
|
|
|
| @@ -1881,7 +2223,10 @@ void Generator::GenerateClassField(const GeneratorOptions& options,
|
| "class", GetPath(options, field->containing_type()),
|
| "name", JSGetterName(field));
|
|
|
| - {
|
| + if (untyped) {
|
| + printer->Print(
|
| + " return ");
|
| + } else {
|
| printer->Print(
|
| " return /** @type {$type$} */ (",
|
| "type", typed_annotation);
|
| @@ -1896,17 +2241,39 @@ void Generator::GenerateClassField(const GeneratorOptions& options,
|
| "default", Proto3PrimitiveFieldDefault(field));
|
| } else {
|
| if (field->has_default_value()) {
|
| - printer->Print("jspb.Message.getField(this, $index$) != null ? "
|
| - "jspb.Message.getField(this, $index$) : $defaultValue$",
|
| + printer->Print("jspb.Message.getField(this, $index$) == null ? "
|
| + "$defaultValue$ : ",
|
| "index", JSFieldIndex(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 if (field->is_optional() && !field->has_default_value()) {
|
| + printer->Print("jspb.Message.getOptionalFloatingPointField("
|
| + "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));
|
| }
|
| }
|
|
|
| - {
|
| + if (untyped) {
|
| + printer->Print(
|
| + ";\n"
|
| + "};\n"
|
| + "\n"
|
| + "\n");
|
| + } else {
|
| printer->Print(
|
| ");\n"
|
| "};\n"
|
| @@ -1914,18 +2281,27 @@ void Generator::GenerateClassField(const GeneratorOptions& options,
|
| "\n");
|
| }
|
|
|
| - {
|
| + if (field->type() == FieldDescriptor::TYPE_BYTES && !untyped) {
|
| + GenerateBytesWrapper(options, printer, field, BYTES_B64);
|
| + GenerateBytesWrapper(options, printer, field, BYTES_U8);
|
| + }
|
| +
|
| + if (untyped) {
|
| + printer->Print(
|
| + "/**\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,
|
| - /* always_singular = */ 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$",
|
| @@ -1938,14 +2314,22 @@ void Generator::GenerateClassField(const GeneratorOptions& options,
|
| "};\n"
|
| "\n"
|
| "\n",
|
| - "type", "",
|
| - "typeclose", "",
|
| + "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"
|
| + " */\n",
|
| + "returndoc", JSReturnDoc(options, field));
|
| + }
|
|
|
| if (HasFieldPresence(field)) {
|
| printer->Print(
|
| @@ -2043,7 +2427,7 @@ void Generator::GenerateClassDeserializeBinary(const GeneratorOptions& options,
|
| " $class$.prototype.getExtension,\n"
|
| " $class$.prototype.setExtension);\n"
|
| " break;\n",
|
| - "extobj", JSExtensionsObjectName(options, desc),
|
| + "extobj", JSExtensionsObjectName(options, desc->file(), desc),
|
| "class", GetPath(options, desc));
|
| } else {
|
| printer->Print(
|
| @@ -2073,7 +2457,7 @@ void Generator::GenerateClassDeserializeBinaryField(
|
| " var value = new $fieldclass$;\n"
|
| " reader.read$msgOrGroup$($grpfield$value,"
|
| "$fieldclass$.deserializeBinaryFromReader);\n",
|
| - "fieldclass", GetPath(options, field->message_type()),
|
| + "fieldclass", SubmessageTypeRef(options, field),
|
| "msgOrGroup", (field->type() == FieldDescriptor::TYPE_GROUP) ?
|
| "Group" : "Message",
|
| "grpfield", (field->type() == FieldDescriptor::TYPE_GROUP) ?
|
| @@ -2083,16 +2467,18 @@ void Generator::GenerateClassDeserializeBinaryField(
|
| " var value = /** @type {$fieldtype$} */ (reader.$reader$());\n",
|
| "fieldtype", JSFieldTypeAnnotation(options, field, false, true,
|
| /* singular_if_not_packed = */ true,
|
| - /* always_singular = */ false),
|
| + BYTES_U8),
|
| "reader", JSBinaryReaderMethodName(field));
|
| }
|
|
|
| if (field->is_repeated() && !field->is_packed()) {
|
| // Repeated fields receive a |value| one at at a time; append to array
|
| - // returned by get$name$().
|
| - printer->Print(
|
| - " msg.get$name$().push(value);\n",
|
| - "name", JSGetterName(field));
|
| + // returned by get$name$(). Annoyingly, we have to call 'set' after
|
| + // changing the array.
|
| + printer->Print(" msg.get$name$().push(value);\n", "name",
|
| + JSGetterName(field));
|
| + printer->Print(" msg.set$name$(msg.get$name$());\n", "name",
|
| + JSGetterName(field));
|
| } else {
|
| // Singular fields, and packed repeated fields, receive a |value| either as
|
| // the field's value or as the array of all the field's values; set this as
|
| @@ -2149,7 +2535,7 @@ void Generator::GenerateClassSerializeBinary(const GeneratorOptions& options,
|
| printer->Print(
|
| " jspb.Message.serializeBinaryExtensions(this, writer, $extobj$,\n"
|
| " $class$.prototype.getExtension);\n",
|
| - "extobj", JSExtensionsObjectName(options, desc),
|
| + "extobj", JSExtensionsObjectName(options, desc->file(), desc),
|
| "class", GetPath(options, desc));
|
| }
|
|
|
| @@ -2165,7 +2551,7 @@ void Generator::GenerateClassSerializeBinaryField(
|
| const FieldDescriptor* field) const {
|
| printer->Print(
|
| " f = this.get$name$();\n",
|
| - "name", JSGetterName(field));
|
| + "name", JSGetterName(field, BYTES_U8));
|
|
|
| if (field->is_repeated()) {
|
| printer->Print(
|
| @@ -2215,14 +2601,13 @@ void Generator::GenerateClassSerializeBinaryField(
|
| " $index$,\n"
|
| " f",
|
| "writer", JSBinaryWriterMethodName(field),
|
| - "name", JSGetterName(field),
|
| "index", SimpleItoa(field->number()));
|
|
|
| if (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) {
|
| printer->Print(
|
| ",\n"
|
| " $submsg$.serializeBinaryToWriter\n",
|
| - "submsg", GetPath(options, field->message_type()));
|
| + "submsg", SubmessageTypeRef(options, field));
|
| } else {
|
| printer->Print("\n");
|
| }
|
| @@ -2277,8 +2662,7 @@ void Generator::GenerateExtension(const GeneratorOptions& options,
|
| options, field,
|
| /* force_optional = */ false,
|
| /* force_present = */ true,
|
| - /* singular_if_not_packed = */ false,
|
| - /* always_singular = */ false));
|
| + /* singular_if_not_packed = */ false));
|
| printer->Print(
|
| " $index$,\n"
|
| " {$name$: 0},\n"
|
| @@ -2290,9 +2674,9 @@ void Generator::GenerateExtension(const GeneratorOptions& options,
|
| "index", SimpleItoa(field->number()),
|
| "name", JSObjectFieldName(field),
|
| "ctor", (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE ?
|
| - GetPath(options, field->message_type()) : string("null")),
|
| + SubmessageTypeRef(options, field) : string("null")),
|
| "toObject", (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE ?
|
| - (GetPath(options, field->message_type()) + ".toObject") :
|
| + (SubmessageTypeRef(options, field) + ".toObject") :
|
| string("null")),
|
| "repeated", (field->is_repeated() ? "1" : "0"));
|
|
|
| @@ -2308,11 +2692,11 @@ void Generator::GenerateExtension(const GeneratorOptions& options,
|
| "binaryWriterFn", JSBinaryWriterMethodName(field),
|
| "binaryMessageSerializeFn",
|
| (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) ?
|
| - (GetPath(options, field->message_type()) +
|
| + (SubmessageTypeRef(options, field) +
|
| ".serializeBinaryToWriter") : "null",
|
| "binaryMessageDeserializeFn",
|
| (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) ?
|
| - (GetPath(options, field->message_type()) +
|
| + (SubmessageTypeRef(options, field) +
|
| ".deserializeBinaryFromReader") : "null",
|
| "isPacked", (field->is_packed() ? "true" : "false"));
|
| } else {
|
| @@ -2324,7 +2708,8 @@ void Generator::GenerateExtension(const GeneratorOptions& options,
|
| "// toObject() will function correctly.\n"
|
| "$extendName$[$index$] = $class$.$name$;\n"
|
| "\n",
|
| - "extendName", JSExtensionsObjectName(options, field->containing_type()),
|
| + "extendName", JSExtensionsObjectName(options, field->file(),
|
| + field->containing_type()),
|
| "index", SimpleItoa(field->number()),
|
| "class", extension_scope,
|
| "name", JSObjectFieldName(field));
|
| @@ -2364,6 +2749,19 @@ bool GeneratorOptions::ParseFromOptions(
|
| namespace_prefix = options[i].second;
|
| } else if (options[i].first == "library") {
|
| library = options[i].second;
|
| + } else if (options[i].first == "import_style") {
|
| + if (options[i].second == "closure") {
|
| + import_style = IMPORT_CLOSURE;
|
| + } else if (options[i].second == "commonjs") {
|
| + import_style = IMPORT_COMMONJS;
|
| + } else if (options[i].second == "browser") {
|
| + import_style = IMPORT_BROWSER;
|
| + } else if (options[i].second == "es6") {
|
| + import_style = IMPORT_ES6;
|
| + } else {
|
| + *error = "Unknown import style " + options[i].second + ", expected " +
|
| + "one of: closure, commonjs, browser, es6.";
|
| + }
|
| } else {
|
| // Assume any other option is an output directory, as long as it is a bare
|
| // `key` rather than a `key=value` option.
|
| @@ -2375,6 +2773,11 @@ bool GeneratorOptions::ParseFromOptions(
|
| }
|
| }
|
|
|
| + if (!library.empty() && import_style != IMPORT_CLOSURE) {
|
| + *error = "The library option should only be used for "
|
| + "import_style=closure";
|
| + }
|
| +
|
| return true;
|
| }
|
|
|
| @@ -2418,6 +2821,63 @@ void Generator::GenerateFileAndDeps(
|
| }
|
| }
|
|
|
| +void Generator::GenerateFile(const GeneratorOptions& options,
|
| + io::Printer* printer,
|
| + const FileDescriptor* file) const {
|
| + GenerateHeader(options, printer);
|
| +
|
| + // Generate "require" statements.
|
| + if (options.import_style == GeneratorOptions::IMPORT_COMMONJS) {
|
| + printer->Print("var jspb = require('google-protobuf');\n");
|
| + printer->Print("var goog = jspb;\n");
|
| + printer->Print("var global = Function('return this')();\n\n");
|
| +
|
| + for (int i = 0; i < file->dependency_count(); i++) {
|
| + const string& name = file->dependency(i)->name();
|
| + printer->Print(
|
| + "var $alias$ = require('$file$');\n",
|
| + "alias", ModuleAlias(name),
|
| + "file", GetRootPath(file->name()) + GetJSFilename(name));
|
| + }
|
| + }
|
| +
|
| + // We aren't using Closure's import system, but we use goog.exportSymbol()
|
| + // to construct the expected tree of objects, eg.
|
| + //
|
| + // goog.exportSymbol('foo.bar.Baz', null, this);
|
| + //
|
| + // // Later generated code expects foo.bar = {} to exist:
|
| + // foo.bar.Baz = function() { /* ... */ }
|
| + 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.
|
| + for (int i = 0; i < file->extension_count(); i++) {
|
| + provided.insert(file->extension(i)->full_name());
|
| + }
|
| +
|
| + FindProvidesForFile(options, printer, file, &provided);
|
| + for (std::set<string>::iterator it = provided.begin();
|
| + it != provided.end(); ++it) {
|
| + printer->Print("goog.exportSymbol('$name$', null, global);\n",
|
| + "name", *it);
|
| + }
|
| +
|
| + GenerateClassesAndEnums(options, printer, file);
|
| +
|
| + // Extensions nested inside messages are emitted inside
|
| + // GenerateClassesAndEnums().
|
| + for (int i = 0; i < file->extension_count(); i++) {
|
| + GenerateExtension(options, printer, file->extension(i));
|
| + }
|
| +
|
| + if (options.import_style == GeneratorOptions::IMPORT_COMMONJS) {
|
| + printer->Print("goog.object.extend(exports, $package$);\n",
|
| + "package", GetPath(options, file));
|
| + }
|
| +}
|
| +
|
| bool Generator::GenerateAll(const vector<const FileDescriptor*>& files,
|
| const string& parameter,
|
| GeneratorContext* context,
|
| @@ -2430,10 +2890,14 @@ bool Generator::GenerateAll(const vector<const FileDescriptor*>& files,
|
| }
|
|
|
|
|
| - // We're either generating a single library file with definitions for message
|
| - // and enum types in *all* FileDescriptor inputs, or we're generating a single
|
| - // file for each type.
|
| - if (options.library != "") {
|
| + // There are three schemes for where output files go:
|
| + //
|
| + // - import_style = IMPORT_CLOSURE, library non-empty: all output in one file
|
| + // - import_style = IMPORT_CLOSURE, library empty: one output file per type
|
| + // - import_style != IMPORT_CLOSURE: one output file per .proto file
|
| + if (options.import_style == GeneratorOptions::IMPORT_CLOSURE &&
|
| + options.library != "") {
|
| + // All output should go in a single file.
|
| string filename = options.output_dir + "/" + options.library + ".js";
|
| google::protobuf::scoped_ptr<io::ZeroCopyOutputStream> output(context->Open(filename));
|
| GOOGLE_CHECK(output.get());
|
| @@ -2456,7 +2920,7 @@ bool Generator::GenerateAll(const vector<const FileDescriptor*>& files,
|
| FindProvidesForFields(options, &printer, extensions, &provided);
|
| GenerateProvides(options, &printer, &provided);
|
| GenerateTestOnly(options, &printer);
|
| - GenerateRequires(options, &printer, files, &provided);
|
| + GenerateRequiresForLibrary(options, &printer, files, &provided);
|
|
|
| GenerateFilesInDepOrder(options, &printer, files);
|
|
|
| @@ -2469,67 +2933,21 @@ bool Generator::GenerateAll(const vector<const FileDescriptor*>& files,
|
| if (printer.failed()) {
|
| return false;
|
| }
|
| - } else {
|
| - // Collect all types, and print each type to a separate file. Pull out
|
| - // free-floating extensions while we make this pass.
|
| - map< string, vector<const FieldDescriptor*> > extensions_by_namespace;
|
| -
|
| - // If we're generating code in file-per-type mode, avoid overwriting files
|
| - // by choosing the last descriptor that writes each filename and permitting
|
| - // only those to generate code.
|
| -
|
| - // Current descriptor that will generate each filename, indexed by filename.
|
| - map<string, const void*> desc_by_filename;
|
| - // Set of descriptors allowed to generate files.
|
| - set<const void*> allowed_descs;
|
| -
|
| - for (int i = 0; i < files.size(); i++) {
|
| - // Collect all (descriptor, filename) pairs.
|
| - map<const void*, string> descs_in_file;
|
| - for (int j = 0; j < files[i]->message_type_count(); j++) {
|
| - const Descriptor* desc = files[i]->message_type(j);
|
| - string filename =
|
| - options.output_dir + "/" + ToFileName(desc->name()) + ".js";
|
| - descs_in_file[desc] = filename;
|
| - }
|
| - for (int j = 0; j < files[i]->enum_type_count(); j++) {
|
| - const EnumDescriptor* desc = files[i]->enum_type(j);
|
| - string filename =
|
| - options.output_dir + "/" + ToFileName(desc->name()) + ".js";
|
| - descs_in_file[desc] = filename;
|
| - }
|
| -
|
| - // For each (descriptor, filename) pair, update the
|
| - // descriptors-by-filename map, and if a previous descriptor was already
|
| - // writing the filename, remove it from the allowed-descriptors set.
|
| - map<const void*, string>::iterator it;
|
| - for (it = descs_in_file.begin(); it != descs_in_file.end(); ++it) {
|
| - const void* desc = it->first;
|
| - const string& filename = it->second;
|
| - if (desc_by_filename.find(filename) != desc_by_filename.end()) {
|
| - if (options.error_on_name_conflict) {
|
| - *error = "Name conflict: file name " + filename +
|
| - " would be generated by two descriptors";
|
| - return false;
|
| - }
|
| - allowed_descs.erase(desc_by_filename[filename]);
|
| - }
|
| - desc_by_filename[filename] = desc;
|
| - allowed_descs.insert(desc);
|
| - }
|
| + } else if (options.import_style == GeneratorOptions::IMPORT_CLOSURE) {
|
| + set<const void*> allowed_set;
|
| + if (!GenerateJspbAllowedSet(options, files, &allowed_set, error)) {
|
| + return false;
|
| }
|
|
|
| - // Generate code.
|
| for (int i = 0; i < files.size(); i++) {
|
| const FileDescriptor* file = files[i];
|
| for (int j = 0; j < file->message_type_count(); j++) {
|
| const Descriptor* desc = file->message_type(j);
|
| - if (allowed_descs.find(desc) == allowed_descs.end()) {
|
| + if (allowed_set.count(desc) == 0) {
|
| continue;
|
| }
|
|
|
| - string filename = options.output_dir + "/" +
|
| - ToFileName(desc->name()) + ".js";
|
| + string filename = GetMessageFileName(options, desc);
|
| google::protobuf::scoped_ptr<io::ZeroCopyOutputStream> output(
|
| context->Open(filename));
|
| GOOGLE_CHECK(output.get());
|
| @@ -2541,7 +2959,7 @@ bool Generator::GenerateAll(const vector<const FileDescriptor*>& files,
|
| FindProvidesForMessage(options, &printer, desc, &provided);
|
| GenerateProvides(options, &printer, &provided);
|
| GenerateTestOnly(options, &printer);
|
| - GenerateRequires(options, &printer, desc, &provided);
|
| + GenerateRequiresForMessage(options, &printer, desc, &provided);
|
|
|
| GenerateClass(options, &printer, desc);
|
|
|
| @@ -2551,13 +2969,11 @@ bool Generator::GenerateAll(const vector<const FileDescriptor*>& files,
|
| }
|
| for (int j = 0; j < file->enum_type_count(); j++) {
|
| const EnumDescriptor* enumdesc = file->enum_type(j);
|
| - if (allowed_descs.find(enumdesc) == allowed_descs.end()) {
|
| + if (allowed_set.count(enumdesc) == 0) {
|
| continue;
|
| }
|
|
|
| - string filename = options.output_dir + "/" +
|
| - ToFileName(enumdesc->name()) + ".js";
|
| -
|
| + string filename = GetEnumFileName(options, enumdesc);
|
| google::protobuf::scoped_ptr<io::ZeroCopyOutputStream> output(
|
| context->Open(filename));
|
| GOOGLE_CHECK(output.get());
|
| @@ -2576,39 +2992,54 @@ bool Generator::GenerateAll(const vector<const FileDescriptor*>& files,
|
| return false;
|
| }
|
| }
|
| - // Pull out all free-floating extensions and generate files for those too.
|
| - for (int j = 0; j < file->extension_count(); j++) {
|
| - const FieldDescriptor* extension = file->extension(j);
|
| - extensions_by_namespace[GetPath(options, files[i])]
|
| - .push_back(extension);
|
| + // File-level extensions (message-level extensions are generated under
|
| + // the enclosing message).
|
| + if (allowed_set.count(file) == 1) {
|
| + string filename = GetExtensionFileName(options, file);
|
| +
|
| + google::protobuf::scoped_ptr<io::ZeroCopyOutputStream> output(
|
| + context->Open(filename));
|
| + GOOGLE_CHECK(output.get());
|
| + io::Printer printer(output.get(), '$');
|
| +
|
| + GenerateHeader(options, &printer);
|
| +
|
| + std::set<string> provided;
|
| + vector<const FieldDescriptor*> fields;
|
| +
|
| + for (int j = 0; j < files[i]->extension_count(); j++) {
|
| + if (ShouldGenerateExtension(files[i]->extension(j))) {
|
| + fields.push_back(files[i]->extension(j));
|
| + }
|
| + }
|
| +
|
| + FindProvidesForFields(options, &printer, fields, &provided);
|
| + GenerateProvides(options, &printer, &provided);
|
| + GenerateTestOnly(options, &printer);
|
| + GenerateRequiresForExtensions(options, &printer, fields, &provided);
|
| +
|
| + for (int j = 0; j < files[i]->extension_count(); j++) {
|
| + if (ShouldGenerateExtension(files[i]->extension(j))) {
|
| + GenerateExtension(options, &printer, files[i]->extension(j));
|
| + }
|
| + }
|
| }
|
| }
|
| + } else {
|
| + // Generate one output file per input (.proto) file.
|
|
|
| - // Generate extensions in separate files.
|
| - map< string, vector<const FieldDescriptor*> >::iterator it;
|
| - for (it = extensions_by_namespace.begin();
|
| - it != extensions_by_namespace.end();
|
| - ++it) {
|
| - string filename = options.output_dir + "/" +
|
| - ToFileName(it->first) + ".js";
|
| + for (int i = 0; i < files.size(); i++) {
|
| + const google::protobuf::FileDescriptor* file = files[i];
|
|
|
| - google::protobuf::scoped_ptr<io::ZeroCopyOutputStream> output(
|
| - context->Open(filename));
|
| + string filename = options.output_dir + "/" + GetJSFilename(file->name());
|
| + google::protobuf::scoped_ptr<io::ZeroCopyOutputStream> output(context->Open(filename));
|
| GOOGLE_CHECK(output.get());
|
| io::Printer printer(output.get(), '$');
|
|
|
| - GenerateHeader(options, &printer);
|
| + GenerateFile(options, &printer, file);
|
|
|
| - std::set<string> provided;
|
| - FindProvidesForFields(options, &printer, it->second, &provided);
|
| - GenerateProvides(options, &printer, &provided);
|
| - GenerateTestOnly(options, &printer);
|
| - GenerateRequires(options, &printer, it->second, &provided);
|
| -
|
| - for (int j = 0; j < it->second.size(); j++) {
|
| - if (ShouldGenerateExtension(it->second[j])) {
|
| - GenerateExtension(options, &printer, it->second[j]);
|
| - }
|
| + if (printer.failed()) {
|
| + return false;
|
| }
|
| }
|
| }
|
|
|