aboutsummaryrefslogtreecommitdiff
path: root/src/google/protobuf/compiler/js/js_generator.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/google/protobuf/compiler/js/js_generator.cc')
-rwxr-xr-xsrc/google/protobuf/compiler/js/js_generator.cc777
1 files changed, 516 insertions, 261 deletions
diff --git a/src/google/protobuf/compiler/js/js_generator.cc b/src/google/protobuf/compiler/js/js_generator.cc
index 0de7e2c6..534a5b5b 100755
--- a/src/google/protobuf/compiler/js/js_generator.cc
+++ b/src/google/protobuf/compiler/js/js_generator.cc
@@ -28,7 +28,7 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-#include <google/protobuf/compiler/js/js_generator.h>
+#include "google/protobuf/compiler/js/js_generator.h"
#include <assert.h>
#include <algorithm>
@@ -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,7 +144,7 @@ bool IsReserved(const string& ident) {
// Returns a copy of |filename| with any trailing ".protodevel" or ".proto
// suffix stripped.
-// TODO(robinson): Unify with copy in compiler/cpp/internal/helpers.cc.
+// TODO(haberman): Unify with copy in compiler/cpp/internal/helpers.cc.
string StripProto(const string& filename) {
const char* suffix = HasSuffixString(filename, ".protodevel")
? ".protodevel" : ".proto";
@@ -144,9 +154,7 @@ string StripProto(const string& filename) {
// Given a filename like foo/bar/baz.proto, returns the correspoding JavaScript
// file foo/bar/baz.js.
string GetJSFilename(const string& filename) {
- const char* suffix = HasSuffixString(filename, ".protodevel")
- ? ".protodevel" : ".proto";
- return StripSuffixString(filename, suffix) + "_pb.js";
+ return StripProto(filename) + "_pb.js";
}
// Given a filename like foo/bar/baz.proto, returns the root directory
@@ -385,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.
@@ -438,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 += "$";
@@ -504,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;
@@ -542,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) {
@@ -740,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";
@@ -801,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";
@@ -819,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:
@@ -836,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;
}
@@ -884,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()) {
@@ -1044,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 +=
@@ -1060,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;
}
@@ -1070,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))) {
@@ -1123,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) ||
@@ -1132,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()) {
@@ -1151,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,
@@ -1251,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;
@@ -1266,55 +1472,50 @@ 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 {
- if (options.import_style == GeneratorOptions::IMPORT_BROWSER) {
- return;
- } else if (options.import_style == GeneratorOptions::IMPORT_CLOSURE) {
- // For Closure imports we need to import every message type individually.
- std::set<string> required;
- std::set<string> forwards;
- bool have_extensions = false;
- bool have_message = false;
+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;
+ bool have_message = false;
- for (int i = 0; i < files.size(); i++) {
- for (int j = 0; j < files[i]->message_type_count(); j++) {
- FindRequiresForMessage(options,
- files[i]->message_type(j),
- &required, &forwards, &have_message);
- }
- if (!have_extensions && HasExtensions(files[i])) {
- have_extensions = true;
- }
+ for (int i = 0; i < files.size(); i++) {
+ for (int j = 0; j < files[i]->message_type_count(); j++) {
+ FindRequiresForMessage(options,
+ files[i]->message_type(j),
+ &required, &forwards, &have_message);
+ }
+ if (!have_extensions && HasExtensions(files[i])) {
+ have_extensions = true;
+ }
- for (int j = 0; j < files[i]->extension_count(); j++) {
- const FieldDescriptor* extension = files[i]->extension(j);
- if (IgnoreField(extension)) {
- continue;
- }
- if (extension->containing_type()->full_name() !=
- "google.protobuf.bridge.MessageSet") {
- required.insert(GetPath(options, extension->containing_type()));
- }
- FindRequiresForField(options, extension, &required, &forwards);
- have_extensions = true;
+ for (int j = 0; j < files[i]->extension_count(); j++) {
+ const FieldDescriptor* extension = files[i]->extension(j);
+ if (IgnoreField(extension)) {
+ continue;
+ }
+ if (extension->containing_type()->full_name() !=
+ "google.protobuf.bridge.MessageSet") {
+ required.insert(GetPath(options, extension->containing_type()));
}
+ FindRequiresForField(options, extension, &required, &forwards);
+ have_extensions = true;
}
-
- GenerateRequiresImpl(options, printer, &required, &forwards, provided,
- /* require_jspb = */ have_message,
- /* require_extension = */ have_extensions);
- } else if (options.import_style == GeneratorOptions::IMPORT_COMMONJS) {
- // CommonJS imports are based on files
}
+
+ GenerateRequiresImpl(options, printer, &required, &forwards, provided,
+ /* require_jspb = */ have_message,
+ /* 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++) {
@@ -1741,17 +1942,34 @@ void Generator::GenerateClassFieldToObject(const GeneratorOptions& options,
}
} 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));
@@ -1860,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 {
@@ -1871,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"
@@ -1890,8 +2141,7 @@ 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", SubmessageTypeRef(options, field),
@@ -1905,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),
@@ -1935,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"
@@ -1951,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);
}
@@ -1960,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);
@@ -1975,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"
@@ -1993,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$",
@@ -2017,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(
@@ -2162,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
@@ -2244,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(
@@ -2294,7 +2601,6 @@ void Generator::GenerateClassSerializeBinaryField(
" $index$,\n"
" f",
"writer", JSBinaryWriterMethodName(field),
- "name", JSGetterName(field),
"index", SimpleItoa(field->number()));
if (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) {
@@ -2356,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"
@@ -2528,7 +2833,7 @@ void Generator::GenerateFile(const GeneratorOptions& options,
printer->Print("var global = Function('return this')();\n\n");
for (int i = 0; i < file->dependency_count(); i++) {
- const std::string& name = file->dependency(i)->name();
+ const string& name = file->dependency(i)->name();
printer->Print(
"var $alias$ = require('$file$');\n",
"alias", ModuleAlias(name),
@@ -2543,7 +2848,7 @@ void Generator::GenerateFile(const GeneratorOptions& options,
//
// // Later generated code expects foo.bar = {} to exist:
// foo.bar.Baz = function() { /* ... */ }
- std::set<std::string> provided;
+ 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
@@ -2615,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);
@@ -2629,66 +2934,20 @@ bool Generator::GenerateAll(const vector<const FileDescriptor*>& files,
return false;
}
} else if (options.import_style == GeneratorOptions::IMPORT_CLOSURE) {
- // Collect all types, and print each type to a separate file. Pull out
- // free-floating extensions while we make this pass.
- map< string, vector<const FieldDescriptor*> > extensions_by_namespace;
-
- // If we're generating code in file-per-type mode, avoid overwriting files
- // by choosing the last descriptor that writes each filename and permitting
- // only those to generate code.
-
- // Current descriptor that will generate each filename, indexed by filename.
- map<string, const void*> desc_by_filename;
- // Set of descriptors allowed to generate files.
- set<const void*> allowed_descs;
-
- for (int i = 0; i < files.size(); i++) {
- // Collect all (descriptor, filename) pairs.
- map<const void*, string> descs_in_file;
- for (int j = 0; j < files[i]->message_type_count(); j++) {
- const Descriptor* desc = files[i]->message_type(j);
- string filename =
- options.output_dir + "/" + ToFileName(desc->name()) + ".js";
- descs_in_file[desc] = filename;
- }
- for (int j = 0; j < files[i]->enum_type_count(); j++) {
- const EnumDescriptor* desc = files[i]->enum_type(j);
- string filename =
- options.output_dir + "/" + ToFileName(desc->name()) + ".js";
- descs_in_file[desc] = filename;
- }
-
- // For each (descriptor, filename) pair, update the
- // descriptors-by-filename map, and if a previous descriptor was already
- // writing the filename, remove it from the allowed-descriptors set.
- map<const void*, string>::iterator it;
- for (it = descs_in_file.begin(); it != descs_in_file.end(); ++it) {
- const void* desc = it->first;
- const string& filename = it->second;
- if (desc_by_filename.find(filename) != desc_by_filename.end()) {
- if (options.error_on_name_conflict) {
- *error = "Name conflict: file name " + filename +
- " would be generated by two descriptors";
- return false;
- }
- allowed_descs.erase(desc_by_filename[filename]);
- }
- desc_by_filename[filename] = desc;
- allowed_descs.insert(desc);
- }
+ 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());
@@ -2700,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);
@@ -2710,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());
@@ -2735,38 +2992,36 @@ 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);
- // Generate extensions in separate files.
- map< string, vector<const FieldDescriptor*> >::iterator it;
- for (it = extensions_by_namespace.begin();
- it != extensions_by_namespace.end();
- ++it) {
- string filename = options.output_dir + "/" +
- ToFileName(it->first) + ".js";
+ google::protobuf::scoped_ptr<io::ZeroCopyOutputStream> output(
+ context->Open(filename));
+ GOOGLE_CHECK(output.get());
+ io::Printer printer(output.get(), '$');
- 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;
- GenerateHeader(options, &printer);
+ for (int j = 0; j < files[i]->extension_count(); j++) {
+ if (ShouldGenerateExtension(files[i]->extension(j))) {
+ fields.push_back(files[i]->extension(j));
+ }
+ }
- std::set<string> provided;
- FindProvidesForFields(options, &printer, it->second, &provided);
- GenerateProvides(options, &printer, &provided);
- GenerateTestOnly(options, &printer);
- GenerateRequires(options, &printer, it->second, &provided);
+ FindProvidesForFields(options, &printer, fields, &provided);
+ GenerateProvides(options, &printer, &provided);
+ GenerateTestOnly(options, &printer);
+ GenerateRequiresForExtensions(options, &printer, fields, &provided);
- for (int j = 0; j < it->second.size(); j++) {
- if (ShouldGenerateExtension(it->second[j])) {
- GenerateExtension(options, &printer, it->second[j]);
+ for (int j = 0; j < files[i]->extension_count(); j++) {
+ if (ShouldGenerateExtension(files[i]->extension(j))) {
+ GenerateExtension(options, &printer, files[i]->extension(j));
+ }
}
}
}
@@ -2777,7 +3032,7 @@ bool Generator::GenerateAll(const vector<const FileDescriptor*>& files,
const google::protobuf::FileDescriptor* file = files[i];
string filename = options.output_dir + "/" + GetJSFilename(file->name());
- google::protobuf::scoped_ptr<io::ZeroCopyOutputStream> output(
+ scoped_ptr<io::ZeroCopyOutputStream> output(
context->Open(filename));
GOOGLE_CHECK(output.get());
io::Printer printer(output.get(), '$');