• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Protocol Buffers - Google's data interchange format
2 // Copyright 2008 Google Inc.  All rights reserved.
3 //
4 // Use of this source code is governed by a BSD-style
5 // license that can be found in the LICENSE file or at
6 // https://developers.google.com/open-source/licenses/bsd
7 
8 // Author: kenton@google.com (Kenton Varda)
9 //  Based on original Protocol Buffers design by
10 //  Sanjay Ghemawat, Jeff Dean, and others.
11 
12 #include "google/protobuf/compiler/java/doc_comment.h"
13 
14 #include <stddef.h>
15 
16 #include <algorithm>
17 #include <cctype>
18 #include <string>
19 #include <vector>
20 
21 #include "absl/strings/str_split.h"
22 #include "absl/strings/string_view.h"
23 #include "google/protobuf/compiler/java/options.h"
24 #include "google/protobuf/descriptor.h"
25 #include "google/protobuf/descriptor.pb.h"
26 #include "google/protobuf/io/printer.h"
27 
28 namespace google {
29 namespace protobuf {
30 namespace compiler {
31 namespace java {
32 
EscapeJavadoc(absl::string_view input)33 std::string EscapeJavadoc(absl::string_view input) {
34   std::string result;
35   result.reserve(input.size() * 2);
36 
37   char prev = '*';
38 
39   for (std::string::size_type i = 0; i < input.size(); i++) {
40     char c = input[i];
41     switch (c) {
42       case '*':
43         // Avoid "/*".
44         if (prev == '/') {
45           result.append("&#42;");
46         } else {
47           result.push_back(c);
48         }
49         break;
50       case '/':
51         // Avoid "*/".
52         if (prev == '*') {
53           result.append("&#47;");
54         } else {
55           result.push_back(c);
56         }
57         break;
58       case '@':
59         // '@' starts javadoc tags including the @deprecated tag, which will
60         // cause a compile-time error if inserted before a declaration that
61         // does not have a corresponding @Deprecated annotation.
62         result.append("&#64;");
63         break;
64       case '<':
65         // Avoid interpretation as HTML.
66         result.append("&lt;");
67         break;
68       case '>':
69         // Avoid interpretation as HTML.
70         result.append("&gt;");
71         break;
72       case '&':
73         // Avoid interpretation as HTML.
74         result.append("&amp;");
75         break;
76       case '\\':
77         // Java interprets Unicode escape sequences anywhere!
78         result.append("&#92;");
79         break;
80       default:
81         result.push_back(c);
82         break;
83     }
84 
85     prev = c;
86   }
87 
88   return result;
89 }
90 
EscapeKdoc(absl::string_view input)91 static std::string EscapeKdoc(absl::string_view input) {
92   std::string result;
93   result.reserve(input.size() * 2);
94 
95   char prev = 'a';
96 
97   for (char c : input) {
98     switch (c) {
99       case '*':
100         // Avoid "/*".
101         if (prev == '/') {
102           result.append("&#42;");
103         } else {
104           result.push_back(c);
105         }
106         break;
107       case '/':
108         // Avoid "*/".
109         if (prev == '*') {
110           result.append("&#47;");
111         } else {
112           result.push_back(c);
113         }
114         break;
115       default:
116         result.push_back(c);
117         break;
118     }
119 
120     prev = c;
121   }
122 
123   return result;
124 }
125 
WriteDocCommentBodyForLocation(io::Printer * printer,const SourceLocation & location,const Options options,const bool kdoc)126 static void WriteDocCommentBodyForLocation(io::Printer* printer,
127                                            const SourceLocation& location,
128                                            const Options options,
129                                            const bool kdoc) {
130   if (options.strip_nonfunctional_codegen) {
131     // TODO: Remove once prototiller can avoid making
132     // extraneous formatting changes to comments.
133     return;
134   }
135 
136   std::string comments = location.leading_comments.empty()
137                              ? location.trailing_comments
138                              : location.leading_comments;
139   if (!comments.empty()) {
140     if (kdoc) {
141       comments = EscapeKdoc(comments);
142     } else {
143       comments = EscapeJavadoc(comments);
144     }
145 
146     std::vector<std::string> lines = absl::StrSplit(comments, '\n');
147     while (!lines.empty() && lines.back().empty()) {
148       lines.pop_back();
149     }
150 
151     if (kdoc) {
152       printer->Print(" * ```\n");
153     } else {
154       printer->Print(" * <pre>\n");
155     }
156 
157     for (size_t i = 0; i < lines.size(); i++) {
158       // Lines should start with a single space and any extraneous leading
159       // spaces should be stripped. For lines starting with a /, the leading
160       // space will prevent putting it right after the leading asterisk from
161       // closing the comment.
162       std::string line = lines[i];
163       line.erase(line.begin(),
164                  std::find_if(line.begin(), line.end(), [](unsigned char ch) {
165                    return !std::isspace(ch);
166                  }));
167       if (!line.empty()) {
168         printer->Print(" * $line$\n", "line", line);
169       } else {
170         printer->Print(" *\n");
171       }
172     }
173 
174     if (kdoc) {
175       printer->Print(" * ```\n");
176     } else {
177       printer->Print(" * </pre>\n");
178     }
179     printer->Print(" *\n");
180   }
181 }
182 
183 template <typename DescriptorType>
WriteDocCommentBody(io::Printer * printer,const DescriptorType * descriptor,const Options options,const bool kdoc)184 static void WriteDocCommentBody(io::Printer* printer,
185                                 const DescriptorType* descriptor,
186                                 const Options options, const bool kdoc) {
187   SourceLocation location;
188   if (descriptor->GetSourceLocation(&location)) {
189     WriteDocCommentBodyForLocation(printer, location, options, kdoc);
190   }
191 }
192 
FirstLineOf(const std::string & value)193 static std::string FirstLineOf(const std::string& value) {
194   std::string result = value;
195 
196   std::string::size_type pos = result.find_first_of('\n');
197   if (pos != std::string::npos) {
198     result.erase(pos);
199   }
200 
201   // If line ends in an opening brace, make it "{ ... }" so it looks nice.
202   if (!result.empty() && result[result.size() - 1] == '{') {
203     result.append(" ... }");
204   }
205 
206   return result;
207 }
208 
WriteDebugString(io::Printer * printer,const FieldDescriptor * field,const Options options,const bool kdoc)209 static void WriteDebugString(io::Printer* printer, const FieldDescriptor* field,
210                              const Options options, const bool kdoc) {
211   std::string field_comment = FirstLineOf(field->DebugString());
212   if (options.strip_nonfunctional_codegen) {
213     field_comment = field->name();
214   }
215   if (kdoc) {
216     printer->Print(" * `$def$`\n", "def", EscapeKdoc(field_comment));
217   } else {
218     printer->Print(" * <code>$def$</code>\n", "def",
219                    EscapeJavadoc(field_comment));
220   }
221 }
222 
WriteMessageDocComment(io::Printer * printer,const Descriptor * message,const Options options,const bool kdoc)223 void WriteMessageDocComment(io::Printer* printer, const Descriptor* message,
224                             const Options options, const bool kdoc) {
225   printer->Print("/**\n");
226   WriteDocCommentBody(printer, message, options, kdoc);
227   if (kdoc) {
228     printer->Print(
229         " * Protobuf type `$fullname$`\n"
230         " */\n",
231         "fullname", EscapeKdoc(message->full_name()));
232   } else {
233     printer->Print(
234         " * Protobuf type {@code $fullname$}\n"
235         " */\n",
236         "fullname", EscapeJavadoc(message->full_name()));
237   }
238 }
239 
WriteFieldDocComment(io::Printer * printer,const FieldDescriptor * field,const Options options,const bool kdoc)240 void WriteFieldDocComment(io::Printer* printer, const FieldDescriptor* field,
241                           const Options options, const bool kdoc) {
242   // We start the comment with the main body based on the comments from the
243   // .proto file (if present). We then continue with the field declaration,
244   // e.g.:
245   //   optional string foo = 5;
246   // And then we end with the javadoc tags if applicable.
247   // If the field is a group, the debug string might end with {.
248   printer->Print("/**\n");
249   WriteDocCommentBody(printer, field, options, kdoc);
250   WriteDebugString(printer, field, options, kdoc);
251   printer->Print(" */\n");
252 }
253 
WriteDeprecatedJavadoc(io::Printer * printer,const FieldDescriptor * field,const FieldAccessorType type,const Options options)254 void WriteDeprecatedJavadoc(io::Printer* printer, const FieldDescriptor* field,
255                             const FieldAccessorType type,
256                             const Options options) {
257   if (!field->options().deprecated()) {
258     return;
259   }
260 
261   // Lite codegen does not annotate set & clear methods with @Deprecated.
262   if (field->file()->options().optimize_for() == FileOptions::LITE_RUNTIME &&
263       (type == SETTER || type == CLEARER)) {
264     return;
265   }
266 
267   std::string startLine = "0";
268   SourceLocation location;
269   if (field->GetSourceLocation(&location)) {
270     startLine = std::to_string(location.start_line);
271   }
272 
273   printer->Print(" * @deprecated $name$ is deprecated.\n", "name",
274                  field->full_name());
275   if (!options.strip_nonfunctional_codegen) {
276     printer->Print(" *     See $file$;l=$line$\n", "file",
277                    field->file()->name(), "line", startLine);
278   }
279 }
280 
WriteFieldAccessorDocComment(io::Printer * printer,const FieldDescriptor * field,const FieldAccessorType type,const Options options,const bool builder,const bool kdoc)281 void WriteFieldAccessorDocComment(io::Printer* printer,
282                                   const FieldDescriptor* field,
283                                   const FieldAccessorType type,
284                                   const Options options, const bool builder,
285                                   const bool kdoc) {
286   printer->Print("/**\n");
287   WriteDocCommentBody(printer, field, options, kdoc);
288   WriteDebugString(printer, field, options, kdoc);
289   if (!kdoc) WriteDeprecatedJavadoc(printer, field, type, options);
290   switch (type) {
291     case HAZZER:
292       printer->Print(" * @return Whether the $name$ field is set.\n", "name",
293                      field->camelcase_name());
294       break;
295     case GETTER:
296       printer->Print(" * @return The $name$.\n", "name",
297                      field->camelcase_name());
298       break;
299     case SETTER:
300       printer->Print(" * @param value The $name$ to set.\n", "name",
301                      field->camelcase_name());
302       break;
303     case CLEARER:
304       // Print nothing
305       break;
306     // Repeated
307     case LIST_COUNT:
308       printer->Print(" * @return The count of $name$.\n", "name",
309                      field->camelcase_name());
310       break;
311     case LIST_GETTER:
312       printer->Print(" * @return A list containing the $name$.\n", "name",
313                      field->camelcase_name());
314       break;
315     case LIST_INDEXED_GETTER:
316       printer->Print(" * @param index The index of the element to return.\n");
317       printer->Print(" * @return The $name$ at the given index.\n", "name",
318                      field->camelcase_name());
319       break;
320     case LIST_INDEXED_SETTER:
321       printer->Print(" * @param index The index to set the value at.\n");
322       printer->Print(" * @param value The $name$ to set.\n", "name",
323                      field->camelcase_name());
324       break;
325     case LIST_ADDER:
326       printer->Print(" * @param value The $name$ to add.\n", "name",
327                      field->camelcase_name());
328       break;
329     case LIST_MULTI_ADDER:
330       printer->Print(" * @param values The $name$ to add.\n", "name",
331                      field->camelcase_name());
332       break;
333   }
334   if (builder) {
335     printer->Print(" * @return This builder for chaining.\n");
336   }
337   printer->Print(" */\n");
338 }
339 
WriteFieldEnumValueAccessorDocComment(io::Printer * printer,const FieldDescriptor * field,const FieldAccessorType type,const Options options,const bool builder,const bool kdoc)340 void WriteFieldEnumValueAccessorDocComment(io::Printer* printer,
341                                            const FieldDescriptor* field,
342                                            const FieldAccessorType type,
343                                            const Options options,
344                                            const bool builder,
345                                            const bool kdoc) {
346   printer->Print("/**\n");
347   WriteDocCommentBody(printer, field, options, kdoc);
348   WriteDebugString(printer, field, options, kdoc);
349   if (!kdoc) WriteDeprecatedJavadoc(printer, field, type, options);
350   switch (type) {
351     case HAZZER:
352       // Should never happen
353       break;
354     case GETTER:
355       printer->Print(
356           " * @return The enum numeric value on the wire for $name$.\n", "name",
357           field->camelcase_name());
358       break;
359     case SETTER:
360       printer->Print(
361           " * @param value The enum numeric value on the wire for $name$ to "
362           "set.\n",
363           "name", field->camelcase_name());
364       break;
365     case CLEARER:
366       // Print nothing
367       break;
368     // Repeated
369     case LIST_COUNT:
370       // Should never happen
371       break;
372     case LIST_GETTER:
373       printer->Print(
374           " * @return A list containing the enum numeric values on the wire "
375           "for $name$.\n",
376           "name", field->camelcase_name());
377       break;
378     case LIST_INDEXED_GETTER:
379       printer->Print(" * @param index The index of the value to return.\n");
380       printer->Print(
381           " * @return The enum numeric value on the wire of $name$ at the "
382           "given index.\n",
383           "name", field->camelcase_name());
384       break;
385     case LIST_INDEXED_SETTER:
386       printer->Print(" * @param index The index to set the value at.\n");
387       printer->Print(
388           " * @param value The enum numeric value on the wire for $name$ to "
389           "set.\n",
390           "name", field->camelcase_name());
391       break;
392     case LIST_ADDER:
393       printer->Print(
394           " * @param value The enum numeric value on the wire for $name$ to "
395           "add.\n",
396           "name", field->camelcase_name());
397       break;
398     case LIST_MULTI_ADDER:
399       printer->Print(
400           " * @param values The enum numeric values on the wire for $name$ to "
401           "add.\n",
402           "name", field->camelcase_name());
403       break;
404   }
405   if (builder) {
406     printer->Print(" * @return This builder for chaining.\n");
407   }
408   printer->Print(" */\n");
409 }
410 
WriteFieldStringBytesAccessorDocComment(io::Printer * printer,const FieldDescriptor * field,const FieldAccessorType type,const Options options,const bool builder,const bool kdoc)411 void WriteFieldStringBytesAccessorDocComment(io::Printer* printer,
412                                              const FieldDescriptor* field,
413                                              const FieldAccessorType type,
414                                              const Options options,
415                                              const bool builder,
416                                              const bool kdoc) {
417   printer->Print("/**\n");
418   WriteDocCommentBody(printer, field, options, kdoc);
419   WriteDebugString(printer, field, options, kdoc);
420   if (!kdoc) WriteDeprecatedJavadoc(printer, field, type, options);
421   switch (type) {
422     case HAZZER:
423       // Should never happen
424       break;
425     case GETTER:
426       printer->Print(" * @return The bytes for $name$.\n", "name",
427                      field->camelcase_name());
428       break;
429     case SETTER:
430       printer->Print(" * @param value The bytes for $name$ to set.\n", "name",
431                      field->camelcase_name());
432       break;
433     case CLEARER:
434       // Print nothing
435       break;
436     // Repeated
437     case LIST_COUNT:
438       // Should never happen
439       break;
440     case LIST_GETTER:
441       printer->Print(" * @return A list containing the bytes for $name$.\n",
442                      "name", field->camelcase_name());
443       break;
444     case LIST_INDEXED_GETTER:
445       printer->Print(" * @param index The index of the value to return.\n");
446       printer->Print(" * @return The bytes of the $name$ at the given index.\n",
447                      "name", field->camelcase_name());
448       break;
449     case LIST_INDEXED_SETTER:
450       printer->Print(" * @param index The index to set the value at.\n");
451       printer->Print(" * @param value The bytes of the $name$ to set.\n",
452                      "name", field->camelcase_name());
453       break;
454     case LIST_ADDER:
455       printer->Print(" * @param value The bytes of the $name$ to add.\n",
456                      "name", field->camelcase_name());
457       break;
458     case LIST_MULTI_ADDER:
459       printer->Print(" * @param values The bytes of the $name$ to add.\n",
460                      "name", field->camelcase_name());
461       break;
462   }
463   if (builder) {
464     printer->Print(" * @return This builder for chaining.\n");
465   }
466   printer->Print(" */\n");
467 }
468 
469 // Enum
470 
WriteEnumDocComment(io::Printer * printer,const EnumDescriptor * enum_,const Options options,const bool kdoc)471 void WriteEnumDocComment(io::Printer* printer, const EnumDescriptor* enum_,
472                          const Options options, const bool kdoc) {
473   printer->Print("/**\n");
474   WriteDocCommentBody(printer, enum_, options, kdoc);
475   if (kdoc) {
476     printer->Print(
477         " * Protobuf enum `$fullname$`\n"
478         " */\n",
479         "fullname", EscapeKdoc(enum_->full_name()));
480   } else {
481     printer->Print(
482         " * Protobuf enum {@code $fullname$}\n"
483         " */\n",
484         "fullname", EscapeJavadoc(enum_->full_name()));
485   }
486 }
487 
WriteEnumValueDocComment(io::Printer * printer,const EnumValueDescriptor * value,const Options options)488 void WriteEnumValueDocComment(io::Printer* printer,
489                               const EnumValueDescriptor* value,
490                               const Options options) {
491   printer->Print("/**\n");
492   WriteDocCommentBody(printer, value, options, /* kdoc */ false);
493 
494   printer->Print(
495       " * <code>$def$</code>\n"
496       " */\n",
497       "def", EscapeJavadoc(FirstLineOf(value->DebugString())));
498 }
499 
WriteServiceDocComment(io::Printer * printer,const ServiceDescriptor * service,const Options options)500 void WriteServiceDocComment(io::Printer* printer,
501                             const ServiceDescriptor* service,
502                             const Options options) {
503   printer->Print("/**\n");
504   WriteDocCommentBody(printer, service, options, /* kdoc */ false);
505   printer->Print(
506       " * Protobuf service {@code $fullname$}\n"
507       " */\n",
508       "fullname", EscapeJavadoc(service->full_name()));
509 }
510 
WriteMethodDocComment(io::Printer * printer,const MethodDescriptor * method,const Options options)511 void WriteMethodDocComment(io::Printer* printer, const MethodDescriptor* method,
512                            const Options options) {
513   printer->Print("/**\n");
514   WriteDocCommentBody(printer, method, options, /* kdoc */ false);
515   printer->Print(
516       " * <code>$def$</code>\n"
517       " */\n",
518       "def", EscapeJavadoc(FirstLineOf(method->DebugString())));
519 }
520 
521 }  // namespace java
522 }  // namespace compiler
523 }  // namespace protobuf
524 }  // namespace google
525