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