use crate::code_block::{Arg, CodeBlock};
use crate::lang::CodeLang;
use crate::spec::annotation_spec::AnnotationSpec;
use crate::spec::modifiers::{DeclarationContext, Modifiers, PropertyStyle, Visibility};
use crate::type_name::TypeName;
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct SetterSpec {
pub(crate) param_name: String,
pub(crate) body: CodeBlock,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct PropertySpec {
pub(crate) name: String,
pub(crate) property_type: TypeName,
pub(crate) modifiers: Modifiers,
pub(crate) doc: Vec<String>,
pub(crate) getter: Option<CodeBlock>,
pub(crate) setter: Option<SetterSpec>,
pub(crate) annotations: Vec<CodeBlock>,
pub(crate) annotation_specs: Vec<AnnotationSpec>,
}
impl PropertySpec {
pub fn builder(name: &str, property_type: TypeName) -> PropertySpecBuilder {
PropertySpecBuilder {
name: name.to_string(),
property_type,
modifiers: Modifiers::default(),
doc: Vec::new(),
getter: None,
setter: None,
annotations: Vec::new(),
annotation_specs: Vec::new(),
}
}
pub fn name(&self) -> &str {
&self.name
}
pub fn emit(
&self,
lang: &dyn CodeLang,
ctx: DeclarationContext,
) -> Result<Vec<CodeBlock>, crate::error::SigilStitchError> {
match lang.property_style() {
PropertyStyle::Accessor => self.emit_accessor(lang, ctx),
PropertyStyle::Field => self.emit_field(lang, ctx),
}
}
fn emit_accessor(
&self,
lang: &dyn CodeLang,
ctx: DeclarationContext,
) -> Result<Vec<CodeBlock>, crate::error::SigilStitchError> {
let mut blocks = Vec::new();
if let Some(getter_body) = &self.getter {
let mut cb = CodeBlock::builder();
self.emit_preamble(&mut cb, lang)?;
let vis = lang.render_visibility(self.modifiers.visibility, ctx);
let mut sig = String::new();
let mut sig_args: Vec<Arg> = Vec::new();
sig.push_str(vis);
if self.modifiers.is_static {
sig.push_str("static ");
}
sig.push_str("get ");
sig.push_str(&self.name);
sig.push_str("()");
if !self.property_type.is_empty() {
sig.push_str(lang.function_syntax().return_type_separator);
sig.push_str("%T");
sig_args.push(Arg::TypeName(self.property_type.clone()));
}
sig.push_str(lang.block_syntax().block_open);
cb.add(&sig, sig_args);
cb.add_line();
cb.add("%>", ());
cb.add_code(getter_body.clone());
cb.add_line();
cb.add("%<", ());
let close = lang.block_syntax().block_close;
if !close.is_empty() {
cb.add(close, ());
cb.add_line();
}
blocks.push(cb.build()?);
}
if let Some(setter) = &self.setter {
let mut cb = CodeBlock::builder();
let vis = lang.render_visibility(self.modifiers.visibility, ctx);
let mut sig = String::new();
let mut sig_args: Vec<Arg> = Vec::new();
sig.push_str(vis);
if self.modifiers.is_static {
sig.push_str("static ");
}
sig.push_str("set ");
sig.push_str(&self.name);
sig.push('(');
sig.push_str(&lang.escape_reserved(&setter.param_name));
if !self.property_type.is_empty() {
sig.push_str(lang.type_decl_syntax().type_annotation_separator);
sig.push_str("%T");
sig_args.push(Arg::TypeName(self.property_type.clone()));
}
sig.push(')');
sig.push_str(lang.block_syntax().block_open);
cb.add(&sig, sig_args);
cb.add_line();
cb.add("%>", ());
cb.add_code(setter.body.clone());
cb.add_line();
cb.add("%<", ());
let close = lang.block_syntax().block_close;
if !close.is_empty() {
cb.add(close, ());
cb.add_line();
}
blocks.push(cb.build()?);
}
Ok(blocks)
}
fn emit_field(
&self,
lang: &dyn CodeLang,
ctx: DeclarationContext,
) -> Result<Vec<CodeBlock>, crate::error::SigilStitchError> {
let mut cb = CodeBlock::builder();
self.emit_preamble(&mut cb, lang)?;
let vis = lang.render_visibility(self.modifiers.visibility, ctx);
let has_setter = self.setter.is_some();
let mut sig = String::new();
let mut sig_args: Vec<Arg> = Vec::new();
sig.push_str(vis);
if self.modifiers.is_static {
sig.push_str("static ");
}
if has_setter {
sig.push_str(lang.enum_and_annotation().mutable_field_keyword);
} else {
sig.push_str(lang.enum_and_annotation().readonly_keyword);
}
sig.push_str(&lang.escape_reserved(&self.name));
if !self.property_type.is_empty() {
sig.push_str(lang.type_decl_syntax().type_annotation_separator);
sig.push_str("%T");
sig_args.push(Arg::TypeName(self.property_type.clone()));
}
sig.push_str(lang.block_syntax().block_open);
cb.add(&sig, sig_args);
cb.add_line();
cb.add("%>", ());
if let Some(getter_body) = &self.getter {
let getter_kw = lang.property_getter_keyword();
let getter_sig = format!("{getter_kw}{}", lang.block_syntax().block_open);
cb.add(&getter_sig, ());
cb.add_line();
cb.add("%>", ());
cb.add_code(getter_body.clone());
cb.add_line();
cb.add("%<", ());
let close = lang.block_syntax().block_close;
if !close.is_empty() {
cb.add(close, ());
cb.add_line();
}
}
if let Some(setter) = &self.setter {
let setter_sig = format!(
"set({}){}",
setter.param_name,
lang.block_syntax().block_open
);
cb.add(&setter_sig, ());
cb.add_line();
cb.add("%>", ());
cb.add_code(setter.body.clone());
cb.add_line();
cb.add("%<", ());
let close = lang.block_syntax().block_close;
if !close.is_empty() {
cb.add(close, ());
cb.add_line();
}
}
cb.add("%<", ());
let close = lang.block_syntax().block_close;
if !close.is_empty() {
cb.add(close, ());
cb.add_line();
}
Ok(vec![cb.build()?])
}
fn emit_preamble(
&self,
cb: &mut crate::code_block::CodeBlockBuilder,
lang: &dyn CodeLang,
) -> Result<(), crate::error::SigilStitchError> {
let emit_doc = || -> Option<String> {
if self.doc.is_empty() || lang.doc_comment_inside_body() {
return None;
}
let doc_lines: Vec<&str> = self.doc.iter().map(|s| s.as_str()).collect();
Some(lang.render_doc_comment(&doc_lines))
};
if lang.doc_before_annotations()
&& let Some(doc_str) = emit_doc()
{
cb.add("%L", doc_str);
cb.add_line();
}
for spec in &self.annotation_specs {
cb.add_code(spec.emit(lang)?);
cb.add_line();
}
for ann in &self.annotations {
cb.add_code(ann.clone());
cb.add_line();
}
if !lang.doc_before_annotations()
&& let Some(doc_str) = emit_doc()
{
cb.add("%L", doc_str);
cb.add_line();
}
Ok(())
}
}
#[derive(Debug)]
pub struct PropertySpecBuilder {
name: String,
property_type: TypeName,
modifiers: Modifiers,
doc: Vec<String>,
getter: Option<CodeBlock>,
setter: Option<SetterSpec>,
annotations: Vec<CodeBlock>,
annotation_specs: Vec<AnnotationSpec>,
}
impl PropertySpecBuilder {
pub fn getter(mut self, body: CodeBlock) -> Self {
self.getter = Some(body);
self
}
pub fn setter(mut self, param_name: &str, body: CodeBlock) -> Self {
self.setter = Some(SetterSpec {
param_name: param_name.to_string(),
body,
});
self
}
pub fn visibility(mut self, vis: Visibility) -> Self {
self.modifiers.visibility = vis;
self
}
pub fn is_static(mut self) -> Self {
self.modifiers.is_static = true;
self
}
pub fn doc(mut self, line: &str) -> Self {
self.doc.push(line.to_string());
self
}
pub fn annotation(mut self, ann: CodeBlock) -> Self {
self.annotations.push(ann);
self
}
pub fn annotate(mut self, spec: AnnotationSpec) -> Self {
self.annotation_specs.push(spec);
self
}
pub fn build(self) -> Result<PropertySpec, crate::error::SigilStitchError> {
snafu::ensure!(
!self.name.is_empty(),
crate::error::EmptyNameSnafu {
builder: "PropertySpecBuilder",
}
);
Ok(PropertySpec {
name: self.name,
property_type: self.property_type,
modifiers: self.modifiers,
doc: self.doc,
getter: self.getter,
setter: self.setter,
annotations: self.annotations,
annotation_specs: self.annotation_specs,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_build_empty_name_errors() {
let result = PropertySpec::builder("", TypeName::primitive("string")).build();
assert!(result.is_err());
assert!(
result
.unwrap_err()
.to_string()
.contains("'name' must not be empty")
);
}
}