use super::{apply_generic, params_ts};
use crate::script::js::tsgen::collect::Api;
use crate::script::js::tsgen::model::{TsDecl, TsInterfaceDef, TsKind};
use std::fmt::Write as _;
pub fn dts(api: &Api, header: &str) -> String {
let mut out = String::with_capacity(8192);
if !header.is_empty() {
out.push_str(header);
out.push_str("\n\n");
}
let mut types: Vec<&str> = api.types.iter().map(|t| t.decl.trim()).collect();
types.sort_unstable();
for ty in types {
out.push_str(ty);
out.push_str("\n\n");
}
let mut ifaces: Vec<&TsInterfaceDef> = api.interfaces.to_vec();
ifaces.sort_by_key(|i| i.name);
for iface in &ifaces {
let dup = api.types.iter().any(|t| {
t.decl
.trim_start()
.strip_prefix("interface ")
.and_then(|s| s.split([' ', '<', '{']).next())
== Some(iface.name)
});
assert!(
!dup,
"interface `{}` declared by both #[ts_export] and a type declaration",
iface.name
);
}
for iface in ifaces {
match iface.ctor {
Some(params) => {
let _ = writeln!(out, "declare class {}{} {{", iface.name, iface.generic);
let _ = writeln!(out, " constructor({});", params_ts(params));
}
None => {
let _ = writeln!(out, "interface {}{} {{", iface.name, iface.generic);
}
}
let mut members: Vec<&TsDecl> = api
.decls
.iter()
.copied()
.filter(|d| d.group == iface.name)
.collect();
members.sort_by(|a, b| {
a.order
.cmp(&b.order)
.then(a.raw.cmp(b.raw))
.then(a.name.cmp(b.name))
});
for d in members {
render_member(&mut out, d, iface.generic);
}
out.push_str("}\n\n");
}
let mut globals: Vec<&TsDecl> = api
.decls
.iter()
.copied()
.filter(|d| d.group == "global")
.collect();
globals.sort_by(|a, b| {
a.name
.cmp(b.name)
.then(a.order.cmp(&b.order))
.then(a.raw.cmp(b.raw))
});
for d in globals {
render_doc(&mut out, d.doc, "");
if d.raw.is_empty() {
let _ = writeln!(
out,
"declare function {}{}({}): {};",
d.name,
d.generic,
params_ts(d.params),
d.ret
);
} else {
let _ = writeln!(out, "declare function {};", d.raw);
}
}
out
}
fn render_member(out: &mut String, d: &TsDecl, generic: &str) {
render_doc(out, d.doc, " ");
if !d.raw.is_empty() {
let _ = writeln!(out, " {};", apply_generic(d.group, generic, d.raw));
} else if d.kind == TsKind::Getter {
let q = if d.optional { "?" } else { "" };
let _ = writeln!(
out,
" readonly {}{}: {};",
d.name,
q,
apply_generic(d.group, generic, d.ret)
);
} else {
let _ = writeln!(
out,
" {}({}): {};",
d.name,
apply_generic(d.group, generic, ¶ms_ts(d.params)),
apply_generic(d.group, generic, d.ret)
);
}
}
fn render_doc(out: &mut String, doc: &[&str], indent: &str) {
match doc {
[] => {}
[only] => {
let _ = writeln!(out, "{indent}/** {only} */");
}
[first, rest @ ..] => {
let _ = writeln!(out, "{indent}/** {first}");
let (last, mid) = rest.split_last().unwrap();
for line in mid {
let _ = writeln!(out, "{indent} * {line}");
}
let _ = writeln!(out, "{indent} * {last} */");
}
}
}