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