Skip to content

Commit 919b200

Browse files
mcartonstepancheg
authored andcommitted
Add documentation in generated code
Fixes stepancheg#402
1 parent 0674fbb commit 919b200

File tree

5 files changed

+202
-11
lines changed

5 files changed

+202
-11
lines changed

protobuf-codegen/src/code_writer.rs

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,85 @@ impl<'a> CodeWriter<'a> {
270270
}
271271
}
272272

273+
fn documentation(&mut self, comment: &str) {
274+
if comment.is_empty() {
275+
self.write_line("///");
276+
} else {
277+
self.write_line(&format!("/// {}", comment));
278+
}
279+
}
280+
281+
/// Writes the documentation of the given path.
282+
///
283+
/// Protobuf paths are defined in proto/google/protobuf/descriptor.proto,
284+
/// in the `SourceCodeInfo` message.
285+
///
286+
/// For example, say we have a file like:
287+
///
288+
/// ```ignore
289+
/// message Foo {
290+
/// optional string foo = 1;
291+
/// }
292+
/// ```
293+
///
294+
/// Let's look at just the field definition. We have the following paths:
295+
///
296+
/// ```ignore
297+
/// path represents
298+
/// [ 4, 0, 2, 0 ] The whole field definition.
299+
/// [ 4, 0, 2, 0, 4 ] The label (optional).
300+
/// [ 4, 0, 2, 0, 5 ] The type (string).
301+
/// [ 4, 0, 2, 0, 1 ] The name (foo).
302+
/// [ 4, 0, 2, 0, 3 ] The number (1).
303+
/// ```
304+
///
305+
/// The `4`s can be obtained using simple introspection:
306+
///
307+
/// ```
308+
/// use protobuf::descriptor::FileDescriptorProto;
309+
/// use protobuf::reflect::MessageDescriptor;
310+
///
311+
/// let id = MessageDescriptor::for_type::<FileDescriptorProto>()
312+
/// .field_by_name("message_type")
313+
/// .expect("`message_type` must exist")
314+
/// .proto()
315+
/// .get_number();
316+
///
317+
/// assert_eq!(id, 4);
318+
/// ```
319+
///
320+
/// The first `0` here means this path refers to the first message.
321+
///
322+
/// The `2` then refers to the `field` field on the `DescriptorProto` message.
323+
///
324+
/// Then comes another `0` to refer to the first field of the current message.
325+
///
326+
/// Etc.
327+
328+
pub fn all_documentation(
329+
&mut self,
330+
info: Option<&protobuf::descriptor::SourceCodeInfo>,
331+
path: &[i32],
332+
) {
333+
let doc = info
334+
.map(|v| &v.location)
335+
.and_then(|ls| ls.iter().find(|l| l.path == path))
336+
.map(|l| l.get_leading_comments());
337+
338+
let lines = doc
339+
.iter()
340+
.map(|doc| doc.lines())
341+
.flatten()
342+
.collect::<Vec<_>>();
343+
344+
// Skip comments with code blocks to avoid rustdoc trying to compile them.
345+
if !lines.iter().any(|line| line.starts_with(" ")) {
346+
for doc in &lines {
347+
self.documentation(doc);
348+
}
349+
}
350+
}
351+
273352
pub fn fn_def(&mut self, sig: &str) {
274353
self.write_line(&format!("fn {};", sig));
275354
}

protobuf-codegen/src/enums.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,17 @@ pub(crate) struct EnumGen<'a> {
5555
type_name: RustIdentWithPath,
5656
lite_runtime: bool,
5757
customize: Customize,
58+
path: &'a [i32],
59+
info: Option<&'a SourceCodeInfo>,
5860
}
5961

