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 #include <string>
39 #include <utility>
40 #include <vector>
41
42 #include <google/protobuf/stubs/logging.h>
43 #include <google/protobuf/stubs/common.h>
44 #include <google/protobuf/stubs/stringprintf.h>
45 #include <google/protobuf/stubs/strutil.h>
46
47 #include <google/protobuf/compiler/scc.h>
48 #include <google/protobuf/compiler/js/well_known_types_embed.h>
49 #include <google/protobuf/io/printer.h>
50 #include <google/protobuf/io/zero_copy_stream.h>
51 #include <google/protobuf/descriptor.pb.h>
52 #include <google/protobuf/descriptor.h>
53
54
55 namespace google {
56 namespace protobuf {
57 namespace compiler {
58 namespace js {
59
60 // Sorted list of JavaScript keywords. These cannot be used as names. If they
61 // appear, we prefix them with "pb_".
62 const char* kKeyword[] = {
63 "abstract", "boolean", "break", "byte", "case",
64 "catch", "char", "class", "const", "continue",
65 "debugger", "default", "delete", "do", "double",
66 "else", "enum", "export", "extends", "false",
67 "final", "finally", "float", "for", "function",
68 "goto", "if", "implements", "import", "in",
69 "instanceof", "int", "interface", "long", "native",
70 "new", "null", "package", "private", "protected",
71 "public", "return", "short", "static", "super",
72 "switch", "synchronized", "this", "throw", "throws",
73 "transient", "try", "typeof", "var", "void",
74 "volatile", "while", "with",
75 };
76
77 static const int kNumKeyword = sizeof(kKeyword) / sizeof(char*);
78
79 namespace {
80
81 // The mode of operation for bytes fields. Historically JSPB always carried
82 // bytes as JS {string}, containing base64 content by convention. With binary
83 // and proto3 serialization the new convention is to represent it as binary
84 // data in Uint8Array. See b/26173701 for background on the migration.
85 enum BytesMode {
86 BYTES_DEFAULT, // Default type for getBytesField to return.
87 BYTES_B64, // Explicitly coerce to base64 string where needed.
88 BYTES_U8, // Explicitly coerce to Uint8Array where needed.
89 };
90
IsReserved(const std::string & ident)91 bool IsReserved(const std::string& ident) {
92 for (int i = 0; i < kNumKeyword; i++) {
93 if (ident == kKeyword[i]) {
94 return true;
95 }
96 }
97 return false;
98 }
99
StrEndsWith(StringPiece sp,StringPiece x)100 bool StrEndsWith(StringPiece sp, StringPiece x) {
101 return sp.size() >= x.size() && sp.substr(sp.size() - x.size()) == x;
102 }
103
104 // Returns a copy of |filename| with any trailing ".protodevel" or ".proto
105 // suffix stripped.
106 // TODO(haberman): Unify with copy in compiler/cpp/internal/helpers.cc.
StripProto(const std::string & filename)107 std::string StripProto(const std::string& filename) {
108 const char* suffix =
109 StrEndsWith(filename, ".protodevel") ? ".protodevel" : ".proto";
110 return StripSuffixString(filename, suffix);
111 }
112
GetSnakeFilename(const std::string & filename)113 std::string GetSnakeFilename(const std::string& filename) {
114 std::string snake_name = filename;
115 ReplaceCharacters(&snake_name, "/", '_');
116 return snake_name;
117 }
118
119 // Given a filename like foo/bar/baz.proto, returns the corresponding JavaScript
120 // file foo/bar/baz.js.
GetJSFilename(const GeneratorOptions & options,const std::string & filename)121 std::string GetJSFilename(const GeneratorOptions& options,
122 const std::string& filename) {
123 return StripProto(filename) + options.GetFileNameExtension();
124 }
125
126 // Given a filename like foo/bar/baz.proto, returns the root directory
127 // path ../../
GetRootPath(const std::string & from_filename,const std::string & to_filename)128 string GetRootPath(const std::string& from_filename,
129 const std::string& to_filename) {
130 if (to_filename.find("google/protobuf") == 0) {
131 // Well-known types (.proto files in the google/protobuf directory) are
132 // assumed to come from the 'google-protobuf' npm package. We may want to
133 // generalize this exception later by letting others put generated code in
134 // their own npm packages.
135 return "google-protobuf/";
136 }
137
138 size_t slashes = std::count(from_filename.begin(), from_filename.end(), '/');
139 if (slashes == 0) {
140 return "./";
141 }
142 std::string result = "";
143 for (size_t i = 0; i < slashes; i++) {
144 result += "../";
145 }
146 return result;
147 }
148
149 // Returns the alias we assign to the module of the given .proto filename
150 // when importing.
ModuleAlias(const std::string & filename)151 std::string ModuleAlias(const std::string& filename) {
152 // This scheme could technically cause problems if a file includes any 2 of:
153 // foo/bar_baz.proto
154 // foo_bar_baz.proto
155 // foo_bar/baz.proto
156 //
157 // We'll worry about this problem if/when we actually see it. This name isn't
158 // exposed to users so we can change it later if we need to.
159 std::string basename = StripProto(filename);
160 ReplaceCharacters(&basename, "-", '$');
161 ReplaceCharacters(&basename, "/", '_');
162 ReplaceCharacters(&basename, ".", '_');
163 return basename + "_pb";
164 }
165
166 // Returns the fully normalized JavaScript namespace for the given
167 // file descriptor's package.
GetNamespace(const GeneratorOptions & options,const FileDescriptor * file)168 std::string GetNamespace(const GeneratorOptions& options,
169 const FileDescriptor* file) {
170 if (!options.namespace_prefix.empty()) {
171 return options.namespace_prefix;
172 } else if (!file->package().empty()) {
173 return "proto." + file->package();
174 } else {
175 return "proto";
176 }
177 }
178
179 // Returns the name of the message with a leading dot and taking into account
180 // nesting, for example ".OuterMessage.InnerMessage", or returns empty if
181 // descriptor is null. This function does not handle namespacing, only message
182 // nesting.
GetNestedMessageName(const Descriptor * descriptor)183 std::string GetNestedMessageName(const Descriptor* descriptor) {
184 if (descriptor == NULL) {
185 return "";
186 }
187 std::string result = StripPrefixString(
188 descriptor->full_name(), descriptor->file()->package());
189 // Add a leading dot if one is not already present.
190 if (!result.empty() && result[0] != '.') {
191 result = "." + result;
192 }
193 return result;
194 }
195
196 // Returns the path prefix for a message or enumeration that
197 // lives under the given file and containing type.
GetPrefix(const GeneratorOptions & options,const FileDescriptor * file_descriptor,const Descriptor * containing_type)198 std::string GetPrefix(const GeneratorOptions& options,
199 const FileDescriptor* file_descriptor,
200 const Descriptor* containing_type) {
201 std::string prefix = GetNamespace(options, file_descriptor) +
202 GetNestedMessageName(containing_type);
203 if (!prefix.empty()) {
204 prefix += ".";
205 }
206 return prefix;
207 }
208
209 // Returns the fully normalized JavaScript path prefix for the given
210 // message descriptor.
GetMessagePathPrefix(const GeneratorOptions & options,const Descriptor * descriptor)211 std::string GetMessagePathPrefix(const GeneratorOptions& options,
212 const Descriptor* descriptor) {
213 return GetPrefix(options, descriptor->file(), descriptor->containing_type());
214 }
215
216 // Returns the fully normalized JavaScript path for the given
217 // message descriptor.
GetMessagePath(const GeneratorOptions & options,const Descriptor * descriptor)218 std::string GetMessagePath(const GeneratorOptions& options,
219 const Descriptor* descriptor) {
220 return GetMessagePathPrefix(options, descriptor) + descriptor->name();
221 }
222
223 // Returns the fully normalized JavaScript path prefix for the given
224 // enumeration descriptor.
GetEnumPathPrefix(const GeneratorOptions & options,const EnumDescriptor * enum_descriptor)225 std::string GetEnumPathPrefix(const GeneratorOptions& options,
226 const EnumDescriptor* enum_descriptor) {
227 return GetPrefix(options, enum_descriptor->file(),
228 enum_descriptor->containing_type());
229 }
230
231 // Returns the fully normalized JavaScript path for the given
232 // enumeration descriptor.
GetEnumPath(const GeneratorOptions & options,const EnumDescriptor * enum_descriptor)233 std::string GetEnumPath(const GeneratorOptions& options,
234 const EnumDescriptor* enum_descriptor) {
235 return GetEnumPathPrefix(options, enum_descriptor) + enum_descriptor->name();
236 }
237
MaybeCrossFileRef(const GeneratorOptions & options,const FileDescriptor * from_file,const Descriptor * to_message)238 std::string MaybeCrossFileRef(const GeneratorOptions& options,
239 const FileDescriptor* from_file,
240 const Descriptor* to_message) {
241 if ((options.import_style == GeneratorOptions::kImportCommonJs ||
242 options.import_style == GeneratorOptions::kImportCommonJsStrict) &&
243 from_file != to_message->file()) {
244 // Cross-file ref in CommonJS needs to use the module alias instead of
245 // the global name.
246 return ModuleAlias(to_message->file()->name()) +
247 GetNestedMessageName(to_message->containing_type()) + "." +
248 to_message->name();
249 } else {
250 // Within a single file we use a full name.
251 return GetMessagePath(options, to_message);
252 }
253 }
254
SubmessageTypeRef(const GeneratorOptions & options,const FieldDescriptor * field)255 std::string SubmessageTypeRef(const GeneratorOptions& options,
256 const FieldDescriptor* field) {
257 GOOGLE_CHECK(field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE);
258 return MaybeCrossFileRef(options, field->file(), field->message_type());
259 }
260
261 // - Object field name: LOWER_UNDERSCORE -> LOWER_CAMEL, except for group fields
262 // (UPPER_CAMEL -> LOWER_CAMEL), with "List" (or "Map") appended if appropriate,
263 // and with reserved words triggering a "pb_" prefix.
264 // - Getters/setters: LOWER_UNDERSCORE -> UPPER_CAMEL, except for group fields
265 // (use the name directly), then append "List" if appropriate, then append "$"
266 // if resulting name is equal to a reserved word.
267 // - Enums: just uppercase.
268
269 // Locale-independent version of ToLower that deals only with ASCII A-Z.
ToLowerASCII(char c)270 char ToLowerASCII(char c) {
271 if (c >= 'A' && c <= 'Z') {
272 return (c - 'A') + 'a';
273 } else {
274 return c;
275 }
276 }
277
ParseLowerUnderscore(const std::string & input)278 std::vector<std::string> ParseLowerUnderscore(const std::string& input) {
279 std::vector<std::string> words;
280 std::string running = "";
281 for (int i = 0; i < input.size(); i++) {
282 if (input[i] == '_') {
283 if (!running.empty()) {
284 words.push_back(running);
285 running.clear();
286 }
287 } else {
288 running += ToLowerASCII(input[i]);
289 }
290 }
291 if (!running.empty()) {
292 words.push_back(running);
293 }
294 return words;
295 }
296
ParseUpperCamel(const std::string & input)297 std::vector<std::string> ParseUpperCamel(const std::string& input) {
298 std::vector<std::string> words;
299 std::string running = "";
300 for (int i = 0; i < input.size(); i++) {
301 if (input[i] >= 'A' && input[i] <= 'Z' && !running.empty()) {
302 words.push_back(running);
303 running.clear();
304 }
305 running += ToLowerASCII(input[i]);
306 }
307 if (!running.empty()) {
308 words.push_back(running);
309 }
310 return words;
311 }
312
ToLowerCamel(const std::vector<std::string> & words)313 std::string ToLowerCamel(const std::vector<std::string>& words) {
314 std::string result;
315 for (int i = 0; i < words.size(); i++) {
316 std::string word = words[i];
317 if (i == 0 && (word[0] >= 'A' && word[0] <= 'Z')) {
318 word[0] = (word[0] - 'A') + 'a';
319 } else if (i != 0 && (word[0] >= 'a' && word[0] <= 'z')) {
320 word[0] = (word[0] - 'a') + 'A';
321 }
322 result += word;
323 }
324 return result;
325 }
326
ToUpperCamel(const std::vector<std::string> & words)327 std::string ToUpperCamel(const std::vector<std::string>& words) {
328 std::string result;
329 for (int i = 0; i < words.size(); i++) {
330 std::string word = words[i];
331 if (word[0] >= 'a' && word[0] <= 'z') {
332 word[0] = (word[0] - 'a') + 'A';
333 }
334 result += word;
335 }
336 return result;
337 }
338
339 // Based on code from descriptor.cc (Thanks Kenton!)
340 // Uppercases the entire string, turning ValueName into
341 // VALUENAME.
ToEnumCase(const std::string & input)342 std::string ToEnumCase(const std::string& input) {
343 std::string result;
344 result.reserve(input.size());
345
346 for (int i = 0; i < input.size(); i++) {
347 if ('a' <= input[i] && input[i] <= 'z') {
348 result.push_back(input[i] - 'a' + 'A');
349 } else {
350 result.push_back(input[i]);
351 }
352 }
353
354 return result;
355 }
356
ToLower(const std::string & input)357 std::string ToLower(const std::string& input) {
358 std::string result;
359 result.reserve(input.size());
360
361 for (int i = 0; i < input.size(); i++) {
362 if ('A' <= input[i] && input[i] <= 'Z') {
363 result.push_back(input[i] - 'A' + 'a');
364 } else {
365 result.push_back(input[i]);
366 }
367 }
368
369 return result;
370 }
371
372 // When we're generating one output file per SCC, this is the filename
373 // that top-level extensions should go in.
374 // e.g. one proto file (test.proto):
375 // package a;
376 // extends Foo {
377 // ...
378 // }
379 // If "with_filename" equals true, the extension filename will be
380 // "proto.a_test_extensions.js", otherwise will be "proto.a.js"
GetExtensionFileName(const GeneratorOptions & options,const FileDescriptor * file,bool with_filename)381 std::string GetExtensionFileName(const GeneratorOptions& options,
382 const FileDescriptor* file,
383 bool with_filename) {
384 std::string snake_name = StripProto(GetSnakeFilename(file->name()));
385 return options.output_dir + "/" + ToLower(GetNamespace(options, file)) +
386 (with_filename ? ("_" + snake_name + "_extensions") : "") +
387 options.GetFileNameExtension();
388 }
389 // When we're generating one output file per SCC, this is the filename
390 // that all messages in the SCC should go in.
391 // If with_package equals true, filename will have package prefix,
392 // If the filename length is longer than 200, the filename will be the
393 // SCC's proto filename with suffix "_long_sccs_(index)" (if with_package equals
394 // true it still has package prefix)
GetMessagesFileName(const GeneratorOptions & options,const SCC * scc,bool with_package)395 std::string GetMessagesFileName(const GeneratorOptions& options, const SCC* scc,
396 bool with_package) {
397 static std::map<const Descriptor*, std::string>* long_name_dict =
398 new std::map<const Descriptor*, std::string>();
399 std::string package_base =
400 with_package
401 ? ToLower(GetNamespace(options, scc->GetRepresentative()->file()) +
402 "_")
403 : "";
404 std::string filename_base = "";
405 std::vector<std::string> all_message_names;
406 for (auto one_desc : scc->descriptors) {
407 if (one_desc->containing_type() == nullptr) {
408 all_message_names.push_back(ToLower(one_desc->name()));
409 }
410 }
411 sort(all_message_names.begin(), all_message_names.end());
412 for (auto one_message : all_message_names) {
413 if (!filename_base.empty()) {
414 filename_base += "_";
415 }
416 filename_base += one_message;
417 }
418 if (filename_base.size() + package_base.size() > 200) {
419 if ((*long_name_dict).find(scc->GetRepresentative()) ==
420 (*long_name_dict).end()) {
421 std::string snake_name = StripProto(
422 GetSnakeFilename(scc->GetRepresentative()->file()->name()));
423 (*long_name_dict)[scc->GetRepresentative()] =
424 StrCat(snake_name, "_long_sccs_",
425 static_cast<uint64>((*long_name_dict).size()));
426 }
427 filename_base = (*long_name_dict)[scc->GetRepresentative()];
428 }
429 return options.output_dir + "/" + package_base + filename_base +
430 options.GetFileNameExtension();
431 }
432
433 // When we're generating one output file per type name, this is the filename
434 // that a top-level enum should go in.
435 // If with_package equals true, filename will have package prefix.
GetEnumFileName(const GeneratorOptions & options,const EnumDescriptor * desc,bool with_package)436 std::string GetEnumFileName(const GeneratorOptions& options,
437 const EnumDescriptor* desc, bool with_package) {
438 return options.output_dir + "/" +
439 (with_package ? ToLower(GetNamespace(options, desc->file()) + "_")
440 : "") +
441 ToLower(desc->name()) + options.GetFileNameExtension();
442 }
443
444 // Returns the message/response ID, if set.
GetMessageId(const Descriptor * desc)445 std::string GetMessageId(const Descriptor* desc) {
446 return std::string();
447 }
448
IgnoreExtensionField(const FieldDescriptor * field)449 bool IgnoreExtensionField(const FieldDescriptor* field) {
450 // Exclude descriptor extensions from output "to avoid clutter" (from original
451 // codegen).
452 if (!field->is_extension()) return false;
453 const FileDescriptor* file = field->containing_type()->file();
454 return file->name() == "net/proto2/proto/descriptor.proto" ||
455 file->name() == "google/protobuf/descriptor.proto";
456 }
457
458
459 // Used inside Google only -- do not remove.
IsResponse(const Descriptor * desc)460 bool IsResponse(const Descriptor* desc) { return false; }
461
IgnoreField(const FieldDescriptor * field)462 bool IgnoreField(const FieldDescriptor* field) {
463 return IgnoreExtensionField(field);
464 }
465
466
467 // Do we ignore this message type?
IgnoreMessage(const Descriptor * d)468 bool IgnoreMessage(const Descriptor* d) { return d->options().map_entry(); }
469
470 // Does JSPB ignore this entire oneof? True only if all fields are ignored.
IgnoreOneof(const OneofDescriptor * oneof)471 bool IgnoreOneof(const OneofDescriptor* oneof) {
472 for (int i = 0; i < oneof->field_count(); i++) {
473 if (!IgnoreField(oneof->field(i))) {
474 return false;
475 }
476 }
477 return true;
478 }
479
JSIdent(const GeneratorOptions & options,const FieldDescriptor * field,bool is_upper_camel,bool is_map,bool drop_list)480 std::string JSIdent(const GeneratorOptions& options,
481 const FieldDescriptor* field, bool is_upper_camel,
482 bool is_map, bool drop_list) {
483 std::string result;
484 if (field->type() == FieldDescriptor::TYPE_GROUP) {
485 result = is_upper_camel
486 ? ToUpperCamel(ParseUpperCamel(field->message_type()->name()))
487 : ToLowerCamel(ParseUpperCamel(field->message_type()->name()));
488 } else {
489 result = is_upper_camel ? ToUpperCamel(ParseLowerUnderscore(field->name()))
490 : ToLowerCamel(ParseLowerUnderscore(field->name()));
491 }
492 if (is_map || field->is_map()) {
493 // JSPB-style or proto3-style map.
494 result += "Map";
495 } else if (!drop_list && field->is_repeated()) {
496 // Repeated field.
497 result += "List";
498 }
499 return result;
500 }
501
JSObjectFieldName(const GeneratorOptions & options,const FieldDescriptor * field)502 std::string JSObjectFieldName(const GeneratorOptions& options,
503 const FieldDescriptor* field) {
504 std::string name = JSIdent(options, field,
505 /* is_upper_camel = */ false,
506 /* is_map = */ false,
507 /* drop_list = */ false);
508 if (IsReserved(name)) {
509 name = "pb_" + name;
510 }
511 return name;
512 }
513
JSByteGetterSuffix(BytesMode bytes_mode)514 std::string JSByteGetterSuffix(BytesMode bytes_mode) {
515 switch (bytes_mode) {
516 case BYTES_DEFAULT:
517 return "";
518 case BYTES_B64:
519 return "B64";
520 case BYTES_U8:
521 return "U8";
522 default:
523 assert(false);
524 }
525 return "";
526 }
527
528 // Returns the field name as a capitalized portion of a getter/setter method
529 // name, e.g. MyField for .getMyField().
JSGetterName(const GeneratorOptions & options,const FieldDescriptor * field,BytesMode bytes_mode=BYTES_DEFAULT,bool drop_list=false)530 std::string JSGetterName(const GeneratorOptions& options,
531 const FieldDescriptor* field,
532 BytesMode bytes_mode = BYTES_DEFAULT,
533 bool drop_list = false) {
534 std::string name = JSIdent(options, field,
535 /* is_upper_camel = */ true,
536 /* is_map = */ false, drop_list);
537 if (field->type() == FieldDescriptor::TYPE_BYTES) {
538 std::string suffix = JSByteGetterSuffix(bytes_mode);
539 if (!suffix.empty()) {
540 name += "_as" + suffix;
541 }
542 }
543 if (name == "Extension" || name == "JsPbMessageId") {
544 // Avoid conflicts with base-class names.
545 name += "$";
546 }
547 return name;
548 }
549
550
JSOneofName(const OneofDescriptor * oneof)551 std::string JSOneofName(const OneofDescriptor* oneof) {
552 return ToUpperCamel(ParseLowerUnderscore(oneof->name()));
553 }
554
555 // Returns the index corresponding to this field in the JSPB array (underlying
556 // data storage array).
JSFieldIndex(const FieldDescriptor * field)557 std::string JSFieldIndex(const FieldDescriptor* field) {
558 // Determine whether this field is a member of a group. Group fields are a bit
559 // wonky: their "containing type" is a message type created just for the
560 // group, and that type's parent type has a field with the group-message type
561 // as its message type and TYPE_GROUP as its field type. For such fields, the
562 // index we use is relative to the field number of the group submessage field.
563 // For all other fields, we just use the field number.
564 const Descriptor* containing_type = field->containing_type();
565 const Descriptor* parent_type = containing_type->containing_type();
566 if (parent_type != NULL) {
567 for (int i = 0; i < parent_type->field_count(); i++) {
568 if (parent_type->field(i)->type() == FieldDescriptor::TYPE_GROUP &&
569 parent_type->field(i)->message_type() == containing_type) {
570 return StrCat(field->number() - parent_type->field(i)->number());
571 }
572 }
573 }
574 return StrCat(field->number());
575 }
576
JSOneofIndex(const OneofDescriptor * oneof)577 std::string JSOneofIndex(const OneofDescriptor* oneof) {
578 int index = -1;
579 for (int i = 0; i < oneof->containing_type()->oneof_decl_count(); i++) {
580 const OneofDescriptor* o = oneof->containing_type()->oneof_decl(i);
581 // If at least one field in this oneof is not JSPB-ignored, count the oneof.
582 for (int j = 0; j < o->field_count(); j++) {
583 const FieldDescriptor* f = o->field(j);
584 if (!IgnoreField(f)) {
585 index++;
586 break; // inner loop
587 }
588 }
589 if (o == oneof) {
590 break;
591 }
592 }
593 return StrCat(index);
594 }
595
596 // Decodes a codepoint in \x0000 -- \xFFFF.
DecodeUTF8Codepoint(uint8 * bytes,size_t * length)597 uint16 DecodeUTF8Codepoint(uint8* bytes, size_t* length) {
598 if (*length == 0) {
599 return 0;
600 }
601 size_t expected = 0;
602 if ((*bytes & 0x80) == 0) {
603 expected = 1;
604 } else if ((*bytes & 0xe0) == 0xc0) {
605 expected = 2;
606 } else if ((*bytes & 0xf0) == 0xe0) {
607 expected = 3;
608 } else {
609 // Too long -- don't accept.
610 *length = 0;
611 return 0;
612 }
613
614 if (*length < expected) {
615 // Not enough bytes -- don't accept.
616 *length = 0;
617 return 0;
618 }
619
620 *length = expected;
621 switch (expected) {
622 case 1:
623 return bytes[0];
624 case 2:
625 return ((bytes[0] & 0x1F) << 6) | ((bytes[1] & 0x3F) << 0);
626 case 3:
627 return ((bytes[0] & 0x0F) << 12) | ((bytes[1] & 0x3F) << 6) |
628 ((bytes[2] & 0x3F) << 0);
629 default:
630 return 0;
631 }
632 }
633
634 // Escapes the contents of a string to be included within double-quotes ("") in
635 // JavaScript. The input data should be a UTF-8 encoded C++ string of chars.
636 // Returns false if |out| was truncated because |in| contained invalid UTF-8 or
637 // codepoints outside the BMP.
638 // TODO(b/115551870): Support codepoints outside the BMP.
EscapeJSString(const std::string & in,std::string * out)639 bool EscapeJSString(const std::string& in, std::string* out) {
640 size_t decoded = 0;
641 for (size_t i = 0; i < in.size(); i += decoded) {
642 uint16 codepoint = 0;
643 // Decode the next UTF-8 codepoint.
644 size_t have_bytes = in.size() - i;
645 uint8 bytes[3] = {
646 static_cast<uint8>(in[i]),
647 static_cast<uint8>(((i + 1) < in.size()) ? in[i + 1] : 0),
648 static_cast<uint8>(((i + 2) < in.size()) ? in[i + 2] : 0),
649 };
650 codepoint = DecodeUTF8Codepoint(bytes, &have_bytes);
651 if (have_bytes == 0) {
652 return false;
653 }
654 decoded = have_bytes;
655
656 switch (codepoint) {
657 case '\'':
658 *out += "\\x27";
659 break;
660 case '"':
661 *out += "\\x22";
662 break;
663 case '<':
664 *out += "\\x3c";
665 break;
666 case '=':
667 *out += "\\x3d";
668 break;
669 case '>':
670 *out += "\\x3e";
671 break;
672 case '&':
673 *out += "\\x26";
674 break;
675 case '\b':
676 *out += "\\b";
677 break;
678 case '\t':
679 *out += "\\t";
680 break;
681 case '\n':
682 *out += "\\n";
683 break;
684 case '\f':
685 *out += "\\f";
686 break;
687 case '\r':
688 *out += "\\r";
689 break;
690 case '\\':
691 *out += "\\\\";
692 break;
693 default:
694 // TODO(b/115551870): Once we're supporting codepoints outside the BMP,
695 // use a single Unicode codepoint escape if the output language is
696 // ECMAScript 2015 or above. Otherwise, use a surrogate pair.
697 // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#String_literals
698 if (codepoint >= 0x20 && codepoint <= 0x7e) {
699 *out += static_cast<char>(codepoint);
700 } else if (codepoint >= 0x100) {
701 *out += StringPrintf("\\u%04x", codepoint);
702 } else {
703 *out += StringPrintf("\\x%02x", codepoint);
704 }
705 break;
706 }
707 }
708 return true;
709 }
710
EscapeBase64(const std::string & in)711 std::string EscapeBase64(const std::string& in) {
712 static const char* kAlphabet =
713 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
714 std::string result;
715
716 for (size_t i = 0; i < in.size(); i += 3) {
717 int value = (in[i] << 16) | (((i + 1) < in.size()) ? (in[i + 1] << 8) : 0) |
718 (((i + 2) < in.size()) ? (in[i + 2] << 0) : 0);
719 result += kAlphabet[(value >> 18) & 0x3f];
720 result += kAlphabet[(value >> 12) & 0x3f];
721 if ((i + 1) < in.size()) {
722 result += kAlphabet[(value >> 6) & 0x3f];
723 } else {
724 result += '=';
725 }
726 if ((i + 2) < in.size()) {
727 result += kAlphabet[(value >> 0) & 0x3f];
728 } else {
729 result += '=';
730 }
731 }
732
733 return result;
734 }
735
736 // Post-process the result of SimpleFtoa/SimpleDtoa to *exactly* match the
737 // original codegen's formatting (which is just .toString() on java.lang.Double
738 // or java.lang.Float).
PostProcessFloat(std::string result)739 std::string PostProcessFloat(std::string result) {
740 // If inf, -inf or nan, replace with +Infinity, -Infinity or NaN.
741 if (result == "inf") {
742 return "Infinity";
743 } else if (result == "-inf") {
744 return "-Infinity";
745 } else if (result == "nan") {
746 return "NaN";
747 }
748
749 // If scientific notation (e.g., "1e10"), (i) capitalize the "e", (ii)
750 // ensure that the mantissa (portion prior to the "e") has at least one
751 // fractional digit (after the decimal point), and (iii) strip any unnecessary
752 // leading zeroes and/or '+' signs from the exponent.
753 std::string::size_type exp_pos = result.find('e');
754 if (exp_pos != std::string::npos) {
755 std::string mantissa = result.substr(0, exp_pos);
756 std::string exponent = result.substr(exp_pos + 1);
757
758 // Add ".0" to mantissa if no fractional part exists.
759 if (mantissa.find('.') == std::string::npos) {
760 mantissa += ".0";
761 }
762
763 // Strip the sign off the exponent and store as |exp_neg|.
764 bool exp_neg = false;
765 if (!exponent.empty() && exponent[0] == '+') {
766 exponent = exponent.substr(1);
767 } else if (!exponent.empty() && exponent[0] == '-') {
768 exp_neg = true;
769 exponent = exponent.substr(1);
770 }
771
772 // Strip any leading zeroes off the exponent.
773 while (exponent.size() > 1 && exponent[0] == '0') {
774 exponent = exponent.substr(1);
775 }
776
777 return mantissa + "E" + string(exp_neg ? "-" : "") + exponent;
778 }
779
780 // Otherwise, this is an ordinary decimal number. Append ".0" if result has no
781 // decimal/fractional part in order to match output of original codegen.
782 if (result.find('.') == std::string::npos) {
783 result += ".0";
784 }
785
786 return result;
787 }
788
FloatToString(float value)789 std::string FloatToString(float value) {
790 std::string result = SimpleFtoa(value);
791 return PostProcessFloat(result);
792 }
793
DoubleToString(double value)794 std::string DoubleToString(double value) {
795 std::string result = SimpleDtoa(value);
796 return PostProcessFloat(result);
797 }
798
799 // Return true if this is an integral field that should be represented as string
800 // in JS.
IsIntegralFieldWithStringJSType(const FieldDescriptor * field)801 bool IsIntegralFieldWithStringJSType(const FieldDescriptor* field) {
802 switch (field->cpp_type()) {
803 case FieldDescriptor::CPPTYPE_INT64:
804 case FieldDescriptor::CPPTYPE_UINT64:
805 // The default value of JSType is JS_NORMAL, which behaves the same as
806 // JS_NUMBER.
807 return field->options().jstype() == FieldOptions::JS_STRING;
808 default:
809 return false;
810 }
811 }
812
MaybeNumberString(const FieldDescriptor * field,const std::string & orig)813 std::string MaybeNumberString(const FieldDescriptor* field,
814 const std::string& orig) {
815 return IsIntegralFieldWithStringJSType(field) ? ("\"" + orig + "\"") : orig;
816 }
817
JSFieldDefault(const FieldDescriptor * field)818 std::string JSFieldDefault(const FieldDescriptor* field) {
819 if (field->is_repeated()) {
820 return "[]";
821 }
822
823 switch (field->cpp_type()) {
824 case FieldDescriptor::CPPTYPE_INT32:
825 return MaybeNumberString(field,
826 StrCat(field->default_value_int32()));
827 case FieldDescriptor::CPPTYPE_UINT32:
828 // The original codegen is in Java, and Java protobufs store unsigned
829 // integer values as signed integer values. In order to exactly match the
830 // output, we need to reinterpret as base-2 signed. Ugh.
831 return MaybeNumberString(
832 field,
833 StrCat(static_cast<int32>(field->default_value_uint32())));
834 case FieldDescriptor::CPPTYPE_INT64:
835 return MaybeNumberString(field,
836 StrCat(field->default_value_int64()));
837 case FieldDescriptor::CPPTYPE_UINT64:
838 // See above note for uint32 -- reinterpreting as signed.
839 return MaybeNumberString(
840 field,
841 StrCat(static_cast<int64>(field->default_value_uint64())));
842 case FieldDescriptor::CPPTYPE_ENUM:
843 return StrCat(field->default_value_enum()->number());
844 case FieldDescriptor::CPPTYPE_BOOL:
845 return field->default_value_bool() ? "true" : "false";
846 case FieldDescriptor::CPPTYPE_FLOAT:
847 return FloatToString(field->default_value_float());
848 case FieldDescriptor::CPPTYPE_DOUBLE:
849 return DoubleToString(field->default_value_double());
850 case FieldDescriptor::CPPTYPE_STRING:
851 if (field->type() == FieldDescriptor::TYPE_STRING) {
852 std::string out;
853 bool is_valid = EscapeJSString(field->default_value_string(), &out);
854 if (!is_valid) {
855 // TODO(b/115551870): Decide whether this should be a hard error.
856 GOOGLE_LOG(WARNING) << "The default value for field " << field->full_name()
857 << " was truncated since it contained invalid UTF-8 or"
858 " codepoints outside the basic multilingual plane.";
859 }
860 return "\"" + out + "\"";
861 } else { // Bytes
862 return "\"" + EscapeBase64(field->default_value_string()) + "\"";
863 }
864 case FieldDescriptor::CPPTYPE_MESSAGE:
865 return "null";
866 }
867 GOOGLE_LOG(FATAL) << "Shouldn't reach here.";
868 return "";
869 }
870
ProtoTypeName(const GeneratorOptions & options,const FieldDescriptor * field)871 std::string ProtoTypeName(const GeneratorOptions& options,
872 const FieldDescriptor* field) {
873 switch (field->type()) {
874 case FieldDescriptor::TYPE_BOOL:
875 return "bool";
876 case FieldDescriptor::TYPE_INT32:
877 return "int32";
878 case FieldDescriptor::TYPE_UINT32:
879 return "uint32";
880 case FieldDescriptor::TYPE_SINT32:
881 return "sint32";
882 case FieldDescriptor::TYPE_FIXED32:
883 return "fixed32";
884 case FieldDescriptor::TYPE_SFIXED32:
885 return "sfixed32";
886 case FieldDescriptor::TYPE_INT64:
887 return "int64";
888 case FieldDescriptor::TYPE_UINT64:
889 return "uint64";
890 case FieldDescriptor::TYPE_SINT64:
891 return "sint64";
892 case FieldDescriptor::TYPE_FIXED64:
893 return "fixed64";
894 case FieldDescriptor::TYPE_SFIXED64:
895 return "sfixed64";
896 case FieldDescriptor::TYPE_FLOAT:
897 return "float";
898 case FieldDescriptor::TYPE_DOUBLE:
899 return "double";
900 case FieldDescriptor::TYPE_STRING:
901 return "string";
902 case FieldDescriptor::TYPE_BYTES:
903 return "bytes";
904 case FieldDescriptor::TYPE_GROUP:
905 return GetMessagePath(options, field->message_type());
906 case FieldDescriptor::TYPE_ENUM:
907 return GetEnumPath(options, field->enum_type());
908 case FieldDescriptor::TYPE_MESSAGE:
909 return GetMessagePath(options, field->message_type());
910 default:
911 return "";
912 }
913 }
914
JSIntegerTypeName(const FieldDescriptor * field)915 std::string JSIntegerTypeName(const FieldDescriptor* field) {
916 return IsIntegralFieldWithStringJSType(field) ? "string" : "number";
917 }
918
JSStringTypeName(const GeneratorOptions & options,const FieldDescriptor * field,BytesMode bytes_mode)919 std::string JSStringTypeName(const GeneratorOptions& options,
920 const FieldDescriptor* field,
921 BytesMode bytes_mode) {
922 if (field->type() == FieldDescriptor::TYPE_BYTES) {
923 switch (bytes_mode) {
924 case BYTES_DEFAULT:
925 return "(string|Uint8Array)";
926 case BYTES_B64:
927 return "string";
928 case BYTES_U8:
929 return "Uint8Array";
930 default:
931 assert(false);
932 }
933 }
934 return "string";
935 }
936
JSTypeName(const GeneratorOptions & options,const FieldDescriptor * field,BytesMode bytes_mode)937 std::string JSTypeName(const GeneratorOptions& options,
938 const FieldDescriptor* field, BytesMode bytes_mode) {
939 switch (field->cpp_type()) {
940 case FieldDescriptor::CPPTYPE_BOOL:
941 return "boolean";
942 case FieldDescriptor::CPPTYPE_INT32:
943 return JSIntegerTypeName(field);
944 case FieldDescriptor::CPPTYPE_INT64:
945 return JSIntegerTypeName(field);
946 case FieldDescriptor::CPPTYPE_UINT32:
947 return JSIntegerTypeName(field);
948 case FieldDescriptor::CPPTYPE_UINT64:
949 return JSIntegerTypeName(field);
950 case FieldDescriptor::CPPTYPE_FLOAT:
951 return "number";
952 case FieldDescriptor::CPPTYPE_DOUBLE:
953 return "number";
954 case FieldDescriptor::CPPTYPE_STRING:
955 return JSStringTypeName(options, field, bytes_mode);
956 case FieldDescriptor::CPPTYPE_ENUM:
957 return GetEnumPath(options, field->enum_type());
958 case FieldDescriptor::CPPTYPE_MESSAGE:
959 return GetMessagePath(options, field->message_type());
960 default:
961 return "";
962 }
963 }
964
965 // Used inside Google only -- do not remove.
UseBrokenPresenceSemantics(const GeneratorOptions & options,const FieldDescriptor * field)966 bool UseBrokenPresenceSemantics(const GeneratorOptions& options,
967 const FieldDescriptor* field) {
968 return false;
969 }
970
971 // Returns true for fields that return "null" from accessors when they are
972 // unset. This should normally only be true for non-repeated submessages, but we
973 // have legacy users who relied on old behavior where accessors behaved this
974 // way.
ReturnsNullWhenUnset(const GeneratorOptions & options,const FieldDescriptor * field)975 bool ReturnsNullWhenUnset(const GeneratorOptions& options,
976 const FieldDescriptor* field) {
977 if (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE &&
978 field->is_optional()) {
979 return true;
980 }
981
982 // TODO(haberman): remove this case and unconditionally return false.
983 return UseBrokenPresenceSemantics(options, field) && !field->is_repeated() &&
984 !field->has_default_value();
985 }
986
987 // In a sane world, this would be the same as ReturnsNullWhenUnset(). But in
988 // the status quo, some fields declare that they never return null/undefined
989 // even though they actually do:
990 // * required fields
991 // * optional enum fields
992 // * proto3 primitive fields.
DeclaredReturnTypeIsNullable(const GeneratorOptions & options,const FieldDescriptor * field)993 bool DeclaredReturnTypeIsNullable(const GeneratorOptions& options,
994 const FieldDescriptor* field) {
995 if (field->is_required() || field->type() == FieldDescriptor::TYPE_ENUM) {
996 return false;
997 }
998
999 if (field->file()->syntax() == FileDescriptor::SYNTAX_PROTO3 &&
1000 field->cpp_type() != FieldDescriptor::CPPTYPE_MESSAGE) {
1001 return false;
1002 }
1003
1004 return ReturnsNullWhenUnset(options, field);
1005 }
1006
SetterAcceptsUndefined(const GeneratorOptions & options,const FieldDescriptor * field)1007 bool SetterAcceptsUndefined(const GeneratorOptions& options,
1008 const FieldDescriptor* field) {
1009 if (ReturnsNullWhenUnset(options, field)) {
1010 return true;
1011 }
1012
1013 // Broken presence semantics always accepts undefined for setters.
1014 return UseBrokenPresenceSemantics(options, field);
1015 }
1016
SetterAcceptsNull(const GeneratorOptions & options,const FieldDescriptor * field)1017 bool SetterAcceptsNull(const GeneratorOptions& options,
1018 const FieldDescriptor* field) {
1019 if (ReturnsNullWhenUnset(options, field)) {
1020 return true;
1021 }
1022
1023 // With broken presence semantics, fields with defaults accept "null" for
1024 // setters, but other fields do not. This is a strange quirk of the old
1025 // codegen.
1026 return UseBrokenPresenceSemantics(options, field) &&
1027 field->has_default_value();
1028 }
1029
1030 // Returns types which are known to by non-nullable by default.
1031 // The style guide requires that we omit "!" in this case.
IsPrimitive(const std::string & type)1032 bool IsPrimitive(const std::string& type) {
1033 return type == "undefined" || type == "string" || type == "number" ||
1034 type == "boolean";
1035 }
1036
JSFieldTypeAnnotation(const GeneratorOptions & options,const FieldDescriptor * field,bool is_setter_argument,bool force_present,bool singular_if_not_packed,BytesMode bytes_mode=BYTES_DEFAULT,bool force_singular=false)1037 std::string JSFieldTypeAnnotation(const GeneratorOptions& options,
1038 const FieldDescriptor* field,
1039 bool is_setter_argument, bool force_present,
1040 bool singular_if_not_packed,
1041 BytesMode bytes_mode = BYTES_DEFAULT,
1042 bool force_singular = false) {
1043 std::string jstype = JSTypeName(options, field, bytes_mode);
1044
1045 if (!force_singular && field->is_repeated() &&
1046 (field->is_packed() || !singular_if_not_packed)) {
1047 if (field->type() == FieldDescriptor::TYPE_BYTES &&
1048 bytes_mode == BYTES_DEFAULT) {
1049 jstype = "(Array<!Uint8Array>|Array<string>)";
1050 } else {
1051 if (!IsPrimitive(jstype)) {
1052 jstype = "!" + jstype;
1053 }
1054 jstype = "Array<" + jstype + ">";
1055 }
1056 }
1057
1058 bool is_null_or_undefined = false;
1059
1060 if (is_setter_argument) {
1061 if (SetterAcceptsNull(options, field)) {
1062 jstype = "?" + jstype;
1063 is_null_or_undefined = true;
1064 }
1065
1066 if (SetterAcceptsUndefined(options, field)) {
1067 jstype += "|undefined";
1068 is_null_or_undefined = true;
1069 }
1070 } else if (force_present) {
1071 // Don't add null or undefined.
1072 } else {
1073 if (DeclaredReturnTypeIsNullable(options, field)) {
1074 jstype = "?" + jstype;
1075 is_null_or_undefined = true;
1076 }
1077 }
1078
1079 if (!is_null_or_undefined && !IsPrimitive(jstype)) {
1080 jstype = "!" + jstype;
1081 }
1082
1083 return jstype;
1084 }
1085
JSBinaryReaderMethodType(const FieldDescriptor * field)1086 std::string JSBinaryReaderMethodType(const FieldDescriptor* field) {
1087 std::string name = field->type_name();
1088 if (name[0] >= 'a' && name[0] <= 'z') {
1089 name[0] = (name[0] - 'a') + 'A';
1090 }
1091 return IsIntegralFieldWithStringJSType(field) ? (name + "String") : name;
1092 }
1093
JSBinaryReadWriteMethodName(const FieldDescriptor * field,bool is_writer)1094 std::string JSBinaryReadWriteMethodName(const FieldDescriptor* field,
1095 bool is_writer) {
1096 std::string name = JSBinaryReaderMethodType(field);
1097 if (field->is_packed()) {
1098 name = "Packed" + name;
1099 } else if (is_writer && field->is_repeated()) {
1100 name = "Repeated" + name;
1101 }
1102 return name;
1103 }
1104
JSBinaryReaderMethodName(const GeneratorOptions & options,const FieldDescriptor * field)1105 std::string JSBinaryReaderMethodName(const GeneratorOptions& options,
1106 const FieldDescriptor* field) {
1107 return "jspb.BinaryReader.prototype.read" +
1108 JSBinaryReadWriteMethodName(field, /* is_writer = */ false);
1109 }
1110
JSBinaryWriterMethodName(const GeneratorOptions & options,const FieldDescriptor * field)1111 std::string JSBinaryWriterMethodName(const GeneratorOptions& options,
1112 const FieldDescriptor* field) {
1113 if (field->containing_type() &&
1114 field->containing_type()->options().message_set_wire_format()) {
1115 return "jspb.BinaryWriter.prototype.writeMessageSet";
1116 }
1117 return "jspb.BinaryWriter.prototype.write" +
1118 JSBinaryReadWriteMethodName(field, /* is_writer = */ true);
1119 }
1120
JSReturnClause(const FieldDescriptor * desc)1121 std::string JSReturnClause(const FieldDescriptor* desc) {
1122 return "";
1123 }
1124
JSTypeTag(const FieldDescriptor * desc)1125 std::string JSTypeTag(const FieldDescriptor* desc) {
1126 switch (desc->type()) {
1127 case FieldDescriptor::TYPE_DOUBLE:
1128 case FieldDescriptor::TYPE_FLOAT:
1129 return "Float";
1130 case FieldDescriptor::TYPE_INT32:
1131 case FieldDescriptor::TYPE_UINT32:
1132 case FieldDescriptor::TYPE_INT64:
1133 case FieldDescriptor::TYPE_UINT64:
1134 case FieldDescriptor::TYPE_FIXED32:
1135 case FieldDescriptor::TYPE_FIXED64:
1136 case FieldDescriptor::TYPE_SINT32:
1137 case FieldDescriptor::TYPE_SINT64:
1138 case FieldDescriptor::TYPE_SFIXED32:
1139 case FieldDescriptor::TYPE_SFIXED64:
1140 if (IsIntegralFieldWithStringJSType(desc)) {
1141 return "StringInt";
1142 } else {
1143 return "Int";
1144 }
1145 case FieldDescriptor::TYPE_BOOL:
1146 return "Boolean";
1147 case FieldDescriptor::TYPE_STRING:
1148 return "String";
1149 case FieldDescriptor::TYPE_BYTES:
1150 return "Bytes";
1151 case FieldDescriptor::TYPE_ENUM:
1152 return "Enum";
1153 default:
1154 assert(false);
1155 }
1156 return "";
1157 }
1158
JSReturnDoc(const GeneratorOptions & options,const FieldDescriptor * desc)1159 std::string JSReturnDoc(const GeneratorOptions& options,
1160 const FieldDescriptor* desc) {
1161 return "";
1162 }
1163
HasRepeatedFields(const GeneratorOptions & options,const Descriptor * desc)1164 bool HasRepeatedFields(const GeneratorOptions& options,
1165 const Descriptor* desc) {
1166 for (int i = 0; i < desc->field_count(); i++) {
1167 if (desc->field(i)->is_repeated() && !desc->field(i)->is_map()) {
1168 return true;
1169 }
1170 }
1171 return false;
1172 }
1173
1174 static const char* kRepeatedFieldArrayName = ".repeatedFields_";
1175
RepeatedFieldsArrayName(const GeneratorOptions & options,const Descriptor * desc)1176 std::string RepeatedFieldsArrayName(const GeneratorOptions& options,
1177 const Descriptor* desc) {
1178 return HasRepeatedFields(options, desc)
1179 ? (GetMessagePath(options, desc) + kRepeatedFieldArrayName)
1180 : "null";
1181 }
1182
HasOneofFields(const Descriptor * desc)1183 bool HasOneofFields(const Descriptor* desc) {
1184 for (int i = 0; i < desc->field_count(); i++) {
1185 if (desc->field(i)->containing_oneof()) {
1186 return true;
1187 }
1188 }
1189 return false;
1190 }
1191
1192 static const char* kOneofGroupArrayName = ".oneofGroups_";
1193
OneofFieldsArrayName(const GeneratorOptions & options,const Descriptor * desc)1194 std::string OneofFieldsArrayName(const GeneratorOptions& options,
1195 const Descriptor* desc) {
1196 return HasOneofFields(desc)
1197 ? (GetMessagePath(options, desc) + kOneofGroupArrayName)
1198 : "null";
1199 }
1200
RepeatedFieldNumberList(const GeneratorOptions & options,const Descriptor * desc)1201 std::string RepeatedFieldNumberList(const GeneratorOptions& options,
1202 const Descriptor* desc) {
1203 std::vector<std::string> numbers;
1204 for (int i = 0; i < desc->field_count(); i++) {
1205 if (desc->field(i)->is_repeated() && !desc->field(i)->is_map()) {
1206 numbers.push_back(JSFieldIndex(desc->field(i)));
1207 }
1208 }
1209 return "[" + Join(numbers, ",") + "]";
1210 }
1211
OneofGroupList(const Descriptor * desc)1212 std::string OneofGroupList(const Descriptor* desc) {
1213 // List of arrays (one per oneof), each of which is a list of field indices
1214 std::vector<std::string> oneof_entries;
1215 for (int i = 0; i < desc->oneof_decl_count(); i++) {
1216 const OneofDescriptor* oneof = desc->oneof_decl(i);
1217 if (IgnoreOneof(oneof)) {
1218 continue;
1219 }
1220
1221 std::vector<std::string> oneof_fields;
1222 for (int j = 0; j < oneof->field_count(); j++) {
1223 if (IgnoreField(oneof->field(j))) {
1224 continue;
1225 }
1226 oneof_fields.push_back(JSFieldIndex(oneof->field(j)));
1227 }
1228 oneof_entries.push_back("[" + Join(oneof_fields, ",") + "]");
1229 }
1230 return "[" + Join(oneof_entries, ",") + "]";
1231 }
1232
JSOneofArray(const GeneratorOptions & options,const FieldDescriptor * field)1233 std::string JSOneofArray(const GeneratorOptions& options,
1234 const FieldDescriptor* field) {
1235 return OneofFieldsArrayName(options, field->containing_type()) + "[" +
1236 JSOneofIndex(field->containing_oneof()) + "]";
1237 }
1238
RelativeTypeName(const FieldDescriptor * field)1239 std::string RelativeTypeName(const FieldDescriptor* field) {
1240 assert(field->cpp_type() == FieldDescriptor::CPPTYPE_ENUM ||
1241 field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE);
1242 // For a field with an enum or message type, compute a name relative to the
1243 // path name of the message type containing this field.
1244 std::string package = field->file()->package();
1245 std::string containing_type = field->containing_type()->full_name() + ".";
1246 std::string type = (field->cpp_type() == FieldDescriptor::CPPTYPE_ENUM)
1247 ? field->enum_type()->full_name()
1248 : field->message_type()->full_name();
1249
1250 // |prefix| is advanced as we find separators '.' past the common package
1251 // prefix that yield common prefixes in the containing type's name and this
1252 // type's name.
1253 int prefix = 0;
1254 for (int i = 0; i < type.size() && i < containing_type.size(); i++) {
1255 if (type[i] != containing_type[i]) {
1256 break;
1257 }
1258 if (type[i] == '.' && i >= package.size()) {
1259 prefix = i + 1;
1260 }
1261 }
1262
1263 return type.substr(prefix);
1264 }
1265
JSExtensionsObjectName(const GeneratorOptions & options,const FileDescriptor * from_file,const Descriptor * desc)1266 std::string JSExtensionsObjectName(const GeneratorOptions& options,
1267 const FileDescriptor* from_file,
1268 const Descriptor* desc) {
1269 if (desc->full_name() == "google.protobuf.bridge.MessageSet") {
1270 // TODO(haberman): fix this for the kImportCommonJs case.
1271 return "jspb.Message.messageSetExtensions";
1272 } else {
1273 return MaybeCrossFileRef(options, from_file, desc) + ".extensions";
1274 }
1275 }
1276
1277 static const int kMapKeyField = 1;
1278 static const int kMapValueField = 2;
1279
MapFieldKey(const FieldDescriptor * field)1280 const FieldDescriptor* MapFieldKey(const FieldDescriptor* field) {
1281 assert(field->is_map());
1282 return field->message_type()->FindFieldByNumber(kMapKeyField);
1283 }
1284
MapFieldValue(const FieldDescriptor * field)1285 const FieldDescriptor* MapFieldValue(const FieldDescriptor* field) {
1286 assert(field->is_map());
1287 return field->message_type()->FindFieldByNumber(kMapValueField);
1288 }
1289
FieldDefinition(const GeneratorOptions & options,const FieldDescriptor * field)1290 std::string FieldDefinition(const GeneratorOptions& options,
1291 const FieldDescriptor* field) {
1292 if (field->is_map()) {
1293 const FieldDescriptor* key_field = MapFieldKey(field);
1294 const FieldDescriptor* value_field = MapFieldValue(field);
1295 std::string key_type = ProtoTypeName(options, key_field);
1296 std::string value_type;
1297 if (value_field->type() == FieldDescriptor::TYPE_ENUM ||
1298 value_field->type() == FieldDescriptor::TYPE_MESSAGE) {
1299 value_type = RelativeTypeName(value_field);
1300 } else {
1301 value_type = ProtoTypeName(options, value_field);
1302 }
1303 return StringPrintf("map<%s, %s> %s = %d;", key_type.c_str(),
1304 value_type.c_str(), field->name().c_str(),
1305 field->number());
1306 } else {
1307 std::string qualifier =
1308 field->is_repeated() ? "repeated"
1309 : (field->is_optional() ? "optional" : "required");
1310 std::string type, name;
1311 if (field->type() == FieldDescriptor::TYPE_ENUM ||
1312 field->type() == FieldDescriptor::TYPE_MESSAGE) {
1313 type = RelativeTypeName(field);
1314 name = field->name();
1315 } else if (field->type() == FieldDescriptor::TYPE_GROUP) {
1316 type = "group";
1317 name = field->message_type()->name();
1318 } else {
1319 type = ProtoTypeName(options, field);
1320 name = field->name();
1321 }
1322 return StringPrintf("%s %s %s = %d;", qualifier.c_str(), type.c_str(),
1323 name.c_str(), field->number());
1324 }
1325 }
1326
FieldComments(const FieldDescriptor * field,BytesMode bytes_mode)1327 std::string FieldComments(const FieldDescriptor* field, BytesMode bytes_mode) {
1328 std::string comments;
1329 if (field->type() == FieldDescriptor::TYPE_BYTES && bytes_mode == BYTES_U8) {
1330 comments +=
1331 " * Note that Uint8Array is not supported on all browsers.\n"
1332 " * @see http://caniuse.com/Uint8Array\n";
1333 }
1334 return comments;
1335 }
1336
ShouldGenerateExtension(const FieldDescriptor * field)1337 bool ShouldGenerateExtension(const FieldDescriptor* field) {
1338 return field->is_extension() && !IgnoreField(field);
1339 }
1340
HasExtensions(const Descriptor * desc)1341 bool HasExtensions(const Descriptor* desc) {
1342 for (int i = 0; i < desc->extension_count(); i++) {
1343 if (ShouldGenerateExtension(desc->extension(i))) {
1344 return true;
1345 }
1346 }
1347 for (int i = 0; i < desc->nested_type_count(); i++) {
1348 if (HasExtensions(desc->nested_type(i))) {
1349 return true;
1350 }
1351 }
1352 return false;
1353 }
1354
HasExtensions(const FileDescriptor * file)1355 bool HasExtensions(const FileDescriptor* file) {
1356 for (int i = 0; i < file->extension_count(); i++) {
1357 if (ShouldGenerateExtension(file->extension(i))) {
1358 return true;
1359 }
1360 }
1361 for (int i = 0; i < file->message_type_count(); i++) {
1362 if (HasExtensions(file->message_type(i))) {
1363 return true;
1364 }
1365 }
1366 return false;
1367 }
1368
HasMap(const GeneratorOptions & options,const Descriptor * desc)1369 bool HasMap(const GeneratorOptions& options, const Descriptor* desc) {
1370 for (int i = 0; i < desc->field_count(); i++) {
1371 if (desc->field(i)->is_map()) {
1372 return true;
1373 }
1374 }
1375 for (int i = 0; i < desc->nested_type_count(); i++) {
1376 if (HasMap(options, desc->nested_type(i))) {
1377 return true;
1378 }
1379 }
1380 return false;
1381 }
1382
FileHasMap(const GeneratorOptions & options,const FileDescriptor * desc)1383 bool FileHasMap(const GeneratorOptions& options, const FileDescriptor* desc) {
1384 for (int i = 0; i < desc->message_type_count(); i++) {
1385 if (HasMap(options, desc->message_type(i))) {
1386 return true;
1387 }
1388 }
1389 return false;
1390 }
1391
IsExtendable(const Descriptor * desc)1392 bool IsExtendable(const Descriptor* desc) {
1393 return desc->extension_range_count() > 0;
1394 }
1395
1396 // Returns the max index in the underlying data storage array beyond which the
1397 // extension object is used.
GetPivot(const Descriptor * desc)1398 std::string GetPivot(const Descriptor* desc) {
1399 static const int kDefaultPivot = 500;
1400
1401 // Find the max field number
1402 int max_field_number = 0;
1403 for (int i = 0; i < desc->field_count(); i++) {
1404 if (!IgnoreField(desc->field(i)) &&
1405 desc->field(i)->number() > max_field_number) {
1406 max_field_number = desc->field(i)->number();
1407 }
1408 }
1409
1410 int pivot = -1;
1411 if (IsExtendable(desc) || (max_field_number >= kDefaultPivot)) {
1412 pivot = ((max_field_number + 1) < kDefaultPivot) ? (max_field_number + 1)
1413 : kDefaultPivot;
1414 }
1415
1416 return StrCat(pivot);
1417 }
1418
1419 // Whether this field represents presence. For fields with presence, we
1420 // generate extra methods (clearFoo() and hasFoo()) for this field.
HasFieldPresence(const GeneratorOptions & options,const FieldDescriptor * field)1421 bool HasFieldPresence(const GeneratorOptions& options,
1422 const FieldDescriptor* field) {
1423 if (field->is_repeated() || field->is_map()) {
1424 // We say repeated fields and maps don't have presence, but we still do
1425 // generate clearFoo() methods for them through a special case elsewhere.
1426 return false;
1427 }
1428
1429 if (UseBrokenPresenceSemantics(options, field)) {
1430 // Proto3 files with broken presence semantics have field presence.
1431 return true;
1432 }
1433
1434 return field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE ||
1435 field->containing_oneof() != NULL ||
1436 field->file()->syntax() == FileDescriptor::SYNTAX_PROTO2;
1437 }
1438
1439 // We use this to implement the semantics that same file can be generated
1440 // multiple times, but only the last one keep the short name. Others all use
1441 // long name with extra information to distinguish (For message and enum, the
1442 // extra information is package name, for file level extension, the extra
1443 // information is proto's filename).
1444 // We never actually write the files, but we keep a set of which descriptors
1445 // were the final one for a given filename.
1446 class FileDeduplicator {
1447 public:
FileDeduplicator(const GeneratorOptions & options)1448 explicit FileDeduplicator(const GeneratorOptions& options)
1449 : error_on_conflict_(options.error_on_name_conflict) {}
1450
1451 // params:
1452 // filenames: a pair of {short filename, full filename}
1453 // (short filename don't have extra information, full filename
1454 // contains extra information)
1455 // desc: The Descriptor or SCC pointer or EnumDescriptor.
1456 // error: The returned error information.
AddFile(const std::pair<std::string,std::string> filenames,const void * desc,std::string * error)1457 bool AddFile(const std::pair<std::string, std::string> filenames,
1458 const void* desc, std::string* error) {
1459 if (descs_by_shortname_.find(filenames.first) !=
1460 descs_by_shortname_.end()) {
1461 if (error_on_conflict_) {
1462 *error = "Name conflict: file name " + filenames.first +
1463 " would be generated by two descriptors";
1464 return false;
1465 }
1466 // Change old pointer's actual name to full name.
1467 auto short_name_desc = descs_by_shortname_[filenames.first];
1468 allowed_descs_actual_name_[short_name_desc] =
1469 allowed_descs_full_name_[short_name_desc];
1470 }
1471 descs_by_shortname_[filenames.first] = desc;
1472 allowed_descs_actual_name_[desc] = filenames.first;
1473 allowed_descs_full_name_[desc] = filenames.second;
1474
1475 return true;
1476 }
1477
GetAllowedMap(std::map<const void *,std::string> * allowed_set)1478 void GetAllowedMap(std::map<const void*, std::string>* allowed_set) {
1479 *allowed_set = allowed_descs_actual_name_;
1480 }
1481
1482 private:
1483 bool error_on_conflict_;
1484 // The map that restores all the descs that are using short name as filename.
1485 std::map<std::string, const void*> descs_by_shortname_;
1486 // The final actual filename map.
1487 std::map<const void*, std::string> allowed_descs_actual_name_;
1488 // The full name map.
1489 std::map<const void*, std::string> allowed_descs_full_name_;
1490 };
1491
DepthFirstSearch(const FileDescriptor * file,std::vector<const FileDescriptor * > * list,std::set<const FileDescriptor * > * seen)1492 void DepthFirstSearch(const FileDescriptor* file,
1493 std::vector<const FileDescriptor*>* list,
1494 std::set<const FileDescriptor*>* seen) {
1495 if (!seen->insert(file).second) {
1496 return;
1497 }
1498
1499 // Add all dependencies.
1500 for (int i = 0; i < file->dependency_count(); i++) {
1501 DepthFirstSearch(file->dependency(i), list, seen);
1502 }
1503
1504 // Add this file.
1505 list->push_back(file);
1506 }
1507
1508 // A functor for the predicate to remove_if() below. Returns true if a given
1509 // FileDescriptor is not in the given set.
1510 class NotInSet {
1511 public:
NotInSet(const std::set<const FileDescriptor * > & file_set)1512 explicit NotInSet(const std::set<const FileDescriptor*>& file_set)
1513 : file_set_(file_set) {}
1514
operator ()(const FileDescriptor * file)1515 bool operator()(const FileDescriptor* file) {
1516 return file_set_.count(file) == 0;
1517 }
1518
1519 private:
1520 const std::set<const FileDescriptor*>& file_set_;
1521 };
1522
1523 // This function generates an ordering of the input FileDescriptors that matches
1524 // the logic of the old code generator. The order is significant because two
1525 // different input files can generate the same output file, and the last one
1526 // needs to win.
GenerateJspbFileOrder(const std::vector<const FileDescriptor * > & input,std::vector<const FileDescriptor * > * ordered)1527 void GenerateJspbFileOrder(const std::vector<const FileDescriptor*>& input,
1528 std::vector<const FileDescriptor*>* ordered) {
1529 // First generate an ordering of all reachable files (including dependencies)
1530 // with depth-first search. This mimics the behavior of --include_imports,
1531 // which is what the old codegen used.
1532 ordered->clear();
1533 std::set<const FileDescriptor*> seen;
1534 std::set<const FileDescriptor*> input_set;
1535 for (int i = 0; i < input.size(); i++) {
1536 DepthFirstSearch(input[i], ordered, &seen);
1537 input_set.insert(input[i]);
1538 }
1539
1540 // Now remove the entries that are not actually in our input list.
1541 ordered->erase(
1542 std::remove_if(ordered->begin(), ordered->end(), NotInSet(input_set)),
1543 ordered->end());
1544 }
1545
1546 // If we're generating code in file-per-type mode, avoid overwriting files
1547 // by choosing the last descriptor that writes each filename and permitting
1548 // only those to generate code.
1549
1550 struct DepsGenerator {
operator ()google::protobuf::compiler::js::__anon397b4de80111::DepsGenerator1551 std::vector<const Descriptor*> operator()(const Descriptor* desc) const {
1552 std::vector<const Descriptor*> deps;
1553 auto maybe_add = [&](const Descriptor* d) {
1554 if (d) deps.push_back(d);
1555 };
1556 for (int i = 0; i < desc->field_count(); i++) {
1557 if (!IgnoreField(desc->field(i))) {
1558 maybe_add(desc->field(i)->message_type());
1559 }
1560 }
1561 for (int i = 0; i < desc->extension_count(); i++) {
1562 maybe_add(desc->extension(i)->message_type());
1563 maybe_add(desc->extension(i)->containing_type());
1564 }
1565 for (int i = 0; i < desc->nested_type_count(); i++) {
1566 maybe_add(desc->nested_type(i));
1567 }
1568 maybe_add(desc->containing_type());
1569
1570 return deps;
1571 }
1572 };
1573
GenerateJspbAllowedMap(const GeneratorOptions & options,const std::vector<const FileDescriptor * > & files,std::map<const void *,std::string> * allowed_set,SCCAnalyzer<DepsGenerator> * analyzer,std::string * error)1574 bool GenerateJspbAllowedMap(const GeneratorOptions& options,
1575 const std::vector<const FileDescriptor*>& files,
1576 std::map<const void*, std::string>* allowed_set,
1577 SCCAnalyzer<DepsGenerator>* analyzer,
1578 std::string* error) {
1579 std::vector<const FileDescriptor*> files_ordered;
1580 GenerateJspbFileOrder(files, &files_ordered);
1581
1582 // Choose the last descriptor for each filename.
1583 FileDeduplicator dedup(options);
1584 std::set<const SCC*> added;
1585 for (int i = 0; i < files_ordered.size(); i++) {
1586 for (int j = 0; j < files_ordered[i]->message_type_count(); j++) {
1587 const Descriptor* desc = files_ordered[i]->message_type(j);
1588 if (added.insert(analyzer->GetSCC(desc)).second &&
1589 !dedup.AddFile(
1590 std::make_pair(
1591 GetMessagesFileName(options, analyzer->GetSCC(desc), false),
1592 GetMessagesFileName(options, analyzer->GetSCC(desc), true)),
1593 analyzer->GetSCC(desc), error)) {
1594 return false;
1595 }
1596 }
1597 for (int j = 0; j < files_ordered[i]->enum_type_count(); j++) {
1598 const EnumDescriptor* desc = files_ordered[i]->enum_type(j);
1599 if (!dedup.AddFile(std::make_pair(GetEnumFileName(options, desc, false),
1600 GetEnumFileName(options, desc, true)),
1601 desc, error)) {
1602 return false;
1603 }
1604 }
1605
1606 // Pull out all free-floating extensions and generate files for those too.
1607 bool has_extension = false;
1608
1609 for (int j = 0; j < files_ordered[i]->extension_count(); j++) {
1610 if (ShouldGenerateExtension(files_ordered[i]->extension(j))) {
1611 has_extension = true;
1612 }
1613 }
1614
1615 if (has_extension) {
1616 if (!dedup.AddFile(
1617 std::make_pair(
1618 GetExtensionFileName(options, files_ordered[i], false),
1619 GetExtensionFileName(options, files_ordered[i], true)),
1620 files_ordered[i], error)) {
1621 return false;
1622 }
1623 }
1624 }
1625
1626 dedup.GetAllowedMap(allowed_set);
1627
1628 return true;
1629 }
1630
1631 // Embeds base64 encoded GeneratedCodeInfo proto in a comment at the end of
1632 // file.
EmbedCodeAnnotations(const GeneratedCodeInfo & annotations,io::Printer * printer)1633 void EmbedCodeAnnotations(const GeneratedCodeInfo& annotations,
1634 io::Printer* printer) {
1635 // Serialize annotations proto into base64 string.
1636 std::string meta_content;
1637 annotations.SerializeToString(&meta_content);
1638 std::string meta_64;
1639 Base64Escape(meta_content, &meta_64);
1640
1641 // Print base64 encoded annotations at the end of output file in
1642 // a comment.
1643 printer->Print("\n// Below is base64 encoded GeneratedCodeInfo proto");
1644 printer->Print("\n// $encoded_proto$\n", "encoded_proto", meta_64);
1645 }
1646
IsWellKnownTypeFile(const FileDescriptor * file)1647 bool IsWellKnownTypeFile(const FileDescriptor* file) {
1648 return HasPrefixString(file->name(), "google/protobuf/");
1649 }
1650
1651 } // anonymous namespace
1652
GenerateHeader(const GeneratorOptions & options,const FileDescriptor * file,io::Printer * printer) const1653 void Generator::GenerateHeader(const GeneratorOptions& options,
1654 const FileDescriptor* file,
1655 io::Printer* printer) const {
1656 if (file != nullptr) {
1657 printer->Print("// source: $filename$\n", "filename", file->name());
1658 }
1659 printer->Print(
1660 "/**\n"
1661 " * @fileoverview\n"
1662 " * @enhanceable\n"
1663 " * @suppress {messageConventions} JS Compiler reports an "
1664 "error if a variable or\n"
1665 " * field starts with 'MSG_' and isn't a translatable "
1666 "message.\n"
1667 " * @public\n"
1668 " */\n"
1669 "// GENERATED CODE -- DO NOT EDIT!\n"
1670 "\n");
1671 }
1672
FindProvidesForFile(const GeneratorOptions & options,io::Printer * printer,const FileDescriptor * file,std::set<std::string> * provided) const1673 void Generator::FindProvidesForFile(const GeneratorOptions& options,
1674 io::Printer* printer,
1675 const FileDescriptor* file,
1676 std::set<std::string>* provided) const {
1677 for (int i = 0; i < file->message_type_count(); i++) {
1678 FindProvidesForMessage(options, printer, file->message_type(i), provided);
1679 }
1680 for (int i = 0; i < file->enum_type_count(); i++) {
1681 FindProvidesForEnum(options, printer, file->enum_type(i), provided);
1682 }
1683 }
1684
FindProvides(const GeneratorOptions & options,io::Printer * printer,const std::vector<const FileDescriptor * > & files,std::set<std::string> * provided) const1685 void Generator::FindProvides(const GeneratorOptions& options,
1686 io::Printer* printer,
1687 const std::vector<const FileDescriptor*>& files,
1688 std::set<std::string>* provided) const {
1689 for (int i = 0; i < files.size(); i++) {
1690 FindProvidesForFile(options, printer, files[i], provided);
1691 }
1692
1693 printer->Print("\n");
1694 }
1695
FindProvidesForOneOfEnum(const GeneratorOptions & options,const OneofDescriptor * oneof,std::set<std::string> * provided)1696 void FindProvidesForOneOfEnum(const GeneratorOptions& options,
1697 const OneofDescriptor* oneof,
1698 std::set<std::string>* provided) {
1699 std::string name = GetMessagePath(options, oneof->containing_type()) + "." +
1700 JSOneofName(oneof) + "Case";
1701 provided->insert(name);
1702 }
1703
FindProvidesForOneOfEnums(const GeneratorOptions & options,io::Printer * printer,const Descriptor * desc,std::set<std::string> * provided)1704 void FindProvidesForOneOfEnums(const GeneratorOptions& options,
1705 io::Printer* printer, const Descriptor* desc,
1706 std::set<std::string>* provided) {
1707 if (HasOneofFields(desc)) {
1708 for (int i = 0; i < desc->oneof_decl_count(); i++) {
1709 if (IgnoreOneof(desc->oneof_decl(i))) {
1710 continue;
1711 }
1712 FindProvidesForOneOfEnum(options, desc->oneof_decl(i), provided);
1713 }
1714 }
1715 }
1716
FindProvidesForMessage(const GeneratorOptions & options,io::Printer * printer,const Descriptor * desc,std::set<std::string> * provided) const1717 void Generator::FindProvidesForMessage(const GeneratorOptions& options,
1718 io::Printer* printer,
1719 const Descriptor* desc,
1720 std::set<std::string>* provided) const {
1721 if (IgnoreMessage(desc)) {
1722 return;
1723 }
1724
1725 std::string name = GetMessagePath(options, desc);
1726 provided->insert(name);
1727
1728 for (int i = 0; i < desc->enum_type_count(); i++) {
1729 FindProvidesForEnum(options, printer, desc->enum_type(i), provided);
1730 }
1731
1732 FindProvidesForOneOfEnums(options, printer, desc, provided);
1733
1734 for (int i = 0; i < desc->nested_type_count(); i++) {
1735 FindProvidesForMessage(options, printer, desc->nested_type(i), provided);
1736 }
1737 }
FindProvidesForEnum(const GeneratorOptions & options,io::Printer * printer,const EnumDescriptor * enumdesc,std::set<std::string> * provided) const1738 void Generator::FindProvidesForEnum(const GeneratorOptions& options,
1739 io::Printer* printer,
1740 const EnumDescriptor* enumdesc,
1741 std::set<std::string>* provided) const {
1742 std::string name = GetEnumPath(options, enumdesc);
1743 provided->insert(name);
1744 }
1745
FindProvidesForFields(const GeneratorOptions & options,io::Printer * printer,const std::vector<const FieldDescriptor * > & fields,std::set<std::string> * provided) const1746 void Generator::FindProvidesForFields(
1747 const GeneratorOptions& options, io::Printer* printer,
1748 const std::vector<const FieldDescriptor*>& fields,
1749 std::set<std::string>* provided) const {
1750 for (int i = 0; i < fields.size(); i++) {
1751 const FieldDescriptor* field = fields[i];
1752
1753 if (IgnoreField(field)) {
1754 continue;
1755 }
1756
1757 std::string name = GetNamespace(options, field->file()) + "." +
1758 JSObjectFieldName(options, field);
1759 provided->insert(name);
1760 }
1761 }
1762
GenerateProvides(const GeneratorOptions & options,io::Printer * printer,std::set<std::string> * provided) const1763 void Generator::GenerateProvides(const GeneratorOptions& options,
1764 io::Printer* printer,
1765 std::set<std::string>* provided) const {
1766 for (std::set<std::string>::iterator it = provided->begin();
1767 it != provided->end(); ++it) {
1768 if (options.import_style == GeneratorOptions::kImportClosure) {
1769 printer->Print("goog.provide('$name$');\n", "name", *it);
1770 } else {
1771 // We aren't using Closure's import system, but we use goog.exportSymbol()
1772 // to construct the expected tree of objects, eg.
1773 //
1774 // goog.exportSymbol('foo.bar.Baz', null, this);
1775 //
1776 // // Later generated code expects foo.bar = {} to exist:
1777 // foo.bar.Baz = function() { /* ... */ }
1778
1779 // Do not use global scope in strict mode
1780 if (options.import_style == GeneratorOptions::kImportCommonJsStrict) {
1781 std::string namespaceObject = *it;
1782 // Remove "proto." from the namespace object
1783 GOOGLE_CHECK_EQ(0, namespaceObject.compare(0, 6, "proto."));
1784 namespaceObject.erase(0, 6);
1785 printer->Print("goog.exportSymbol('$name$', null, proto);\n", "name",
1786 namespaceObject);
1787 } else {
1788 printer->Print("goog.exportSymbol('$name$', null, global);\n", "name",
1789 *it);
1790 }
1791 }
1792 }
1793 }
1794
GenerateRequiresForSCC(const GeneratorOptions & options,io::Printer * printer,const SCC * scc,std::set<std::string> * provided) const1795 void Generator::GenerateRequiresForSCC(const GeneratorOptions& options,
1796 io::Printer* printer, const SCC* scc,
1797 std::set<std::string>* provided) const {
1798 std::set<std::string> required;
1799 std::set<std::string> forwards;
1800 bool have_message = false;
1801 bool has_extension = false;
1802 bool has_map = false;
1803 for (auto desc : scc->descriptors) {
1804 if (desc->containing_type() == nullptr) {
1805 FindRequiresForMessage(options, desc, &required, &forwards,
1806 &have_message);
1807 has_extension = (has_extension || HasExtensions(desc));
1808 has_map = (has_map || HasMap(options, desc));
1809 }
1810 }
1811
1812 GenerateRequiresImpl(options, printer, &required, &forwards, provided,
1813 /* require_jspb = */ have_message,
1814 /* require_extension = */ has_extension,
1815 /* require_map = */ has_map);
1816 }
1817
GenerateRequiresForLibrary(const GeneratorOptions & options,io::Printer * printer,const std::vector<const FileDescriptor * > & files,std::set<std::string> * provided) const1818 void Generator::GenerateRequiresForLibrary(
1819 const GeneratorOptions& options, io::Printer* printer,
1820 const std::vector<const FileDescriptor*>& files,
1821 std::set<std::string>* provided) const {
1822 GOOGLE_CHECK_EQ(options.import_style, GeneratorOptions::kImportClosure);
1823 // For Closure imports we need to import every message type individually.
1824 std::set<std::string> required;
1825 std::set<std::string> forwards;
1826 bool have_extensions = false;
1827 bool have_map = false;
1828 bool have_message = false;
1829
1830 for (int i = 0; i < files.size(); i++) {
1831 for (int j = 0; j < files[i]->message_type_count(); j++) {
1832 const Descriptor* desc = files[i]->message_type(j);
1833 if (!IgnoreMessage(desc)) {
1834 FindRequiresForMessage(options, desc, &required, &forwards,
1835 &have_message);
1836 }
1837 }
1838
1839 if (!have_extensions && HasExtensions(files[i])) {
1840 have_extensions = true;
1841 }
1842
1843 if (!have_map && FileHasMap(options, files[i])) {
1844 have_map = true;
1845 }
1846
1847 for (int j = 0; j < files[i]->extension_count(); j++) {
1848 const FieldDescriptor* extension = files[i]->extension(j);
1849 if (IgnoreField(extension)) {
1850 continue;
1851 }
1852 if (extension->containing_type()->full_name() !=
1853 "google.protobuf.bridge.MessageSet") {
1854 required.insert(GetMessagePath(options, extension->containing_type()));
1855 }
1856 FindRequiresForField(options, extension, &required, &forwards);
1857 have_extensions = true;
1858 }
1859 }
1860
1861 GenerateRequiresImpl(options, printer, &required, &forwards, provided,
1862 /* require_jspb = */ have_message,
1863 /* require_extension = */ have_extensions,
1864 /* require_map = */ have_map);
1865 }
1866
GenerateRequiresForExtensions(const GeneratorOptions & options,io::Printer * printer,const std::vector<const FieldDescriptor * > & fields,std::set<std::string> * provided) const1867 void Generator::GenerateRequiresForExtensions(
1868 const GeneratorOptions& options, io::Printer* printer,
1869 const std::vector<const FieldDescriptor*>& fields,
1870 std::set<std::string>* provided) const {
1871 std::set<std::string> required;
1872 std::set<std::string> forwards;
1873 for (int i = 0; i < fields.size(); i++) {
1874 const FieldDescriptor* field = fields[i];
1875 if (IgnoreField(field)) {
1876 continue;
1877 }
1878 FindRequiresForExtension(options, field, &required, &forwards);
1879 }
1880
1881 GenerateRequiresImpl(options, printer, &required, &forwards, provided,
1882 /* require_jspb = */ false,
1883 /* require_extension = */ fields.size() > 0,
1884 /* require_map = */ false);
1885 }
1886
GenerateRequiresImpl(const GeneratorOptions & options,io::Printer * printer,std::set<std::string> * required,std::set<std::string> * forwards,std::set<std::string> * provided,bool require_jspb,bool require_extension,bool require_map) const1887 void Generator::GenerateRequiresImpl(const GeneratorOptions& options,
1888 io::Printer* printer,
1889 std::set<std::string>* required,
1890 std::set<std::string>* forwards,
1891 std::set<std::string>* provided,
1892 bool require_jspb, bool require_extension,
1893 bool require_map) const {
1894 if (require_jspb) {
1895 required->insert("jspb.Message");
1896 required->insert("jspb.BinaryReader");
1897 required->insert("jspb.BinaryWriter");
1898 }
1899 if (require_extension) {
1900 required->insert("jspb.ExtensionFieldBinaryInfo");
1901 required->insert("jspb.ExtensionFieldInfo");
1902 }
1903 if (require_map) {
1904 required->insert("jspb.Map");
1905 }
1906
1907 std::set<std::string>::iterator it;
1908 for (it = required->begin(); it != required->end(); ++it) {
1909 if (provided->find(*it) != provided->end()) {
1910 continue;
1911 }
1912 printer->Print("goog.require('$name$');\n", "name", *it);
1913 }
1914
1915 printer->Print("\n");
1916
1917 for (it = forwards->begin(); it != forwards->end(); ++it) {
1918 if (provided->find(*it) != provided->end()) {
1919 continue;
1920 }
1921 printer->Print("goog.forwardDeclare('$name$');\n", "name", *it);
1922 }
1923 }
1924
NamespaceOnly(const Descriptor * desc)1925 bool NamespaceOnly(const Descriptor* desc) {
1926 return false;
1927 }
1928
FindRequiresForMessage(const GeneratorOptions & options,const Descriptor * desc,std::set<std::string> * required,std::set<std::string> * forwards,bool * have_message) const1929 void Generator::FindRequiresForMessage(const GeneratorOptions& options,
1930 const Descriptor* desc,
1931 std::set<std::string>* required,
1932 std::set<std::string>* forwards,
1933 bool* have_message) const {
1934
1935 if (!NamespaceOnly(desc)) {
1936 *have_message = true;
1937 for (int i = 0; i < desc->field_count(); i++) {
1938 const FieldDescriptor* field = desc->field(i);
1939 if (IgnoreField(field)) {
1940 continue;
1941 }
1942 FindRequiresForField(options, field, required, forwards);
1943 }
1944 }
1945
1946 for (int i = 0; i < desc->extension_count(); i++) {
1947 const FieldDescriptor* field = desc->extension(i);
1948 if (IgnoreField(field)) {
1949 continue;
1950 }
1951 FindRequiresForExtension(options, field, required, forwards);
1952 }
1953
1954 for (int i = 0; i < desc->nested_type_count(); i++) {
1955 FindRequiresForMessage(options, desc->nested_type(i), required, forwards,
1956 have_message);
1957 }
1958 }
1959
FindRequiresForField(const GeneratorOptions & options,const FieldDescriptor * field,std::set<std::string> * required,std::set<std::string> * forwards) const1960 void Generator::FindRequiresForField(const GeneratorOptions& options,
1961 const FieldDescriptor* field,
1962 std::set<std::string>* required,
1963 std::set<std::string>* forwards) const {
1964 if (field->cpp_type() == FieldDescriptor::CPPTYPE_ENUM &&
1965 // N.B.: file-level extensions with enum type do *not* create
1966 // dependencies, as per original codegen.
1967 !(field->is_extension() && field->extension_scope() == nullptr)) {
1968 if (options.add_require_for_enums) {
1969 required->insert(GetEnumPath(options, field->enum_type()));
1970 } else {
1971 forwards->insert(GetEnumPath(options, field->enum_type()));
1972 }
1973 } else if (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) {
1974 if (!IgnoreMessage(field->message_type())) {
1975 required->insert(GetMessagePath(options, field->message_type()));
1976 }
1977 }
1978 }
1979
FindRequiresForExtension(const GeneratorOptions & options,const FieldDescriptor * field,std::set<std::string> * required,std::set<std::string> * forwards) const1980 void Generator::FindRequiresForExtension(
1981 const GeneratorOptions& options, const FieldDescriptor* field,
1982 std::set<std::string>* required, std::set<std::string>* forwards) const {
1983 if (field->containing_type()->full_name() != "google.protobuf.bridge.MessageSet") {
1984 required->insert(GetMessagePath(options, field->containing_type()));
1985 }
1986 FindRequiresForField(options, field, required, forwards);
1987 }
1988
GenerateTestOnly(const GeneratorOptions & options,io::Printer * printer) const1989 void Generator::GenerateTestOnly(const GeneratorOptions& options,
1990 io::Printer* printer) const {
1991 if (options.testonly) {
1992 printer->Print("goog.setTestOnly();\n\n");
1993 }
1994 printer->Print("\n");
1995 }
1996
GenerateClassesAndEnums(const GeneratorOptions & options,io::Printer * printer,const FileDescriptor * file) const1997 void Generator::GenerateClassesAndEnums(const GeneratorOptions& options,
1998 io::Printer* printer,
1999 const FileDescriptor* file) const {
2000 for (int i = 0; i < file->message_type_count(); i++) {
2001 GenerateClassConstructorAndDeclareExtensionFieldInfo(options, printer,
2002 file->message_type(i));
2003 }
2004 for (int i = 0; i < file->message_type_count(); i++) {
2005 GenerateClass(options, printer, file->message_type(i));
2006 }
2007 for (int i = 0; i < file->enum_type_count(); i++) {
2008 GenerateEnum(options, printer, file->enum_type(i));
2009 }
2010 }
2011
GenerateClass(const GeneratorOptions & options,io::Printer * printer,const Descriptor * desc) const2012 void Generator::GenerateClass(const GeneratorOptions& options,
2013 io::Printer* printer,
2014 const Descriptor* desc) const {
2015 if (IgnoreMessage(desc)) {
2016 return;
2017 }
2018
2019 if (!NamespaceOnly(desc)) {
2020 printer->Print("\n");
2021 GenerateClassFieldInfo(options, printer, desc);
2022
2023
2024 GenerateClassToObject(options, printer, desc);
2025 // These must come *before* the extension-field info generation in
2026 // GenerateClassRegistration so that references to the binary
2027 // serialization/deserialization functions may be placed in the extension
2028 // objects.
2029 GenerateClassDeserializeBinary(options, printer, desc);
2030 GenerateClassSerializeBinary(options, printer, desc);
2031 }
2032
2033 // Recurse on nested types. These must come *before* the extension-field
2034 // info generation in GenerateClassRegistration so that extensions that
2035 // reference nested types proceed the definitions of the nested types.
2036 for (int i = 0; i < desc->enum_type_count(); i++) {
2037 GenerateEnum(options, printer, desc->enum_type(i));
2038 }
2039 for (int i = 0; i < desc->nested_type_count(); i++) {
2040 GenerateClass(options, printer, desc->nested_type(i));
2041 }
2042
2043 if (!NamespaceOnly(desc)) {
2044 GenerateClassRegistration(options, printer, desc);
2045 GenerateClassFields(options, printer, desc);
2046
2047 if (options.import_style != GeneratorOptions::kImportClosure) {
2048 for (int i = 0; i < desc->extension_count(); i++) {
2049 GenerateExtension(options, printer, desc->extension(i));
2050 }
2051 }
2052 }
2053 }
2054
GenerateClassConstructor(const GeneratorOptions & options,io::Printer * printer,const Descriptor * desc) const2055 void Generator::GenerateClassConstructor(const GeneratorOptions& options,
2056 io::Printer* printer,
2057 const Descriptor* desc) const {
2058 printer->Print(
2059 "/**\n"
2060 " * Generated by JsPbCodeGenerator.\n"
2061 " * @param {Array=} opt_data Optional initial data array, typically "
2062 "from a\n"
2063 " * server response, or constructed directly in Javascript. The array "
2064 "is used\n"
2065 " * in place and becomes part of the constructed object. It is not "
2066 "cloned.\n"
2067 " * If no data is provided, the constructed object will be empty, but "
2068 "still\n"
2069 " * valid.\n"
2070 " * @extends {jspb.Message}\n"
2071 " * @constructor\n"
2072 " */\n"
2073 "$classprefix$$classname$ = function(opt_data) {\n",
2074 "classprefix", GetMessagePathPrefix(options, desc), "classname",
2075 desc->name());
2076 printer->Annotate("classname", desc);
2077 std::string message_id = GetMessageId(desc);
2078 printer->Print(
2079 " jspb.Message.initialize(this, opt_data, $messageId$, $pivot$, "
2080 "$rptfields$, $oneoffields$);\n",
2081 "messageId",
2082 !message_id.empty() ? ("'" + message_id + "'")
2083 : (IsResponse(desc) ? "''" : "0"),
2084 "pivot", GetPivot(desc), "rptfields",
2085 RepeatedFieldsArrayName(options, desc), "oneoffields",
2086 OneofFieldsArrayName(options, desc));
2087 printer->Print(
2088 "};\n"
2089 "goog.inherits($classname$, jspb.Message);\n"
2090 "if (goog.DEBUG && !COMPILED) {\n"
2091 // displayName overrides Function.prototype.displayName
2092 // http://google3/javascript/externs/es3.js?l=511
2093 " /**\n"
2094 " * @public\n"
2095 " * @override\n"
2096 " */\n"
2097 " $classname$.displayName = '$classname$';\n"
2098 "}\n",
2099 "classname", GetMessagePath(options, desc));
2100 }
2101
GenerateClassConstructorAndDeclareExtensionFieldInfo(const GeneratorOptions & options,io::Printer * printer,const Descriptor * desc) const2102 void Generator::GenerateClassConstructorAndDeclareExtensionFieldInfo(
2103 const GeneratorOptions& options, io::Printer* printer,
2104 const Descriptor* desc) const {
2105 if (!NamespaceOnly(desc)) {
2106 GenerateClassConstructor(options, printer, desc);
2107 if (IsExtendable(desc) && desc->full_name() != "google.protobuf.bridge.MessageSet") {
2108 GenerateClassExtensionFieldInfo(options, printer, desc);
2109 }
2110 }
2111 for (int i = 0; i < desc->nested_type_count(); i++) {
2112 if (!IgnoreMessage(desc->nested_type(i))) {
2113 GenerateClassConstructorAndDeclareExtensionFieldInfo(
2114 options, printer, desc->nested_type(i));
2115 }
2116 }
2117 }
2118
GenerateClassFieldInfo(const GeneratorOptions & options,io::Printer * printer,const Descriptor * desc) const2119 void Generator::GenerateClassFieldInfo(const GeneratorOptions& options,
2120 io::Printer* printer,
2121 const Descriptor* desc) const {
2122 if (HasRepeatedFields(options, desc)) {
2123 printer->Print(
2124 "/**\n"
2125 " * List of repeated fields within this message type.\n"
2126 " * @private {!Array<number>}\n"
2127 " * @const\n"
2128 " */\n"
2129 "$classname$$rptfieldarray$ = $rptfields$;\n"
2130 "\n",
2131 "classname", GetMessagePath(options, desc), "rptfieldarray",
2132 kRepeatedFieldArrayName, "rptfields",
2133 RepeatedFieldNumberList(options, desc));
2134 }
2135
2136 if (HasOneofFields(desc)) {
2137 printer->Print(
2138 "/**\n"
2139 " * Oneof group definitions for this message. Each group defines the "
2140 "field\n"
2141 " * numbers belonging to that group. When of these fields' value is "
2142 "set, all\n"
2143 " * other fields in the group are cleared. During deserialization, if "
2144 "multiple\n"
2145 " * fields are encountered for a group, only the last value seen will "
2146 "be kept.\n"
2147 " * @private {!Array<!Array<number>>}\n"
2148 " * @const\n"
2149 " */\n"
2150 "$classname$$oneofgrouparray$ = $oneofgroups$;\n"
2151 "\n",
2152 "classname", GetMessagePath(options, desc), "oneofgrouparray",
2153 kOneofGroupArrayName, "oneofgroups", OneofGroupList(desc));
2154
2155 for (int i = 0; i < desc->oneof_decl_count(); i++) {
2156 if (IgnoreOneof(desc->oneof_decl(i))) {
2157 continue;
2158 }
2159 GenerateOneofCaseDefinition(options, printer, desc->oneof_decl(i));
2160 }
2161 }
2162 }
2163
GenerateClassXid(const GeneratorOptions & options,io::Printer * printer,const Descriptor * desc) const2164 void Generator::GenerateClassXid(const GeneratorOptions& options,
2165 io::Printer* printer,
2166 const Descriptor* desc) const {
2167 printer->Print(
2168 "\n"
2169 "\n"
2170 "$class$.prototype.messageXid = xid('$class$');\n",
2171 "class", GetMessagePath(options, desc));
2172 }
2173
GenerateOneofCaseDefinition(const GeneratorOptions & options,io::Printer * printer,const OneofDescriptor * oneof) const2174 void Generator::GenerateOneofCaseDefinition(
2175 const GeneratorOptions& options, io::Printer* printer,
2176 const OneofDescriptor* oneof) const {
2177 printer->Print(
2178 "/**\n"
2179 " * @enum {number}\n"
2180 " */\n"
2181 "$classname$.$oneof$Case = {\n"
2182 " $upcase$_NOT_SET: 0",
2183 "classname", GetMessagePath(options, oneof->containing_type()), "oneof",
2184 JSOneofName(oneof), "upcase", ToEnumCase(oneof->name()));
2185
2186 for (int i = 0; i < oneof->field_count(); i++) {
2187 if (IgnoreField(oneof->field(i))) {
2188 continue;
2189 }
2190
2191 printer->Print(
2192 ",\n"
2193 " $upcase$: $number$",
2194 "upcase", ToEnumCase(oneof->field(i)->name()), "number",
2195 JSFieldIndex(oneof->field(i)));
2196 printer->Annotate("upcase", oneof->field(i));
2197 }
2198
2199 printer->Print(
2200 "\n"
2201 "};\n"
2202 "\n"
2203 "/**\n"
2204 " * @return {$class$.$oneof$Case}\n"
2205 " */\n"
2206 "$class$.prototype.get$oneof$Case = function() {\n"
2207 " return /** @type {$class$.$oneof$Case} */(jspb.Message."
2208 "computeOneofCase(this, $class$.oneofGroups_[$oneofindex$]));\n"
2209 "};\n"
2210 "\n",
2211 "class", GetMessagePath(options, oneof->containing_type()), "oneof",
2212 JSOneofName(oneof), "oneofindex", JSOneofIndex(oneof));
2213 }
2214
GenerateClassToObject(const GeneratorOptions & options,io::Printer * printer,const Descriptor * desc) const2215 void Generator::GenerateClassToObject(const GeneratorOptions& options,
2216 io::Printer* printer,
2217 const Descriptor* desc) const {
2218 printer->Print(
2219 "\n"
2220 "\n"
2221 "if (jspb.Message.GENERATE_TO_OBJECT) {\n"
2222 "/**\n"
2223 " * Creates an object representation of this proto.\n"
2224 " * Field names that are reserved in JavaScript and will be renamed to "
2225 "pb_name.\n"
2226 " * Optional fields that are not set will be set to undefined.\n"
2227 " * To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.\n"
2228 " * For the list of reserved names please see:\n"
2229 " * net/proto2/compiler/js/internal/generator.cc#kKeyword.\n"
2230 " * @param {boolean=} opt_includeInstance Deprecated. whether to include "
2231 "the\n"
2232 " * JSPB instance for transitional soy proto support:\n"
2233 " * http://goto/soy-param-migration\n"
2234 " * @return {!Object}\n"
2235 " */\n"
2236 "$classname$.prototype.toObject = function(opt_includeInstance) {\n"
2237 " return $classname$.toObject(opt_includeInstance, this);\n"
2238 "};\n"
2239 "\n"
2240 "\n"
2241 "/**\n"
2242 " * Static version of the {@see toObject} method.\n"
2243 " * @param {boolean|undefined} includeInstance Deprecated. Whether to "
2244 "include\n"
2245 " * the JSPB instance for transitional soy proto support:\n"
2246 " * http://goto/soy-param-migration\n"
2247 " * @param {!$classname$} msg The msg instance to transform.\n"
2248 " * @return {!Object}\n"
2249 " * @suppress {unusedLocalVariables} f is only used for nested messages\n"
2250 " */\n"
2251 "$classname$.toObject = function(includeInstance, msg) {\n"
2252 " var f, obj = {",
2253 "classname", GetMessagePath(options, desc));
2254
2255 bool first = true;
2256 for (int i = 0; i < desc->field_count(); i++) {
2257 const FieldDescriptor* field = desc->field(i);
2258 if (IgnoreField(field)) {
2259 continue;
2260 }
2261
2262 if (!first) {
2263 printer->Print(",\n ");
2264 } else {
2265 printer->Print("\n ");
2266 first = false;
2267 }
2268
2269 GenerateClassFieldToObject(options, printer, field);
2270 }
2271
2272 if (!first) {
2273 printer->Print("\n };\n\n");
2274 } else {
2275 printer->Print("\n\n };\n\n");
2276 }
2277
2278 if (IsExtendable(desc)) {
2279 printer->Print(
2280 " jspb.Message.toObjectExtension(/** @type {!jspb.Message} */ (msg), "
2281 "obj,\n"
2282 " $extObject$, $class$.prototype.getExtension,\n"
2283 " includeInstance);\n",
2284 "extObject", JSExtensionsObjectName(options, desc->file(), desc),
2285 "class", GetMessagePath(options, desc));
2286 }
2287
2288 printer->Print(
2289 " if (includeInstance) {\n"
2290 " obj.$$jspbMessageInstance = msg;\n"
2291 " }\n"
2292 " return obj;\n"
2293 "};\n"
2294 "}\n"
2295 "\n"
2296 "\n",
2297 "classname", GetMessagePath(options, desc));
2298 }
2299
GenerateFieldValueExpression(io::Printer * printer,const char * obj_reference,const FieldDescriptor * field,bool use_default) const2300 void Generator::GenerateFieldValueExpression(io::Printer* printer,
2301 const char* obj_reference,
2302 const FieldDescriptor* field,
2303 bool use_default) const {
2304 const bool is_float_or_double =
2305 field->cpp_type() == FieldDescriptor::CPPTYPE_FLOAT ||
2306 field->cpp_type() == FieldDescriptor::CPPTYPE_DOUBLE;
2307 const bool is_boolean = field->cpp_type() == FieldDescriptor::CPPTYPE_BOOL;
2308
2309 const string with_default = use_default ? "WithDefault" : "";
2310 const string default_arg =
2311 use_default ? StrCat(", ", JSFieldDefault(field)) : "";
2312 const string cardinality = field->is_repeated() ? "Repeated" : "";
2313 string type = "";
2314 if (is_float_or_double) {
2315 type = "FloatingPoint";
2316 }
2317 if (is_boolean) {
2318 type = "Boolean";
2319 }
2320
2321 // Prints the appropriate function, among:
2322 // - getField
2323 // - getBooleanField
2324 // - getFloatingPointField => Replaced by getOptionalFloatingPointField to
2325 // preserve backward compatibility.
2326 // - getFieldWithDefault
2327 // - getBooleanFieldWithDefault
2328 // - getFloatingPointFieldWithDefault
2329 // - getRepeatedField
2330 // - getRepeatedBooleanField
2331 // - getRepeatedFloatingPointField
2332 if (is_float_or_double && !field->is_repeated() && !use_default) {
2333 printer->Print(
2334 "jspb.Message.getOptionalFloatingPointField($obj$, "
2335 "$index$$default$)",
2336 "obj", obj_reference, "index", JSFieldIndex(field), "default",
2337 default_arg);
2338 } else {
2339 printer->Print(
2340 "jspb.Message.get$cardinality$$type$Field$with_default$($obj$, "
2341 "$index$$default$)",
2342 "cardinality", cardinality, "type", type, "with_default", with_default,
2343 "obj", obj_reference, "index", JSFieldIndex(field), "default",
2344 default_arg);
2345 }
2346 }
2347
GenerateClassFieldToObject(const GeneratorOptions & options,io::Printer * printer,const FieldDescriptor * field) const2348 void Generator::GenerateClassFieldToObject(const GeneratorOptions& options,
2349 io::Printer* printer,
2350 const FieldDescriptor* field) const {
2351 printer->Print("$fieldname$: ", "fieldname",
2352 JSObjectFieldName(options, field));
2353
2354 if (field->is_map()) {
2355 const FieldDescriptor* value_field = MapFieldValue(field);
2356 // If the map values are of a message type, we must provide their static
2357 // toObject() method; otherwise we pass undefined for that argument.
2358 std::string value_to_object;
2359 if (value_field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) {
2360 value_to_object =
2361 GetMessagePath(options, value_field->message_type()) + ".toObject";
2362 } else {
2363 value_to_object = "undefined";
2364 }
2365 printer->Print(
2366 "(f = msg.get$name$()) ? f.toObject(includeInstance, $valuetoobject$) "
2367 ": []",
2368 "name", JSGetterName(options, field), "valuetoobject", value_to_object);
2369 } else if (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) {
2370 // Message field.
2371 if (field->is_repeated()) {
2372 {
2373 printer->Print(
2374 "jspb.Message.toObjectList(msg.get$getter$(),\n"
2375 " $type$.toObject, includeInstance)",
2376 "getter", JSGetterName(options, field), "type",
2377 SubmessageTypeRef(options, field));
2378 }
2379 } else {
2380 printer->Print(
2381 "(f = msg.get$getter$()) && "
2382 "$type$.toObject(includeInstance, f)",
2383 "getter", JSGetterName(options, field), "type",
2384 SubmessageTypeRef(options, field));
2385 }
2386 } else if (field->type() == FieldDescriptor::TYPE_BYTES) {
2387 // For bytes fields we want to always return the B64 data.
2388 printer->Print("msg.get$getter$()", "getter",
2389 JSGetterName(options, field, BYTES_B64));
2390 } else {
2391 bool use_default = field->has_default_value();
2392
2393 if (field->file()->syntax() == FileDescriptor::SYNTAX_PROTO3 &&
2394 // Repeated fields get initialized to their default in the constructor
2395 // (why?), so we emit a plain getField() call for them.
2396 !field->is_repeated() && !UseBrokenPresenceSemantics(options, field)) {
2397 // Proto3 puts all defaults (including implicit defaults) in toObject().
2398 // But for proto2 we leave the existing semantics unchanged: unset fields
2399 // without default are unset.
2400 use_default = true;
2401 }
2402
2403 // We don't implement this by calling the accessors, because the semantics
2404 // of the accessors are changing independently of the toObject() semantics.
2405 // We are migrating the accessors to return defaults instead of null, but
2406 // it may take longer to migrate toObject (or we might not want to do it at
2407 // all). So we want to generate independent code.
2408 // The accessor for unset optional values without default should return
2409 // null. Those are converted to undefined in the generated object.
2410 if (!use_default) {
2411 printer->Print("(f = ");
2412 }
2413 GenerateFieldValueExpression(printer, "msg", field, use_default);
2414 if (!use_default) {
2415 printer->Print(") == null ? undefined : f");
2416 }
2417 }
2418 }
2419
GenerateObjectTypedef(const GeneratorOptions & options,io::Printer * printer,const Descriptor * desc) const2420 void Generator::GenerateObjectTypedef(const GeneratorOptions& options,
2421 io::Printer* printer,
2422 const Descriptor* desc) const {
2423 // TODO(b/122687752): Consider renaming nested messages called ObjectFormat
2424 // to prevent collisions.
2425 const std::string type_name = GetMessagePath(options, desc) + ".ObjectFormat";
2426
2427 printer->Print(
2428 "/**\n"
2429 " * The raw object form of $messageName$ as accepted by the `fromObject` "
2430 "method.\n"
2431 " * @record\n"
2432 " */\n"
2433 "$typeName$ = function() {\n",
2434 "messageName", desc->name(), "typeName", type_name);
2435
2436 for (int i = 0; i < desc->field_count(); i++) {
2437 if (i > 0) {
2438 printer->Print("\n");
2439 }
2440 printer->Print(
2441 " /** @type {$fieldType$|undefined} */\n"
2442 " this.$fieldName$;\n",
2443 "fieldName", JSObjectFieldName(options, desc->field(i)),
2444 // TODO(b/121097361): Add type checking for field values.
2445 "fieldType", "?");
2446 }
2447
2448 printer->Print("};\n\n");
2449 }
2450
GenerateClassFromObject(const GeneratorOptions & options,io::Printer * printer,const Descriptor * desc) const2451 void Generator::GenerateClassFromObject(const GeneratorOptions& options,
2452 io::Printer* printer,
2453 const Descriptor* desc) const {
2454 printer->Print("if (jspb.Message.GENERATE_FROM_OBJECT) {\n\n");
2455
2456 GenerateObjectTypedef(options, printer, desc);
2457
2458 printer->Print(
2459 "/**\n"
2460 " * Loads data from an object into a new instance of this proto.\n"
2461 " * @param {!$classname$.ObjectFormat} obj\n"
2462 " * The object representation of this proto to load the data from.\n"
2463 " * @return {!$classname$}\n"
2464 " */\n"
2465 "$classname$.fromObject = function(obj) {\n"
2466 " var msg = new $classname$();\n",
2467 "classname", GetMessagePath(options, desc));
2468
2469 for (int i = 0; i < desc->field_count(); i++) {
2470 const FieldDescriptor* field = desc->field(i);
2471 if (!IgnoreField(field)) {
2472 GenerateClassFieldFromObject(options, printer, field);
2473 }
2474 }
2475
2476 printer->Print(
2477 " return msg;\n"
2478 "};\n"
2479 "}\n\n");
2480 }
2481
GenerateClassFieldFromObject(const GeneratorOptions & options,io::Printer * printer,const FieldDescriptor * field) const2482 void Generator::GenerateClassFieldFromObject(
2483 const GeneratorOptions& options, io::Printer* printer,
2484 const FieldDescriptor* field) const {
2485 if (field->is_map()) {
2486 const FieldDescriptor* value_field = MapFieldValue(field);
2487 if (value_field->type() == FieldDescriptor::TYPE_MESSAGE) {
2488 // Since the map values are of message type, we have to do some extra work
2489 // to recursively call fromObject() on them before setting the map field.
2490 printer->Print(
2491 " obj.$name$ && jspb.Message.setWrapperField(\n"
2492 " msg, $index$, jspb.Map.fromObject(obj.$name$, $fieldclass$, "
2493 "$fieldclass$.fromObject));\n",
2494 "name", JSObjectFieldName(options, field), "index",
2495 JSFieldIndex(field), "fieldclass",
2496 GetMessagePath(options, value_field->message_type()));
2497 } else {
2498 // `msg` is a newly-constructed message object that has not yet built any
2499 // map containers wrapping underlying arrays, so we can simply directly
2500 // set the array here without fear of a stale wrapper.
2501 printer->Print(
2502 " obj.$name$ && "
2503 "jspb.Message.setField(msg, $index$, obj.$name$);\n",
2504 "name", JSObjectFieldName(options, field), "index",
2505 JSFieldIndex(field));
2506 }
2507 } else if (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) {
2508 // Message field (singular or repeated)
2509 if (field->is_repeated()) {
2510 {
2511 printer->Print(
2512 " obj.$name$ && "
2513 "jspb.Message.setRepeatedWrapperField(\n"
2514 " msg, $index$, obj.$name$.map(\n"
2515 " $fieldclass$.fromObject));\n",
2516 "name", JSObjectFieldName(options, field), "index",
2517 JSFieldIndex(field), "fieldclass",
2518 SubmessageTypeRef(options, field));
2519 }
2520 } else {
2521 printer->Print(
2522 " obj.$name$ && jspb.Message.setWrapperField(\n"
2523 " msg, $index$, $fieldclass$.fromObject(obj.$name$));\n",
2524 "name", JSObjectFieldName(options, field), "index",
2525 JSFieldIndex(field), "fieldclass", SubmessageTypeRef(options, field));
2526 }
2527 } else {
2528 // Simple (primitive) field.
2529 printer->Print(
2530 " obj.$name$ != null && jspb.Message.setField(msg, $index$, "
2531 "obj.$name$);\n",
2532 "name", JSObjectFieldName(options, field), "index",
2533 JSFieldIndex(field));
2534 }
2535 }
2536
GenerateClassRegistration(const GeneratorOptions & options,io::Printer * printer,const Descriptor * desc) const2537 void Generator::GenerateClassRegistration(const GeneratorOptions& options,
2538 io::Printer* printer,
2539 const Descriptor* desc) const {
2540 // Register any extensions defined inside this message type.
2541 for (int i = 0; i < desc->extension_count(); i++) {
2542 const FieldDescriptor* extension = desc->extension(i);
2543 if (ShouldGenerateExtension(extension)) {
2544 GenerateExtension(options, printer, extension);
2545 }
2546 }
2547
2548 }
2549
GenerateClassFields(const GeneratorOptions & options,io::Printer * printer,const Descriptor * desc) const2550 void Generator::GenerateClassFields(const GeneratorOptions& options,
2551 io::Printer* printer,
2552 const Descriptor* desc) const {
2553 for (int i = 0; i < desc->field_count(); i++) {
2554 if (!IgnoreField(desc->field(i))) {
2555 GenerateClassField(options, printer, desc->field(i));
2556 }
2557 }
2558 }
2559
GenerateBytesWrapper(const GeneratorOptions & options,io::Printer * printer,const FieldDescriptor * field,BytesMode bytes_mode)2560 void GenerateBytesWrapper(const GeneratorOptions& options, io::Printer* printer,
2561 const FieldDescriptor* field, BytesMode bytes_mode) {
2562 std::string type =
2563 JSFieldTypeAnnotation(options, field,
2564 /* is_setter_argument = */ false,
2565 /* force_present = */ false,
2566 /* singular_if_not_packed = */ false, bytes_mode);
2567 printer->Print(
2568 "/**\n"
2569 " * $fielddef$\n"
2570 "$comment$"
2571 " * This is a type-conversion wrapper around `get$defname$()`\n"
2572 " * @return {$type$}\n"
2573 " */\n"
2574 "$class$.prototype.get$name$ = function() {\n"
2575 " return /** @type {$type$} */ (jspb.Message.bytes$list$As$suffix$(\n"
2576 " this.get$defname$()));\n"
2577 "};\n"
2578 "\n"
2579 "\n",
2580 "fielddef", FieldDefinition(options, field), "comment",
2581 FieldComments(field, bytes_mode), "type", type, "class",
2582 GetMessagePath(options, field->containing_type()), "name",
2583 JSGetterName(options, field, bytes_mode), "list",
2584 field->is_repeated() ? "List" : "", "suffix",
2585 JSByteGetterSuffix(bytes_mode), "defname",
2586 JSGetterName(options, field, BYTES_DEFAULT));
2587 }
2588
GenerateClassField(const GeneratorOptions & options,io::Printer * printer,const FieldDescriptor * field) const2589 void Generator::GenerateClassField(const GeneratorOptions& options,
2590 io::Printer* printer,
2591 const FieldDescriptor* field) const {
2592 if (field->is_map()) {
2593 const FieldDescriptor* key_field = MapFieldKey(field);
2594 const FieldDescriptor* value_field = MapFieldValue(field);
2595 // Map field: special handling to instantiate the map object on demand.
2596 std::string key_type =
2597 JSFieldTypeAnnotation(options, key_field,
2598 /* is_setter_argument = */ false,
2599 /* force_present = */ true,
2600 /* singular_if_not_packed = */ false);
2601 std::string value_type =
2602 JSFieldTypeAnnotation(options, value_field,
2603 /* is_setter_argument = */ false,
2604 /* force_present = */ true,
2605 /* singular_if_not_packed = */ false);
2606
2607 printer->Print(
2608 "/**\n"
2609 " * $fielddef$\n"
2610 " * @param {boolean=} opt_noLazyCreate Do not create the map if\n"
2611 " * empty, instead returning `undefined`\n"
2612 " * @return {!jspb.Map<$keytype$,$valuetype$>}\n"
2613 " */\n",
2614 "fielddef", FieldDefinition(options, field), "keytype", key_type,
2615 "valuetype", value_type);
2616 printer->Print(
2617 "$class$.prototype.$gettername$ = function(opt_noLazyCreate) {\n"
2618 " return /** @type {!jspb.Map<$keytype$,$valuetype$>} */ (\n",
2619 "class", GetMessagePath(options, field->containing_type()),
2620 "gettername", "get" + JSGetterName(options, field), "keytype", key_type,
2621 "valuetype", value_type);
2622 printer->Annotate("gettername", field);
2623 printer->Print(
2624 " jspb.Message.getMapField(this, $index$, opt_noLazyCreate",
2625 "index", JSFieldIndex(field));
2626
2627 if (value_field->type() == FieldDescriptor::TYPE_MESSAGE) {
2628 printer->Print(
2629 ",\n"
2630 " $messageType$",
2631 "messageType", GetMessagePath(options, value_field->message_type()));
2632 } else {
2633 printer->Print(
2634 ",\n"
2635 " null");
2636 }
2637
2638 printer->Print("));\n");
2639
2640 printer->Print(
2641 "};\n"
2642 "\n"
2643 "\n");
2644 } else if (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) {
2645 // Message field: special handling in order to wrap the underlying data
2646 // array with a message object.
2647
2648 printer->Print(
2649 "/**\n"
2650 " * $fielddef$\n"
2651 "$comment$"
2652 " * @return {$type$}\n"
2653 " */\n",
2654 "fielddef", FieldDefinition(options, field), "comment",
2655 FieldComments(field, BYTES_DEFAULT), "type",
2656 JSFieldTypeAnnotation(options, field,
2657 /* is_setter_argument = */ false,
2658 /* force_present = */ false,
2659 /* singular_if_not_packed = */ false));
2660 printer->Print(
2661 "$class$.prototype.$gettername$ = function() {\n"
2662 " return /** @type{$type$} */ (\n"
2663 " jspb.Message.get$rpt$WrapperField(this, $wrapperclass$, "
2664 "$index$$required$));\n"
2665 "};\n"
2666 "\n"
2667 "\n",
2668 "class", GetMessagePath(options, field->containing_type()),
2669 "gettername", "get" + JSGetterName(options, field), "type",
2670 JSFieldTypeAnnotation(options, field,
2671 /* is_setter_argument = */ false,
2672 /* force_present = */ false,
2673 /* singular_if_not_packed = */ false),
2674 "rpt", (field->is_repeated() ? "Repeated" : ""), "index",
2675 JSFieldIndex(field), "wrapperclass", SubmessageTypeRef(options, field),
2676 "required",
2677 (field->label() == FieldDescriptor::LABEL_REQUIRED ? ", 1" : ""));
2678 printer->Annotate("gettername", field);
2679 printer->Print(
2680 "/** @param {$optionaltype$} value$returndoc$ */\n"
2681 "$class$.prototype.$settername$ = function(value) {\n"
2682 " jspb.Message.set$oneoftag$$repeatedtag$WrapperField(",
2683 "optionaltype",
2684 JSFieldTypeAnnotation(options, field,
2685 /* is_setter_argument = */ true,
2686 /* force_present = */ false,
2687 /* singular_if_not_packed = */ false),
2688 "returndoc", JSReturnDoc(options, field), "class",
2689 GetMessagePath(options, field->containing_type()), "settername",
2690 "set" + JSGetterName(options, field), "oneoftag",
2691 (field->containing_oneof() ? "Oneof" : ""), "repeatedtag",
2692 (field->is_repeated() ? "Repeated" : ""));
2693 printer->Annotate("settername", field);
2694
2695 printer->Print(
2696 "this, $index$$oneofgroup$, value);$returnvalue$\n"
2697 "};\n"
2698 "\n"
2699 "\n",
2700 "index", JSFieldIndex(field), "oneofgroup",
2701 (field->containing_oneof() ? (", " + JSOneofArray(options, field))
2702 : ""),
2703 "returnvalue", JSReturnClause(field));
2704
2705 if (field->is_repeated()) {
2706 GenerateRepeatedMessageHelperMethods(options, printer, field);
2707 }
2708
2709 } else {
2710 bool untyped =
2711 false;
2712
2713 // Simple (primitive) field, either singular or repeated.
2714
2715 // TODO(b/26173701): Always use BYTES_DEFAULT for the getter return type;
2716 // at this point we "lie" to non-binary users and tell the return
2717 // type is always base64 string, pending a LSC to migrate to typed getters.
2718 BytesMode bytes_mode =
2719 field->type() == FieldDescriptor::TYPE_BYTES && !options.binary
2720 ? BYTES_B64
2721 : BYTES_DEFAULT;
2722 std::string typed_annotation =
2723 JSFieldTypeAnnotation(options, field,
2724 /* is_setter_argument = */ false,
2725 /* force_present = */ false,
2726 /* singular_if_not_packed = */ false,
2727 /* bytes_mode = */ bytes_mode);
2728 if (untyped) {
2729 printer->Print(
2730 "/**\n"
2731 " * @return {?} Raw field, untyped.\n"
2732 " */\n");
2733 } else {
2734 printer->Print(
2735 "/**\n"
2736 " * $fielddef$\n"
2737 "$comment$"
2738 " * @return {$type$}\n"
2739 " */\n",
2740 "fielddef", FieldDefinition(options, field), "comment",
2741 FieldComments(field, bytes_mode), "type", typed_annotation);
2742 }
2743
2744 printer->Print("$class$.prototype.$gettername$ = function() {\n", "class",
2745 GetMessagePath(options, field->containing_type()),
2746 "gettername", "get" + JSGetterName(options, field));
2747 printer->Annotate("gettername", field);
2748
2749 if (untyped) {
2750 printer->Print(" return ");
2751 } else {
2752 printer->Print(" return /** @type {$type$} */ (", "type",
2753 typed_annotation);
2754 }
2755
2756 bool use_default = !ReturnsNullWhenUnset(options, field);
2757
2758 // Raw fields with no default set should just return undefined.
2759 if (untyped && !field->has_default_value()) {
2760 use_default = false;
2761 }
2762
2763 // Repeated fields get initialized to their default in the constructor
2764 // (why?), so we emit a plain getField() call for them.
2765 if (field->is_repeated()) {
2766 use_default = false;
2767 }
2768
2769 GenerateFieldValueExpression(printer, "this", field, use_default);
2770
2771 if (untyped) {
2772 printer->Print(
2773 ";\n"
2774 "};\n"
2775 "\n"
2776 "\n");
2777 } else {
2778 printer->Print(
2779 ");\n"
2780 "};\n"
2781 "\n"
2782 "\n");
2783 }
2784
2785 if (field->type() == FieldDescriptor::TYPE_BYTES && !untyped) {
2786 GenerateBytesWrapper(options, printer, field, BYTES_B64);
2787 GenerateBytesWrapper(options, printer, field, BYTES_U8);
2788 }
2789
2790 if (untyped) {
2791 printer->Print(
2792 "/**\n"
2793 " * @param {*} value$returndoc$\n"
2794 " */\n",
2795 "returndoc", JSReturnDoc(options, field));
2796 } else {
2797 printer->Print(
2798 "/** @param {$optionaltype$} value$returndoc$ */\n", "optionaltype",
2799 JSFieldTypeAnnotation(options, field,
2800 /* is_setter_argument = */ true,
2801 /* force_present = */ false,
2802 /* singular_if_not_packed = */ false),
2803 "returndoc", JSReturnDoc(options, field));
2804 }
2805
2806 if (field->file()->syntax() == FileDescriptor::SYNTAX_PROTO3 &&
2807 !field->is_repeated() && !field->is_map() &&
2808 !HasFieldPresence(options, field)) {
2809 // Proto3 non-repeated and non-map fields without presence use the
2810 // setProto3*Field function.
2811 printer->Print(
2812 "$class$.prototype.$settername$ = function(value) {\n"
2813 " jspb.Message.setProto3$typetag$Field(this, $index$, "
2814 "value);$returnvalue$\n"
2815 "};\n"
2816 "\n"
2817 "\n",
2818 "class", GetMessagePath(options, field->containing_type()),
2819 "settername", "set" + JSGetterName(options, field), "typetag",
2820 JSTypeTag(field), "index", JSFieldIndex(field), "returnvalue",
2821 JSReturnClause(field));
2822 printer->Annotate("settername", field);
2823 } else {
2824 // Otherwise, use the regular setField function.
2825 printer->Print(
2826 "$class$.prototype.$settername$ = function(value) {\n"
2827 " jspb.Message.set$oneoftag$Field(this, $index$",
2828 "class", GetMessagePath(options, field->containing_type()),
2829 "settername", "set" + JSGetterName(options, field), "oneoftag",
2830 (field->containing_oneof() ? "Oneof" : ""), "index",
2831 JSFieldIndex(field));
2832 printer->Annotate("settername", field);
2833 printer->Print(
2834 "$oneofgroup$, $type$value$rptvalueinit$$typeclose$);$returnvalue$\n"
2835 "};\n"
2836 "\n"
2837 "\n",
2838 "type",
2839 untyped ? "/** @type{string|number|boolean|Array|undefined} */(" : "",
2840 "typeclose", untyped ? ")" : "", "oneofgroup",
2841 (field->containing_oneof() ? (", " + JSOneofArray(options, field))
2842 : ""),
2843 "returnvalue", JSReturnClause(field), "rptvalueinit",
2844 (field->is_repeated() ? " || []" : ""));
2845 }
2846
2847 if (untyped) {
2848 printer->Print(
2849 "/**\n"
2850 " * Clears the value.$returndoc$\n"
2851 " */\n",
2852 "returndoc", JSReturnDoc(options, field));
2853 }
2854
2855 if (field->is_repeated()) {
2856 GenerateRepeatedPrimitiveHelperMethods(options, printer, field, untyped);
2857 }
2858 }
2859
2860 // Generate clearFoo() method for map fields, repeated fields, and other
2861 // fields with presence.
2862 if (field->is_map()) {
2863 // clang-format off
2864 printer->Print(
2865 "/**\n"
2866 " * Clears values from the map. The map will be non-null."
2867 "$returndoc$\n"
2868 " */\n"
2869 "$class$.prototype.$clearername$ = function() {\n"
2870 " this.$gettername$().clear();$returnvalue$\n"
2871 "};\n"
2872 "\n"
2873 "\n",
2874 "returndoc", JSReturnDoc(options, field),
2875 "class", GetMessagePath(options, field->containing_type()),
2876 "clearername", "clear" + JSGetterName(options, field),
2877 "gettername", "get" + JSGetterName(options, field),
2878 "returnvalue", JSReturnClause(field));
2879 // clang-format on
2880 printer->Annotate("clearername", field);
2881 } else if (field->is_repeated() ||
2882 (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE &&
2883 !field->is_required())) {
2884 // Fields where we can delegate to the regular setter.
2885 // clang-format off
2886 printer->Print(
2887 "/**\n"
2888 " * $jsdoc$$returndoc$\n"
2889 " */\n"
2890 "$class$.prototype.$clearername$ = function() {\n"
2891 " this.$settername$($clearedvalue$);$returnvalue$\n"
2892 "};\n"
2893 "\n"
2894 "\n",
2895 "jsdoc", field->is_repeated()
2896 ? "Clears the list making it empty but non-null."
2897 : "Clears the message field making it undefined.",
2898 "returndoc", JSReturnDoc(options, field),
2899 "class", GetMessagePath(options, field->containing_type()),
2900 "clearername", "clear" + JSGetterName(options, field),
2901 "settername", "set" + JSGetterName(options, field),
2902 "clearedvalue", (field->is_repeated() ? "[]" : "undefined"),
2903 "returnvalue", JSReturnClause(field));
2904 // clang-format on
2905 printer->Annotate("clearername", field);
2906 } else if (HasFieldPresence(options, field)) {
2907 // Fields where we can't delegate to the regular setter because it doesn't
2908 // accept "undefined" as an argument.
2909 // clang-format off
2910 printer->Print(
2911 "/**\n"
2912 " * Clears the field making it undefined.$returndoc$\n"
2913 " */\n"
2914 "$class$.prototype.$clearername$ = function() {\n"
2915 " jspb.Message.set$maybeoneof$Field(this, "
2916 "$index$$maybeoneofgroup$, ",
2917 "returndoc", JSReturnDoc(options, field),
2918 "class", GetMessagePath(options, field->containing_type()),
2919 "clearername", "clear" + JSGetterName(options, field),
2920 "maybeoneof", (field->containing_oneof() ? "Oneof" : ""),
2921 "maybeoneofgroup", (field->containing_oneof()
2922 ? (", " + JSOneofArray(options, field))
2923 : ""),
2924 "index", JSFieldIndex(field));
2925 // clang-format on
2926 printer->Annotate("clearername", field);
2927 printer->Print(
2928 "$clearedvalue$);$returnvalue$\n"
2929 "};\n"
2930 "\n"
2931 "\n",
2932 "clearedvalue", (field->is_repeated() ? "[]" : "undefined"),
2933 "returnvalue", JSReturnClause(field));
2934 }
2935
2936 if (HasFieldPresence(options, field)) {
2937 printer->Print(
2938 "/**\n"
2939 " * Returns whether this field is set.\n"
2940 " * @return {boolean}\n"
2941 " */\n"
2942 "$class$.prototype.$hasername$ = function() {\n"
2943 " return jspb.Message.getField(this, $index$) != null;\n"
2944 "};\n"
2945 "\n"
2946 "\n",
2947 "class", GetMessagePath(options, field->containing_type()), "hasername",
2948 "has" + JSGetterName(options, field), "index", JSFieldIndex(field));
2949 printer->Annotate("hasername", field);
2950 }
2951 }
2952
GenerateRepeatedPrimitiveHelperMethods(const GeneratorOptions & options,io::Printer * printer,const FieldDescriptor * field,bool untyped) const2953 void Generator::GenerateRepeatedPrimitiveHelperMethods(
2954 const GeneratorOptions& options, io::Printer* printer,
2955 const FieldDescriptor* field, bool untyped) const {
2956 // clang-format off
2957 printer->Print(
2958 "/**\n"
2959 " * @param {$optionaltype$} value\n"
2960 " * @param {number=} opt_index$returndoc$\n"
2961 " */\n"
2962 "$class$.prototype.$addername$ = function(value, opt_index) {\n"
2963 " jspb.Message.addToRepeatedField(this, $index$",
2964 "class", GetMessagePath(options, field->containing_type()), "addername",
2965 "add" + JSGetterName(options, field, BYTES_DEFAULT,
2966 /* drop_list = */ true),
2967 "optionaltype",
2968 JSFieldTypeAnnotation(
2969 options, field,
2970 /* is_setter_argument = */ false,
2971 /* force_present = */ true,
2972 /* singular_if_not_packed = */ false,
2973 BYTES_DEFAULT,
2974 /* force_singular = */ true),
2975 "index", JSFieldIndex(field),
2976 "returndoc", JSReturnDoc(options, field));
2977 printer->Annotate("addername", field);
2978 printer->Print(
2979 "$oneofgroup$, $type$value$rptvalueinit$$typeclose$, "
2980 "opt_index);$returnvalue$\n"
2981 "};\n"
2982 "\n"
2983 "\n",
2984 "type", untyped ? "/** @type{string|number|boolean|!Uint8Array} */(" : "",
2985 "typeclose", untyped ? ")" : "", "oneofgroup",
2986 (field->containing_oneof() ? (", " + JSOneofArray(options, field)) : ""),
2987 "rptvalueinit", "",
2988 "returnvalue", JSReturnClause(field));
2989 // clang-format on
2990 }
2991
GenerateRepeatedMessageHelperMethods(const GeneratorOptions & options,io::Printer * printer,const FieldDescriptor * field) const2992 void Generator::GenerateRepeatedMessageHelperMethods(
2993 const GeneratorOptions& options, io::Printer* printer,
2994 const FieldDescriptor* field) const {
2995 printer->Print(
2996 "/**\n"
2997 " * @param {!$optionaltype$=} opt_value\n"
2998 " * @param {number=} opt_index\n"
2999 " * @return {!$optionaltype$}\n"
3000 " */\n"
3001 "$class$.prototype.$addername$ = function(opt_value, opt_index) {\n"
3002 " return jspb.Message.addTo$repeatedtag$WrapperField(",
3003 "optionaltype", JSTypeName(options, field, BYTES_DEFAULT), "class",
3004 GetMessagePath(options, field->containing_type()), "addername",
3005 "add" + JSGetterName(options, field, BYTES_DEFAULT,
3006 /* drop_list = */ true),
3007 "repeatedtag", (field->is_repeated() ? "Repeated" : ""));
3008
3009 printer->Annotate("addername", field);
3010 printer->Print(
3011 "this, $index$$oneofgroup$, opt_value, $ctor$, opt_index);\n"
3012 "};\n"
3013 "\n"
3014 "\n",
3015 "index", JSFieldIndex(field), "oneofgroup",
3016 (field->containing_oneof() ? (", " + JSOneofArray(options, field)) : ""),
3017 "ctor", GetMessagePath(options, field->message_type()));
3018 }
3019
GenerateClassExtensionFieldInfo(const GeneratorOptions & options,io::Printer * printer,const Descriptor * desc) const3020 void Generator::GenerateClassExtensionFieldInfo(const GeneratorOptions& options,
3021 io::Printer* printer,
3022 const Descriptor* desc) const {
3023 if (IsExtendable(desc)) {
3024 printer->Print(
3025 "\n"
3026 "/**\n"
3027 " * The extensions registered with this message class. This is a "
3028 "map of\n"
3029 " * extension field number to fieldInfo object.\n"
3030 " *\n"
3031 " * For example:\n"
3032 " * { 123: {fieldIndex: 123, fieldName: {my_field_name: 0}, "
3033 "ctor: proto.example.MyMessage} }\n"
3034 " *\n"
3035 " * fieldName contains the JsCompiler renamed field name property "
3036 "so that it\n"
3037 " * works in OPTIMIZED mode.\n"
3038 " *\n"
3039 " * @type {!Object<number, jspb.ExtensionFieldInfo>}\n"
3040 " */\n"
3041 "$class$.extensions = {};\n"
3042 "\n",
3043 "class", GetMessagePath(options, desc));
3044
3045 printer->Print(
3046 "\n"
3047 "/**\n"
3048 " * The extensions registered with this message class. This is a "
3049 "map of\n"
3050 " * extension field number to fieldInfo object.\n"
3051 " *\n"
3052 " * For example:\n"
3053 " * { 123: {fieldIndex: 123, fieldName: {my_field_name: 0}, "
3054 "ctor: proto.example.MyMessage} }\n"
3055 " *\n"
3056 " * fieldName contains the JsCompiler renamed field name property "
3057 "so that it\n"
3058 " * works in OPTIMIZED mode.\n"
3059 " *\n"
3060 " * @type {!Object<number, jspb.ExtensionFieldBinaryInfo>}\n"
3061 " */\n"
3062 "$class$.extensionsBinary = {};\n"
3063 "\n",
3064 "class", GetMessagePath(options, desc));
3065 }
3066 }
3067
3068
GenerateClassDeserializeBinary(const GeneratorOptions & options,io::Printer * printer,const Descriptor * desc) const3069 void Generator::GenerateClassDeserializeBinary(const GeneratorOptions& options,
3070 io::Printer* printer,
3071 const Descriptor* desc) const {
3072 // TODO(cfallin): Handle lazy decoding when requested by field option and/or
3073 // by default for 'bytes' fields and packed repeated fields.
3074
3075 printer->Print(
3076 "/**\n"
3077 " * Deserializes binary data (in protobuf wire format).\n"
3078 " * @param {jspb.ByteSource} bytes The bytes to deserialize.\n"
3079 " * @return {!$class$}\n"
3080 " */\n"
3081 "$class$.deserializeBinary = function(bytes) {\n"
3082 " var reader = new jspb.BinaryReader(bytes);\n"
3083 " var msg = new $class$;\n"
3084 " return $class$.deserializeBinaryFromReader(msg, reader);\n"
3085 "};\n"
3086 "\n"
3087 "\n"
3088 "/**\n"
3089 " * Deserializes binary data (in protobuf wire format) from the\n"
3090 " * given reader into the given message object.\n"
3091 " * @param {!$class$} msg The message object to deserialize into.\n"
3092 " * @param {!jspb.BinaryReader} reader The BinaryReader to use.\n"
3093 " * @return {!$class$}\n"
3094 " */\n"
3095 "$class$.deserializeBinaryFromReader = function(msg, reader) {\n"
3096 " while (reader.nextField()) {\n",
3097 "class", GetMessagePath(options, desc));
3098 printer->Print(
3099 " if (reader.isEndGroup()) {\n"
3100 " break;\n"
3101 " }\n"
3102 " var field = reader.getFieldNumber();\n"
3103 " switch (field) {\n");
3104
3105 for (int i = 0; i < desc->field_count(); i++) {
3106 if (!IgnoreField(desc->field(i))) {
3107 GenerateClassDeserializeBinaryField(options, printer, desc->field(i));
3108 }
3109 }
3110
3111 printer->Print(" default:\n");
3112 if (IsExtendable(desc)) {
3113 printer->Print(
3114 " jspb.Message.readBinaryExtension(msg, reader,\n"
3115 " $extobj$Binary,\n"
3116 " $class$.prototype.getExtension,\n"
3117 " $class$.prototype.setExtension);\n"
3118 " break;\n"
3119 " }\n",
3120 "extobj", JSExtensionsObjectName(options, desc->file(), desc),
3121 "class", GetMessagePath(options, desc));
3122 } else {
3123 printer->Print(
3124 " reader.skipField();\n"
3125 " break;\n"
3126 " }\n");
3127 }
3128
3129 printer->Print(
3130 " }\n"
3131 " return msg;\n"
3132 "};\n"
3133 "\n"
3134 "\n");
3135 }
3136
GenerateClassDeserializeBinaryField(const GeneratorOptions & options,io::Printer * printer,const FieldDescriptor * field) const3137 void Generator::GenerateClassDeserializeBinaryField(
3138 const GeneratorOptions& options, io::Printer* printer,
3139 const FieldDescriptor* field) const {
3140 printer->Print(" case $num$:\n", "num", StrCat(field->number()));
3141
3142 if (field->is_map()) {
3143 const FieldDescriptor* key_field = MapFieldKey(field);
3144 const FieldDescriptor* value_field = MapFieldValue(field);
3145 printer->Print(
3146 " var value = msg.get$name$();\n"
3147 " reader.readMessage(value, function(message, reader) {\n",
3148 "name", JSGetterName(options, field));
3149
3150 printer->Print(
3151 " jspb.Map.deserializeBinary(message, reader, "
3152 "$keyReaderFn$, $valueReaderFn$",
3153 "keyReaderFn", JSBinaryReaderMethodName(options, key_field),
3154 "valueReaderFn", JSBinaryReaderMethodName(options, value_field));
3155
3156 if (value_field->type() == FieldDescriptor::TYPE_MESSAGE) {
3157 printer->Print(", $messageType$.deserializeBinaryFromReader",
3158 "messageType",
3159 GetMessagePath(options, value_field->message_type()));
3160 } else {
3161 printer->Print(", null");
3162 }
3163 printer->Print(", $defaultKey$", "defaultKey", JSFieldDefault(key_field));
3164 printer->Print(");\n");
3165 printer->Print(" });\n");
3166 } else {
3167 if (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) {
3168 printer->Print(
3169 " var value = new $fieldclass$;\n"
3170 " reader.read$msgOrGroup$($grpfield$value,"
3171 "$fieldclass$.deserializeBinaryFromReader);\n",
3172 "fieldclass", SubmessageTypeRef(options, field), "msgOrGroup",
3173 (field->type() == FieldDescriptor::TYPE_GROUP) ? "Group" : "Message",
3174 "grpfield",
3175 (field->type() == FieldDescriptor::TYPE_GROUP)
3176 ? (StrCat(field->number()) + ", ")
3177 : "");
3178 } else {
3179 printer->Print(
3180 " var value = /** @type {$fieldtype$} */ "
3181 "(reader.read$reader$());\n",
3182 "fieldtype",
3183 JSFieldTypeAnnotation(options, field, false, true,
3184 /* singular_if_not_packed */ true, BYTES_U8),
3185 "reader",
3186 JSBinaryReadWriteMethodName(field, /* is_writer = */ false));
3187 }
3188
3189 if (field->is_repeated() && !field->is_packed()) {
3190 printer->Print(
3191 " msg.add$name$(value);\n", "name",
3192 JSGetterName(options, field, BYTES_DEFAULT, /* drop_list = */ true));
3193 } else {
3194 // Singular fields, and packed repeated fields, receive a |value| either
3195 // as the field's value or as the array of all the field's values; set
3196 // this as the field's value directly.
3197 printer->Print(" msg.set$name$(value);\n", "name",
3198 JSGetterName(options, field));
3199 }
3200 }
3201
3202 printer->Print(" break;\n");
3203 }
3204
GenerateClassSerializeBinary(const GeneratorOptions & options,io::Printer * printer,const Descriptor * desc) const3205 void Generator::GenerateClassSerializeBinary(const GeneratorOptions& options,
3206 io::Printer* printer,
3207 const Descriptor* desc) const {
3208 printer->Print(
3209 "/**\n"
3210 " * Serializes the message to binary data (in protobuf wire format).\n"
3211 " * @return {!Uint8Array}\n"
3212 " */\n"
3213 "$class$.prototype.serializeBinary = function() {\n"
3214 " var writer = new jspb.BinaryWriter();\n"
3215 " $class$.serializeBinaryToWriter(this, writer);\n"
3216 " return writer.getResultBuffer();\n"
3217 "};\n"
3218 "\n"
3219 "\n"
3220 "/**\n"
3221 " * Serializes the given message to binary data (in protobuf wire\n"
3222 " * format), writing to the given BinaryWriter.\n"
3223 " * @param {!$class$} message\n"
3224 " * @param {!jspb.BinaryWriter} writer\n"
3225 " * @suppress {unusedLocalVariables} f is only used for nested messages\n"
3226 " */\n"
3227 "$class$.serializeBinaryToWriter = function(message, "
3228 "writer) {\n"
3229 " var f = undefined;\n",
3230 "class", GetMessagePath(options, desc));
3231
3232 for (int i = 0; i < desc->field_count(); i++) {
3233 if (!IgnoreField(desc->field(i))) {
3234 GenerateClassSerializeBinaryField(options, printer, desc->field(i));
3235 }
3236 }
3237
3238 if (IsExtendable(desc)) {
3239 printer->Print(
3240 " jspb.Message.serializeBinaryExtensions(message, writer,\n"
3241 " $extobj$Binary, $class$.prototype.getExtension);\n",
3242 "extobj", JSExtensionsObjectName(options, desc->file(), desc), "class",
3243 GetMessagePath(options, desc));
3244 }
3245
3246 printer->Print(
3247 "};\n"
3248 "\n"
3249 "\n");
3250 }
3251
GenerateClassSerializeBinaryField(const GeneratorOptions & options,io::Printer * printer,const FieldDescriptor * field) const3252 void Generator::GenerateClassSerializeBinaryField(
3253 const GeneratorOptions& options, io::Printer* printer,
3254 const FieldDescriptor* field) const {
3255 if (HasFieldPresence(options, field) &&
3256 field->cpp_type() != FieldDescriptor::CPPTYPE_MESSAGE) {
3257 std::string typed_annotation =
3258 JSFieldTypeAnnotation(options, field,
3259 /* is_setter_argument = */ false,
3260 /* force_present = */ false,
3261 /* singular_if_not_packed = */ false,
3262 /* bytes_mode = */ BYTES_DEFAULT);
3263 printer->Print(
3264 " f = /** @type {$type$} */ "
3265 "(jspb.Message.getField(message, $index$));\n",
3266 "index", JSFieldIndex(field), "type", typed_annotation);
3267 } else {
3268 printer->Print(
3269 " f = message.get$name$($nolazy$);\n", "name",
3270 JSGetterName(options, field, BYTES_U8),
3271 // No lazy creation for maps containers -- fastpath the empty case.
3272 "nolazy", field->is_map() ? "true" : "");
3273 }
3274
3275 // Print an `if (condition)` statement that evaluates to true if the field
3276 // goes on the wire.
3277 if (field->is_map()) {
3278 printer->Print(" if (f && f.getLength() > 0) {\n");
3279 } else if (field->is_repeated()) {
3280 printer->Print(" if (f.length > 0) {\n");
3281 } else {
3282 if (HasFieldPresence(options, field)) {
3283 printer->Print(" if (f != null) {\n");
3284 } else {
3285 // No field presence: serialize onto the wire only if value is
3286 // non-default. Defaults are documented here:
3287 // https://goto.google.com/lhdfm
3288 switch (field->cpp_type()) {
3289 case FieldDescriptor::CPPTYPE_INT32:
3290 case FieldDescriptor::CPPTYPE_INT64:
3291 case FieldDescriptor::CPPTYPE_UINT32:
3292 case FieldDescriptor::CPPTYPE_UINT64: {
3293 if (IsIntegralFieldWithStringJSType(field)) {
3294 // We can use `parseInt` here even though it will not be precise for
3295 // 64-bit quantities because we are only testing for zero/nonzero,
3296 // and JS numbers (64-bit floating point values, i.e., doubles) are
3297 // integer-precise in the range that includes zero.
3298 printer->Print(" if (parseInt(f, 10) !== 0) {\n");
3299 } else {
3300 printer->Print(" if (f !== 0) {\n");
3301 }
3302 break;
3303 }
3304
3305 case FieldDescriptor::CPPTYPE_ENUM:
3306 case FieldDescriptor::CPPTYPE_FLOAT:
3307 case FieldDescriptor::CPPTYPE_DOUBLE:
3308 printer->Print(" if (f !== 0.0) {\n");
3309 break;
3310 case FieldDescriptor::CPPTYPE_BOOL:
3311 printer->Print(" if (f) {\n");
3312 break;
3313 case FieldDescriptor::CPPTYPE_STRING:
3314 printer->Print(" if (f.length > 0) {\n");
3315 break;
3316 default:
3317 assert(false);
3318 break;
3319 }
3320 }
3321 }
3322
3323 // Write the field on the wire.
3324 if (field->is_map()) {
3325 const FieldDescriptor* key_field = MapFieldKey(field);
3326 const FieldDescriptor* value_field = MapFieldValue(field);
3327 printer->Print(
3328 " f.serializeBinary($index$, writer, "
3329 "$keyWriterFn$, $valueWriterFn$",
3330 "index", StrCat(field->number()), "keyWriterFn",
3331 JSBinaryWriterMethodName(options, key_field), "valueWriterFn",
3332 JSBinaryWriterMethodName(options, value_field));
3333
3334 if (value_field->type() == FieldDescriptor::TYPE_MESSAGE) {
3335 printer->Print(", $messageType$.serializeBinaryToWriter", "messageType",
3336 GetMessagePath(options, value_field->message_type()));
3337 }
3338
3339 printer->Print(");\n");
3340 } else {
3341 printer->Print(
3342 " writer.write$method$(\n"
3343 " $index$,\n"
3344 " f",
3345 "method", JSBinaryReadWriteMethodName(field, /* is_writer = */ true),
3346 "index", StrCat(field->number()));
3347
3348 if (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE &&
3349 !field->is_map()) {
3350 printer->Print(
3351 ",\n"
3352 " $submsg$.serializeBinaryToWriter\n",
3353 "submsg", SubmessageTypeRef(options, field));
3354 } else {
3355 printer->Print("\n");
3356 }
3357
3358 printer->Print(" );\n");
3359 }
3360
3361 // Close the `if`.
3362 printer->Print(" }\n");
3363 }
3364
GenerateEnum(const GeneratorOptions & options,io::Printer * printer,const EnumDescriptor * enumdesc) const3365 void Generator::GenerateEnum(const GeneratorOptions& options,
3366 io::Printer* printer,
3367 const EnumDescriptor* enumdesc) const {
3368 printer->Print(
3369 "/**\n"
3370 " * @enum {number}\n"
3371 " */\n"
3372 "$enumprefix$$name$ = {\n",
3373 "enumprefix", GetEnumPathPrefix(options, enumdesc), "name",
3374 enumdesc->name());
3375 printer->Annotate("name", enumdesc);
3376
3377 for (int i = 0; i < enumdesc->value_count(); i++) {
3378 const EnumValueDescriptor* value = enumdesc->value(i);
3379 printer->Print(" $name$: $value$$comma$\n", "name",
3380 ToEnumCase(value->name()), "value",
3381 StrCat(value->number()), "comma",
3382 (i == enumdesc->value_count() - 1) ? "" : ",");
3383 printer->Annotate("name", value);
3384 }
3385
3386 printer->Print(
3387 "};\n"
3388 "\n");
3389 }
3390
GenerateExtension(const GeneratorOptions & options,io::Printer * printer,const FieldDescriptor * field) const3391 void Generator::GenerateExtension(const GeneratorOptions& options,
3392 io::Printer* printer,
3393 const FieldDescriptor* field) const {
3394 std::string extension_scope =
3395 (field->extension_scope()
3396 ? GetMessagePath(options, field->extension_scope())
3397 : GetNamespace(options, field->file()));
3398
3399 const std::string extension_object_name = JSObjectFieldName(options, field);
3400 printer->Print(
3401 "\n"
3402 "/**\n"
3403 " * A tuple of {field number, class constructor} for the extension\n"
3404 " * field named `$nameInComment$`.\n"
3405 " * @type {!jspb.ExtensionFieldInfo<$extensionType$>}\n"
3406 " */\n"
3407 "$class$.$name$ = new jspb.ExtensionFieldInfo(\n",
3408 "nameInComment", extension_object_name, "name", extension_object_name,
3409 "class", extension_scope, "extensionType",
3410 JSFieldTypeAnnotation(options, field,
3411 /* is_setter_argument = */ false,
3412 /* force_present = */ true,
3413 /* singular_if_not_packed = */ false));
3414 printer->Annotate("name", field);
3415 printer->Print(
3416 " $index$,\n"
3417 " {$name$: 0},\n"
3418 " $ctor$,\n"
3419 " /** @type {?function((boolean|undefined),!jspb.Message=): "
3420 "!Object} */ (\n"
3421 " $toObject$),\n"
3422 " $repeated$);\n",
3423 "index", StrCat(field->number()), "name", extension_object_name,
3424 "ctor",
3425 (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE
3426 ? SubmessageTypeRef(options, field)
3427 : std::string("null")),
3428 "toObject",
3429 (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE
3430 ? (SubmessageTypeRef(options, field) + ".toObject")
3431 : std::string("null")),
3432 "repeated", (field->is_repeated() ? "1" : "0"));
3433
3434 printer->Print(
3435 "\n"
3436 "$extendName$Binary[$index$] = new jspb.ExtensionFieldBinaryInfo(\n"
3437 " $class$.$name$,\n"
3438 " $binaryReaderFn$,\n"
3439 " $binaryWriterFn$,\n"
3440 " $binaryMessageSerializeFn$,\n"
3441 " $binaryMessageDeserializeFn$,\n",
3442 "extendName",
3443 JSExtensionsObjectName(options, field->file(), field->containing_type()),
3444 "index", StrCat(field->number()), "class", extension_scope, "name",
3445 extension_object_name, "binaryReaderFn",
3446 JSBinaryReaderMethodName(options, field), "binaryWriterFn",
3447 JSBinaryWriterMethodName(options, field), "binaryMessageSerializeFn",
3448 (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE)
3449 ? (SubmessageTypeRef(options, field) + ".serializeBinaryToWriter")
3450 : "undefined",
3451 "binaryMessageDeserializeFn",
3452 (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE)
3453 ? (SubmessageTypeRef(options, field) + ".deserializeBinaryFromReader")
3454 : "undefined");
3455
3456 printer->Print(" $isPacked$);\n", "isPacked",
3457 (field->is_packed() ? "true" : "false"));
3458
3459 printer->Print(
3460 "// This registers the extension field with the extended class, so that\n"
3461 "// toObject() will function correctly.\n"
3462 "$extendName$[$index$] = $class$.$name$;\n"
3463 "\n",
3464 "extendName",
3465 JSExtensionsObjectName(options, field->file(), field->containing_type()),
3466 "index", StrCat(field->number()), "class", extension_scope, "name",
3467 extension_object_name);
3468 }
3469
ParseFromOptions(const std::vector<std::pair<std::string,std::string>> & options,std::string * error)3470 bool GeneratorOptions::ParseFromOptions(
3471 const std::vector<std::pair<std::string, std::string> >& options,
3472 std::string* error) {
3473 for (int i = 0; i < options.size(); i++) {
3474 if (options[i].first == "add_require_for_enums") {
3475 if (options[i].second != "") {
3476 *error = "Unexpected option value for add_require_for_enums";
3477 return false;
3478 }
3479 add_require_for_enums = true;
3480 } else if (options[i].first == "binary") {
3481 if (options[i].second != "") {
3482 *error = "Unexpected option value for binary";
3483 return false;
3484 }
3485 binary = true;
3486 } else if (options[i].first == "testonly") {
3487 if (options[i].second != "") {
3488 *error = "Unexpected option value for testonly";
3489 return false;
3490 }
3491 testonly = true;
3492 } else if (options[i].first == "error_on_name_conflict") {
3493 if (options[i].second != "") {
3494 *error = "Unexpected option value for error_on_name_conflict";
3495 return false;
3496 }
3497 error_on_name_conflict = true;
3498 } else if (options[i].first == "output_dir") {
3499 output_dir = options[i].second;
3500 } else if (options[i].first == "namespace_prefix") {
3501 namespace_prefix = options[i].second;
3502 } else if (options[i].first == "library") {
3503 library = options[i].second;
3504 } else if (options[i].first == "import_style") {
3505 if (options[i].second == "closure") {
3506 import_style = kImportClosure;
3507 } else if (options[i].second == "commonjs") {
3508 import_style = kImportCommonJs;
3509 } else if (options[i].second == "commonjs_strict") {
3510 import_style = kImportCommonJsStrict;
3511 } else if (options[i].second == "browser") {
3512 import_style = kImportBrowser;
3513 } else if (options[i].second == "es6") {
3514 import_style = kImportEs6;
3515 } else {
3516 *error = "Unknown import style " + options[i].second + ", expected " +
3517 "one of: closure, commonjs, browser, es6.";
3518 }
3519 } else if (options[i].first == "extension") {
3520 extension = options[i].second;
3521 } else if (options[i].first == "one_output_file_per_input_file") {
3522 if (!options[i].second.empty()) {
3523 *error = "Unexpected option value for one_output_file_per_input_file";
3524 return false;
3525 }
3526 one_output_file_per_input_file = true;
3527 } else if (options[i].first == "annotate_code") {
3528 if (!options[i].second.empty()) {
3529 *error = "Unexpected option value for annotate_code";
3530 return false;
3531 }
3532 annotate_code = true;
3533 } else {
3534 // Assume any other option is an output directory, as long as it is a bare
3535 // `key` rather than a `key=value` option.
3536 if (options[i].second != "") {
3537 *error = "Unknown option: " + options[i].first;
3538 return false;
3539 }
3540 output_dir = options[i].first;
3541 }
3542 }
3543
3544 if (import_style != kImportClosure &&
3545 (add_require_for_enums || testonly || !library.empty() ||
3546 error_on_name_conflict || extension != ".js" ||
3547 one_output_file_per_input_file)) {
3548 *error =
3549 "The add_require_for_enums, testonly, library, error_on_name_conflict, "
3550 "extension, and one_output_file_per_input_file options should only be "
3551 "used for import_style=closure";
3552 return false;
3553 }
3554
3555 return true;
3556 }
3557
output_mode() const3558 GeneratorOptions::OutputMode GeneratorOptions::output_mode() const {
3559 // We use one output file per input file if we are not using Closure or if
3560 // this is explicitly requested.
3561 if (import_style != kImportClosure || one_output_file_per_input_file) {
3562 return kOneOutputFilePerInputFile;
3563 }
3564
3565 // If a library name is provided, we put everything in that one file.
3566 if (!library.empty()) {
3567 return kEverythingInOneFile;
3568 }
3569
3570 // Otherwise, we create one output file per SCC.
3571 return kOneOutputFilePerSCC;
3572 }
3573
GenerateFilesInDepOrder(const GeneratorOptions & options,io::Printer * printer,const std::vector<const FileDescriptor * > & files) const3574 void Generator::GenerateFilesInDepOrder(
3575 const GeneratorOptions& options, io::Printer* printer,
3576 const std::vector<const FileDescriptor*>& files) const {
3577 // Build a std::set over all files so that the DFS can detect when it recurses
3578 // into a dep not specified in the user's command line.
3579 std::set<const FileDescriptor*> all_files(files.begin(), files.end());
3580 // Track the in-progress set of files that have been generated already.
3581 std::set<const FileDescriptor*> generated;
3582 for (int i = 0; i < files.size(); i++) {
3583 GenerateFileAndDeps(options, printer, files[i], &all_files, &generated);
3584 }
3585 }
3586
GenerateFileAndDeps(const GeneratorOptions & options,io::Printer * printer,const FileDescriptor * root,std::set<const FileDescriptor * > * all_files,std::set<const FileDescriptor * > * generated) const3587 void Generator::GenerateFileAndDeps(
3588 const GeneratorOptions& options, io::Printer* printer,
3589 const FileDescriptor* root, std::set<const FileDescriptor*>* all_files,
3590 std::set<const FileDescriptor*>* generated) const {
3591 // Skip if already generated.
3592 if (generated->find(root) != generated->end()) {
3593 return;
3594 }
3595 generated->insert(root);
3596
3597 // Generate all dependencies before this file's content.
3598 for (int i = 0; i < root->dependency_count(); i++) {
3599 const FileDescriptor* dep = root->dependency(i);
3600 GenerateFileAndDeps(options, printer, dep, all_files, generated);
3601 }
3602
3603 // Generate this file's content. Only generate if the file is part of the
3604 // original set requested to be generated; i.e., don't take all transitive
3605 // deps down to the roots.
3606 if (all_files->find(root) != all_files->end()) {
3607 GenerateClassesAndEnums(options, printer, root);
3608 }
3609 }
3610
GenerateFile(const FileDescriptor * file,const GeneratorOptions & options,GeneratorContext * context,bool use_short_name) const3611 bool Generator::GenerateFile(const FileDescriptor* file,
3612 const GeneratorOptions& options,
3613 GeneratorContext* context,
3614 bool use_short_name) const {
3615 std::string filename =
3616 options.output_dir + "/" +
3617 GetJSFilename(options, use_short_name
3618 ? file->name().substr(file->name().rfind('/'))
3619 : file->name());
3620 std::unique_ptr<io::ZeroCopyOutputStream> output(context->Open(filename));
3621 GOOGLE_CHECK(output);
3622 GeneratedCodeInfo annotations;
3623 io::AnnotationProtoCollector<GeneratedCodeInfo> annotation_collector(
3624 &annotations);
3625 io::Printer printer(output.get(), '$',
3626 options.annotate_code ? &annotation_collector : nullptr);
3627
3628 GenerateFile(options, &printer, file);
3629
3630 if (printer.failed()) {
3631 return false;
3632 }
3633
3634 if (options.annotate_code) {
3635 EmbedCodeAnnotations(annotations, &printer);
3636 }
3637
3638 return true;
3639 }
3640
GenerateFile(const GeneratorOptions & options,io::Printer * printer,const FileDescriptor * file) const3641 void Generator::GenerateFile(const GeneratorOptions& options,
3642 io::Printer* printer,
3643 const FileDescriptor* file) const {
3644 GenerateHeader(options, file, printer);
3645
3646 // Generate "require" statements.
3647 if ((options.import_style == GeneratorOptions::kImportCommonJs ||
3648 options.import_style == GeneratorOptions::kImportCommonJsStrict)) {
3649 printer->Print("var jspb = require('google-protobuf');\n");
3650 printer->Print("var goog = jspb;\n");
3651
3652 // Do not use global scope in strict mode
3653 if (options.import_style == GeneratorOptions::kImportCommonJsStrict) {
3654 printer->Print("var proto = {};\n\n");
3655 } else {
3656 printer->Print("var global = Function('return this')();\n\n");
3657 }
3658
3659 for (int i = 0; i < file->dependency_count(); i++) {
3660 const std::string& name = file->dependency(i)->name();
3661 printer->Print(
3662 "var $alias$ = require('$file$');\n"
3663 "goog.object.extend(proto, $alias$);\n",
3664 "alias", ModuleAlias(name), "file",
3665 GetRootPath(file->name(), name) + GetJSFilename(options, name));
3666 }
3667 }
3668
3669 std::set<std::string> provided;
3670 std::set<const FieldDescriptor*> extensions;
3671 for (int i = 0; i < file->extension_count(); i++) {
3672 // We honor the jspb::ignore option here only when working with
3673 // Closure-style imports. Use of this option is discouraged and so we want
3674 // to avoid adding new support for it.
3675 if (options.import_style == GeneratorOptions::kImportClosure &&
3676 IgnoreField(file->extension(i))) {
3677 continue;
3678 }
3679 provided.insert(GetNamespace(options, file) + "." +
3680 JSObjectFieldName(options, file->extension(i)));
3681 extensions.insert(file->extension(i));
3682 }
3683
3684 FindProvidesForFile(options, printer, file, &provided);
3685 GenerateProvides(options, printer, &provided);
3686 std::vector<const FileDescriptor*> files;
3687 files.push_back(file);
3688 if (options.import_style == GeneratorOptions::kImportClosure) {
3689 GenerateRequiresForLibrary(options, printer, files, &provided);
3690 }
3691
3692 GenerateClassesAndEnums(options, printer, file);
3693
3694 // Generate code for top-level extensions. Extensions nested inside messages
3695 // are emitted inside GenerateClassesAndEnums().
3696 for (std::set<const FieldDescriptor*>::const_iterator it = extensions.begin();
3697 it != extensions.end(); ++it) {
3698 GenerateExtension(options, printer, *it);
3699 }
3700
3701 // if provided is empty, do not export anything
3702 if (options.import_style == GeneratorOptions::kImportCommonJs &&
3703 !provided.empty()) {
3704 printer->Print("goog.object.extend(exports, $package$);\n", "package",
3705 GetNamespace(options, file));
3706 } else if (options.import_style == GeneratorOptions::kImportCommonJsStrict) {
3707 printer->Print("goog.object.extend(exports, proto);\n", "package",
3708 GetNamespace(options, file));
3709 }
3710
3711 // Emit well-known type methods.
3712 for (FileToc* toc = well_known_types_js; toc->name != NULL; toc++) {
3713 std::string name = std::string("google/protobuf/") + toc->name;
3714 if (name == StripProto(file->name()) + ".js") {
3715 printer->Print(toc->data);
3716 }
3717 }
3718 }
3719
GenerateAll(const std::vector<const FileDescriptor * > & files,const std::string & parameter,GeneratorContext * context,std::string * error) const3720 bool Generator::GenerateAll(const std::vector<const FileDescriptor*>& files,
3721 const std::string& parameter,
3722 GeneratorContext* context,
3723 std::string* error) const {
3724 std::vector<std::pair<std::string, std::string> > option_pairs;
3725 ParseGeneratorParameter(parameter, &option_pairs);
3726 GeneratorOptions options;
3727 if (!options.ParseFromOptions(option_pairs, error)) {
3728 return false;
3729 }
3730
3731
3732 if (options.output_mode() == GeneratorOptions::kEverythingInOneFile) {
3733 // All output should go in a single file.
3734 std::string filename = options.output_dir + "/" + options.library +
3735 options.GetFileNameExtension();
3736 std::unique_ptr<io::ZeroCopyOutputStream> output(context->Open(filename));
3737 GOOGLE_CHECK(output.get());
3738 GeneratedCodeInfo annotations;
3739 io::AnnotationProtoCollector<GeneratedCodeInfo> annotation_collector(
3740 &annotations);
3741 io::Printer printer(
3742 output.get(), '$',
3743 options.annotate_code ? &annotation_collector : nullptr);
3744
3745 // Pull out all extensions -- we need these to generate all
3746 // provides/requires.
3747 std::vector<const FieldDescriptor*> extensions;
3748 for (int i = 0; i < files.size(); i++) {
3749 for (int j = 0; j < files[i]->extension_count(); j++) {
3750 const FieldDescriptor* extension = files[i]->extension(j);
3751 extensions.push_back(extension);
3752 }
3753 }
3754
3755 if (files.size() == 1) {
3756 GenerateHeader(options, files[0], &printer);
3757 } else {
3758 GenerateHeader(options, nullptr, &printer);
3759 }
3760
3761 std::set<std::string> provided;
3762 FindProvides(options, &printer, files, &provided);
3763 FindProvidesForFields(options, &printer, extensions, &provided);
3764 GenerateProvides(options, &printer, &provided);
3765 GenerateTestOnly(options, &printer);
3766 GenerateRequiresForLibrary(options, &printer, files, &provided);
3767
3768 GenerateFilesInDepOrder(options, &printer, files);
3769
3770 for (int i = 0; i < extensions.size(); i++) {
3771 if (ShouldGenerateExtension(extensions[i])) {
3772 GenerateExtension(options, &printer, extensions[i]);
3773 }
3774 }
3775
3776 if (printer.failed()) {
3777 return false;
3778 }
3779 if (options.annotate_code) {
3780 EmbedCodeAnnotations(annotations, &printer);
3781 }
3782 } else if (options.output_mode() == GeneratorOptions::kOneOutputFilePerSCC) {
3783 std::set<const Descriptor*> have_printed;
3784 SCCAnalyzer<DepsGenerator> analyzer;
3785 std::map<const void*, std::string> allowed_map;
3786 if (!GenerateJspbAllowedMap(options, files, &allowed_map, &analyzer,
3787 error)) {
3788 return false;
3789 }
3790
3791 bool generated = false;
3792 for (int i = 0; i < files.size(); i++) {
3793 const FileDescriptor* file = files[i];
3794 // Force well known type to generate in a whole file.
3795 if (IsWellKnownTypeFile(file)) {
3796 if (!GenerateFile(file, options, context, true)) {
3797 return false;
3798 }
3799 generated = true;
3800 continue;
3801 }
3802 for (int j = 0; j < file->message_type_count(); j++) {
3803 const Descriptor* desc = file->message_type(j);
3804 if (have_printed.count(desc) ||
3805 allowed_map.count(analyzer.GetSCC(desc)) == 0) {
3806 continue;
3807 }
3808
3809 generated = true;
3810 const SCC* scc = analyzer.GetSCC(desc);
3811 const std::string& filename = allowed_map[scc];
3812 std::unique_ptr<io::ZeroCopyOutputStream> output(
3813 context->Open(filename));
3814 GOOGLE_CHECK(output.get());
3815 GeneratedCodeInfo annotations;
3816 io::AnnotationProtoCollector<GeneratedCodeInfo> annotation_collector(
3817 &annotations);
3818 io::Printer printer(
3819 output.get(), '$',
3820 options.annotate_code ? &annotation_collector : nullptr);
3821
3822 GenerateHeader(options, file, &printer);
3823
3824 std::set<std::string> provided;
3825 for (auto one_desc : scc->descriptors) {
3826 if (one_desc->containing_type() == nullptr) {
3827 FindProvidesForMessage(options, &printer, one_desc, &provided);
3828 }
3829 }
3830 GenerateProvides(options, &printer, &provided);
3831 GenerateTestOnly(options, &printer);
3832 GenerateRequiresForSCC(options, &printer, scc, &provided);
3833
3834 for (auto one_desc : scc->descriptors) {
3835 if (one_desc->containing_type() == nullptr) {
3836 GenerateClassConstructorAndDeclareExtensionFieldInfo(
3837 options, &printer, one_desc);
3838 }
3839 }
3840 for (auto one_desc : scc->descriptors) {
3841 if (one_desc->containing_type() == nullptr) {
3842 GenerateClass(options, &printer, one_desc);
3843 }
3844 }
3845
3846 for (auto one_desc : scc->descriptors) {
3847 have_printed.insert(one_desc);
3848 }
3849
3850 if (printer.failed()) {
3851 return false;
3852 }
3853 if (options.annotate_code) {
3854 EmbedCodeAnnotations(annotations, &printer);
3855 }
3856 }
3857 for (int j = 0; j < file->enum_type_count(); j++) {
3858 const EnumDescriptor* enumdesc = file->enum_type(j);
3859 if (allowed_map.count(enumdesc) == 0) {
3860 continue;
3861 }
3862
3863 generated = true;
3864 const std::string& filename = allowed_map[enumdesc];
3865 std::unique_ptr<io::ZeroCopyOutputStream> output(
3866 context->Open(filename));
3867 GOOGLE_CHECK(output.get());
3868 GeneratedCodeInfo annotations;
3869 io::AnnotationProtoCollector<GeneratedCodeInfo> annotation_collector(
3870 &annotations);
3871 io::Printer printer(
3872 output.get(), '$',
3873 options.annotate_code ? &annotation_collector : nullptr);
3874
3875 GenerateHeader(options, file, &printer);
3876
3877 std::set<std::string> provided;
3878 FindProvidesForEnum(options, &printer, enumdesc, &provided);
3879 GenerateProvides(options, &printer, &provided);
3880 GenerateTestOnly(options, &printer);
3881
3882 GenerateEnum(options, &printer, enumdesc);
3883
3884 if (printer.failed()) {
3885 return false;
3886 }
3887 if (options.annotate_code) {
3888 EmbedCodeAnnotations(annotations, &printer);
3889 }
3890 }
3891 // File-level extensions (message-level extensions are generated under
3892 // the enclosing message).
3893 if (allowed_map.count(file) == 1) {
3894 generated = true;
3895 const std::string& filename = allowed_map[file];
3896
3897 std::unique_ptr<io::ZeroCopyOutputStream> output(
3898 context->Open(filename));
3899 GOOGLE_CHECK(output.get());
3900 GeneratedCodeInfo annotations;
3901 io::AnnotationProtoCollector<GeneratedCodeInfo> annotation_collector(
3902 &annotations);
3903 io::Printer printer(
3904 output.get(), '$',
3905 options.annotate_code ? &annotation_collector : nullptr);
3906
3907 GenerateHeader(options, file, &printer);
3908
3909 std::set<std::string> provided;
3910 std::vector<const FieldDescriptor*> fields;
3911
3912 for (int j = 0; j < files[i]->extension_count(); j++) {
3913 if (ShouldGenerateExtension(files[i]->extension(j))) {
3914 fields.push_back(files[i]->extension(j));
3915 }
3916 }
3917
3918 FindProvidesForFields(options, &printer, fields, &provided);
3919 GenerateProvides(options, &printer, &provided);
3920 GenerateTestOnly(options, &printer);
3921 GenerateRequiresForExtensions(options, &printer, fields, &provided);
3922
3923 for (int j = 0; j < files[i]->extension_count(); j++) {
3924 if (ShouldGenerateExtension(files[i]->extension(j))) {
3925 GenerateExtension(options, &printer, files[i]->extension(j));
3926 }
3927 }
3928 if (options.annotate_code) {
3929 EmbedCodeAnnotations(annotations, &printer);
3930 }
3931 }
3932 }
3933 if (!generated) {
3934 std::string filename = options.output_dir + "/" +
3935 "empty_no_content_void_file" +
3936 options.GetFileNameExtension();
3937 std::unique_ptr<io::ZeroCopyOutputStream> output(context->Open(filename));
3938 }
3939 } else /* options.output_mode() == kOneOutputFilePerInputFile */ {
3940 // Generate one output file per input (.proto) file.
3941
3942 for (int i = 0; i < files.size(); i++) {
3943 const FileDescriptor* file = files[i];
3944 if (!GenerateFile(file, options, context, false)) {
3945 return false;
3946 }
3947 }
3948 }
3949 return true;
3950 }
3951
3952 } // namespace js
3953 } // namespace compiler
3954 } // namespace protobuf
3955 } // namespace google
3956