1 // Protocol Buffers - Google's data interchange format
2 // Copyright 2008 Google Inc. All rights reserved.
3 // https://developers.google.com/protocol-buffers/
4 //
5 // Redistribution and use in source and binary forms, with or without
6 // modification, are permitted provided that the following conditions are
7 // met:
8 //
9 // * Redistributions of source code must retain the above copyright
10 // notice, this list of conditions and the following disclaimer.
11 // * Redistributions in binary form must reproduce the above
12 // copyright notice, this list of conditions and the following disclaimer
13 // in the documentation and/or other materials provided with the
14 // distribution.
15 // * Neither the name of Google Inc. nor the names of its
16 // contributors may be used to endorse or promote products derived from
17 // this software without specific prior written permission.
18 //
19 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
31 #include "google/protobuf/compiler/js/js_generator.h"
32
33 #include <assert.h>
34 #include <algorithm>
35 #include <limits>
36 #include <map>
37 #include <memory>
38 #ifndef _SHARED_PTR_H
39 #include <google/protobuf/stubs/shared_ptr.h>
40 #endif
41 #include <string>
42 #include <utility>
43 #include <vector>
44
45 #include <google/protobuf/stubs/logging.h>
46 #include <google/protobuf/stubs/common.h>
47 #include <google/protobuf/stubs/stringprintf.h>
48 #include <google/protobuf/io/printer.h>
49 #include <google/protobuf/io/zero_copy_stream.h>
50 #include <google/protobuf/descriptor.pb.h>
51 #include <google/protobuf/descriptor.h>
52 #include <google/protobuf/stubs/strutil.h>
53
54 namespace google {
55 namespace protobuf {
56 namespace compiler {
57 namespace js {
58
59 // Sorted list of JavaScript keywords. These cannot be used as names. If they
60 // appear, we prefix them with "pb_".
61 const char* kKeyword[] = {
62 "abstract",
63 "boolean",
64 "break",
65 "byte",
66 "case",
67 "catch",
68 "char",
69 "class",
70 "const",
71 "continue",
72 "debugger",
73 "default",
74 "delete",
75 "do",
76 "double",
77 "else",
78 "enum",
79 "export",
80 "extends",
81 "false",
82 "final",
83 "finally",
84 "float",
85 "for",
86 "function",
87 "goto",
88 "if",
89 "implements",
90 "import",
91 "in",
92 "instanceof",
93 "int",
94 "interface",
95 "long",
96 "native",
97 "new",
98 "null",
99 "package",
100 "private",
101 "protected",
102 "public",
103 "return",
104 "short",
105 "static",
106 "super",
107 "switch",
108 "synchronized",
109 "this",
110 "throw",
111 "throws",
112 "transient",
113 "try",
114 "typeof",
115 "var",
116 "void",
117 "volatile",
118 "while",
119 "with",
120 };
121
122 static const int kNumKeyword = sizeof(kKeyword) / sizeof(char*);
123
124 namespace {
125
126 // The mode of operation for bytes fields. Historically JSPB always carried
127 // bytes as JS {string}, containing base64 content by convention. With binary
128 // and proto3 serialization the new convention is to represent it as binary
129 // data in Uint8Array. See b/26173701 for background on the migration.
130 enum BytesMode {
131 BYTES_DEFAULT, // Default type for getBytesField to return.
132 BYTES_B64, // Explicitly coerce to base64 string where needed.
133 BYTES_U8, // Explicitly coerce to Uint8Array where needed.
134 };
135
IsReserved(const string & ident)136 bool IsReserved(const string& ident) {
137 for (int i = 0; i < kNumKeyword; i++) {
138 if (ident == kKeyword[i]) {
139 return true;
140 }
141 }
142 return false;
143 }
144
145 // Returns a copy of |filename| with any trailing ".protodevel" or ".proto
146 // suffix stripped.
147 // TODO(haberman): Unify with copy in compiler/cpp/internal/helpers.cc.
StripProto(const string & filename)148 string StripProto(const string& filename) {
149 const char* suffix = HasSuffixString(filename, ".protodevel")
150 ? ".protodevel" : ".proto";
151 return StripSuffixString(filename, suffix);
152 }
153
154 // Given a filename like foo/bar/baz.proto, returns the corresponding JavaScript
155 // file foo/bar/baz.js.
GetJSFilename(const string & filename)156 string GetJSFilename(const string& filename) {
157 return StripProto(filename) + "_pb.js";
158 }
159
160 // Given a filename like foo/bar/baz.proto, returns the root directory
161 // path ../../
GetRootPath(const string & from_filename,const string & to_filename)162 string GetRootPath(const string& from_filename, const string& to_filename) {
163 if (to_filename.find("google/protobuf") == 0) {
164 // Well-known types (.proto files in the google/protobuf directory) are
165 // assumed to come from the 'google-protobuf' npm package. We may want to
166 // generalize this exception later by letting others put generated code in
167 // their own npm packages.
168 return "google-protobuf/";
169 }
170
171 size_t slashes = std::count(from_filename.begin(), from_filename.end(), '/');
172 if (slashes == 0) {
173 return "./";
174 }
175 string result = "";
176 for (size_t i = 0; i < slashes; i++) {
177 result += "../";
178 }
179 return result;
180 }
181
182 // Returns the alias we assign to the module of the given .proto filename
183 // when importing.
ModuleAlias(const string & filename)184 string ModuleAlias(const string& filename) {
185 // This scheme could technically cause problems if a file includes any 2 of:
186 // foo/bar_baz.proto
187 // foo_bar_baz.proto
188 // foo_bar/baz.proto
189 //
190 // We'll worry about this problem if/when we actually see it. This name isn't
191 // exposed to users so we can change it later if we need to.
192 string basename = StripProto(filename);
193 StripString(&basename, "-", '$');
194 StripString(&basename, "/", '_');
195 return basename + "_pb";
196 }
197
198 // Returns the fully normalized JavaScript path for the given
199 // file descriptor's package.
GetPath(const GeneratorOptions & options,const FileDescriptor * file)200 string GetPath(const GeneratorOptions& options,
201 const FileDescriptor* file) {
202 if (!options.namespace_prefix.empty()) {
203 return options.namespace_prefix;
204 } else if (!file->package().empty()) {
205 return "proto." + file->package();
206 } else {
207 return "proto";
208 }
209 }
210
211 // Forward declare, so that GetPrefix can call this method,
212 // which in turn, calls GetPrefix.
213 string GetPath(const GeneratorOptions& options,
214 const Descriptor* descriptor);
215
216 // Returns the path prefix for a message or enumeration that
217 // lives under the given file and containing type.
GetPrefix(const GeneratorOptions & options,const FileDescriptor * file_descriptor,const Descriptor * containing_type)218 string GetPrefix(const GeneratorOptions& options,
219 const FileDescriptor* file_descriptor,
220 const Descriptor* containing_type) {
221 string prefix = "";
222
223 if (containing_type == NULL) {
224 prefix = GetPath(options, file_descriptor);
225 } else {
226 prefix = GetPath(options, containing_type);
227 }
228
229 if (!prefix.empty()) {
230 prefix += ".";
231 }
232
233 return prefix;
234 }
235
236
237 // Returns the fully normalized JavaScript path for the given
238 // message descriptor.
GetPath(const GeneratorOptions & options,const Descriptor * descriptor)239 string GetPath(const GeneratorOptions& options,
240 const Descriptor* descriptor) {
241 return GetPrefix(
242 options, descriptor->file(),
243 descriptor->containing_type()) + descriptor->name();
244 }
245
246
247 // Returns the fully normalized JavaScript path for the given
248 // field's containing message descriptor.
GetPath(const GeneratorOptions & options,const FieldDescriptor * descriptor)249 string GetPath(const GeneratorOptions& options,
250 const FieldDescriptor* descriptor) {
251 return GetPath(options, descriptor->containing_type());
252 }
253
254 // Returns the fully normalized JavaScript path for the given
255 // enumeration descriptor.
GetPath(const GeneratorOptions & options,const EnumDescriptor * enum_descriptor)256 string GetPath(const GeneratorOptions& options,
257 const EnumDescriptor* enum_descriptor) {
258 return GetPrefix(
259 options, enum_descriptor->file(),
260 enum_descriptor->containing_type()) + enum_descriptor->name();
261 }
262
263
264 // Returns the fully normalized JavaScript path for the given
265 // enumeration value descriptor.
GetPath(const GeneratorOptions & options,const EnumValueDescriptor * value_descriptor)266 string GetPath(const GeneratorOptions& options,
267 const EnumValueDescriptor* value_descriptor) {
268 return GetPath(
269 options,
270 value_descriptor->type()) + "." + value_descriptor->name();
271 }
272
MaybeCrossFileRef(const GeneratorOptions & options,const FileDescriptor * from_file,const Descriptor * to_message)273 string MaybeCrossFileRef(const GeneratorOptions& options,
274 const FileDescriptor* from_file,
275 const Descriptor* to_message) {
276 if (options.import_style == GeneratorOptions::IMPORT_COMMONJS &&
277 from_file != to_message->file()) {
278 // Cross-file ref in CommonJS needs to use the module alias instead of
279 // the global name.
280 return ModuleAlias(to_message->file()->name()) + "." + to_message->name();
281 } else {
282 // Within a single file we use a full name.
283 return GetPath(options, to_message);
284 }
285 }
286
SubmessageTypeRef(const GeneratorOptions & options,const FieldDescriptor * field)287 string SubmessageTypeRef(const GeneratorOptions& options,
288 const FieldDescriptor* field) {
289 GOOGLE_CHECK(field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE);
290 return MaybeCrossFileRef(options, field->file(), field->message_type());
291 }
292
293 // - Object field name: LOWER_UNDERSCORE -> LOWER_CAMEL, except for group fields
294 // (UPPER_CAMEL -> LOWER_CAMEL), with "List" (or "Map") appended if appropriate,
295 // and with reserved words triggering a "pb_" prefix.
296 // - Getters/setters: LOWER_UNDERSCORE -> UPPER_CAMEL, except for group fields
297 // (use the name directly), then append "List" if appropriate, then append "$"
298 // if resulting name is equal to a reserved word.
299 // - Enums: just uppercase.
300
301 // Locale-independent version of ToLower that deals only with ASCII A-Z.
ToLowerASCII(char c)302 char ToLowerASCII(char c) {
303 if (c >= 'A' && c <= 'Z') {
304 return (c - 'A') + 'a';
305 } else {
306 return c;
307 }
308 }
309
ParseLowerUnderscore(const string & input)310 vector<string> ParseLowerUnderscore(const string& input) {
311 vector<string> words;
312 string running = "";
313 for (int i = 0; i < input.size(); i++) {
314 if (input[i] == '_') {
315 if (!running.empty()) {
316 words.push_back(running);
317 running.clear();
318 }
319 } else {
320 running += ToLowerASCII(input[i]);
321 }
322 }
323 if (!running.empty()) {
324 words.push_back(running);
325 }
326 return words;
327 }
328
ParseUpperCamel(const string & input)329 vector<string> ParseUpperCamel(const string& input) {
330 vector<string> words;
331 string running = "";
332 for (int i = 0; i < input.size(); i++) {
333 if (input[i] >= 'A' && input[i] <= 'Z' && !running.empty()) {
334 words.push_back(running);
335 running.clear();
336 }
337 running += ToLowerASCII(input[i]);
338 }
339 if (!running.empty()) {
340 words.push_back(running);
341 }
342 return words;
343 }
344
ToLowerCamel(const vector<string> & words)345 string ToLowerCamel(const vector<string>& words) {
346 string result;
347 for (int i = 0; i < words.size(); i++) {
348 string word = words[i];
349 if (i == 0 && (word[0] >= 'A' && word[0] <= 'Z')) {
350 word[0] = (word[0] - 'A') + 'a';
351 } else if (i != 0 && (word[0] >= 'a' && word[0] <= 'z')) {
352 word[0] = (word[0] - 'a') + 'A';
353 }
354 result += word;
355 }
356 return result;
357 }
358
ToUpperCamel(const vector<string> & words)359 string ToUpperCamel(const vector<string>& words) {
360 string result;
361 for (int i = 0; i < words.size(); i++) {
362 string word = words[i];
363 if (word[0] >= 'a' && word[0] <= 'z') {
364 word[0] = (word[0] - 'a') + 'A';
365 }
366 result += word;
367 }
368 return result;
369 }
370
371 // Based on code from descriptor.cc (Thanks Kenton!)
372 // Uppercases the entire string, turning ValueName into
373 // VALUENAME.
ToEnumCase(const string & input)374 string ToEnumCase(const string& input) {
375 string result;
376 result.reserve(input.size());
377
378 for (int i = 0; i < input.size(); i++) {
379 if ('a' <= input[i] && input[i] <= 'z') {
380 result.push_back(input[i] - 'a' + 'A');
381 } else {
382 result.push_back(input[i]);
383 }
384 }
385
386 return result;
387 }
388
ToFileName(const string & input)389 string ToFileName(const string& input) {
390 string result;
391 result.reserve(input.size());
392
393 for (int i = 0; i < input.size(); i++) {
394 if ('A' <= input[i] && input[i] <= 'Z') {
395 result.push_back(input[i] - 'A' + 'a');
396 } else {
397 result.push_back(input[i]);
398 }
399 }
400
401 return result;
402 }
403
404 // When we're generating one output file per type name, this is the filename
405 // that top-level extensions should go in.
GetExtensionFileName(const GeneratorOptions & options,const FileDescriptor * file)406 string GetExtensionFileName(const GeneratorOptions& options,
407 const FileDescriptor* file) {
408 return options.output_dir + "/" + ToFileName(GetPath(options, file)) + ".js";
409 }
410
411 // When we're generating one output file per type name, this is the filename
412 // that a top-level message should go in.
GetMessageFileName(const GeneratorOptions & options,const Descriptor * desc)413 string GetMessageFileName(const GeneratorOptions& options,
414 const Descriptor* desc) {
415 return options.output_dir + "/" + ToFileName(desc->name()) + ".js";
416 }
417
418 // When we're generating one output file per type name, this is the filename
419 // that a top-level message should go in.
GetEnumFileName(const GeneratorOptions & options,const EnumDescriptor * desc)420 string GetEnumFileName(const GeneratorOptions& options,
421 const EnumDescriptor* desc) {
422 return options.output_dir + "/" + ToFileName(desc->name()) + ".js";
423 }
424
425 // Returns the message/response ID, if set.
GetMessageId(const Descriptor * desc)426 string GetMessageId(const Descriptor* desc) {
427 return string();
428 }
429
IgnoreExtensionField(const FieldDescriptor * field)430 bool IgnoreExtensionField(const FieldDescriptor* field) {
431 // Exclude descriptor extensions from output "to avoid clutter" (from original
432 // codegen).
433 return field->is_extension() &&
434 field->containing_type()->file()->name() ==
435 "google/protobuf/descriptor.proto";
436 }
437
438
439 // Used inside Google only -- do not remove.
IsResponse(const Descriptor * desc)440 bool IsResponse(const Descriptor* desc) { return false; }
441
IgnoreField(const FieldDescriptor * field)442 bool IgnoreField(const FieldDescriptor* field) {
443 return IgnoreExtensionField(field);
444 }
445
446
447 // Do we ignore this message type?
IgnoreMessage(const GeneratorOptions & options,const Descriptor * d)448 bool IgnoreMessage(const GeneratorOptions& options, const Descriptor* d) {
449 return d->options().map_entry();
450 }
451
452 // Does JSPB ignore this entire oneof? True only if all fields are ignored.
IgnoreOneof(const OneofDescriptor * oneof)453 bool IgnoreOneof(const OneofDescriptor* oneof) {
454 for (int i = 0; i < oneof->field_count(); i++) {
455 if (!IgnoreField(oneof->field(i))) {
456 return false;
457 }
458 }
459 return true;
460 }
461
JSIdent(const GeneratorOptions & options,const FieldDescriptor * field,bool is_upper_camel,bool is_map)462 string JSIdent(const GeneratorOptions& options,
463 const FieldDescriptor* field,
464 bool is_upper_camel,
465 bool is_map) {
466 string result;
467 if (field->type() == FieldDescriptor::TYPE_GROUP) {
468 result = is_upper_camel ?
469 ToUpperCamel(ParseUpperCamel(field->message_type()->name())) :
470 ToLowerCamel(ParseUpperCamel(field->message_type()->name()));
471 } else {
472 result = is_upper_camel ?
473 ToUpperCamel(ParseLowerUnderscore(field->name())) :
474 ToLowerCamel(ParseLowerUnderscore(field->name()));
475 }
476 if (is_map || (field->is_map())) {
477 // JSPB-style or proto3-style map.
478 result += "Map";
479 } else if (field->is_repeated()) {
480 // Repeated field.
481 result += "List";
482 }
483 return result;
484 }
485
JSObjectFieldName(const GeneratorOptions & options,const FieldDescriptor * field)486 string JSObjectFieldName(const GeneratorOptions& options,
487 const FieldDescriptor* field) {
488 string name = JSIdent(
489 options,
490 field,
491 /* is_upper_camel = */ false,
492 /* is_map = */ false);
493 if (IsReserved(name)) {
494 name = "pb_" + name;
495 }
496 return name;
497 }
498
JSByteGetterSuffix(BytesMode bytes_mode)499 string JSByteGetterSuffix(BytesMode bytes_mode) {
500 switch (bytes_mode) {
501 case BYTES_DEFAULT:
502 return "";
503 case BYTES_B64:
504 return "B64";
505 case BYTES_U8:
506 return "U8";
507 default:
508 assert(false);
509 }
510 return "";
511 }
512
513 // Returns the field name as a capitalized portion of a getter/setter method
514 // name, e.g. MyField for .getMyField().
JSGetterName(const GeneratorOptions & options,const FieldDescriptor * field,BytesMode bytes_mode=BYTES_DEFAULT)515 string JSGetterName(const GeneratorOptions& options,
516 const FieldDescriptor* field,
517 BytesMode bytes_mode = BYTES_DEFAULT) {
518 string name = JSIdent(options, field,
519 /* is_upper_camel = */ true,
520 /* is_map = */ false);
521 if (field->type() == FieldDescriptor::TYPE_BYTES) {
522 string suffix = JSByteGetterSuffix(bytes_mode);
523 if (!suffix.empty()) {
524 name += "_as" + suffix;
525 }
526 }
527 if (name == "Extension" || name == "JsPbMessageId") {
528 // Avoid conflicts with base-class names.
529 name += "$";
530 }
531 return name;
532 }
533
JSMapGetterName(const GeneratorOptions & options,const FieldDescriptor * field)534 string JSMapGetterName(const GeneratorOptions& options,
535 const FieldDescriptor* field) {
536 return JSIdent(options, field,
537 /* is_upper_camel = */ true,
538 /* is_map = */ true);
539 }
540
541
542
JSOneofName(const OneofDescriptor * oneof)543 string JSOneofName(const OneofDescriptor* oneof) {
544 return ToUpperCamel(ParseLowerUnderscore(oneof->name()));
545 }
546
547 // Returns the index corresponding to this field in the JSPB array (underlying
548 // data storage array).
JSFieldIndex(const FieldDescriptor * field)549 string JSFieldIndex(const FieldDescriptor* field) {
550 // Determine whether this field is a member of a group. Group fields are a bit
551 // wonky: their "containing type" is a message type created just for the
552 // group, and that type's parent type has a field with the group-message type
553 // as its message type and TYPE_GROUP as its field type. For such fields, the
554 // index we use is relative to the field number of the group submessage field.
555 // For all other fields, we just use the field number.
556 const Descriptor* containing_type = field->containing_type();
557 const Descriptor* parent_type = containing_type->containing_type();
558 if (parent_type != NULL) {
559 for (int i = 0; i < parent_type->field_count(); i++) {
560 if (parent_type->field(i)->type() == FieldDescriptor::TYPE_GROUP &&
561 parent_type->field(i)->message_type() == containing_type) {
562 return SimpleItoa(field->number() - parent_type->field(i)->number());
563 }
564 }
565 }
566 return SimpleItoa(field->number());
567 }
568
JSOneofIndex(const OneofDescriptor * oneof)569 string JSOneofIndex(const OneofDescriptor* oneof) {
570 int index = -1;
571 for (int i = 0; i < oneof->containing_type()->oneof_decl_count(); i++) {
572 const OneofDescriptor* o = oneof->containing_type()->oneof_decl(i);
573 // If at least one field in this oneof is not JSPB-ignored, count the oneof.
574 for (int j = 0; j < o->field_count(); j++) {
575 const FieldDescriptor* f = o->field(j);
576 if (!IgnoreField(f)) {
577 index++;
578 break; // inner loop
579 }
580 }
581 if (o == oneof) {
582 break;
583 }
584 }
585 return SimpleItoa(index);
586 }
587
588 // Decodes a codepoint in \x0000 -- \xFFFF.
DecodeUTF8Codepoint(uint8 * bytes,size_t * length)589 uint16 DecodeUTF8Codepoint(uint8* bytes, size_t* length) {
590 if (*length == 0) {
591 return 0;
592 }
593 size_t expected = 0;
594 if ((*bytes & 0x80) == 0) {
595 expected = 1;
596 } else if ((*bytes & 0xe0) == 0xc0) {
597 expected = 2;
598 } else if ((*bytes & 0xf0) == 0xe0) {
599 expected = 3;
600 } else {
601 // Too long -- don't accept.
602 *length = 0;
603 return 0;
604 }
605
606 if (*length < expected) {
607 // Not enough bytes -- don't accept.
608 *length = 0;
609 return 0;
610 }
611
612 *length = expected;
613 switch (expected) {
614 case 1: return bytes[0];
615 case 2: return ((bytes[0] & 0x1F) << 6) |
616 ((bytes[1] & 0x3F) << 0);
617 case 3: return ((bytes[0] & 0x0F) << 12) |
618 ((bytes[1] & 0x3F) << 6) |
619 ((bytes[2] & 0x3F) << 0);
620 default: return 0;
621 }
622 }
623
624 // Escapes the contents of a string to be included within double-quotes ("") in
625 // JavaScript. The input data should be a UTF-8 encoded C++ string of chars.
626 // Returns false if |out| was truncated because |in| contained invalid UTF-8 or
627 // codepoints outside the BMP.
628 // TODO(lukestebbing): Support codepoints outside the BMP.
EscapeJSString(const string & in,string * out)629 bool EscapeJSString(const string& in, string* out) {
630 size_t decoded = 0;
631 for (size_t i = 0; i < in.size(); i += decoded) {
632 uint16 codepoint = 0;
633 // Decode the next UTF-8 codepoint.
634 size_t have_bytes = in.size() - i;
635 uint8 bytes[3] = {
636 static_cast<uint8>(in[i]),
637 static_cast<uint8>(((i + 1) < in.size()) ? in[i + 1] : 0),
638 static_cast<uint8>(((i + 2) < in.size()) ? in[i + 2] : 0),
639 };
640 codepoint = DecodeUTF8Codepoint(bytes, &have_bytes);
641 if (have_bytes == 0) {
642 return false;
643 }
644 decoded = have_bytes;
645
646 switch (codepoint) {
647 case '\'': *out += "\\x27"; break;
648 case '"': *out += "\\x22"; break;
649 case '<': *out += "\\x3c"; break;
650 case '=': *out += "\\x3d"; break;
651 case '>': *out += "\\x3e"; break;
652 case '&': *out += "\\x26"; break;
653 case '\b': *out += "\\b"; break;
654 case '\t': *out += "\\t"; break;
655 case '\n': *out += "\\n"; break;
656 case '\f': *out += "\\f"; break;
657 case '\r': *out += "\\r"; break;
658 case '\\': *out += "\\\\"; break;
659 default:
660 // TODO(lukestebbing): Once we're supporting codepoints outside the BMP,
661 // use a single Unicode codepoint escape if the output language is
662 // ECMAScript 2015 or above. Otherwise, use a surrogate pair.
663 // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#String_literals
664 if (codepoint >= 0x20 && codepoint <= 0x7e) {
665 *out += static_cast<char>(codepoint);
666 } else if (codepoint >= 0x100) {
667 *out += StringPrintf("\\u%04x", codepoint);
668 } else {
669 *out += StringPrintf("\\x%02x", codepoint);
670 }
671 break;
672 }
673 }
674 return true;
675 }
676
EscapeBase64(const string & in)677 string EscapeBase64(const string& in) {
678 static const char* kAlphabet =
679 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
680 string result;
681
682 for (size_t i = 0; i < in.size(); i += 3) {
683 int value = (in[i] << 16) |
684 (((i + 1) < in.size()) ? (in[i + 1] << 8) : 0) |
685 (((i + 2) < in.size()) ? (in[i + 2] << 0) : 0);
686 result += kAlphabet[(value >> 18) & 0x3f];
687 result += kAlphabet[(value >> 12) & 0x3f];
688 if ((i + 1) < in.size()) {
689 result += kAlphabet[(value >> 6) & 0x3f];
690 } else {
691 result += '=';
692 }
693 if ((i + 2) < in.size()) {
694 result += kAlphabet[(value >> 0) & 0x3f];
695 } else {
696 result += '=';
697 }
698 }
699
700 return result;
701 }
702
703 // Post-process the result of SimpleFtoa/SimpleDtoa to *exactly* match the
704 // original codegen's formatting (which is just .toString() on java.lang.Double
705 // or java.lang.Float).
PostProcessFloat(string result)706 string PostProcessFloat(string result) {
707 // If inf, -inf or nan, replace with +Infinity, -Infinity or NaN.
708 if (result == "inf") {
709 return "Infinity";
710 } else if (result == "-inf") {
711 return "-Infinity";
712 } else if (result == "nan") {
713 return "NaN";
714 }
715
716 // If scientific notation (e.g., "1e10"), (i) capitalize the "e", (ii)
717 // ensure that the mantissa (portion prior to the "e") has at least one
718 // fractional digit (after the decimal point), and (iii) strip any unnecessary
719 // leading zeroes and/or '+' signs from the exponent.
720 string::size_type exp_pos = result.find('e');
721 if (exp_pos != string::npos) {
722 string mantissa = result.substr(0, exp_pos);
723 string exponent = result.substr(exp_pos + 1);
724
725 // Add ".0" to mantissa if no fractional part exists.
726 if (mantissa.find('.') == string::npos) {
727 mantissa += ".0";
728 }
729
730 // Strip the sign off the exponent and store as |exp_neg|.
731 bool exp_neg = false;
732 if (!exponent.empty() && exponent[0] == '+') {
733 exponent = exponent.substr(1);
734 } else if (!exponent.empty() && exponent[0] == '-') {
735 exp_neg = true;
736 exponent = exponent.substr(1);
737 }
738
739 // Strip any leading zeroes off the exponent.
740 while (exponent.size() > 1 && exponent[0] == '0') {
741 exponent = exponent.substr(1);
742 }
743
744 return mantissa + "E" + string(exp_neg ? "-" : "") + exponent;
745 }
746
747 // Otherwise, this is an ordinary decimal number. Append ".0" if result has no
748 // decimal/fractional part in order to match output of original codegen.
749 if (result.find('.') == string::npos) {
750 result += ".0";
751 }
752
753 return result;
754 }
755
FloatToString(float value)756 string FloatToString(float value) {
757 string result = SimpleFtoa(value);
758 return PostProcessFloat(result);
759 }
760
DoubleToString(double value)761 string DoubleToString(double value) {
762 string result = SimpleDtoa(value);
763 return PostProcessFloat(result);
764 }
765
MaybeNumberString(const FieldDescriptor * field,const string & orig)766 string MaybeNumberString(const FieldDescriptor* field, const string& orig) {
767 return orig;
768 }
769
JSFieldDefault(const FieldDescriptor * field)770 string JSFieldDefault(const FieldDescriptor* field) {
771 switch (field->cpp_type()) {
772 case FieldDescriptor::CPPTYPE_INT32:
773 return MaybeNumberString(
774 field, SimpleItoa(field->default_value_int32()));
775 case FieldDescriptor::CPPTYPE_UINT32:
776 // The original codegen is in Java, and Java protobufs store unsigned
777 // integer values as signed integer values. In order to exactly match the
778 // output, we need to reinterpret as base-2 signed. Ugh.
779 return MaybeNumberString(
780 field, SimpleItoa(static_cast<int32>(field->default_value_uint32())));
781 case FieldDescriptor::CPPTYPE_INT64:
782 return MaybeNumberString(
783 field, SimpleItoa(field->default_value_int64()));
784 case FieldDescriptor::CPPTYPE_UINT64:
785 // See above note for uint32 -- reinterpreting as signed.
786 return MaybeNumberString(
787 field, SimpleItoa(static_cast<int64>(field->default_value_uint64())));
788 case FieldDescriptor::CPPTYPE_ENUM:
789 return SimpleItoa(field->default_value_enum()->number());
790 case FieldDescriptor::CPPTYPE_BOOL:
791 return field->default_value_bool() ? "true" : "false";
792 case FieldDescriptor::CPPTYPE_FLOAT:
793 return FloatToString(field->default_value_float());
794 case FieldDescriptor::CPPTYPE_DOUBLE:
795 return DoubleToString(field->default_value_double());
796 case FieldDescriptor::CPPTYPE_STRING:
797 if (field->type() == FieldDescriptor::TYPE_STRING) {
798 string out;
799 bool is_valid = EscapeJSString(field->default_value_string(), &out);
800 if (!is_valid) {
801 // TODO(lukestebbing): Decide whether this should be a hard error.
802 GOOGLE_LOG(WARNING) << "The default value for field " << field->full_name()
803 << " was truncated since it contained invalid UTF-8 or"
804 " codepoints outside the basic multilingual plane.";
805 }
806 return "\"" + out + "\"";
807 } else { // Bytes
808 return "\"" + EscapeBase64(field->default_value_string()) + "\"";
809 }
810 case FieldDescriptor::CPPTYPE_MESSAGE:
811 return "null";
812 }
813 GOOGLE_LOG(FATAL) << "Shouldn't reach here.";
814 return "";
815 }
816
ProtoTypeName(const GeneratorOptions & options,const FieldDescriptor * field)817 string ProtoTypeName(const GeneratorOptions& options,
818 const FieldDescriptor* field) {
819 switch (field->type()) {
820 case FieldDescriptor::TYPE_BOOL:
821 return "bool";
822 case FieldDescriptor::TYPE_INT32:
823 return "int32";
824 case FieldDescriptor::TYPE_UINT32:
825 return "uint32";
826 case FieldDescriptor::TYPE_SINT32:
827 return "sint32";
828 case FieldDescriptor::TYPE_FIXED32:
829 return "fixed32";
830 case FieldDescriptor::TYPE_SFIXED32:
831 return "sfixed32";
832 case FieldDescriptor::TYPE_INT64:
833 return "int64";
834 case FieldDescriptor::TYPE_UINT64:
835 return "uint64";
836 case FieldDescriptor::TYPE_SINT64:
837 return "sint64";
838 case FieldDescriptor::TYPE_FIXED64:
839 return "fixed64";
840 case FieldDescriptor::TYPE_SFIXED64:
841 return "sfixed64";
842 case FieldDescriptor::TYPE_FLOAT:
843 return "float";
844 case FieldDescriptor::TYPE_DOUBLE:
845 return "double";
846 case FieldDescriptor::TYPE_STRING:
847 return "string";
848 case FieldDescriptor::TYPE_BYTES:
849 return "bytes";
850 case FieldDescriptor::TYPE_GROUP:
851 return GetPath(options, field->message_type());
852 case FieldDescriptor::TYPE_ENUM:
853 return GetPath(options, field->enum_type());
854 case FieldDescriptor::TYPE_MESSAGE:
855 return GetPath(options, field->message_type());
856 default:
857 return "";
858 }
859 }
860
JSIntegerTypeName(const FieldDescriptor * field)861 string JSIntegerTypeName(const FieldDescriptor* field) {
862 return "number";
863 }
864
JSStringTypeName(const GeneratorOptions & options,const FieldDescriptor * field,BytesMode bytes_mode)865 string JSStringTypeName(const GeneratorOptions& options,
866 const FieldDescriptor* field,
867 BytesMode bytes_mode) {
868 if (field->type() == FieldDescriptor::TYPE_BYTES) {
869 switch (bytes_mode) {
870 case BYTES_DEFAULT:
871 return "(string|Uint8Array)";
872 case BYTES_B64:
873 return "string";
874 case BYTES_U8:
875 return "Uint8Array";
876 default:
877 assert(false);
878 }
879 }
880 return "string";
881 }
882
JSTypeName(const GeneratorOptions & options,const FieldDescriptor * field,BytesMode bytes_mode)883 string JSTypeName(const GeneratorOptions& options,
884 const FieldDescriptor* field,
885 BytesMode bytes_mode) {
886 switch (field->cpp_type()) {
887 case FieldDescriptor::CPPTYPE_BOOL:
888 return "boolean";
889 case FieldDescriptor::CPPTYPE_INT32:
890 return JSIntegerTypeName(field);
891 case FieldDescriptor::CPPTYPE_INT64:
892 return JSIntegerTypeName(field);
893 case FieldDescriptor::CPPTYPE_UINT32:
894 return JSIntegerTypeName(field);
895 case FieldDescriptor::CPPTYPE_UINT64:
896 return JSIntegerTypeName(field);
897 case FieldDescriptor::CPPTYPE_FLOAT:
898 return "number";
899 case FieldDescriptor::CPPTYPE_DOUBLE:
900 return "number";
901 case FieldDescriptor::CPPTYPE_STRING:
902 return JSStringTypeName(options, field, bytes_mode);
903 case FieldDescriptor::CPPTYPE_ENUM:
904 return GetPath(options, field->enum_type());
905 case FieldDescriptor::CPPTYPE_MESSAGE:
906 return GetPath(options, field->message_type());
907 default:
908 return "";
909 }
910 }
911
912 bool HasFieldPresence(const FieldDescriptor* field);
913
JSFieldTypeAnnotation(const GeneratorOptions & options,const FieldDescriptor * field,bool force_optional,bool force_present,bool singular_if_not_packed,BytesMode bytes_mode=BYTES_DEFAULT)914 string JSFieldTypeAnnotation(const GeneratorOptions& options,
915 const FieldDescriptor* field,
916 bool force_optional,
917 bool force_present,
918 bool singular_if_not_packed,
919 BytesMode bytes_mode = BYTES_DEFAULT) {
920 bool is_primitive =
921 (field->cpp_type() != FieldDescriptor::CPPTYPE_ENUM &&
922 field->cpp_type() != FieldDescriptor::CPPTYPE_MESSAGE &&
923 (field->type() != FieldDescriptor::TYPE_BYTES ||
924 bytes_mode == BYTES_B64));
925
926 string jstype = JSTypeName(options, field, bytes_mode);
927
928 if (field->is_repeated() &&
929 (field->is_packed() || !singular_if_not_packed)) {
930 if (field->type() == FieldDescriptor::TYPE_BYTES &&
931 bytes_mode == BYTES_DEFAULT) {
932 jstype = "(Array<!Uint8Array>|Array<string>)";
933 } else {
934 if (!is_primitive) {
935 jstype = "!" + jstype;
936 }
937 jstype = "Array.<" + jstype + ">";
938 }
939 if (!force_optional) {
940 jstype = "!" + jstype;
941 }
942 }
943
944 if (field->is_optional() && is_primitive &&
945 force_optional && !force_present) {
946 jstype += "?";
947 } else if (field->is_required() && !is_primitive && !force_optional) {
948 jstype = "!" + jstype;
949 }
950
951 if (force_optional && HasFieldPresence(field)) {
952 jstype += "|undefined";
953 }
954 if (force_present && jstype[0] != '!' && !is_primitive) {
955 jstype = "!" + jstype;
956 }
957
958 return jstype;
959 }
960
JSBinaryReaderMethodType(const FieldDescriptor * field)961 string JSBinaryReaderMethodType(const FieldDescriptor* field) {
962 string name = field->type_name();
963 if (name[0] >= 'a' && name[0] <= 'z') {
964 name[0] = (name[0] - 'a') + 'A';
965 }
966
967 return name;
968 }
969
JSBinaryReadWriteMethodName(const FieldDescriptor * field,bool is_writer)970 string JSBinaryReadWriteMethodName(const FieldDescriptor* field,
971 bool is_writer) {
972 string name = JSBinaryReaderMethodType(field);
973 if (field->is_packed()) {
974 name = "Packed" + name;
975 } else if (is_writer && field->is_repeated()) {
976 name = "Repeated" + name;
977 }
978 return name;
979 }
980
JSBinaryReaderMethodName(const GeneratorOptions & options,const FieldDescriptor * field)981 string JSBinaryReaderMethodName(const GeneratorOptions& options,
982 const FieldDescriptor* field) {
983 if (options.binary) {
984 return "jspb.BinaryReader.prototype.read" +
985 JSBinaryReadWriteMethodName(field, /* is_writer = */ false);
986 } else {
987 return "null";
988 }
989 }
990
JSBinaryWriterMethodName(const GeneratorOptions & options,const FieldDescriptor * field)991 string JSBinaryWriterMethodName(const GeneratorOptions& options,
992 const FieldDescriptor* field) {
993 if (options.binary) {
994 return "jspb.BinaryWriter.prototype.write" +
995 JSBinaryReadWriteMethodName(field, /* is_writer = */ true);
996 } else {
997 return "null";
998 }
999 }
1000
JSReturnClause(const FieldDescriptor * desc)1001 string JSReturnClause(const FieldDescriptor* desc) {
1002 return "";
1003 }
1004
JSReturnDoc(const GeneratorOptions & options,const FieldDescriptor * desc)1005 string JSReturnDoc(const GeneratorOptions& options,
1006 const FieldDescriptor* desc) {
1007 return "";
1008 }
1009
HasRepeatedFields(const Descriptor * desc)1010 bool HasRepeatedFields(const Descriptor* desc) {
1011 for (int i = 0; i < desc->field_count(); i++) {
1012 if (desc->field(i)->is_repeated() && !desc->field(i)->is_map()) {
1013 return true;
1014 }
1015 }
1016 return false;
1017 }
1018
1019 static const char* kRepeatedFieldArrayName = ".repeatedFields_";
1020
RepeatedFieldsArrayName(const GeneratorOptions & options,const Descriptor * desc)1021 string RepeatedFieldsArrayName(const GeneratorOptions& options,
1022 const Descriptor* desc) {
1023 return HasRepeatedFields(desc) ?
1024 (GetPath(options, desc) + kRepeatedFieldArrayName) : "null";
1025 }
1026
HasOneofFields(const Descriptor * desc)1027 bool HasOneofFields(const Descriptor* desc) {
1028 for (int i = 0; i < desc->field_count(); i++) {
1029 if (desc->field(i)->containing_oneof()) {
1030 return true;
1031 }
1032 }
1033 return false;
1034 }
1035
1036 static const char* kOneofGroupArrayName = ".oneofGroups_";
1037
OneofFieldsArrayName(const GeneratorOptions & options,const Descriptor * desc)1038 string OneofFieldsArrayName(const GeneratorOptions& options,
1039 const Descriptor* desc) {
1040 return HasOneofFields(desc) ?
1041 (GetPath(options, desc) + kOneofGroupArrayName) : "null";
1042 }
1043
RepeatedFieldNumberList(const Descriptor * desc)1044 string RepeatedFieldNumberList(const Descriptor* desc) {
1045 std::vector<string> numbers;
1046 for (int i = 0; i < desc->field_count(); i++) {
1047 if (desc->field(i)->is_repeated() && !desc->field(i)->is_map()) {
1048 numbers.push_back(JSFieldIndex(desc->field(i)));
1049 }
1050 }
1051 return "[" + Join(numbers, ",") + "]";
1052 }
1053
OneofGroupList(const Descriptor * desc)1054 string OneofGroupList(const Descriptor* desc) {
1055 // List of arrays (one per oneof), each of which is a list of field indices
1056 std::vector<string> oneof_entries;
1057 for (int i = 0; i < desc->oneof_decl_count(); i++) {
1058 const OneofDescriptor* oneof = desc->oneof_decl(i);
1059 if (IgnoreOneof(oneof)) {
1060 continue;
1061 }
1062
1063 std::vector<string> oneof_fields;
1064 for (int j = 0; j < oneof->field_count(); j++) {
1065 if (IgnoreField(oneof->field(j))) {
1066 continue;
1067 }
1068 oneof_fields.push_back(JSFieldIndex(oneof->field(j)));
1069 }
1070 oneof_entries.push_back("[" + Join(oneof_fields, ",") + "]");
1071 }
1072 return "[" + Join(oneof_entries, ",") + "]";
1073 }
1074
JSOneofArray(const GeneratorOptions & options,const FieldDescriptor * field)1075 string JSOneofArray(const GeneratorOptions& options,
1076 const FieldDescriptor* field) {
1077 return OneofFieldsArrayName(options, field->containing_type()) + "[" +
1078 JSOneofIndex(field->containing_oneof()) + "]";
1079 }
1080
RelativeTypeName(const FieldDescriptor * field)1081 string RelativeTypeName(const FieldDescriptor* field) {
1082 assert(field->cpp_type() == FieldDescriptor::CPPTYPE_ENUM ||
1083 field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE);
1084 // For a field with an enum or message type, compute a name relative to the
1085 // path name of the message type containing this field.
1086 string package = field->file()->package();
1087 string containing_type = field->containing_type()->full_name() + ".";
1088 string type = (field->cpp_type() == FieldDescriptor::CPPTYPE_ENUM) ?
1089 field->enum_type()->full_name() : field->message_type()->full_name();
1090
1091 // |prefix| is advanced as we find separators '.' past the common package
1092 // prefix that yield common prefixes in the containing type's name and this
1093 // type's name.
1094 int prefix = 0;
1095 for (int i = 0; i < type.size() && i < containing_type.size(); i++) {
1096 if (type[i] != containing_type[i]) {
1097 break;
1098 }
1099 if (type[i] == '.' && i >= package.size()) {
1100 prefix = i + 1;
1101 }
1102 }
1103
1104 return type.substr(prefix);
1105 }
1106
JSExtensionsObjectName(const GeneratorOptions & options,const FileDescriptor * from_file,const Descriptor * desc)1107 string JSExtensionsObjectName(const GeneratorOptions& options,
1108 const FileDescriptor* from_file,
1109 const Descriptor* desc) {
1110 if (desc->full_name() == "google.protobuf.bridge.MessageSet") {
1111 // TODO(haberman): fix this for the IMPORT_COMMONJS case.
1112 return "jspb.Message.messageSetExtensions";
1113 } else {
1114 return MaybeCrossFileRef(options, from_file, desc) + ".extensions";
1115 }
1116 }
1117
1118 static const int kMapKeyField = 1;
1119 static const int kMapValueField = 2;
1120
MapFieldKey(const FieldDescriptor * field)1121 const FieldDescriptor* MapFieldKey(const FieldDescriptor* field) {
1122 assert(field->is_map());
1123 return field->message_type()->FindFieldByNumber(kMapKeyField);
1124 }
1125
MapFieldValue(const FieldDescriptor * field)1126 const FieldDescriptor* MapFieldValue(const FieldDescriptor* field) {
1127 assert(field->is_map());
1128 return field->message_type()->FindFieldByNumber(kMapValueField);
1129 }
1130
FieldDefinition(const GeneratorOptions & options,const FieldDescriptor * field)1131 string FieldDefinition(const GeneratorOptions& options,
1132 const FieldDescriptor* field) {
1133 if (field->is_map()) {
1134 const FieldDescriptor* key_field = MapFieldKey(field);
1135 const FieldDescriptor* value_field = MapFieldValue(field);
1136 string key_type = ProtoTypeName(options, key_field);
1137 string value_type;
1138 if (value_field->type() == FieldDescriptor::TYPE_ENUM ||
1139 value_field->type() == FieldDescriptor::TYPE_MESSAGE) {
1140 value_type = RelativeTypeName(value_field);
1141 } else {
1142 value_type = ProtoTypeName(options, value_field);
1143 }
1144 return StringPrintf("map<%s, %s> %s = %d;",
1145 key_type.c_str(),
1146 value_type.c_str(),
1147 field->name().c_str(),
1148 field->number());
1149 } else {
1150 string qualifier = field->is_repeated() ? "repeated" :
1151 (field->is_optional() ? "optional" : "required");
1152 string type, name;
1153 if (field->type() == FieldDescriptor::TYPE_ENUM ||
1154 field->type() == FieldDescriptor::TYPE_MESSAGE) {
1155 type = RelativeTypeName(field);
1156 name = field->name();
1157 } else if (field->type() == FieldDescriptor::TYPE_GROUP) {
1158 type = "group";
1159 name = field->message_type()->name();
1160 } else {
1161 type = ProtoTypeName(options, field);
1162 name = field->name();
1163 }
1164 return StringPrintf("%s %s %s = %d;",
1165 qualifier.c_str(),
1166 type.c_str(),
1167 name.c_str(),
1168 field->number());
1169 }
1170 }
1171
FieldComments(const FieldDescriptor * field,BytesMode bytes_mode)1172 string FieldComments(const FieldDescriptor* field, BytesMode bytes_mode) {
1173 string comments;
1174 if (field->cpp_type() == FieldDescriptor::CPPTYPE_BOOL) {
1175 comments +=
1176 " * Note that Boolean fields may be set to 0/1 when serialized from "
1177 "a Java server.\n"
1178 " * You should avoid comparisons like {@code val === true/false} in "
1179 "those cases.\n";
1180 }
1181 if (field->is_repeated()) {
1182 comments +=
1183 " * If you change this array by adding, removing or replacing "
1184 "elements, or if you\n"
1185 " * replace the array itself, then you must call the setter to "
1186 "update it.\n";
1187 }
1188 if (field->type() == FieldDescriptor::TYPE_BYTES && bytes_mode == BYTES_U8) {
1189 comments +=
1190 " * Note that Uint8Array is not supported on all browsers.\n"
1191 " * @see http://caniuse.com/Uint8Array\n";
1192 }
1193 return comments;
1194 }
1195
ShouldGenerateExtension(const FieldDescriptor * field)1196 bool ShouldGenerateExtension(const FieldDescriptor* field) {
1197 return
1198 field->is_extension() &&
1199 !IgnoreField(field);
1200 }
1201
HasExtensions(const Descriptor * desc)1202 bool HasExtensions(const Descriptor* desc) {
1203 for (int i = 0; i < desc->extension_count(); i++) {
1204 if (ShouldGenerateExtension(desc->extension(i))) {
1205 return true;
1206 }
1207 }
1208 for (int i = 0; i < desc->nested_type_count(); i++) {
1209 if (HasExtensions(desc->nested_type(i))) {
1210 return true;
1211 }
1212 }
1213 return false;
1214 }
1215
HasExtensions(const FileDescriptor * file)1216 bool HasExtensions(const FileDescriptor* file) {
1217 for (int i = 0; i < file->extension_count(); i++) {
1218 if (ShouldGenerateExtension(file->extension(i))) {
1219 return true;
1220 }
1221 }
1222 for (int i = 0; i < file->message_type_count(); i++) {
1223 if (HasExtensions(file->message_type(i))) {
1224 return true;
1225 }
1226 }
1227 return false;
1228 }
1229
IsExtendable(const Descriptor * desc)1230 bool IsExtendable(const Descriptor* desc) {
1231 return desc->extension_range_count() > 0;
1232 }
1233
1234 // Returns the max index in the underlying data storage array beyond which the
1235 // extension object is used.
GetPivot(const Descriptor * desc)1236 string GetPivot(const Descriptor* desc) {
1237 static const int kDefaultPivot = (1 << 29); // max field number (29 bits)
1238
1239 // Find the max field number
1240 int max_field_number = 0;
1241 for (int i = 0; i < desc->field_count(); i++) {
1242 if (!IgnoreField(desc->field(i)) &&
1243 desc->field(i)->number() > max_field_number) {
1244 max_field_number = desc->field(i)->number();
1245 }
1246 }
1247
1248 int pivot = -1;
1249 if (IsExtendable(desc)) {
1250 pivot = ((max_field_number + 1) < kDefaultPivot) ?
1251 (max_field_number + 1) : kDefaultPivot;
1252 }
1253
1254 return SimpleItoa(pivot);
1255 }
1256
1257 // Returns true for fields that represent "null" as distinct from the default
1258 // value. See http://go/proto3#heading=h.kozewqqcqhuz for more information.
HasFieldPresence(const FieldDescriptor * field)1259 bool HasFieldPresence(const FieldDescriptor* field) {
1260 if (field->is_repeated()) {
1261 return false;
1262 }
1263
1264 return
1265 (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) ||
1266 (field->containing_oneof() != NULL) ||
1267 (field->file()->syntax() != FileDescriptor::SYNTAX_PROTO3);
1268 }
1269
1270 // For proto3 fields without presence, returns a string representing the default
1271 // value in JavaScript. See http://go/proto3#heading=h.kozewqqcqhuz for more
1272 // information.
Proto3PrimitiveFieldDefault(const FieldDescriptor * field)1273 string Proto3PrimitiveFieldDefault(const FieldDescriptor* field) {
1274 switch (field->cpp_type()) {
1275 case FieldDescriptor::CPPTYPE_INT32:
1276 case FieldDescriptor::CPPTYPE_INT64:
1277 case FieldDescriptor::CPPTYPE_UINT32:
1278 case FieldDescriptor::CPPTYPE_UINT64: {
1279 return "0";
1280 }
1281
1282 case FieldDescriptor::CPPTYPE_ENUM:
1283 case FieldDescriptor::CPPTYPE_FLOAT:
1284 case FieldDescriptor::CPPTYPE_DOUBLE:
1285 return "0";
1286
1287 case FieldDescriptor::CPPTYPE_BOOL:
1288 return "false";
1289
1290 case FieldDescriptor::CPPTYPE_STRING: // includes BYTES
1291 return "\"\"";
1292
1293 default:
1294 // MESSAGE is handled separately.
1295 assert(false);
1296 return "";
1297 }
1298 }
1299
1300 // We use this to implement the semantics that same file can be generated
1301 // multiple times, but the last one wins. We never actually write the files,
1302 // but we keep a set of which descriptors were the final one for a given
1303 // filename.
1304 class FileDeduplicator {
1305 public:
FileDeduplicator(const GeneratorOptions & options)1306 explicit FileDeduplicator(const GeneratorOptions& options)
1307 : error_on_conflict_(options.error_on_name_conflict) {}
1308
AddFile(const string & filename,const void * desc,string * error)1309 bool AddFile(const string& filename, const void* desc, string* error) {
1310 if (descs_by_filename_.find(filename) != descs_by_filename_.end()) {
1311 if (error_on_conflict_) {
1312 *error = "Name conflict: file name " + filename +
1313 " would be generated by two descriptors";
1314 return false;
1315 }
1316 allowed_descs_.erase(descs_by_filename_[filename]);
1317 }
1318
1319 descs_by_filename_[filename] = desc;
1320 allowed_descs_.insert(desc);
1321 return true;
1322 }
1323
GetAllowedSet(set<const void * > * allowed_set)1324 void GetAllowedSet(set<const void*>* allowed_set) {
1325 *allowed_set = allowed_descs_;
1326 }
1327
1328 private:
1329 bool error_on_conflict_;
1330 map<string, const void*> descs_by_filename_;
1331 set<const void*> allowed_descs_;
1332 };
1333
DepthFirstSearch(const FileDescriptor * file,vector<const FileDescriptor * > * list,set<const FileDescriptor * > * seen)1334 void DepthFirstSearch(const FileDescriptor* file,
1335 vector<const FileDescriptor*>* list,
1336 set<const FileDescriptor*>* seen) {
1337 if (!seen->insert(file).second) {
1338 return;
1339 }
1340
1341 // Add all dependencies.
1342 for (int i = 0; i < file->dependency_count(); i++) {
1343 DepthFirstSearch(file->dependency(i), list, seen);
1344 }
1345
1346 // Add this file.
1347 list->push_back(file);
1348 }
1349
1350 // A functor for the predicate to remove_if() below. Returns true if a given
1351 // FileDescriptor is not in the given set.
1352 class NotInSet {
1353 public:
NotInSet(const set<const FileDescriptor * > & file_set)1354 explicit NotInSet(const set<const FileDescriptor*>& file_set)
1355 : file_set_(file_set) {}
1356
operator ()(const FileDescriptor * file)1357 bool operator()(const FileDescriptor* file) {
1358 return file_set_.count(file) == 0;
1359 }
1360
1361 private:
1362 const set<const FileDescriptor*>& file_set_;
1363 };
1364
1365 // This function generates an ordering of the input FileDescriptors that matches
1366 // the logic of the old code generator. The order is significant because two
1367 // different input files can generate the same output file, and the last one
1368 // needs to win.
GenerateJspbFileOrder(const vector<const FileDescriptor * > & input,vector<const FileDescriptor * > * ordered)1369 void GenerateJspbFileOrder(const vector<const FileDescriptor*>& input,
1370 vector<const FileDescriptor*>* ordered) {
1371 // First generate an ordering of all reachable files (including dependencies)
1372 // with depth-first search. This mimics the behavior of --include_imports,
1373 // which is what the old codegen used.
1374 ordered->clear();
1375 set<const FileDescriptor*> seen;
1376 set<const FileDescriptor*> input_set;
1377 for (int i = 0; i < input.size(); i++) {
1378 DepthFirstSearch(input[i], ordered, &seen);
1379 input_set.insert(input[i]);
1380 }
1381
1382 // Now remove the entries that are not actually in our input list.
1383 ordered->erase(
1384 std::remove_if(ordered->begin(), ordered->end(), NotInSet(input_set)),
1385 ordered->end());
1386 }
1387
1388 // If we're generating code in file-per-type mode, avoid overwriting files
1389 // by choosing the last descriptor that writes each filename and permitting
1390 // only those to generate code.
1391
GenerateJspbAllowedSet(const GeneratorOptions & options,const vector<const FileDescriptor * > & files,set<const void * > * allowed_set,string * error)1392 bool GenerateJspbAllowedSet(const GeneratorOptions& options,
1393 const vector<const FileDescriptor*>& files,
1394 set<const void*>* allowed_set,
1395 string* error) {
1396 vector<const FileDescriptor*> files_ordered;
1397 GenerateJspbFileOrder(files, &files_ordered);
1398
1399 // Choose the last descriptor for each filename.
1400 FileDeduplicator dedup(options);
1401 for (int i = 0; i < files_ordered.size(); i++) {
1402 for (int j = 0; j < files_ordered[i]->message_type_count(); j++) {
1403 const Descriptor* desc = files_ordered[i]->message_type(j);
1404 if (!dedup.AddFile(GetMessageFileName(options, desc), desc, error)) {
1405 return false;
1406 }
1407 }
1408 for (int j = 0; j < files_ordered[i]->enum_type_count(); j++) {
1409 const EnumDescriptor* desc = files_ordered[i]->enum_type(j);
1410 if (!dedup.AddFile(GetEnumFileName(options, desc), desc, error)) {
1411 return false;
1412 }
1413 }
1414
1415 // Pull out all free-floating extensions and generate files for those too.
1416 bool has_extension = false;
1417
1418 for (int j = 0; j < files_ordered[i]->extension_count(); j++) {
1419 if (ShouldGenerateExtension(files_ordered[i]->extension(j))) {
1420 has_extension = true;
1421 }
1422 }
1423
1424 if (has_extension) {
1425 if (!dedup.AddFile(GetExtensionFileName(options, files_ordered[i]),
1426 files_ordered[i], error)) {
1427 return false;
1428 }
1429 }
1430 }
1431
1432 dedup.GetAllowedSet(allowed_set);
1433
1434 return true;
1435 }
1436
1437 } // anonymous namespace
1438
GenerateHeader(const GeneratorOptions & options,io::Printer * printer) const1439 void Generator::GenerateHeader(const GeneratorOptions& options,
1440 io::Printer* printer) const {
1441 printer->Print("/**\n"
1442 " * @fileoverview\n"
1443 " * @enhanceable\n"
1444 " * @public\n"
1445 " */\n"
1446 "// GENERATED CODE -- DO NOT EDIT!\n"
1447 "\n");
1448 }
1449
FindProvidesForFile(const GeneratorOptions & options,io::Printer * printer,const FileDescriptor * file,std::set<string> * provided) const1450 void Generator::FindProvidesForFile(const GeneratorOptions& options,
1451 io::Printer* printer,
1452 const FileDescriptor* file,
1453 std::set<string>* provided) const {
1454 for (int i = 0; i < file->message_type_count(); i++) {
1455 FindProvidesForMessage(options, printer, file->message_type(i), provided);
1456 }
1457 for (int i = 0; i < file->enum_type_count(); i++) {
1458 FindProvidesForEnum(options, printer, file->enum_type(i), provided);
1459 }
1460 }
1461
FindProvides(const GeneratorOptions & options,io::Printer * printer,const vector<const FileDescriptor * > & files,std::set<string> * provided) const1462 void Generator::FindProvides(const GeneratorOptions& options,
1463 io::Printer* printer,
1464 const vector<const FileDescriptor*>& files,
1465 std::set<string>* provided) const {
1466 for (int i = 0; i < files.size(); i++) {
1467 FindProvidesForFile(options, printer, files[i], provided);
1468 }
1469
1470 printer->Print("\n");
1471 }
1472
FindProvidesForMessage(const GeneratorOptions & options,io::Printer * printer,const Descriptor * desc,std::set<string> * provided) const1473 void Generator::FindProvidesForMessage(
1474 const GeneratorOptions& options,
1475 io::Printer* printer,
1476 const Descriptor* desc,
1477 std::set<string>* provided) const {
1478 if (IgnoreMessage(options, desc)) {
1479 return;
1480 }
1481
1482 string name = GetPath(options, desc);
1483 provided->insert(name);
1484
1485 for (int i = 0; i < desc->enum_type_count(); i++) {
1486 FindProvidesForEnum(options, printer, desc->enum_type(i),
1487 provided);
1488 }
1489 for (int i = 0; i < desc->nested_type_count(); i++) {
1490 FindProvidesForMessage(options, printer, desc->nested_type(i),
1491 provided);
1492 }
1493 }
1494
FindProvidesForEnum(const GeneratorOptions & options,io::Printer * printer,const EnumDescriptor * enumdesc,std::set<string> * provided) const1495 void Generator::FindProvidesForEnum(const GeneratorOptions& options,
1496 io::Printer* printer,
1497 const EnumDescriptor* enumdesc,
1498 std::set<string>* provided) const {
1499 string name = GetPath(options, enumdesc);
1500 provided->insert(name);
1501 }
1502
FindProvidesForFields(const GeneratorOptions & options,io::Printer * printer,const vector<const FieldDescriptor * > & fields,std::set<string> * provided) const1503 void Generator::FindProvidesForFields(
1504 const GeneratorOptions& options,
1505 io::Printer* printer,
1506 const vector<const FieldDescriptor*>& fields,
1507 std::set<string>* provided) const {
1508 for (int i = 0; i < fields.size(); i++) {
1509 const FieldDescriptor* field = fields[i];
1510
1511 if (IgnoreField(field)) {
1512 continue;
1513 }
1514
1515 string name =
1516 GetPath(options, field->file()) + "." +
1517 JSObjectFieldName(options, field);
1518 provided->insert(name);
1519 }
1520 }
1521
GenerateProvides(const GeneratorOptions & options,io::Printer * printer,std::set<string> * provided) const1522 void Generator::GenerateProvides(const GeneratorOptions& options,
1523 io::Printer* printer,
1524 std::set<string>* provided) const {
1525 for (std::set<string>::iterator it = provided->begin();
1526 it != provided->end(); ++it) {
1527 printer->Print("goog.provide('$name$');\n",
1528 "name", *it);
1529 }
1530 }
1531
GenerateRequiresForMessage(const GeneratorOptions & options,io::Printer * printer,const Descriptor * desc,std::set<string> * provided) const1532 void Generator::GenerateRequiresForMessage(const GeneratorOptions& options,
1533 io::Printer* printer,
1534 const Descriptor* desc,
1535 std::set<string>* provided) const {
1536 std::set<string> required;
1537 std::set<string> forwards;
1538 bool have_message = false;
1539 FindRequiresForMessage(options, desc,
1540 &required, &forwards, &have_message);
1541
1542 GenerateRequiresImpl(options, printer, &required, &forwards, provided,
1543 /* require_jspb = */ have_message,
1544 /* require_extension = */ HasExtensions(desc));
1545 }
1546
GenerateRequiresForLibrary(const GeneratorOptions & options,io::Printer * printer,const vector<const FileDescriptor * > & files,std::set<string> * provided) const1547 void Generator::GenerateRequiresForLibrary(
1548 const GeneratorOptions& options, io::Printer* printer,
1549 const vector<const FileDescriptor*>& files,
1550 std::set<string>* provided) const {
1551 GOOGLE_CHECK_EQ(options.import_style, GeneratorOptions::IMPORT_CLOSURE);
1552 // For Closure imports we need to import every message type individually.
1553 std::set<string> required;
1554 std::set<string> forwards;
1555 bool have_extensions = false;
1556 bool have_message = false;
1557
1558 for (int i = 0; i < files.size(); i++) {
1559 for (int j = 0; j < files[i]->message_type_count(); j++) {
1560 const Descriptor* desc = files[i]->message_type(j);
1561 if (!IgnoreMessage(options, desc)) {
1562 FindRequiresForMessage(options, desc, &required, &forwards,
1563 &have_message);
1564 }
1565 }
1566
1567 if (!have_extensions && HasExtensions(files[i])) {
1568 have_extensions = true;
1569 }
1570
1571 for (int j = 0; j < files[i]->extension_count(); j++) {
1572 const FieldDescriptor* extension = files[i]->extension(j);
1573 if (IgnoreField(extension)) {
1574 continue;
1575 }
1576 if (extension->containing_type()->full_name() !=
1577 "google.protobuf.bridge.MessageSet") {
1578 required.insert(GetPath(options, extension->containing_type()));
1579 }
1580 FindRequiresForField(options, extension, &required, &forwards);
1581 have_extensions = true;
1582 }
1583 }
1584
1585 GenerateRequiresImpl(options, printer, &required, &forwards, provided,
1586 /* require_jspb = */ have_message,
1587 /* require_extension = */ have_extensions);
1588 }
1589
GenerateRequiresForExtensions(const GeneratorOptions & options,io::Printer * printer,const vector<const FieldDescriptor * > & fields,std::set<string> * provided) const1590 void Generator::GenerateRequiresForExtensions(
1591 const GeneratorOptions& options, io::Printer* printer,
1592 const vector<const FieldDescriptor*>& fields,
1593 std::set<string>* provided) const {
1594 std::set<string> required;
1595 std::set<string> forwards;
1596 for (int i = 0; i < fields.size(); i++) {
1597 const FieldDescriptor* field = fields[i];
1598 if (IgnoreField(field)) {
1599 continue;
1600 }
1601 FindRequiresForExtension(options, field, &required, &forwards);
1602 }
1603
1604 GenerateRequiresImpl(options, printer, &required, &forwards, provided,
1605 /* require_jspb = */ false,
1606 /* require_extension = */ fields.size() > 0);
1607 }
1608
GenerateRequiresImpl(const GeneratorOptions & options,io::Printer * printer,std::set<string> * required,std::set<string> * forwards,std::set<string> * provided,bool require_jspb,bool require_extension) const1609 void Generator::GenerateRequiresImpl(const GeneratorOptions& options,
1610 io::Printer* printer,
1611 std::set<string>* required,
1612 std::set<string>* forwards,
1613 std::set<string>* provided,
1614 bool require_jspb,
1615 bool require_extension) const {
1616 if (require_jspb) {
1617 printer->Print(
1618 "goog.require('jspb.Message');\n");
1619 if (options.binary) {
1620 printer->Print(
1621 "goog.require('jspb.BinaryReader');\n"
1622 "goog.require('jspb.BinaryWriter');\n");
1623 }
1624 }
1625 if (require_extension) {
1626 printer->Print(
1627 "goog.require('jspb.ExtensionFieldInfo');\n");
1628 }
1629
1630 std::set<string>::iterator it;
1631 for (it = required->begin(); it != required->end(); ++it) {
1632 if (provided->find(*it) != provided->end()) {
1633 continue;
1634 }
1635 printer->Print("goog.require('$name$');\n",
1636 "name", *it);
1637 }
1638
1639 printer->Print("\n");
1640
1641 for (it = forwards->begin(); it != forwards->end(); ++it) {
1642 if (provided->find(*it) != provided->end()) {
1643 continue;
1644 }
1645 printer->Print("goog.forwardDeclare('$name$');\n",
1646 "name", *it);
1647 }
1648 }
1649
NamespaceOnly(const Descriptor * desc)1650 bool NamespaceOnly(const Descriptor* desc) {
1651 return false;
1652 }
1653
FindRequiresForMessage(const GeneratorOptions & options,const Descriptor * desc,std::set<string> * required,std::set<string> * forwards,bool * have_message) const1654 void Generator::FindRequiresForMessage(
1655 const GeneratorOptions& options,
1656 const Descriptor* desc,
1657 std::set<string>* required,
1658 std::set<string>* forwards,
1659 bool* have_message) const {
1660
1661
1662 if (!NamespaceOnly(desc)) {
1663 *have_message = true;
1664 for (int i = 0; i < desc->field_count(); i++) {
1665 const FieldDescriptor* field = desc->field(i);
1666 if (IgnoreField(field)) {
1667 continue;
1668 }
1669 FindRequiresForField(options, field, required, forwards);
1670 }
1671 }
1672
1673 for (int i = 0; i < desc->extension_count(); i++) {
1674 const FieldDescriptor* field = desc->extension(i);
1675 if (IgnoreField(field)) {
1676 continue;
1677 }
1678 FindRequiresForExtension(options, field, required, forwards);
1679 }
1680
1681 for (int i = 0; i < desc->nested_type_count(); i++) {
1682 FindRequiresForMessage(options, desc->nested_type(i), required, forwards,
1683 have_message);
1684 }
1685 }
1686
FindRequiresForField(const GeneratorOptions & options,const FieldDescriptor * field,std::set<string> * required,std::set<string> * forwards) const1687 void Generator::FindRequiresForField(const GeneratorOptions& options,
1688 const FieldDescriptor* field,
1689 std::set<string>* required,
1690 std::set<string>* forwards) const {
1691 if (field->cpp_type() == FieldDescriptor::CPPTYPE_ENUM &&
1692 // N.B.: file-level extensions with enum type do *not* create
1693 // dependencies, as per original codegen.
1694 !(field->is_extension() && field->extension_scope() == NULL)) {
1695 if (options.add_require_for_enums) {
1696 required->insert(GetPath(options, field->enum_type()));
1697 } else {
1698 forwards->insert(GetPath(options, field->enum_type()));
1699 }
1700 } else if (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) {
1701 if (!IgnoreMessage(options, field->message_type())) {
1702 required->insert(GetPath(options, field->message_type()));
1703 }
1704 }
1705 }
1706
FindRequiresForExtension(const GeneratorOptions & options,const FieldDescriptor * field,std::set<string> * required,std::set<string> * forwards) const1707 void Generator::FindRequiresForExtension(const GeneratorOptions& options,
1708 const FieldDescriptor* field,
1709 std::set<string>* required,
1710 std::set<string>* forwards) const {
1711 if (field->containing_type()->full_name() != "google.protobuf.bridge.MessageSet") {
1712 required->insert(GetPath(options, field->containing_type()));
1713 }
1714 FindRequiresForField(options, field, required, forwards);
1715 }
1716
GenerateTestOnly(const GeneratorOptions & options,io::Printer * printer) const1717 void Generator::GenerateTestOnly(const GeneratorOptions& options,
1718 io::Printer* printer) const {
1719 if (options.testonly) {
1720 printer->Print("goog.setTestOnly();\n\n");
1721 }
1722 printer->Print("\n");
1723 }
1724
GenerateClassesAndEnums(const GeneratorOptions & options,io::Printer * printer,const FileDescriptor * file) const1725 void Generator::GenerateClassesAndEnums(const GeneratorOptions& options,
1726 io::Printer* printer,
1727 const FileDescriptor* file) const {
1728 for (int i = 0; i < file->message_type_count(); i++) {
1729 GenerateClass(options, printer, file->message_type(i));
1730 }
1731 for (int i = 0; i < file->enum_type_count(); i++) {
1732 GenerateEnum(options, printer, file->enum_type(i));
1733 }
1734 }
1735
GenerateClass(const GeneratorOptions & options,io::Printer * printer,const Descriptor * desc) const1736 void Generator::GenerateClass(const GeneratorOptions& options,
1737 io::Printer* printer,
1738 const Descriptor* desc) const {
1739 if (IgnoreMessage(options, desc)) {
1740 return;
1741 }
1742
1743 if (!NamespaceOnly(desc)) {
1744 printer->Print("\n");
1745 GenerateClassConstructor(options, printer, desc);
1746 GenerateClassFieldInfo(options, printer, desc);
1747
1748
1749 GenerateClassToObject(options, printer, desc);
1750 if (options.binary) {
1751 // These must come *before* the extension-field info generation in
1752 // GenerateClassRegistration so that references to the binary
1753 // serialization/deserialization functions may be placed in the extension
1754 // objects.
1755 GenerateClassDeserializeBinary(options, printer, desc);
1756 GenerateClassSerializeBinary(options, printer, desc);
1757 }
1758 GenerateClassClone(options, printer, desc);
1759 GenerateClassRegistration(options, printer, desc);
1760 GenerateClassFields(options, printer, desc);
1761 if (IsExtendable(desc) && desc->full_name() != "google.protobuf.bridge.MessageSet") {
1762 GenerateClassExtensionFieldInfo(options, printer, desc);
1763 }
1764
1765 if (options.import_style != GeneratorOptions:: IMPORT_CLOSURE) {
1766 for (int i = 0; i < desc->extension_count(); i++) {
1767 GenerateExtension(options, printer, desc->extension(i));
1768 }
1769 }
1770 }
1771
1772 // Recurse on nested types.
1773 for (int i = 0; i < desc->enum_type_count(); i++) {
1774 GenerateEnum(options, printer, desc->enum_type(i));
1775 }
1776 for (int i = 0; i < desc->nested_type_count(); i++) {
1777 GenerateClass(options, printer, desc->nested_type(i));
1778 }
1779 }
1780
GenerateClassConstructor(const GeneratorOptions & options,io::Printer * printer,const Descriptor * desc) const1781 void Generator::GenerateClassConstructor(const GeneratorOptions& options,
1782 io::Printer* printer,
1783 const Descriptor* desc) const {
1784 printer->Print(
1785 "/**\n"
1786 " * Generated by JsPbCodeGenerator.\n"
1787 " * @param {Array=} opt_data Optional initial data array, typically "
1788 "from a\n"
1789 " * server response, or constructed directly in Javascript. The array "
1790 "is used\n"
1791 " * in place and becomes part of the constructed object. It is not "
1792 "cloned.\n"
1793 " * If no data is provided, the constructed object will be empty, but "
1794 "still\n"
1795 " * valid.\n"
1796 " * @extends {jspb.Message}\n"
1797 " * @constructor\n"
1798 " */\n"
1799 "$classname$ = function(opt_data) {\n",
1800 "classname", GetPath(options, desc));
1801 string message_id = GetMessageId(desc);
1802 printer->Print(
1803 " jspb.Message.initialize(this, opt_data, $messageId$, $pivot$, "
1804 "$rptfields$, $oneoffields$);\n",
1805 "messageId", !message_id.empty() ?
1806 ("'" + message_id + "'") :
1807 (IsResponse(desc) ? "''" : "0"),
1808 "pivot", GetPivot(desc),
1809 "rptfields", RepeatedFieldsArrayName(options, desc),
1810 "oneoffields", OneofFieldsArrayName(options, desc));
1811 printer->Print(
1812 "};\n"
1813 "goog.inherits($classname$, jspb.Message);\n"
1814 "if (goog.DEBUG && !COMPILED) {\n"
1815 " $classname$.displayName = '$classname$';\n"
1816 "}\n",
1817 "classname", GetPath(options, desc));
1818 }
1819
GenerateClassFieldInfo(const GeneratorOptions & options,io::Printer * printer,const Descriptor * desc) const1820 void Generator::GenerateClassFieldInfo(const GeneratorOptions& options,
1821 io::Printer* printer,
1822 const Descriptor* desc) const {
1823 if (HasRepeatedFields(desc)) {
1824 printer->Print(
1825 "/**\n"
1826 " * List of repeated fields within this message type.\n"
1827 " * @private {!Array<number>}\n"
1828 " * @const\n"
1829 " */\n"
1830 "$classname$$rptfieldarray$ = $rptfields$;\n"
1831 "\n",
1832 "classname", GetPath(options, desc),
1833 "rptfieldarray", kRepeatedFieldArrayName,
1834 "rptfields", RepeatedFieldNumberList(desc));
1835 }
1836
1837 if (HasOneofFields(desc)) {
1838 printer->Print(
1839 "/**\n"
1840 " * Oneof group definitions for this message. Each group defines the "
1841 "field\n"
1842 " * numbers belonging to that group. When of these fields' value is "
1843 "set, all\n"
1844 " * other fields in the group are cleared. During deserialization, if "
1845 "multiple\n"
1846 " * fields are encountered for a group, only the last value seen will "
1847 "be kept.\n"
1848 " * @private {!Array<!Array<number>>}\n"
1849 " * @const\n"
1850 " */\n"
1851 "$classname$$oneofgrouparray$ = $oneofgroups$;\n"
1852 "\n",
1853 "classname", GetPath(options, desc),
1854 "oneofgrouparray", kOneofGroupArrayName,
1855 "oneofgroups", OneofGroupList(desc));
1856
1857 for (int i = 0; i < desc->oneof_decl_count(); i++) {
1858 if (IgnoreOneof(desc->oneof_decl(i))) {
1859 continue;
1860 }
1861 GenerateOneofCaseDefinition(options, printer, desc->oneof_decl(i));
1862 }
1863 }
1864 }
1865
GenerateClassXid(const GeneratorOptions & options,io::Printer * printer,const Descriptor * desc) const1866 void Generator::GenerateClassXid(const GeneratorOptions& options,
1867 io::Printer* printer,
1868 const Descriptor* desc) const {
1869 printer->Print(
1870 "\n"
1871 "\n"
1872 "$class$.prototype.messageXid = xid('$class$');\n",
1873 "class", GetPath(options, desc));
1874 }
1875
GenerateOneofCaseDefinition(const GeneratorOptions & options,io::Printer * printer,const OneofDescriptor * oneof) const1876 void Generator::GenerateOneofCaseDefinition(
1877 const GeneratorOptions& options,
1878 io::Printer* printer,
1879 const OneofDescriptor* oneof) const {
1880 printer->Print(
1881 "/**\n"
1882 " * @enum {number}\n"
1883 " */\n"
1884 "$classname$.$oneof$Case = {\n"
1885 " $upcase$_NOT_SET: 0",
1886 "classname", GetPath(options, oneof->containing_type()),
1887 "oneof", JSOneofName(oneof),
1888 "upcase", ToEnumCase(oneof->name()));
1889
1890 for (int i = 0; i < oneof->field_count(); i++) {
1891 if (IgnoreField(oneof->field(i))) {
1892 continue;
1893 }
1894
1895 printer->Print(
1896 ",\n"
1897 " $upcase$: $number$",
1898 "upcase", ToEnumCase(oneof->field(i)->name()),
1899 "number", JSFieldIndex(oneof->field(i)));
1900 }
1901
1902 printer->Print(
1903 "\n"
1904 "};\n"
1905 "\n"
1906 "/**\n"
1907 " * @return {$class$.$oneof$Case}\n"
1908 " */\n"
1909 "$class$.prototype.get$oneof$Case = function() {\n"
1910 " return /** @type {$class$.$oneof$Case} */(jspb.Message."
1911 "computeOneofCase(this, $class$.oneofGroups_[$oneofindex$]));\n"
1912 "};\n"
1913 "\n",
1914 "class", GetPath(options, oneof->containing_type()),
1915 "oneof", JSOneofName(oneof),
1916 "oneofindex", JSOneofIndex(oneof));
1917 }
1918
GenerateClassToObject(const GeneratorOptions & options,io::Printer * printer,const Descriptor * desc) const1919 void Generator::GenerateClassToObject(const GeneratorOptions& options,
1920 io::Printer* printer,
1921 const Descriptor* desc) const {
1922 printer->Print(
1923 "\n"
1924 "\n"
1925 "if (jspb.Message.GENERATE_TO_OBJECT) {\n"
1926 "/**\n"
1927 " * Creates an object representation of this proto suitable for use in "
1928 "Soy templates.\n"
1929 " * Field names that are reserved in JavaScript and will be renamed to "
1930 "pb_name.\n"
1931 " * To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.\n"
1932 " * For the list of reserved names please see:\n"
1933 " * com.google.apps.jspb.JsClassTemplate.JS_RESERVED_WORDS.\n"
1934 " * @param {boolean=} opt_includeInstance Whether to include the JSPB "
1935 "instance\n"
1936 " * for transitional soy proto support: http://goto/soy-param-"
1937 "migration\n"
1938 " * @return {!Object}\n"
1939 " */\n"
1940 "$classname$.prototype.toObject = function(opt_includeInstance) {\n"
1941 " return $classname$.toObject(opt_includeInstance, this);\n"
1942 "};\n"
1943 "\n"
1944 "\n"
1945 "/**\n"
1946 " * Static version of the {@see toObject} method.\n"
1947 " * @param {boolean|undefined} includeInstance Whether to include the "
1948 "JSPB\n"
1949 " * instance for transitional soy proto support:\n"
1950 " * http://goto/soy-param-migration\n"
1951 " * @param {!$classname$} msg The msg instance to transform.\n"
1952 " * @return {!Object}\n"
1953 " */\n"
1954 "$classname$.toObject = function(includeInstance, msg) {\n"
1955 " var f, obj = {",
1956 "classname", GetPath(options, desc));
1957
1958 bool first = true;
1959 for (int i = 0; i < desc->field_count(); i++) {
1960 const FieldDescriptor* field = desc->field(i);
1961 if (IgnoreField(field)) {
1962 continue;
1963 }
1964
1965 if (!first) {
1966 printer->Print(",\n ");
1967 } else {
1968 printer->Print("\n ");
1969 first = false;
1970 }
1971
1972 GenerateClassFieldToObject(options, printer, field);
1973 }
1974
1975 if (!first) {
1976 printer->Print("\n };\n\n");
1977 } else {
1978 printer->Print("\n\n };\n\n");
1979 }
1980
1981 if (IsExtendable(desc)) {
1982 printer->Print(
1983 " jspb.Message.toObjectExtension(/** @type {!jspb.Message} */ (msg), "
1984 "obj,\n"
1985 " $extObject$, $class$.prototype.getExtension,\n"
1986 " includeInstance);\n",
1987 "extObject", JSExtensionsObjectName(options, desc->file(), desc),
1988 "class", GetPath(options, desc));
1989 }
1990
1991 printer->Print(
1992 " if (includeInstance) {\n"
1993 " obj.$$jspbMessageInstance = msg;\n"
1994 " }\n"
1995 " return obj;\n"
1996 "};\n"
1997 "}\n"
1998 "\n"
1999 "\n",
2000 "classname", GetPath(options, desc));
2001 }
2002
GenerateClassFieldToObject(const GeneratorOptions & options,io::Printer * printer,const FieldDescriptor * field) const2003 void Generator::GenerateClassFieldToObject(const GeneratorOptions& options,
2004 io::Printer* printer,
2005 const FieldDescriptor* field) const {
2006 printer->Print("$fieldname$: ",
2007 "fieldname", JSObjectFieldName(options, field));
2008
2009 if (field->is_map()) {
2010 printer->Print("(f = msg.get$name$(true)) ? f.toArray() : []",
2011 "name", JSGetterName(options, field));
2012 } else if (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) {
2013 // Message field.
2014 if (field->is_repeated()) {
2015 {
2016 printer->Print("jspb.Message.toObjectList(msg.get$getter$(),\n"
2017 " $type$.toObject, includeInstance)",
2018 "getter", JSGetterName(options, field),
2019 "type", SubmessageTypeRef(options, field));
2020 }
2021 } else {
2022 printer->Print("(f = msg.get$getter$()) && "
2023 "$type$.toObject(includeInstance, f)",
2024 "getter", JSGetterName(options, field),
2025 "type", SubmessageTypeRef(options, field));
2026 }
2027 } else {
2028 // Simple field (singular or repeated).
2029 if ((!HasFieldPresence(field) && !field->is_repeated()) ||
2030 field->type() == FieldDescriptor::TYPE_BYTES) {
2031 // Delegate to the generated get<field>() method in order not to duplicate
2032 // the proto3-field-default-value or byte-coercion logic here.
2033 printer->Print("msg.get$getter$()",
2034 "getter", JSGetterName(options, field, BYTES_B64));
2035 } else {
2036 if (field->has_default_value()) {
2037 printer->Print("!msg.has$name$() ? $defaultValue$ : ",
2038 "name", JSGetterName(options, field),
2039 "defaultValue", JSFieldDefault(field));
2040 }
2041 if (field->cpp_type() == FieldDescriptor::CPPTYPE_FLOAT ||
2042 field->cpp_type() == FieldDescriptor::CPPTYPE_DOUBLE) {
2043 if (field->is_repeated()) {
2044 printer->Print("jspb.Message.getRepeatedFloatingPointField("
2045 "msg, $index$)",
2046 "index", JSFieldIndex(field));
2047 } else if (field->is_optional() && !field->has_default_value()) {
2048 printer->Print("jspb.Message.getOptionalFloatingPointField("
2049 "msg, $index$)",
2050 "index", JSFieldIndex(field));
2051 } else {
2052 // Convert "NaN" to NaN.
2053 printer->Print("+jspb.Message.getField(msg, $index$)",
2054 "index", JSFieldIndex(field));
2055 }
2056 } else {
2057 printer->Print("jspb.Message.getField(msg, $index$)",
2058 "index", JSFieldIndex(field));
2059 }
2060 }
2061 }
2062 }
2063
GenerateClassFromObject(const GeneratorOptions & options,io::Printer * printer,const Descriptor * desc) const2064 void Generator::GenerateClassFromObject(const GeneratorOptions& options,
2065 io::Printer* printer,
2066 const Descriptor* desc) const {
2067 printer->Print(
2068 "if (jspb.Message.GENERATE_FROM_OBJECT) {\n"
2069 "/**\n"
2070 " * Loads data from an object into a new instance of this proto.\n"
2071 " * @param {!Object} obj The object representation of this proto to\n"
2072 " * load the data from.\n"
2073 " * @return {!$classname$}\n"
2074 " */\n"
2075 "$classname$.fromObject = function(obj) {\n"
2076 " var f, msg = new $classname$();\n",
2077 "classname", GetPath(options, desc));
2078
2079 for (int i = 0; i < desc->field_count(); i++) {
2080 const FieldDescriptor* field = desc->field(i);
2081 GenerateClassFieldFromObject(options, printer, field);
2082 }
2083
2084 printer->Print(
2085 " return msg;\n"
2086 "};\n"
2087 "}\n");
2088 }
2089
GenerateClassFieldFromObject(const GeneratorOptions & options,io::Printer * printer,const FieldDescriptor * field) const2090 void Generator::GenerateClassFieldFromObject(
2091 const GeneratorOptions& options,
2092 io::Printer* printer,
2093 const FieldDescriptor* field) const {
2094
2095 if (field->is_map()) {
2096 // `msg` is a newly-constructed message object that has not yet built any
2097 // map containers wrapping underlying arrays, so we can simply directly set
2098 // the array here without fear of a stale wrapper.
2099 printer->Print(
2100 " goog.isDef(obj.$name$) && "
2101 "jspb.Message.setField(msg, $index$, obj.$name$);\n",
2102 "name", JSObjectFieldName(options, field),
2103 "index", JSFieldIndex(field));
2104 } else if (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) {
2105 // Message field (singular or repeated)
2106 if (field->is_repeated()) {
2107 {
2108 printer->Print(
2109 " goog.isDef(obj.$name$) && "
2110 "jspb.Message.setRepeatedWrapperField(\n"
2111 " msg, $index$, goog.array.map(obj.$name$, function(i) {\n"
2112 " return $fieldclass$.fromObject(i);\n"
2113 " }));\n",
2114 "name", JSObjectFieldName(options, field),
2115 "index", JSFieldIndex(field),
2116 "fieldclass", SubmessageTypeRef(options, field));
2117 }
2118 } else {
2119 printer->Print(
2120 " goog.isDef(obj.$name$) && jspb.Message.setWrapperField(\n"
2121 " msg, $index$, $fieldclass$.fromObject(obj.$name$));\n",
2122 "name", JSObjectFieldName(options, field),
2123 "index", JSFieldIndex(field),
2124 "fieldclass", SubmessageTypeRef(options, field));
2125 }
2126 } else {
2127 // Simple (primitive) field.
2128 printer->Print(
2129 " goog.isDef(obj.$name$) && jspb.Message.setField(msg, $index$, "
2130 "obj.$name$);\n",
2131 "name", JSObjectFieldName(options, field),
2132 "index", JSFieldIndex(field));
2133 }
2134 }
2135
GenerateClassClone(const GeneratorOptions & options,io::Printer * printer,const Descriptor * desc) const2136 void Generator::GenerateClassClone(const GeneratorOptions& options,
2137 io::Printer* printer,
2138 const Descriptor* desc) const {
2139 printer->Print(
2140 "/**\n"
2141 " * Creates a deep clone of this proto. No data is shared with the "
2142 "original.\n"
2143 " * @return {!$name$} The clone.\n"
2144 " */\n"
2145 "$name$.prototype.cloneMessage = function() {\n"
2146 " return /** @type {!$name$} */ (jspb.Message.cloneMessage(this));\n"
2147 "};\n\n\n",
2148 "name", GetPath(options, desc));
2149 }
2150
GenerateClassRegistration(const GeneratorOptions & options,io::Printer * printer,const Descriptor * desc) const2151 void Generator::GenerateClassRegistration(const GeneratorOptions& options,
2152 io::Printer* printer,
2153 const Descriptor* desc) const {
2154 // Register any extensions defined inside this message type.
2155 for (int i = 0; i < desc->extension_count(); i++) {
2156 const FieldDescriptor* extension = desc->extension(i);
2157 if (ShouldGenerateExtension(extension)) {
2158 GenerateExtension(options, printer, extension);
2159 }
2160 }
2161
2162 }
2163
GenerateClassFields(const GeneratorOptions & options,io::Printer * printer,const Descriptor * desc) const2164 void Generator::GenerateClassFields(const GeneratorOptions& options,
2165 io::Printer* printer,
2166 const Descriptor* desc) const {
2167 for (int i = 0; i < desc->field_count(); i++) {
2168 if (!IgnoreField(desc->field(i))) {
2169 GenerateClassField(options, printer, desc->field(i));
2170 }
2171 }
2172 }
2173
GenerateBytesWrapper(const GeneratorOptions & options,io::Printer * printer,const FieldDescriptor * field,BytesMode bytes_mode)2174 void GenerateBytesWrapper(const GeneratorOptions& options,
2175 io::Printer* printer,
2176 const FieldDescriptor* field,
2177 BytesMode bytes_mode) {
2178 string type =
2179 JSFieldTypeAnnotation(options, field,
2180 /* force_optional = */ false,
2181 /* force_present = */ !HasFieldPresence(field),
2182 /* singular_if_not_packed = */ false,
2183 bytes_mode);
2184 printer->Print(
2185 "/**\n"
2186 " * $fielddef$\n"
2187 "$comment$"
2188 " * This is a type-conversion wrapper around `get$defname$()`\n"
2189 " * @return {$type$}\n"
2190 " */\n"
2191 "$class$.prototype.get$name$ = function() {\n"
2192 " return /** @type {$type$} */ (jspb.Message.bytes$list$As$suffix$(\n"
2193 " this.get$defname$()));\n"
2194 "};\n"
2195 "\n"
2196 "\n",
2197 "fielddef", FieldDefinition(options, field),
2198 "comment", FieldComments(field, bytes_mode),
2199 "type", type,
2200 "class", GetPath(options, field->containing_type()),
2201 "name", JSGetterName(options, field, bytes_mode),
2202 "list", field->is_repeated() ? "List" : "",
2203 "suffix", JSByteGetterSuffix(bytes_mode),
2204 "defname", JSGetterName(options, field, BYTES_DEFAULT));
2205 }
2206
2207
GenerateClassField(const GeneratorOptions & options,io::Printer * printer,const FieldDescriptor * field) const2208 void Generator::GenerateClassField(const GeneratorOptions& options,
2209 io::Printer* printer,
2210 const FieldDescriptor* field) const {
2211 if (field->is_map()) {
2212 const FieldDescriptor* key_field = MapFieldKey(field);
2213 const FieldDescriptor* value_field = MapFieldValue(field);
2214 // Map field: special handling to instantiate the map object on demand.
2215 string key_type =
2216 JSFieldTypeAnnotation(
2217 options, key_field,
2218 /* force_optional = */ false,
2219 /* force_present = */ true,
2220 /* singular_if_not_packed = */ false);
2221 string value_type =
2222 JSFieldTypeAnnotation(
2223 options, value_field,
2224 /* force_optional = */ false,
2225 /* force_present = */ true,
2226 /* singular_if_not_packed = */ false);
2227
2228 printer->Print(
2229 "/**\n"
2230 " * $fielddef$\n"
2231 " * @param {boolean=} opt_noLazyCreate Do not create the map if\n"
2232 " * empty, instead returning `undefined`\n"
2233 " * @return {!jspb.Map<$keytype$,$valuetype$>}\n"
2234 " */\n",
2235 "fielddef", FieldDefinition(options, field),
2236 "keytype", key_type,
2237 "valuetype", value_type);
2238 printer->Print(
2239 "$class$.prototype.get$name$ = function(opt_noLazyCreate) {\n"
2240 " return /** @type {!jspb.Map<$keytype$,$valuetype$>} */ (\n",
2241 "class", GetPath(options, field->containing_type()),
2242 "name", JSGetterName(options, field),
2243 "keytype", key_type,
2244 "valuetype", value_type);
2245 printer->Print(
2246 " jspb.Message.getMapField(this, $index$, opt_noLazyCreate",
2247 "index", JSFieldIndex(field));
2248
2249 if (value_field->type() == FieldDescriptor::TYPE_MESSAGE) {
2250 printer->Print(",\n"
2251 " $messageType$",
2252 "messageType", GetPath(options, value_field->message_type()));
2253 } else if (options.binary) {
2254 printer->Print(",\n"
2255 " null");
2256 }
2257
2258 printer->Print(
2259 "));\n");
2260
2261 printer->Print(
2262 "};\n"
2263 "\n"
2264 "\n");
2265 } else if (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) {
2266 // Message field: special handling in order to wrap the underlying data
2267 // array with a message object.
2268
2269 printer->Print(
2270 "/**\n"
2271 " * $fielddef$\n"
2272 "$comment$"
2273 " * @return {$type$}\n"
2274 " */\n",
2275 "fielddef", FieldDefinition(options, field),
2276 "comment", FieldComments(field, BYTES_DEFAULT),
2277 "type", JSFieldTypeAnnotation(options, field,
2278 /* force_optional = */ false,
2279 /* force_present = */ false,
2280 /* singular_if_not_packed = */ false));
2281 printer->Print(
2282 "$class$.prototype.get$name$ = function() {\n"
2283 " return /** @type{$type$} */ (\n"
2284 " jspb.Message.get$rpt$WrapperField(this, $wrapperclass$, "
2285 "$index$$required$));\n"
2286 "};\n"
2287 "\n"
2288 "\n",
2289 "class", GetPath(options, field->containing_type()),
2290 "name", JSGetterName(options, field),
2291 "type", JSFieldTypeAnnotation(options, field,
2292 /* force_optional = */ false,
2293 /* force_present = */ false,
2294 /* singular_if_not_packed = */ false),
2295 "rpt", (field->is_repeated() ? "Repeated" : ""),
2296 "index", JSFieldIndex(field),
2297 "wrapperclass", SubmessageTypeRef(options, field),
2298 "required", (field->label() == FieldDescriptor::LABEL_REQUIRED ?
2299 ", 1" : ""));
2300 printer->Print(
2301 "/** @param {$optionaltype$} value $returndoc$ */\n"
2302 "$class$.prototype.set$name$ = function(value) {\n"
2303 " jspb.Message.set$oneoftag$$repeatedtag$WrapperField(",
2304 "optionaltype",
2305 JSFieldTypeAnnotation(options, field,
2306 /* force_optional = */ true,
2307 /* force_present = */ false,
2308 /* singular_if_not_packed = */ false),
2309 "returndoc", JSReturnDoc(options, field),
2310 "class", GetPath(options, field->containing_type()),
2311 "name", JSGetterName(options, field),
2312 "oneoftag", (field->containing_oneof() ? "Oneof" : ""),
2313 "repeatedtag", (field->is_repeated() ? "Repeated" : ""));
2314
2315 printer->Print(
2316 "this, $index$$oneofgroup$, value);$returnvalue$\n"
2317 "};\n"
2318 "\n"
2319 "\n",
2320 "index", JSFieldIndex(field),
2321 "oneofgroup", (field->containing_oneof() ?
2322 (", " + JSOneofArray(options, field)) : ""),
2323 "returnvalue", JSReturnClause(field));
2324
2325 printer->Print(
2326 "$class$.prototype.clear$name$ = function() {\n"
2327 " this.set$name$($clearedvalue$);$returnvalue$\n"
2328 "};\n"
2329 "\n"
2330 "\n",
2331 "class", GetPath(options, field->containing_type()),
2332 "name", JSGetterName(options, field),
2333 "clearedvalue", (field->is_repeated() ? "[]" : "undefined"),
2334 "returnvalue", JSReturnClause(field));
2335
2336 } else {
2337 bool untyped =
2338 false;
2339
2340 // Simple (primitive) field, either singular or repeated.
2341
2342 // TODO(b/26173701): Always use BYTES_DEFAULT for the getter return type;
2343 // at this point we "lie" to non-binary users and tell the the return
2344 // type is always base64 string, pending a LSC to migrate to typed getters.
2345 BytesMode bytes_mode =
2346 field->type() == FieldDescriptor::TYPE_BYTES && !options.binary ?
2347 BYTES_B64 : BYTES_DEFAULT;
2348 string typed_annotation =
2349 JSFieldTypeAnnotation(options, field,
2350 /* force_optional = */ false,
2351 /* force_present = */ !HasFieldPresence(field),
2352 /* singular_if_not_packed = */ false,
2353 /* bytes_mode = */ bytes_mode);
2354 if (untyped) {
2355 printer->Print(
2356 "/**\n"
2357 " * @return {?} Raw field, untyped.\n"
2358 " */\n");
2359 } else {
2360 printer->Print(
2361 "/**\n"
2362 " * $fielddef$\n"
2363 "$comment$"
2364 " * @return {$type$}\n"
2365 " */\n",
2366 "fielddef", FieldDefinition(options, field),
2367 "comment", FieldComments(field, bytes_mode),
2368 "type", typed_annotation);
2369 }
2370
2371 printer->Print(
2372 "$class$.prototype.get$name$ = function() {\n",
2373 "class", GetPath(options, field->containing_type()),
2374 "name", JSGetterName(options, field));
2375
2376 if (untyped) {
2377 printer->Print(
2378 " return ");
2379 } else {
2380 printer->Print(
2381 " return /** @type {$type$} */ (",
2382 "type", typed_annotation);
2383 }
2384
2385 // For proto3 fields without presence, use special getters that will return
2386 // defaults when the field is unset, possibly constructing a value if
2387 // required.
2388 if (!HasFieldPresence(field) && !field->is_repeated()) {
2389 printer->Print("jspb.Message.getFieldProto3(this, $index$, $default$)",
2390 "index", JSFieldIndex(field),
2391 "default", Proto3PrimitiveFieldDefault(field));
2392 } else {
2393 if (!field->is_repeated()) {
2394 printer->Print("!this.has$name$() ? $defaultValue$ : ",
2395 "name", JSGetterName(options, field),
2396 "defaultValue", JSFieldDefault(field));
2397 }
2398 if (field->cpp_type() == FieldDescriptor::CPPTYPE_FLOAT ||
2399 field->cpp_type() == FieldDescriptor::CPPTYPE_DOUBLE) {
2400 if (field->is_repeated()) {
2401 printer->Print("jspb.Message.getRepeatedFloatingPointField("
2402 "this, $index$)",
2403 "index", JSFieldIndex(field));
2404 } else {
2405 // Convert "NaN" to NaN.
2406 printer->Print("+jspb.Message.getField(this, $index$)",
2407 "index", JSFieldIndex(field));
2408 }
2409 } else {
2410 printer->Print("jspb.Message.getField(this, $index$)",
2411 "index", JSFieldIndex(field));
2412 }
2413 }
2414
2415 if (untyped) {
2416 printer->Print(
2417 ";\n"
2418 "};\n"
2419 "\n"
2420 "\n");
2421 } else {
2422 printer->Print(
2423 ");\n"
2424 "};\n"
2425 "\n"
2426 "\n");
2427 }
2428
2429 if (field->type() == FieldDescriptor::TYPE_BYTES && !untyped) {
2430 GenerateBytesWrapper(options, printer, field, BYTES_B64);
2431 GenerateBytesWrapper(options, printer, field, BYTES_U8);
2432 }
2433
2434 if (untyped) {
2435 printer->Print(
2436 "/**\n"
2437 " * @param {*} value $returndoc$\n"
2438 " */\n",
2439 "returndoc", JSReturnDoc(options, field));
2440 } else {
2441 printer->Print(
2442 "/** @param {$optionaltype$} value $returndoc$ */\n",
2443 "optionaltype",
2444 JSFieldTypeAnnotation(options, field,
2445 /* force_optional = */ true,
2446 /* force_present = */ !HasFieldPresence(field),
2447 /* singular_if_not_packed = */ false),
2448 "returndoc", JSReturnDoc(options, field));
2449 }
2450 printer->Print(
2451 "$class$.prototype.set$name$ = function(value) {\n"
2452 " jspb.Message.set$oneoftag$Field(this, $index$",
2453 "class", GetPath(options, field->containing_type()),
2454 "name", JSGetterName(options, field),
2455 "oneoftag", (field->containing_oneof() ? "Oneof" : ""),
2456 "index", JSFieldIndex(field));
2457 printer->Print(
2458 "$oneofgroup$, $type$value$rptvalueinit$$typeclose$);$returnvalue$\n"
2459 "};\n"
2460 "\n"
2461 "\n",
2462 "type",
2463 untyped ? "/** @type{string|number|boolean|Array|undefined} */(" : "",
2464 "typeclose", untyped ? ")" : "",
2465 "oneofgroup",
2466 (field->containing_oneof() ? (", " + JSOneofArray(options, field))
2467 : ""),
2468 "returnvalue", JSReturnClause(field), "rptvalueinit",
2469 (field->is_repeated() ? " || []" : ""));
2470
2471 if (untyped) {
2472 printer->Print(
2473 "/**\n"
2474 " * Clears the value. $returndoc$\n"
2475 " */\n",
2476 "returndoc", JSReturnDoc(options, field));
2477 }
2478
2479 if (HasFieldPresence(field) || field->is_repeated()) {
2480 printer->Print(
2481 "$class$.prototype.clear$name$ = function() {\n"
2482 " jspb.Message.set$oneoftag$Field(this, $index$$oneofgroup$, ",
2483 "class", GetPath(options, field->containing_type()),
2484 "name", JSGetterName(options, field),
2485 "oneoftag", (field->containing_oneof() ? "Oneof" : ""),
2486 "oneofgroup", (field->containing_oneof() ?
2487 (", " + JSOneofArray(options, field)) : ""),
2488 "index", JSFieldIndex(field));
2489 printer->Print(
2490 "$clearedvalue$);$returnvalue$\n"
2491 "};\n"
2492 "\n"
2493 "\n",
2494 "clearedvalue", (field->is_repeated() ? "[]" : "undefined"),
2495 "returnvalue", JSReturnClause(field));
2496 }
2497 }
2498
2499 if (HasFieldPresence(field)) {
2500 printer->Print(
2501 "/**\n"
2502 " * Returns whether this field is set.\n"
2503 " * @return{!boolean}\n"
2504 " */\n"
2505 "$class$.prototype.has$name$ = function() {\n"
2506 " return jspb.Message.getField(this, $index$) != null;\n"
2507 "};\n"
2508 "\n"
2509 "\n",
2510 "class", GetPath(options, field->containing_type()),
2511 "name", JSGetterName(options, field),
2512 "index", JSFieldIndex(field));
2513 }
2514 }
2515
GenerateClassExtensionFieldInfo(const GeneratorOptions & options,io::Printer * printer,const Descriptor * desc) const2516 void Generator::GenerateClassExtensionFieldInfo(const GeneratorOptions& options,
2517 io::Printer* printer,
2518 const Descriptor* desc) const {
2519 if (IsExtendable(desc)) {
2520 printer->Print(
2521 "\n"
2522 "/**\n"
2523 " * The extensions registered with this message class. This is a "
2524 "map of\n"
2525 " * extension field number to fieldInfo object.\n"
2526 " *\n"
2527 " * For example:\n"
2528 " * { 123: {fieldIndex: 123, fieldName: {my_field_name: 0}, "
2529 "ctor: proto.example.MyMessage} }\n"
2530 " *\n"
2531 " * fieldName contains the JsCompiler renamed field name property "
2532 "so that it\n"
2533 " * works in OPTIMIZED mode.\n"
2534 " *\n"
2535 " * @type {!Object.<number, jspb.ExtensionFieldInfo>}\n"
2536 " */\n"
2537 "$class$.extensions = {};\n"
2538 "\n",
2539 "class", GetPath(options, desc));
2540
2541 if (options.binary) {
2542 printer->Print(
2543 "\n"
2544 "/**\n"
2545 " * The extensions registered with this message class. This is a "
2546 "map of\n"
2547 " * extension field number to fieldInfo object.\n"
2548 " *\n"
2549 " * For example:\n"
2550 " * { 123: {fieldIndex: 123, fieldName: {my_field_name: 0}, "
2551 "ctor: proto.example.MyMessage} }\n"
2552 " *\n"
2553 " * fieldName contains the JsCompiler renamed field name property "
2554 "so that it\n"
2555 " * works in OPTIMIZED mode.\n"
2556 " *\n"
2557 " * @type {!Object.<number, jspb.ExtensionFieldInfo>}\n"
2558 " */\n"
2559 "$class$.extensionsBinary = {};\n"
2560 "\n",
2561 "class", GetPath(options, desc));
2562 }
2563 }
2564 }
2565
2566
GenerateClassDeserializeBinary(const GeneratorOptions & options,io::Printer * printer,const Descriptor * desc) const2567 void Generator::GenerateClassDeserializeBinary(const GeneratorOptions& options,
2568 io::Printer* printer,
2569 const Descriptor* desc) const {
2570 // TODO(cfallin): Handle lazy decoding when requested by field option and/or
2571 // by default for 'bytes' fields and packed repeated fields.
2572
2573 printer->Print(
2574 "/**\n"
2575 " * Deserializes binary data (in protobuf wire format).\n"
2576 " * @param {jspb.ByteSource} bytes The bytes to deserialize.\n"
2577 " * @return {!$class$}\n"
2578 " */\n"
2579 "$class$.deserializeBinary = function(bytes) {\n"
2580 " var reader = new jspb.BinaryReader(bytes);\n"
2581 " var msg = new $class$;\n"
2582 " return $class$.deserializeBinaryFromReader(msg, reader);\n"
2583 "};\n"
2584 "\n"
2585 "\n"
2586 "/**\n"
2587 " * Deserializes binary data (in protobuf wire format) from the\n"
2588 " * given reader into the given message object.\n"
2589 " * @param {!$class$} msg The message object to deserialize into.\n"
2590 " * @param {!jspb.BinaryReader} reader The BinaryReader to use.\n"
2591 " * @return {!$class$}\n"
2592 " */\n"
2593 "$class$.deserializeBinaryFromReader = function(msg, reader) {\n"
2594 " while (reader.nextField()) {\n"
2595 " if (reader.isEndGroup()) {\n"
2596 " break;\n"
2597 " }\n"
2598 " var field = reader.getFieldNumber();\n"
2599 " switch (field) {\n",
2600 "class", GetPath(options, desc));
2601
2602 for (int i = 0; i < desc->field_count(); i++) {
2603 GenerateClassDeserializeBinaryField(options, printer, desc->field(i));
2604 }
2605
2606 printer->Print(
2607 " default:\n");
2608 if (IsExtendable(desc)) {
2609 printer->Print(
2610 " jspb.Message.readBinaryExtension(msg, reader, $extobj$Binary,\n"
2611 " $class$.prototype.getExtension,\n"
2612 " $class$.prototype.setExtension);\n"
2613 " break;\n",
2614 "extobj", JSExtensionsObjectName(options, desc->file(), desc),
2615 "class", GetPath(options, desc));
2616 } else {
2617 printer->Print(
2618 " reader.skipField();\n"
2619 " break;\n");
2620 }
2621
2622 printer->Print(
2623 " }\n"
2624 " }\n"
2625 " return msg;\n"
2626 "};\n"
2627 "\n"
2628 "\n");
2629 }
2630
GenerateClassDeserializeBinaryField(const GeneratorOptions & options,io::Printer * printer,const FieldDescriptor * field) const2631 void Generator::GenerateClassDeserializeBinaryField(
2632 const GeneratorOptions& options,
2633 io::Printer* printer,
2634 const FieldDescriptor* field) const {
2635
2636 printer->Print(" case $num$:\n",
2637 "num", SimpleItoa(field->number()));
2638
2639 if (field->is_map()) {
2640 const FieldDescriptor* key_field = MapFieldKey(field);
2641 const FieldDescriptor* value_field = MapFieldValue(field);
2642 printer->Print(
2643 " var value = msg.get$name$();\n"
2644 " reader.readMessage(value, function(message, reader) {\n",
2645 "name", JSGetterName(options, field));
2646
2647 printer->Print(" jspb.Map.deserializeBinary(message, reader, "
2648 "$keyReaderFn$, $valueReaderFn$",
2649 "keyReaderFn", JSBinaryReaderMethodName(options, key_field),
2650 "valueReaderFn", JSBinaryReaderMethodName(options, value_field));
2651
2652 if (value_field->type() == FieldDescriptor::TYPE_MESSAGE) {
2653 printer->Print(", $messageType$.deserializeBinaryFromReader",
2654 "messageType", GetPath(options, value_field->message_type()));
2655 }
2656
2657 printer->Print(");\n");
2658 printer->Print(" });\n");
2659 } else {
2660 if (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) {
2661 printer->Print(
2662 " var value = new $fieldclass$;\n"
2663 " reader.read$msgOrGroup$($grpfield$value,"
2664 "$fieldclass$.deserializeBinaryFromReader);\n",
2665 "fieldclass", SubmessageTypeRef(options, field),
2666 "msgOrGroup", (field->type() == FieldDescriptor::TYPE_GROUP) ?
2667 "Group" : "Message",
2668 "grpfield", (field->type() == FieldDescriptor::TYPE_GROUP) ?
2669 (SimpleItoa(field->number()) + ", ") : "");
2670 } else {
2671 printer->Print(
2672 " var value = /** @type {$fieldtype$} */ "
2673 "(reader.read$reader$());\n",
2674 "fieldtype", JSFieldTypeAnnotation(options, field, false, true,
2675 /* singular_if_not_packed */ true,
2676 BYTES_U8),
2677 "reader",
2678 JSBinaryReadWriteMethodName(field, /* is_writer = */ false));
2679 }
2680
2681 if (field->is_repeated() && !field->is_packed()) {
2682 // Repeated fields receive a |value| one at at a time; append to array
2683 // returned by get$name$(). Annoyingly, we have to call 'set' after
2684 // changing the array.
2685 printer->Print(" msg.get$name$().push(value);\n", "name",
2686 JSGetterName(options, field));
2687 printer->Print(" msg.set$name$(msg.get$name$());\n", "name",
2688 JSGetterName(options, field));
2689 } else {
2690 // Singular fields, and packed repeated fields, receive a |value| either
2691 // as the field's value or as the array of all the field's values; set
2692 // this as the field's value directly.
2693 printer->Print(
2694 " msg.set$name$(value);\n",
2695 "name", JSGetterName(options, field));
2696 }
2697 }
2698
2699 printer->Print(" break;\n");
2700 }
2701
GenerateClassSerializeBinary(const GeneratorOptions & options,io::Printer * printer,const Descriptor * desc) const2702 void Generator::GenerateClassSerializeBinary(const GeneratorOptions& options,
2703 io::Printer* printer,
2704 const Descriptor* desc) const {
2705 printer->Print(
2706 "/**\n"
2707 " * Class method variant: serializes the given message to binary data\n"
2708 " * (in protobuf wire format), writing to the given BinaryWriter.\n"
2709 " * @param {!$class$} message\n"
2710 " * @param {!jspb.BinaryWriter} writer\n"
2711 " */\n"
2712 "$class$.serializeBinaryToWriter = function(message, "
2713 "writer) {\n"
2714 " message.serializeBinaryToWriter(writer);\n"
2715 "};\n"
2716 "\n"
2717 "\n"
2718 "/**\n"
2719 " * Serializes the message to binary data (in protobuf wire format).\n"
2720 " * @return {!Uint8Array}\n"
2721 " */\n"
2722 "$class$.prototype.serializeBinary = function() {\n"
2723 " var writer = new jspb.BinaryWriter();\n"
2724 " this.serializeBinaryToWriter(writer);\n"
2725 " return writer.getResultBuffer();\n"
2726 "};\n"
2727 "\n"
2728 "\n"
2729 "/**\n"
2730 " * Serializes the message to binary data (in protobuf wire format),\n"
2731 " * writing to the given BinaryWriter.\n"
2732 " * @param {!jspb.BinaryWriter} writer\n"
2733 " */\n"
2734 "$class$.prototype.serializeBinaryToWriter = function (writer) {\n"
2735 " var f = undefined;\n",
2736 "class", GetPath(options, desc));
2737
2738 for (int i = 0; i < desc->field_count(); i++) {
2739 GenerateClassSerializeBinaryField(options, printer, desc->field(i));
2740 }
2741
2742 if (IsExtendable(desc)) {
2743 printer->Print(
2744 " jspb.Message.serializeBinaryExtensions(this, writer,\n"
2745 " $extobj$Binary, $class$.prototype.getExtension);\n",
2746 "extobj", JSExtensionsObjectName(options, desc->file(), desc),
2747 "class", GetPath(options, desc));
2748 }
2749
2750 printer->Print(
2751 "};\n"
2752 "\n"
2753 "\n");
2754 }
2755
GenerateClassSerializeBinaryField(const GeneratorOptions & options,io::Printer * printer,const FieldDescriptor * field) const2756 void Generator::GenerateClassSerializeBinaryField(
2757 const GeneratorOptions& options,
2758 io::Printer* printer,
2759 const FieldDescriptor* field) const {
2760 if (HasFieldPresence(field) &&
2761 field->cpp_type() != FieldDescriptor::CPPTYPE_MESSAGE) {
2762 printer->Print(
2763 " f = jspb.Message.getField(this, $index$);\n",
2764 "index", JSFieldIndex(field));
2765 } else {
2766 printer->Print(
2767 " f = this.get$name$($nolazy$);\n",
2768 "name", JSGetterName(options, field, BYTES_U8),
2769 // No lazy creation for maps containers -- fastpath the empty case.
2770 "nolazy", (field->is_map()) ? "true" : "");
2771 }
2772
2773
2774 // Print an `if (condition)` statement that evaluates to true if the field
2775 // goes on the wire.
2776 if (field->is_map()) {
2777 printer->Print(
2778 " if (f && f.getLength() > 0) {\n");
2779 } else if (field->is_repeated()) {
2780 printer->Print(
2781 " if (f.length > 0) {\n");
2782 } else {
2783 if (HasFieldPresence(field)) {
2784 printer->Print(
2785 " if (f != null) {\n");
2786 } else {
2787 // No field presence: serialize onto the wire only if value is
2788 // non-default. Defaults are documented here:
2789 // https://goto.google.com/lhdfm
2790 switch (field->cpp_type()) {
2791 case FieldDescriptor::CPPTYPE_INT32:
2792 case FieldDescriptor::CPPTYPE_INT64:
2793 case FieldDescriptor::CPPTYPE_UINT32:
2794 case FieldDescriptor::CPPTYPE_UINT64: {
2795 {
2796 printer->Print(" if (f !== 0) {\n");
2797 }
2798 break;
2799 }
2800
2801 case FieldDescriptor::CPPTYPE_ENUM:
2802 case FieldDescriptor::CPPTYPE_FLOAT:
2803 case FieldDescriptor::CPPTYPE_DOUBLE:
2804 printer->Print(
2805 " if (f !== 0.0) {\n");
2806 break;
2807 case FieldDescriptor::CPPTYPE_BOOL:
2808 printer->Print(
2809 " if (f) {\n");
2810 break;
2811 case FieldDescriptor::CPPTYPE_STRING:
2812 printer->Print(
2813 " if (f.length > 0) {\n");
2814 break;
2815 default:
2816 assert(false);
2817 break;
2818 }
2819 }
2820 }
2821
2822 // Write the field on the wire.
2823 if (field->is_map()) {
2824 const FieldDescriptor* key_field = MapFieldKey(field);
2825 const FieldDescriptor* value_field = MapFieldValue(field);
2826 printer->Print(
2827 " f.serializeBinary($index$, writer, "
2828 "$keyWriterFn$, $valueWriterFn$",
2829 "index", SimpleItoa(field->number()),
2830 "keyWriterFn", JSBinaryWriterMethodName(options, key_field),
2831 "valueWriterFn", JSBinaryWriterMethodName(options, value_field));
2832
2833 if (value_field->type() == FieldDescriptor::TYPE_MESSAGE) {
2834 printer->Print(", $messageType$.serializeBinaryToWriter",
2835 "messageType", GetPath(options, value_field->message_type()));
2836 }
2837
2838 printer->Print(");\n");
2839 } else {
2840 printer->Print(
2841 " writer.write$method$(\n"
2842 " $index$,\n"
2843 " f",
2844 "method", JSBinaryReadWriteMethodName(field, /* is_writer = */ true),
2845 "index", SimpleItoa(field->number()));
2846
2847 if (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE &&
2848 !(field->is_map())) {
2849 printer->Print(
2850 ",\n"
2851 " $submsg$.serializeBinaryToWriter\n",
2852 "submsg", SubmessageTypeRef(options, field));
2853 } else {
2854 printer->Print("\n");
2855 }
2856
2857 printer->Print(
2858 " );\n");
2859 }
2860
2861 // Close the `if`.
2862 printer->Print(
2863 " }\n");
2864 }
2865
GenerateEnum(const GeneratorOptions & options,io::Printer * printer,const EnumDescriptor * enumdesc) const2866 void Generator::GenerateEnum(const GeneratorOptions& options,
2867 io::Printer* printer,
2868 const EnumDescriptor* enumdesc) const {
2869 printer->Print(
2870 "/**\n"
2871 " * @enum {number}\n"
2872 " */\n"
2873 "$name$ = {\n",
2874 "name", GetPath(options, enumdesc));
2875
2876 for (int i = 0; i < enumdesc->value_count(); i++) {
2877 const EnumValueDescriptor* value = enumdesc->value(i);
2878 printer->Print(
2879 " $name$: $value$$comma$\n",
2880 "name", ToEnumCase(value->name()),
2881 "value", SimpleItoa(value->number()),
2882 "comma", (i == enumdesc->value_count() - 1) ? "" : ",");
2883 }
2884
2885 printer->Print(
2886 "};\n"
2887 "\n");
2888 }
2889
GenerateExtension(const GeneratorOptions & options,io::Printer * printer,const FieldDescriptor * field) const2890 void Generator::GenerateExtension(const GeneratorOptions& options,
2891 io::Printer* printer,
2892 const FieldDescriptor* field) const {
2893 string extension_scope =
2894 (field->extension_scope() ?
2895 GetPath(options, field->extension_scope()) :
2896 GetPath(options, field->file()));
2897
2898 printer->Print(
2899 "\n"
2900 "/**\n"
2901 " * A tuple of {field number, class constructor} for the extension\n"
2902 " * field named `$name$`.\n"
2903 " * @type {!jspb.ExtensionFieldInfo.<$extensionType$>}\n"
2904 " */\n"
2905 "$class$.$name$ = new jspb.ExtensionFieldInfo(\n",
2906 "name", JSObjectFieldName(options, field),
2907 "class", extension_scope,
2908 "extensionType", JSFieldTypeAnnotation(
2909 options, field,
2910 /* force_optional = */ false,
2911 /* force_present = */ true,
2912 /* singular_if_not_packed = */ false));
2913 printer->Print(
2914 " $index$,\n"
2915 " {$name$: 0},\n"
2916 " $ctor$,\n"
2917 " /** @type {?function((boolean|undefined),!jspb.Message=): "
2918 "!Object} */ (\n"
2919 " $toObject$),\n"
2920 " $repeated$);\n",
2921 "index", SimpleItoa(field->number()),
2922 "name", JSObjectFieldName(options, field),
2923 "ctor", (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE ?
2924 SubmessageTypeRef(options, field) : string("null")),
2925 "toObject", (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE ?
2926 (SubmessageTypeRef(options, field) + ".toObject") :
2927 string("null")),
2928 "repeated", (field->is_repeated() ? "1" : "0"));
2929
2930 if (options.binary) {
2931 printer->Print(
2932 "\n"
2933 "$extendName$Binary[$index$] = new jspb.ExtensionFieldBinaryInfo(\n"
2934 " $class$.$name$,\n"
2935 " $binaryReaderFn$,\n"
2936 " $binaryWriterFn$,\n"
2937 " $binaryMessageSerializeFn$,\n"
2938 " $binaryMessageDeserializeFn$,\n",
2939 "extendName", JSExtensionsObjectName(options, field->file(),
2940 field->containing_type()),
2941 "index", SimpleItoa(field->number()),
2942 "class", extension_scope,
2943 "name", JSObjectFieldName(options, field),
2944 "binaryReaderFn", JSBinaryReaderMethodName(options, field),
2945 "binaryWriterFn", JSBinaryWriterMethodName(options, field),
2946 "binaryMessageSerializeFn",
2947 (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) ?
2948 (SubmessageTypeRef(options, field) +
2949 ".serializeBinaryToWriter") : "null",
2950 "binaryMessageDeserializeFn",
2951 (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) ?
2952 (SubmessageTypeRef(options, field) +
2953 ".deserializeBinaryFromReader") : "null");
2954
2955 printer->Print(
2956 " $isPacked$);\n",
2957 "isPacked", (field->is_packed() ? "true" : "false"));
2958 }
2959
2960 printer->Print(
2961 "// This registers the extension field with the extended class, so that\n"
2962 "// toObject() will function correctly.\n"
2963 "$extendName$[$index$] = $class$.$name$;\n"
2964 "\n",
2965 "extendName", JSExtensionsObjectName(options, field->file(),
2966 field->containing_type()),
2967 "index", SimpleItoa(field->number()),
2968 "class", extension_scope,
2969 "name", JSObjectFieldName(options, field));
2970 }
2971
ParseFromOptions(const vector<pair<string,string>> & options,string * error)2972 bool GeneratorOptions::ParseFromOptions(
2973 const vector< pair< string, string > >& options,
2974 string* error) {
2975 for (int i = 0; i < options.size(); i++) {
2976 if (options[i].first == "add_require_for_enums") {
2977 if (options[i].second != "") {
2978 *error = "Unexpected option value for add_require_for_enums";
2979 return false;
2980 }
2981 add_require_for_enums = true;
2982 } else if (options[i].first == "binary") {
2983 if (options[i].second != "") {
2984 *error = "Unexpected option value for binary";
2985 return false;
2986 }
2987 binary = true;
2988 } else if (options[i].first == "testonly") {
2989 if (options[i].second != "") {
2990 *error = "Unexpected option value for testonly";
2991 return false;
2992 }
2993 testonly = true;
2994 } else if (options[i].first == "error_on_name_conflict") {
2995 if (options[i].second != "") {
2996 *error = "Unexpected option value for error_on_name_conflict";
2997 return false;
2998 }
2999 error_on_name_conflict = true;
3000 } else if (options[i].first == "output_dir") {
3001 output_dir = options[i].second;
3002 } else if (options[i].first == "namespace_prefix") {
3003 namespace_prefix = options[i].second;
3004 } else if (options[i].first == "library") {
3005 library = options[i].second;
3006 } else if (options[i].first == "import_style") {
3007 if (options[i].second == "closure") {
3008 import_style = IMPORT_CLOSURE;
3009 } else if (options[i].second == "commonjs") {
3010 import_style = IMPORT_COMMONJS;
3011 } else if (options[i].second == "browser") {
3012 import_style = IMPORT_BROWSER;
3013 } else if (options[i].second == "es6") {
3014 import_style = IMPORT_ES6;
3015 } else {
3016 *error = "Unknown import style " + options[i].second + ", expected " +
3017 "one of: closure, commonjs, browser, es6.";
3018 }
3019 } else {
3020 // Assume any other option is an output directory, as long as it is a bare
3021 // `key` rather than a `key=value` option.
3022 if (options[i].second != "") {
3023 *error = "Unknown option: " + options[i].first;
3024 return false;
3025 }
3026 output_dir = options[i].first;
3027 }
3028 }
3029
3030 if (!library.empty() && import_style != IMPORT_CLOSURE) {
3031 *error = "The library option should only be used for "
3032 "import_style=closure";
3033 }
3034
3035 return true;
3036 }
3037
GenerateFilesInDepOrder(const GeneratorOptions & options,io::Printer * printer,const vector<const FileDescriptor * > & files) const3038 void Generator::GenerateFilesInDepOrder(
3039 const GeneratorOptions& options,
3040 io::Printer* printer,
3041 const vector<const FileDescriptor*>& files) const {
3042 // Build a std::set over all files so that the DFS can detect when it recurses
3043 // into a dep not specified in the user's command line.
3044 std::set<const FileDescriptor*> all_files(files.begin(), files.end());
3045 // Track the in-progress set of files that have been generated already.
3046 std::set<const FileDescriptor*> generated;
3047 for (int i = 0; i < files.size(); i++) {
3048 GenerateFileAndDeps(options, printer, files[i], &all_files, &generated);
3049 }
3050 }
3051
GenerateFileAndDeps(const GeneratorOptions & options,io::Printer * printer,const FileDescriptor * root,std::set<const FileDescriptor * > * all_files,std::set<const FileDescriptor * > * generated) const3052 void Generator::GenerateFileAndDeps(
3053 const GeneratorOptions& options,
3054 io::Printer* printer,
3055 const FileDescriptor* root,
3056 std::set<const FileDescriptor*>* all_files,
3057 std::set<const FileDescriptor*>* generated) const {
3058 // Skip if already generated.
3059 if (generated->find(root) != generated->end()) {
3060 return;
3061 }
3062 generated->insert(root);
3063
3064 // Generate all dependencies before this file's content.
3065 for (int i = 0; i < root->dependency_count(); i++) {
3066 const FileDescriptor* dep = root->dependency(i);
3067 GenerateFileAndDeps(options, printer, dep, all_files, generated);
3068 }
3069
3070 // Generate this file's content. Only generate if the file is part of the
3071 // original set requested to be generated; i.e., don't take all transitive
3072 // deps down to the roots.
3073 if (all_files->find(root) != all_files->end()) {
3074 GenerateClassesAndEnums(options, printer, root);
3075 }
3076 }
3077
GenerateFile(const GeneratorOptions & options,io::Printer * printer,const FileDescriptor * file) const3078 void Generator::GenerateFile(const GeneratorOptions& options,
3079 io::Printer* printer,
3080 const FileDescriptor* file) const {
3081 GenerateHeader(options, printer);
3082
3083 // Generate "require" statements.
3084 if (options.import_style == GeneratorOptions::IMPORT_COMMONJS) {
3085 printer->Print("var jspb = require('google-protobuf');\n");
3086 printer->Print("var goog = jspb;\n");
3087 printer->Print("var global = Function('return this')();\n\n");
3088
3089 for (int i = 0; i < file->dependency_count(); i++) {
3090 const string& name = file->dependency(i)->name();
3091 printer->Print(
3092 "var $alias$ = require('$file$');\n",
3093 "alias", ModuleAlias(name),
3094 "file", GetRootPath(file->name(), name) + GetJSFilename(name));
3095 }
3096 }
3097
3098 // We aren't using Closure's import system, but we use goog.exportSymbol()
3099 // to construct the expected tree of objects, eg.
3100 //
3101 // goog.exportSymbol('foo.bar.Baz', null, this);
3102 //
3103 // // Later generated code expects foo.bar = {} to exist:
3104 // foo.bar.Baz = function() { /* ... */ }
3105 set<string> provided;
3106
3107 // Cover the case where this file declares extensions but no messages.
3108 // This will ensure that the file-level object will be declared to hold
3109 // the extensions.
3110 for (int i = 0; i < file->extension_count(); i++) {
3111 provided.insert(file->extension(i)->full_name());
3112 }
3113
3114 FindProvidesForFile(options, printer, file, &provided);
3115 for (std::set<string>::iterator it = provided.begin();
3116 it != provided.end(); ++it) {
3117 printer->Print("goog.exportSymbol('$name$', null, global);\n",
3118 "name", *it);
3119 }
3120
3121 GenerateClassesAndEnums(options, printer, file);
3122
3123 // Extensions nested inside messages are emitted inside
3124 // GenerateClassesAndEnums().
3125 for (int i = 0; i < file->extension_count(); i++) {
3126 GenerateExtension(options, printer, file->extension(i));
3127 }
3128
3129 if (options.import_style == GeneratorOptions::IMPORT_COMMONJS) {
3130 printer->Print("goog.object.extend(exports, $package$);\n",
3131 "package", GetPath(options, file));
3132 }
3133 }
3134
GenerateAll(const vector<const FileDescriptor * > & files,const string & parameter,GeneratorContext * context,string * error) const3135 bool Generator::GenerateAll(const vector<const FileDescriptor*>& files,
3136 const string& parameter,
3137 GeneratorContext* context,
3138 string* error) const {
3139 vector< pair< string, string > > option_pairs;
3140 ParseGeneratorParameter(parameter, &option_pairs);
3141 GeneratorOptions options;
3142 if (!options.ParseFromOptions(option_pairs, error)) {
3143 return false;
3144 }
3145
3146
3147 // There are three schemes for where output files go:
3148 //
3149 // - import_style = IMPORT_CLOSURE, library non-empty: all output in one file
3150 // - import_style = IMPORT_CLOSURE, library empty: one output file per type
3151 // - import_style != IMPORT_CLOSURE: one output file per .proto file
3152 if (options.import_style == GeneratorOptions::IMPORT_CLOSURE &&
3153 options.library != "") {
3154 // All output should go in a single file.
3155 string filename = options.output_dir + "/" + options.library + ".js";
3156 google::protobuf::scoped_ptr<io::ZeroCopyOutputStream> output(context->Open(filename));
3157 GOOGLE_CHECK(output.get());
3158 io::Printer printer(output.get(), '$');
3159
3160 // Pull out all extensions -- we need these to generate all
3161 // provides/requires.
3162 vector<const FieldDescriptor*> extensions;
3163 for (int i = 0; i < files.size(); i++) {
3164 for (int j = 0; j < files[i]->extension_count(); j++) {
3165 const FieldDescriptor* extension = files[i]->extension(j);
3166 extensions.push_back(extension);
3167 }
3168 }
3169
3170 GenerateHeader(options, &printer);
3171
3172 std::set<string> provided;
3173 FindProvides(options, &printer, files, &provided);
3174 FindProvidesForFields(options, &printer, extensions, &provided);
3175 GenerateProvides(options, &printer, &provided);
3176 GenerateTestOnly(options, &printer);
3177 GenerateRequiresForLibrary(options, &printer, files, &provided);
3178
3179 GenerateFilesInDepOrder(options, &printer, files);
3180
3181 for (int i = 0; i < extensions.size(); i++) {
3182 if (ShouldGenerateExtension(extensions[i])) {
3183 GenerateExtension(options, &printer, extensions[i]);
3184 }
3185 }
3186
3187 if (printer.failed()) {
3188 return false;
3189 }
3190 } else if (options.import_style == GeneratorOptions::IMPORT_CLOSURE) {
3191 set<const void*> allowed_set;
3192 if (!GenerateJspbAllowedSet(options, files, &allowed_set, error)) {
3193 return false;
3194 }
3195
3196 for (int i = 0; i < files.size(); i++) {
3197 const FileDescriptor* file = files[i];
3198 for (int j = 0; j < file->message_type_count(); j++) {
3199 const Descriptor* desc = file->message_type(j);
3200 if (allowed_set.count(desc) == 0) {
3201 continue;
3202 }
3203
3204 string filename = GetMessageFileName(options, desc);
3205 google::protobuf::scoped_ptr<io::ZeroCopyOutputStream> output(
3206 context->Open(filename));
3207 GOOGLE_CHECK(output.get());
3208 io::Printer printer(output.get(), '$');
3209
3210 GenerateHeader(options, &printer);
3211
3212 std::set<string> provided;
3213 FindProvidesForMessage(options, &printer, desc, &provided);
3214 GenerateProvides(options, &printer, &provided);
3215 GenerateTestOnly(options, &printer);
3216 GenerateRequiresForMessage(options, &printer, desc, &provided);
3217
3218 GenerateClass(options, &printer, desc);
3219
3220 if (printer.failed()) {
3221 return false;
3222 }
3223 }
3224 for (int j = 0; j < file->enum_type_count(); j++) {
3225 const EnumDescriptor* enumdesc = file->enum_type(j);
3226 if (allowed_set.count(enumdesc) == 0) {
3227 continue;
3228 }
3229
3230 string filename = GetEnumFileName(options, enumdesc);
3231 google::protobuf::scoped_ptr<io::ZeroCopyOutputStream> output(
3232 context->Open(filename));
3233 GOOGLE_CHECK(output.get());
3234 io::Printer printer(output.get(), '$');
3235
3236 GenerateHeader(options, &printer);
3237
3238 std::set<string> provided;
3239 FindProvidesForEnum(options, &printer, enumdesc, &provided);
3240 GenerateProvides(options, &printer, &provided);
3241 GenerateTestOnly(options, &printer);
3242
3243 GenerateEnum(options, &printer, enumdesc);
3244
3245 if (printer.failed()) {
3246 return false;
3247 }
3248 }
3249 // File-level extensions (message-level extensions are generated under
3250 // the enclosing message).
3251 if (allowed_set.count(file) == 1) {
3252 string filename = GetExtensionFileName(options, file);
3253
3254 google::protobuf::scoped_ptr<io::ZeroCopyOutputStream> output(
3255 context->Open(filename));
3256 GOOGLE_CHECK(output.get());
3257 io::Printer printer(output.get(), '$');
3258
3259 GenerateHeader(options, &printer);
3260
3261 std::set<string> provided;
3262 vector<const FieldDescriptor*> fields;
3263
3264 for (int j = 0; j < files[i]->extension_count(); j++) {
3265 if (ShouldGenerateExtension(files[i]->extension(j))) {
3266 fields.push_back(files[i]->extension(j));
3267 }
3268 }
3269
3270 FindProvidesForFields(options, &printer, fields, &provided);
3271 GenerateProvides(options, &printer, &provided);
3272 GenerateTestOnly(options, &printer);
3273 GenerateRequiresForExtensions(options, &printer, fields, &provided);
3274
3275 for (int j = 0; j < files[i]->extension_count(); j++) {
3276 if (ShouldGenerateExtension(files[i]->extension(j))) {
3277 GenerateExtension(options, &printer, files[i]->extension(j));
3278 }
3279 }
3280 }
3281 }
3282 } else {
3283 // Generate one output file per input (.proto) file.
3284
3285 for (int i = 0; i < files.size(); i++) {
3286 const google::protobuf::FileDescriptor* file = files[i];
3287
3288 string filename = options.output_dir + "/" + GetJSFilename(file->name());
3289 google::protobuf::scoped_ptr<io::ZeroCopyOutputStream> output(context->Open(filename));
3290 GOOGLE_CHECK(output.get());
3291 io::Printer printer(output.get(), '$');
3292
3293 GenerateFile(options, &printer, file);
3294
3295 if (printer.failed()) {
3296 return false;
3297 }
3298 }
3299 }
3300
3301 return true;
3302 }
3303
3304 } // namespace js
3305 } // namespace compiler
3306 } // namespace protobuf
3307 } // namespace google
3308