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; |
} |
} |
} |