diff --git a/prost-build/src/code_generator.rs b/prost-build/src/code_generator.rs index 500c8cadb..ba960b055 100644 --- a/prost-build/src/code_generator.rs +++ b/prost-build/src/code_generator.rs @@ -185,7 +185,7 @@ impl<'a> CodeGenerator<'a> { } }); - self.append_doc(); + self.append_doc(&fq_message_name, None); self.append_type_attributes(&fq_message_name); self.push_indent(); self.buf @@ -264,11 +264,11 @@ impl<'a> CodeGenerator<'a> { } } - fn append_type_attributes(&mut self, msg_name: &str) { - assert_eq!(b'.', msg_name.as_bytes()[0]); + fn append_type_attributes(&mut self, fq_message_name: &str) { + assert_eq!(b'.', fq_message_name.as_bytes()[0]); // TODO: this clone is dirty, but expedious. for (matcher, attribute) in self.config.type_attributes.clone() { - if match_ident(&matcher, msg_name, None) { + if match_ident(&matcher, fq_message_name, None) { self.push_indent(); self.buf.push_str(&attribute); self.buf.push('\n'); @@ -276,11 +276,11 @@ impl<'a> CodeGenerator<'a> { } } - fn append_field_attributes(&mut self, msg_name: &str, field_name: &str) { - assert_eq!(b'.', msg_name.as_bytes()[0]); + fn append_field_attributes(&mut self, fq_message_name: &str, field_name: &str) { + assert_eq!(b'.', fq_message_name.as_bytes()[0]); // TODO: this clone is dirty, but expedious. for (matcher, attribute) in self.config.field_attributes.clone() { - if match_ident(&matcher, msg_name, Some(field_name)) { + if match_ident(&matcher, fq_message_name, Some(field_name)) { self.push_indent(); self.buf.push_str(&attribute); self.buf.push('\n'); @@ -288,16 +288,18 @@ impl<'a> CodeGenerator<'a> { } } - fn append_field(&mut self, msg_name: &str, field: FieldDescriptorProto) { + fn append_field(&mut self, fq_message_name: &str, field: FieldDescriptorProto) { let type_ = field.r#type(); let repeated = field.label == Some(Label::Repeated as i32); let deprecated = self.deprecated(&field); let optional = self.optional(&field); - let ty = self.resolve_type(&field, msg_name); + let ty = self.resolve_type(&field, fq_message_name); let boxed = !repeated && (type_ == Type::Message || type_ == Type::Group) - && self.message_graph.is_nested(field.type_name(), msg_name); + && self + .message_graph + .is_nested(field.type_name(), fq_message_name); debug!( " field: {:?}, type: {:?}, boxed: {}", @@ -306,7 +308,7 @@ impl<'a> CodeGenerator<'a> { boxed ); - self.append_doc(); + self.append_doc(fq_message_name, Some(field.name())); if deprecated { self.push_indent(); @@ -321,7 +323,7 @@ impl<'a> CodeGenerator<'a> { if type_ == Type::Bytes { self.buf.push_str("="); self.buf - .push_str(self.bytes_backing_type(&field, msg_name).as_str()); + .push_str(self.bytes_backing_type(&field, fq_message_name).as_str()); } match field.label() { @@ -386,7 +388,7 @@ impl<'a> CodeGenerator<'a> { } self.buf.push_str("\")]\n"); - self.append_field_attributes(msg_name, field.name()); + self.append_field_attributes(fq_message_name, field.name()); self.push_indent(); self.buf.push_str("pub "); self.buf.push_str(&to_snake(field.name())); @@ -411,13 +413,13 @@ impl<'a> CodeGenerator<'a> { fn append_map_field( &mut self, - msg_name: &str, + fq_message_name: &str, field: FieldDescriptorProto, key: &FieldDescriptorProto, value: &FieldDescriptorProto, ) { - let key_ty = self.resolve_type(key, msg_name); - let value_ty = self.resolve_type(value, msg_name); + let key_ty = self.resolve_type(key, fq_message_name); + let value_ty = self.resolve_type(value, fq_message_name); debug!( " map field: {:?}, key type: {:?}, value type: {:?}", @@ -426,14 +428,14 @@ impl<'a> CodeGenerator<'a> { value_ty ); - self.append_doc(); + self.append_doc(fq_message_name, Some(field.name())); self.push_indent(); let btree_map = self .config .btree_map .iter() - .any(|matcher| match_ident(matcher, msg_name, Some(field.name()))); + .any(|matcher| match_ident(matcher, fq_message_name, Some(field.name()))); let (annotation_ty, lib_name, rust_ty) = if btree_map { ("btree_map", "::prost::alloc::collections", "BTreeMap") } else { @@ -450,7 +452,7 @@ impl<'a> CodeGenerator<'a> { value_tag, field.number() )); - self.append_field_attributes(msg_name, field.name()); + self.append_field_attributes(fq_message_name, field.name()); self.push_indent(); self.buf.push_str(&format!( "pub {}: {}::{}<{}, {}>,\n", @@ -474,7 +476,7 @@ impl<'a> CodeGenerator<'a> { to_snake(message_name), to_upper_camel(oneof.name()) ); - self.append_doc(); + self.append_doc(fq_message_name, None); self.push_indent(); self.buf.push_str(&format!( "#[prost(oneof=\"{}\", tags=\"{}\")]\n", @@ -495,18 +497,18 @@ impl<'a> CodeGenerator<'a> { fn append_oneof( &mut self, - msg_name: &str, + fq_message_name: &str, oneof: OneofDescriptorProto, idx: i32, fields: Vec<(FieldDescriptorProto, usize)>, ) { self.path.push(8); self.path.push(idx); - self.append_doc(); + self.append_doc(fq_message_name, None); self.path.pop(); self.path.pop(); - let oneof_name = format!("{}.{}", msg_name, oneof.name()); + let oneof_name = format!("{}.{}", fq_message_name, oneof.name()); self.append_type_attributes(&oneof_name); self.push_indent(); self.buf @@ -522,7 +524,7 @@ impl<'a> CodeGenerator<'a> { let type_ = field.r#type(); self.path.push(idx as i32); - self.append_doc(); + self.append_doc(fq_message_name, Some(field.name())); self.path.pop(); self.push_indent(); @@ -535,10 +537,12 @@ impl<'a> CodeGenerator<'a> { self.append_field_attributes(&oneof_name, field.name()); self.push_indent(); - let ty = self.resolve_type(&field, msg_name); + let ty = self.resolve_type(&field, fq_message_name); let boxed = (type_ == Type::Message || type_ == Type::Group) - && self.message_graph.is_nested(field.type_name(), msg_name); + && self + .message_graph + .is_nested(field.type_name(), fq_message_name); debug!( " oneof: {:?}, type: {:?}, boxed: {}", @@ -575,8 +579,15 @@ impl<'a> CodeGenerator<'a> { &self.source_info.location[idx] } - fn append_doc(&mut self) { - Comments::from_location(self.location()).append_with_indent(self.depth, &mut self.buf); + fn append_doc(&mut self, fq_name: &str, field_name: Option<&str>) { + if !self + .config + .disable_comments + .iter() + .any(|matcher| match_ident(matcher, fq_name, field_name)) + { + Comments::from_location(self.location()).append_with_indent(self.depth, &mut self.buf) + } } fn append_enum(&mut self, desc: EnumDescriptorProto) { @@ -590,7 +601,7 @@ impl<'a> CodeGenerator<'a> { return; } - self.append_doc(); + self.append_doc(&fq_enum_name, None); self.append_type_attributes(&fq_enum_name); self.push_indent(); self.buf.push_str( @@ -636,7 +647,7 @@ impl<'a> CodeGenerator<'a> { value: &EnumValueDescriptorProto, prefix_to_strip: Option, ) { - self.append_doc(); + self.append_doc(fq_enum_name, Some(value.name())); self.append_field_attributes(fq_enum_name, &value.name()); self.push_indent(); let name = to_upper_camel(value.name()); @@ -738,7 +749,7 @@ impl<'a> CodeGenerator<'a> { self.buf.push_str("}\n"); } - fn resolve_type(&self, field: &FieldDescriptorProto, msg_name: &str) -> String { + fn resolve_type(&self, field: &FieldDescriptorProto, fq_message_name: &str) -> String { match field.r#type() { Type::Float => String::from("f32"), Type::Double => String::from("f64"), @@ -748,7 +759,7 @@ impl<'a> CodeGenerator<'a> { Type::Int64 | Type::Sfixed64 | Type::Sint64 => String::from("i64"), Type::Bool => String::from("bool"), Type::String => String::from("::prost::alloc::string::String"), - Type::Bytes => match self.bytes_backing_type(field, msg_name) { + Type::Bytes => match self.bytes_backing_type(field, fq_message_name) { BytesTy::Bytes => String::from("::prost::bytes::Bytes"), BytesTy::Vec => String::from("::prost::alloc::vec::Vec"), }, @@ -834,12 +845,12 @@ impl<'a> CodeGenerator<'a> { } } - fn bytes_backing_type(&self, field: &FieldDescriptorProto, msg_name: &str) -> BytesTy { + fn bytes_backing_type(&self, field: &FieldDescriptorProto, fq_message_name: &str) -> BytesTy { let bytes = self .config .bytes .iter() - .any(|matcher| match_ident(matcher, msg_name, Some(field.name()))); + .any(|matcher| match_ident(matcher, fq_message_name, Some(field.name()))); if bytes { BytesTy::Bytes } else { diff --git a/prost-build/src/ident.rs b/prost-build/src/ident.rs index 046fb1efb..0b6890847 100644 --- a/prost-build/src/ident.rs +++ b/prost-build/src/ident.rs @@ -41,8 +41,8 @@ pub fn to_upper_camel(s: &str) -> String { } /// Matches a 'matcher' against a fully qualified identifier. -pub fn match_ident(matcher: &str, msg: &str, field: Option<&str>) -> bool { - assert_eq!(b'.', msg.as_bytes()[0]); +pub fn match_ident(matcher: &str, fq_name: &str, field_name: Option<&str>) -> bool { + assert_eq!(b'.', fq_name.as_bytes()[0]); if matcher.is_empty() { return false; @@ -52,9 +52,9 @@ pub fn match_ident(matcher: &str, msg: &str, field: Option<&str>) -> bool { let match_paths = matcher.split('.').collect::>(); let field_paths = { - let mut paths = msg.split('.').collect::>(); - if let Some(field) = field { - paths.push(field); + let mut paths = fq_name.split('.').collect::>(); + if let Some(field_name) = field_name { + paths.push(field_name); } paths }; diff --git a/prost-build/src/lib.rs b/prost-build/src/lib.rs index 521b2d2a2..700fa9763 100644 --- a/prost-build/src/lib.rs +++ b/prost-build/src/lib.rs @@ -193,6 +193,7 @@ pub struct Config { out_dir: Option, extern_paths: Vec<(String, String)>, protoc_args: Vec, + disable_comments: Vec, } impl Config { @@ -410,6 +411,35 @@ impl Config { self } + /// Configures the code generator to omit documentation comments on generated Protobuf types. + /// + /// # Example + /// + /// Occasionally `.proto` files contain code blocks which are not valid Rust. To avoid doctest + /// failures, annotate the invalid code blocks with an [`ignore` or `no_run` attribute][1], or + /// disable doctests for the crate with a [Cargo.toml entry][2]. If neither of these options + /// are possible, then omit comments on generated code during doctest builds: + /// + /// ```rust,ignore + /// let mut config = prost_build::Config::new(); + /// config.disable_comments("."); + /// config.compile_protos(&["src/frontend.proto", "src/backend.proto"], &["src"])?; + /// ``` + /// + /// As with other options which take a set of paths, comments can be disabled on a per-package + /// or per-symbol basis. + /// + /// [1]: https://doc.rust-lang.org/rustdoc/documentation-tests.html#attributes + /// [2]: https://doc.rust-lang.org/cargo/reference/cargo-targets.html#configuring-a-target + pub fn disable_comments(&mut self, paths: I) -> &mut Self + where + I: IntoIterator, + S: AsRef, + { + self.disable_comments = paths.into_iter().map(|s| s.as_ref().to_string()).collect(); + self + } + /// Declare an externally provided Protobuf package or type. /// /// `extern_path` allows `prost` types in external crates to be referenced in generated code. @@ -720,6 +750,7 @@ impl default::Default for Config { out_dir: None, extern_paths: Vec::new(), protoc_args: Vec::new(), + disable_comments: Vec::new(), } } } diff --git a/tests/Cargo.toml b/tests/Cargo.toml index 4207ac29d..e369e9966 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -7,9 +7,6 @@ edition = "2018" build = "src/build.rs" -[lib] -doctest = false - [features] default = ["std"] std = [] diff --git a/tests/src/build.rs b/tests/src/build.rs index 4c3cffa19..6434856a8 100644 --- a/tests/src/build.rs +++ b/tests/src/build.rs @@ -81,11 +81,20 @@ fn main() { .compile_protos(&[src.join("deprecated_field.proto")], includes) .unwrap(); - config + prost_build::Config::new() .protoc_arg("--experimental_allow_proto3_optional") .compile_protos(&[src.join("proto3_presence.proto")], includes) .unwrap(); + { + let mut config = prost_build::Config::new(); + config.disable_comments(&["."]); + + config + .compile_protos(&[src.join("invalid_doctest.proto")], includes) + .unwrap(); + } + config .compile_protos(&[src.join("well_known_types.proto")], includes) .unwrap(); diff --git a/tests/src/invalid_doctest.proto b/tests/src/invalid_doctest.proto new file mode 100644 index 000000000..9cf0b0829 --- /dev/null +++ b/tests/src/invalid_doctest.proto @@ -0,0 +1,9 @@ +syntax = "proto3"; + +package invalid.doctest; + +// ``` +// invalid +// ``` +message MessageWithInvalidDoctest { +} diff --git a/tests/src/lib.rs b/tests/src/lib.rs index 26d86b184..0c3bf0dc6 100644 --- a/tests/src/lib.rs +++ b/tests/src/lib.rs @@ -95,6 +95,12 @@ pub mod proto3 { } } +pub mod invalid { + pub mod doctest { + include!(concat!(env!("OUT_DIR"), "/invalid.doctest.rs")); + } +} + use alloc::format; use alloc::vec::Vec;