6062
impl<'a> EnumGen<'a> {
6163
pub fn new(
6264
enum_with_scope: &'a EnumWithScope<'a>,
6365
customize: &Customize,
6466
_root_scope: &RootScope,
67+
path: &'a [i32],
68+
info: Option<&'a SourceCodeInfo>,
6569
) -> EnumGen<'a> {
6670
let lite_runtime = customize.lite_runtime.unwrap_or_else(|| {
6771
enum_with_scope
@@ -78,6 +82,8 @@ impl<'a> EnumGen<'a> {
7882
type_name: enum_with_scope.rust_name().to_path(),
7983
lite_runtime,
8084
customize: customize.clone(),
85+
path,
86+
info,
8187
}
8288
}
8389

@@ -112,7 +118,7 @@ impl<'a> EnumGen<'a> {
112118
}
113119

114120
pub fn write(&self, w: &mut CodeWriter) {
115-
self.write_struct(w);
121+
self.write_enum(w);
116122
if self.allow_alias() {
117123
w.write_line("");
118124
self.write_impl_eq(w);
@@ -127,7 +133,9 @@ impl<'a> EnumGen<'a> {
127133
self.write_impl_value(w);
128134
}
129135

130-
fn write_struct(&self, w: &mut CodeWriter) {
136+
fn write_enum(&self, w: &mut CodeWriter) {
137+
w.all_documentation(self.info, self.path);
138+
131139
let mut derive = Vec::new();
132140
derive.push("Clone");
133141
derive.push("Copy");

protobuf-codegen/src/field.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -580,13 +580,17 @@ pub(crate) struct FieldGen<'a> {
580580
pub generate_accessors: bool,
581581
pub generate_getter: bool,
582582
customize: Customize,
583+
path: Vec<i32>,
584+
info: Option<&'a SourceCodeInfo>,
583585
}
584586

585587
impl<'a> FieldGen<'a> {
586588
pub fn parse(
587589
field: FieldWithContext<'a>,
588590
root_scope: &'a RootScope<'a>,
589591
customize: &Customize,
592+
path: Vec<i32>,
593+
info: Option<&'a SourceCodeInfo>,
590594
) -> FieldGen<'a> {
591595
let mut customize = customize.clone();
592596
customize.update_with(&customize_from_rustproto_for_field(
@@ -691,6 +695,8 @@ impl<'a> FieldGen<'a> {
691695
generate_accessors,
692696
generate_getter,
693697
customize,
698+
path,
699+
info,
694700
}
695701
}
696702

@@ -1454,6 +1460,8 @@ impl<'a> FieldGen<'a> {
14541460
if self.proto_type == field_descriptor_proto::Type::TYPE_GROUP {
14551461
w.comment(&format!("{}: <group>", &self.rust_name));
14561462
} else {
1463+
w.all_documentation(self.info, &self.path);
1464+
14571465
let vis = self.visibility();
14581466
w.field_decl_vis(
14591467
vis,

protobuf-codegen/src/lib.rs

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -191,16 +191,55 @@ fn gen_file(
191191
));
192192
}
193193

194-
for message in &scope.get_messages() {
194+
static NESTED_TYPE_NUMBER: protobuf::rt::Lazy<i32> = protobuf::rt::Lazy::INIT;
195+
let message_type_number = *NESTED_TYPE_NUMBER.get(|| {
196+
protobuf::reflect::MessageDescriptor::for_type::<FileDescriptorProto>()
197+
.field_by_name("message_type")
198+
.expect("`message_type` must exist")
199+
.proto()
200+
.get_number()
201+
});
202+
203+
let mut path = vec![message_type_number, 0];
204+
for (id, message) in scope.get_messages().iter().enumerate() {
195205
// ignore map entries, because they are not used in map fields
196206
if map_entry(message).is_none() {
207+
path[1] = id as i32;
208+
197209
w.write_line("");
198-
MessageGen::new(message, &root_scope, &customize).write(&mut w);
210+
MessageGen::new(
211+
message,
212+
&root_scope,
213+
&customize,
214+
&path,
215+
file.source_code_info.as_ref(),
216+
)
217+
.write(&mut w);
199218
}
200219
}
201-
for enum_type in &scope.get_enums() {
220+
221+
static ENUM_TYPE_NUMBER: protobuf::rt::Lazy<i32> = protobuf::rt::Lazy::INIT;
222+
let enum_type_number = *ENUM_TYPE_NUMBER.get(|| {
223+
protobuf::reflect::MessageDescriptor::for_type::<FileDescriptorProto>()
224+
.field_by_name("enum_type")
225+
.expect("`enum_type` must exist")
226+
.proto()
227+
.get_number()
228+
});
229+
230+
let mut path = vec![enum_type_number, 0];
231+
for (id, enum_type) in scope.get_enums().iter().enumerate() {
232+
path[1] = id as i32;
233+
202234
w.write_line("");
203-
EnumGen::new(enum_type, &customize, root_scope).write(&mut w);
235+
EnumGen::new(
236+
enum_type,
237+
&customize,
238+
root_scope,
239+
&path,
240+
file.source_code_info.as_ref(),
241+
)
242+
.write(&mut w);
204243
}
205244

206245
write_extensions(file, &root_scope, &mut w, &customize);

protobuf-codegen/src/message.rs

Lines changed: 62 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,23 +31,41 @@ pub(crate) struct MessageGen<'a> {
3131
pub fields: Vec<FieldGen<'a>>,
3232
pub lite_runtime: bool,
3333
customize: Customize,
34+
path: &'a [i32],
35+
info: Option<&'a SourceCodeInfo>,
3436
}
3537

3638
impl<'a> MessageGen<'a> {
3739
pub fn new(
3840
message: &'a MessageWithScope<'a>,
3941
root_scope: &'a RootScope<'a>,
4042
customize: &Customize,
43+
path: &'a [i32],
44+
info: Option<&'a SourceCodeInfo>,
4145
) -> MessageGen<'a> {
4246
let mut customize = customize.clone();
4347
customize.update_with(&customize_from_rustproto_for_message(
4448
message.message.options.get_message(),
4549
));
4650

51+
static FIELD_NUMBER: protobuf::rt::Lazy<i32> = protobuf::rt::Lazy::INIT;
52+
let field_number = *FIELD_NUMBER.get(|| {
53+
protobuf::reflect::MessageDescriptor::for_type::<DescriptorProto>()
54+
.field_by_name("field")
55+
.expect("`field` must exist")
56+
.proto()
57+
.get_number()
58+
});
59+
4760
let fields: Vec<_> = message
4861
.fields()
4962
.into_iter()
50-
.map(|field| FieldGen::parse(field, root_scope, &customize))
63+
.enumerate()
64+
.map(|(id, field)| {
65+
let mut path = path.to_vec();
66+
path.extend_from_slice(&[field_number, id as i32]);
67+
FieldGen::parse(field, root_scope, &customize, path, info)
68+
})
5169
.collect();
5270
let lite_runtime = customize.lite_runtime.unwrap_or_else(|| {
5371
message
@@ -64,6 +82,8 @@ impl<'a> MessageGen<'a> {
6482
fields: fields,
6583
lite_runtime,
6684
customize,
85+
path,
86+
info,
6787
}
6888
}
6989

@@ -527,6 +547,7 @@ impl<'a> MessageGen<'a> {
527547
}
528548

529549
pub fn write(&self, w: &mut CodeWriter) {
550+
w.all_documentation(self.info, self.path);
530551
self.write_struct(w);
531552

532553
w.write_line("");
@@ -579,20 +600,56 @@ impl<'a> MessageGen<'a> {
579600
oneof.write(w);
580601
}
581602

582-
for nested in &nested_messages {
603+
static NESTED_TYPE_NUMBER: protobuf::rt::Lazy<i32> = protobuf::rt::Lazy::INIT;
604+
let nested_type_number = *NESTED_TYPE_NUMBER.get(|| {
605+
protobuf::reflect::MessageDescriptor::for_type::<DescriptorProto>()
606+
.field_by_name("nested_type")
607+
.expect("`nested_type` must exist")
608+
.proto()
609+
.get_number()
610+
});
611+
612+
let mut path = self.path.to_vec();
613+
path.extend(&[nested_type_number, 0]);
614+
for (id, nested) in nested_messages.iter().enumerate() {
615+
let len = path.len() - 1;
616+
path[len] = id as i32;
617+
583618
if !first {
584619
w.write_line("");
585620
}
586621
first = false;
587-
MessageGen::new(nested, self.root_scope, &self.customize).write(w);
622+
MessageGen::new(nested, self.root_scope, &self.customize, &path, self.info)
623+
.write(w);
588624
}
589625

590-
for enum_type in &self.message.to_scope().get_enums() {
626+
static ENUM_TYPE_NUMBER: protobuf::rt::Lazy<i32> = protobuf::rt::Lazy::INIT;
627+
let enum_type_number = *ENUM_TYPE_NUMBER.get(|| {
628+
protobuf::reflect::MessageDescriptor::for_type::<DescriptorProto>()
629+
.field_by_name("enum_type")
630+
.expect("`enum_type` must exist")
631+
.proto()
632+
.get_number()
633+
});
634+
635+
let len = path.len() - 2;
636+
path[len] = enum_type_number;
637+
for (id, enum_type) in self.message.to_scope().get_enums().iter().enumerate() {
638+
let len = path.len() - 1;
639+
path[len] = id as i32;
640+
591641
if !first {
592642
w.write_line("");
593643
}
594644
first = false;
595-
EnumGen::new(enum_type, &self.customize, self.root_scope).write(w);
645+
EnumGen::new(
646+
enum_type,
647+
&self.customize,
648+
self.root_scope,
649+
&path,
650+
self.info,
651+
)
652+
.write(w);
596653
}
597654
});
598655
}

0 commit comments

Comments
 (0)