pub(crate) fn jsdoc(lines: &[&str]) -> String {
if lines.is_empty() {
return String::new();
}
let mut out = String::from("/**\n");
for line in lines {
out.push_str(" *");
out.push_str(line);
out.push('\n');
}
out.push_str(" */\n");
out
}
pub(crate) struct Prop {
pub(crate) key: &'static str,
pub(crate) optional: bool,
pub(crate) ty: String,
pub(crate) doc: &'static [&'static str],
}
impl Prop {
pub(crate) fn new(
key: &'static str,
ty: impl Into<String>,
doc: &'static [&'static str],
) -> Self {
Self {
key,
optional: false,
ty: ty.into(),
doc,
}
}
pub(crate) fn optional(
key: &'static str,
ty: impl Into<String>,
doc: &'static [&'static str],
) -> Self {
Self {
optional: true,
..Self::new(key, ty, doc)
}
}
}
pub(crate) fn object(props: &[Prop]) -> String {
let fields: Vec<String> = props
.iter()
.map(|p| {
let doc = match p.doc {
[] => String::new(),
doc => format!("\n{}", jsdoc(doc)),
};
let mark = if p.optional { "?" } else { "" };
format!("{doc}{}{mark}: {},", p.key, p.ty)
})
.collect();
format!("{{ {} }}", fields.join(" "))
}
pub(crate) fn literals(names: impl IntoIterator<Item = impl std::fmt::Display>) -> String {
names
.into_iter()
.map(|n| format!("\"{n}\""))
.collect::<Vec<_>>()
.join(" | ")
}
macro_rules! ts_decl {
($ty:ty, $name:literal, [$($doc:literal),* $(,)?], |$cfg:ident| $inline:expr) => {
impl ts_rs::TS for $ty {
type WithoutGenerics = Self;
type OptionInnerType = Self;
fn docs() -> Option<String> {
Some($crate::base::ts::jsdoc(&[$($doc),*]))
}
fn ident(_: &ts_rs::Config) -> String {
$name.to_owned()
}
fn name(_: &ts_rs::Config) -> String {
$name.to_owned()
}
fn inline($cfg: &ts_rs::Config) -> String {
$inline
}
fn decl(cfg: &ts_rs::Config) -> String {
format!("type {} = {};", $name, <Self as ts_rs::TS>::inline(cfg))
}
fn decl_concrete(cfg: &ts_rs::Config) -> String {
<Self as ts_rs::TS>::decl(cfg)
}
fn output_path() -> Option<std::path::PathBuf> {
Some(std::path::PathBuf::from(concat!($name, ".ts")))
}
}
};
}
pub(crate) use ts_decl;