use super::{push_inline_comment_lines, strip_leading_dot};
use crate::populate::comments::{CommentIndex, path};
use crate::populate::source::{SourceCache, push_indented_lines};
use buffa_descriptor::generated::descriptor::field_descriptor_proto::Type;
use buffa_descriptor::generated::descriptor::{DescriptorProto, FieldDescriptorProto};
pub fn synthesize_message_body(
file_name: &str,
idx: &CommentIndex<'_>,
mi: usize,
msg: &DescriptorProto,
source: Option<&mut SourceCache>,
) -> String {
let name = msg.name.as_deref().unwrap_or("Message");
let file_source =
source.and_then(|cache| cache.load(file_name).map(|contents| contents.to_string()));
let mut body = format!("message {name} {{\n");
if let Some(src) = file_source.as_deref() {
let opt_path = [path::FILE_MESSAGE, mi as i32, path::MSG_OPTIONS];
if let Some(snippet) = idx.span_snippet(src, &opt_path) {
push_indented_lines(&mut body, &snippet, " ");
}
}
synthesize_message_fields(&mut body, idx, mi, msg, file_source.as_deref());
body.push_str("}\n");
body
}
fn synthesize_message_fields(
body: &mut String,
idx: &CommentIndex<'_>,
mi: usize,
msg: &DescriptorProto,
file_source: Option<&str>,
) {
let mut fi = 0;
while fi < msg.field.len() {
let field = &msg.field[fi];
if let Some(oi) = field.oneof_index {
if try_push_oneof_span(body, idx, mi, oi, file_source) {
while fi < msg.field.len() && msg.field[fi].oneof_index == Some(oi) {
fi += 1;
}
continue;
}
let name = oneof_name(msg, oi);
body.push_str(&format!(" oneof {name} {{\n"));
while fi < msg.field.len() && msg.field[fi].oneof_index == Some(oi) {
synthesize_message_field(body, idx, mi, fi, &msg.field[fi], " ", file_source);
fi += 1;
}
body.push_str(" }\n");
} else {
synthesize_message_field(body, idx, mi, fi, field, " ", file_source);
fi += 1;
}
}
}
fn synthesize_message_field(
body: &mut String,
idx: &CommentIndex<'_>,
mi: usize,
fi: usize,
field: &FieldDescriptorProto,
indent: &str,
file_source: Option<&str>,
) {
if let Some(c) = idx.leading_message_field(mi, fi) {
push_inline_comment_lines(body, c);
}
let field_path = [path::FILE_MESSAGE, mi as i32, path::MSG_FIELD, fi as i32];
if let Some(src) = file_source
&& let Some(snippet) = idx.span_snippet(src, &field_path)
{
push_indented_lines(body, &snippet, indent);
return;
}
append_field(body, field, indent);
}
fn try_push_oneof_span(
body: &mut String,
idx: &CommentIndex<'_>,
mi: usize,
oi: i32,
file_source: Option<&str>,
) -> bool {
let Some(src) = file_source else {
return false;
};
let oneof_path = [path::FILE_MESSAGE, mi as i32, path::MSG_ONEOF, oi];
let Some(snippet) = idx.span_snippet(src, &oneof_path) else {
return false;
};
if !snippet.contains("oneof") {
return false;
}
push_indented_lines(body, &snippet, " ");
true
}
fn oneof_name(msg: &DescriptorProto, oi: i32) -> &str {
msg.oneof_decl
.get(oi as usize)
.and_then(|o| o.name.as_deref())
.unwrap_or("payload")
}
fn append_field(out: &mut String, field: &FieldDescriptorProto, indent: &str) {
let label = match field.label {
Some(
buffa_descriptor::generated::descriptor::field_descriptor_proto::Label::LABEL_REPEATED,
) => "repeated ",
_ => {
if field.proto3_optional == Some(true) {
"optional "
} else {
""
}
}
};
let ty = field_type_name(field);
let name = field.name.as_deref().unwrap_or("field");
let number = field.number.unwrap_or(0);
out.push_str(&format!("{indent}{label}{ty} {name} = {number};\n"));
}
fn field_type_name(field: &FieldDescriptorProto) -> String {
if let Some(ref tn) = field.type_name {
return strip_leading_dot(tn).to_string();
}
scalar_type_name(field.r#type).unwrap_or_else(|| "bytes".into())
}
fn scalar_type_name(ty: Option<Type>) -> Option<String> {
let ty = ty?;
Some(
match ty {
Type::TYPE_DOUBLE => "double",
Type::TYPE_FLOAT => "float",
Type::TYPE_INT64 => "int64",
Type::TYPE_UINT64 => "uint64",
Type::TYPE_INT32 => "int32",
Type::TYPE_FIXED64 => "fixed64",
Type::TYPE_FIXED32 => "fixed32",
Type::TYPE_BOOL => "bool",
Type::TYPE_STRING => "string",
Type::TYPE_GROUP => "group",
Type::TYPE_MESSAGE => "message",
Type::TYPE_BYTES => "bytes",
Type::TYPE_UINT32 => "uint32",
Type::TYPE_ENUM => "enum",
Type::TYPE_SFIXED32 => "sfixed32",
Type::TYPE_SFIXED64 => "sfixed64",
Type::TYPE_SINT32 => "sint32",
Type::TYPE_SINT64 => "sint64",
}
.into(),
)
}