use crate::code_block::{CodeBlock, CodeBlockBuilder};
use crate::lang::CodeLang;
use crate::type_name::TypeName;
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct AnnotationSpec {
pub(crate) name: AnnotationName,
pub(crate) arguments: Vec<String>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub(crate) enum AnnotationName {
Simple(String),
Importable(TypeName),
}
impl AnnotationSpec {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: AnnotationName::Simple(name.into()),
arguments: Vec::new(),
}
}
pub fn importable(type_name: TypeName) -> Self {
Self {
name: AnnotationName::Importable(type_name),
arguments: Vec::new(),
}
}
pub fn arg(mut self, argument: impl Into<String>) -> Self {
self.arguments.push(argument.into());
self
}
pub fn emit(&self, lang: &dyn CodeLang) -> Result<CodeBlock, crate::error::SigilStitchError> {
let ea = lang.enum_and_annotation();
let (prefix, suffix) = (ea.annotation_prefix, ea.annotation_suffix);
let args_str = if self.arguments.is_empty() {
String::new()
} else {
format!("({})", self.arguments.join(", "))
};
match &self.name {
AnnotationName::Simple(name) => {
let rendered = format!("{prefix}{name}{args_str}{suffix}");
CodeBlock::of("%L", rendered)
}
AnnotationName::Importable(type_name) => {
let mut cb = CodeBlockBuilder::new();
if !prefix.is_empty() {
cb.add("%L", prefix.to_string());
}
cb.add("%T", type_name.clone());
let tail = format!("{args_str}{suffix}");
if !tail.is_empty() {
cb.add("%L", tail);
}
cb.build()
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::lang::rust_lang::RustLang;
use crate::lang::typescript::TypeScript;
#[test]
fn test_simple_annotation_ts() {
let ts = TypeScript::new();
let ann = AnnotationSpec::new("deprecated");
let cb = ann.emit(&ts).unwrap();
assert!(!cb.is_empty());
}
#[test]
fn test_simple_annotation_with_args() {
let ts = TypeScript::new();
let ann = AnnotationSpec::new("deprecated").arg("reason: 'use v2'");
let cb = ann.emit(&ts).unwrap();
assert!(!cb.is_empty());
}
#[test]
fn test_rust_prefix() {
let rs = RustLang::new();
let ann = AnnotationSpec::new("allow").arg("dead_code");
let cb = ann.emit(&rs).unwrap();
assert!(!cb.is_empty());
}
#[test]
fn test_importable_annotation() {
let ts = TypeScript::new();
let type_name = TypeName::importable("./decorators", "Component");
let ann = AnnotationSpec::importable(type_name);
let cb = ann.emit(&ts).unwrap();
assert!(!cb.is_empty());
}
#[test]
fn test_arg_chaining() {
let rs = RustLang::new();
let ann = AnnotationSpec::new("cfg")
.arg("test")
.arg("feature = \"nightly\"");
let cb = ann.emit(&rs).unwrap();
assert!(!cb.is_empty());
}
}