Skip to content

Commit c004d95

Browse files
committed
refactor(truapi-codegen): use writedoc!/formatdoc! in host_callbacks emitter
The new TS HostCallbacks emitter from #18 hand-chained 15 writeln! calls to build the output. CLAUDE.md (this commit also) now requires codegen modules to prefer indoc::writedoc! / formatdoc! over writeln! chains so the emitted shape stays visible in source. Switched host_callbacks.rs to: - writedoc! for the header + import block (two multi-line writes) - formatdoc! returning strings for trait interfaces, methods, and the super-interface; the parent function joins them - render_jsdoc returns a String instead of writing through a sink writeln! count: 15 → 2 (both inside writedoc! invocations themselves). Golden test rebuilt clean; workspace tests all pass.
1 parent 1a62857 commit c004d95

2 files changed

Lines changed: 75 additions & 55 deletions

File tree

CLAUDE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ scripts/codegen.sh regenerate the TS client from the Rust crate
5555
- In Rust format strings, prefer inlined variables: `"log value: {value:?}"` over `"log value: {:?}", value`.
5656
- Don't introduce typealias chains that just rename a public type from another crate (e.g. `pub type StorageError = crate::v01::HostLocalStorageReadError`). Use the canonical name directly. A typealias is only worth its indirection when it captures a real abstraction.
5757
- After any code change, update `README.md` (and CLAUDE.md if the layout changed) so the top-level docs reflect what the repo actually contains. Stale docs are a regression.
58+
- In codegen emitters, prefer `indoc::writedoc!` / `formatdoc!` over chains of `writeln!`. A single `writedoc!` with a multi-line raw string keeps the emitted shape visible in source instead of fragmenting it across one-line `writeln!` calls. Reserve `writeln!` for the genuinely-one-line case (a single import, a single statement inside a loop).
5859

5960
## First-time setup
6061

rust/crates/truapi-codegen/src/ts/host_callbacks.rs

Lines changed: 74 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ use std::fs;
1313
use std::path::Path;
1414

1515
use anyhow::{Result, bail};
16+
use indoc::{formatdoc, writedoc};
1617

