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