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