1718
use crate::platform::{
1819
PlatformDefinition, PlatformInner, PlatformMethod, PlatformReturn, PlatformTrait,
@@ -29,62 +30,78 @@ pub fn generate(definition: &PlatformDefinition, output_dir: &str) -> Result<()>
2930

3031
fn emit_host_callbacks(definition: &PlatformDefinition) -> Result<String> {
3132
let mut out = String::new();
32-
writeln!(
33+
writedoc!(
3334
out,
34-
"// Auto-generated by truapi-codegen. Do not edit.\n//\n\
35-
// Typed host-callbacks surface derived from the `truapi-platform`\n\
36-
// capability traits. One interface per Rust trait + a composite\n\
37-
// `HostCallbacks` interface that mirrors the `Platform` super-trait.\n"
35+
r#"
36+
// Auto-generated by truapi-codegen. Do not edit.
37+
//
38+
// Typed host-callbacks surface derived from the `truapi-platform`
39+
// capability traits. One interface per Rust trait + a composite
40+
// `HostCallbacks` interface that mirrors the `Platform` super-trait.
41+
42+
"#,
3843
)
3944
.unwrap();
4045

4146
let imports = collect_named_types(definition);
4247
if !imports.is_empty() {
43-
writeln!(out, "import type {{").unwrap();
44-
for name in &imports {
45-
writeln!(out, " {name},").unwrap();
46-
}
47-
writeln!(out, "}} from \"@parity/truapi\";\n").unwrap();
48+
let entries = imports
49+
.iter()
50+
.map(|name| format!(" {name},"))
51+
.collect::<Vec<_>>()
52+
.join("\n");
53+
writedoc!(
54+
out,
55+
r#"
56+
import type {{
57+
{entries}
58+
}} from "@parity/truapi";
59+
60+
"#,
61+
)
62+
.unwrap();
4863
}
4964

5065
for trait_def in &definition.traits {
51-
emit_trait_interface(&mut out, trait_def)?;
52-
writeln!(out).unwrap();
66+
out.push_str(&emit_trait_interface(trait_def)?);
67+
out.push('\n');
5368
}
5469

5570
// The Rust super-trait `Platform` becomes `HostCallbacks` on the TS
5671
// surface: that is the name every host implementer reaches for, and
5772
// it stays stable even if the Rust trait is renamed.
58-
if let Some(super_trait) = &definition.super_trait {
59-
emit_super_interface(
60-
&mut out,
61-
"HostCallbacks",
62-
&super_trait.composes,
63-
super_trait.docs.as_deref(),
64-
);
65-
} else {
66-
let names: Vec<String> = definition.traits.iter().map(|t| t.name.clone()).collect();
67-
emit_super_interface(&mut out, "HostCallbacks", &names, None);
68-
}
73+
let (composes, docs): (Vec<String>, Option<&str>) = match &definition.super_trait {
74+
Some(s) => (s.composes.clone(), s.docs.as_deref()),
75+
None => (
76+
definition.traits.iter().map(|t| t.name.clone()).collect(),
77+
None,
78+
),
79+
};
80+
out.push_str(&emit_super_interface("HostCallbacks", &composes, docs));
6981

7082
Ok(out)
7183
}
7284

73-
fn emit_trait_interface(out: &mut String, trait_def: &PlatformTrait) -> Result<()> {
74-
write_jsdoc(out, "", trait_def.docs.as_deref());
75-
writeln!(out, "export interface {} {{", trait_def.name).unwrap();
76-
for (idx, method) in trait_def.methods.iter().enumerate() {
77-
if idx > 0 {
78-
writeln!(out).unwrap();
79-
}
80-
emit_method(out, method)?;
81-
}
82-
writeln!(out, "}}").unwrap();
83-
Ok(())
85+
fn emit_trait_interface(trait_def: &PlatformTrait) -> Result<String> {
86+
let jsdoc = render_jsdoc("", trait_def.docs.as_deref());
87+
let body = trait_def
88+
.methods
89+
.iter()
90+
.map(emit_method)
91+
.collect::<Result<Vec<_>>>()?
92+
.join("\n\n");
93+
Ok(formatdoc! {
94+
r#"
95+
{jsdoc}export interface {name} {{
96+
{body}
97+
}}
98+
"#,
99+
name = trait_def.name,
100+
})
84101
}
85102

86-
fn emit_method(out: &mut String, method: &PlatformMethod) -> Result<()> {
87-
write_jsdoc(out, " ", method.docs.as_deref());
103+
fn emit_method(method: &PlatformMethod) -> Result<String> {
104+
let jsdoc = render_jsdoc(" ", method.docs.as_deref());
88105
let params = method
89106
.params
90107
.iter()
@@ -95,18 +112,17 @@ fn emit_method(out: &mut String, method: &PlatformMethod) -> Result<()> {
95112
.collect::<Result<Vec<_>>>()?
96113
.join(", ");
97114
let ret = format_return(&method.return_shape)?;
98-
writeln!(out, " {}({params}): {ret};", to_camel_case(&method.name)).unwrap();
99-
Ok(())
115+
let name = to_camel_case(&method.name);
116+
Ok(format!("{jsdoc} {name}({params}): {ret};"))
100117
}
101118

102-
fn emit_super_interface(out: &mut String, name: &str, composes: &[String], docs: Option<&str>) {
103-
write_jsdoc(out, "", docs);
119+
fn emit_super_interface(name: &str, composes: &[String], docs: Option<&str>) -> String {
120+
let jsdoc = render_jsdoc("", docs);
104121
if composes.is_empty() {
105-
writeln!(out, "export interface {name} {{}}").unwrap();
106-
return;
122+
return format!("{jsdoc}export interface {name} {{}}\n");
107123
}
108124
let extends = composes.join(", ");
109-
writeln!(out, "export interface {name} extends {extends} {{}}").unwrap();
125+
format!("{jsdoc}export interface {name} extends {extends} {{}}\n")
110126
}
111127

112128
fn collect_named_types(definition: &PlatformDefinition) -> BTreeSet<String> {
@@ -213,23 +229,26 @@ fn ts_type(ty: &TypeRef) -> Result<String> {
213229
}
214230
}
215231

216-
fn write_jsdoc(out: &mut String, indent: &str, docs: Option<&str>) {
232+
fn render_jsdoc(indent: &str, docs: Option<&str>) -> String {
217233
let Some(docs) = docs else {
218-
return;
234+
return String::new();
219235
};
220236
let docs = docs.trim();
221237
if docs.is_empty() {
222-
return;
238+
return String::new();
223239
}
224-
writeln!(out, "{indent}/**").unwrap();
225-
for line in docs.lines() {
226-
if line.is_empty() {
227-
writeln!(out, "{indent} *").unwrap();
228-
} else {
229-
writeln!(out, "{indent} * {line}").unwrap();
230-
}
231-
}
232-
writeln!(out, "{indent} */").unwrap();
240+
let body = docs
241+
.lines()
242+
.map(|line| {
243+
if line.is_empty() {
244+
format!("{indent} *")
245+
} else {
246+
format!("{indent} * {line}")
247+
}
248+
})
249+
.collect::<Vec<_>>()
250+
.join("\n");
251+
format!("{indent}/**\n{body}\n{indent} */\n")
233252
}
234253

235254
fn to_camel_case(name: &str) -> String {

0 commit comments

Comments
 (0)