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("*");
46 } else {
47 result.push_back(c);
48 }
49 break;
50 case '/':
51 // Avoid "*/".
52 if (prev == '*') {
53 result.append("/");
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("@");
63 break;
64 case '<':
65 // Avoid interpretation as HTML.
66 result.append("<");
67 break;
68 case '>':
69 // Avoid interpretation as HTML.
70 result.append(">");
71 break;
72 case '&':
73 // Avoid interpretation as HTML.
74 result.append("&");
75 break;
76 case '\\':
77 // Java interprets Unicode escape sequences anywhere!
78 result.append("\");
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("*");
103 } else {
104 result.push_back(c);
105 }
106 break;
107 case '/':
108 // Avoid "*/".
109 if (prev == '*') {
110 result.append("/");
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