///|
fn optional_name(value : String?) -> String {
match value {
Some(name) => "`\{name}`"
None => "—"
}
}
///|
fn change_marker(change : ApiChange) -> String {
if change.change_kind is Added {
"ADD"
} else if change.safely_shimmable {
"SHIM"
} else {
"BLOCK"
}
}
///|
/// Renders a human-readable Markdown migration report.
pub fn render_markdown(
plan : MigrationPlan,
old_name : StringView,
new_name : StringView,
) -> String {
let builder = StringBuilder()
builder.write_string("# MoonShim migration report\n\n")
builder.write_string("- Old interface: `\{old_name}`\n")
builder.write_string("- New interface: `\{new_name}`\n")
builder.write_string("- Changes: \{plan.changes.length()}\n")
builder.write_string("- Generated shims: \{plan.shim_sources.length()}\n")
builder.write_string(
"- Review required: \{has_unresolved_breaking_changes(plan)}\n\n",
)
builder.write_string("## Changes\n\n")
if plan.changes.is_empty() {
builder.write_string("No public API changes detected.\n")
} else {
builder.write_string("| Status | Kind | Old | New | Reason |\n")
builder.write_string("| --- | --- | --- | --- | --- |\n")
for change in plan.changes {
builder.write_string(
"| \{change_marker(change)} | \{symbol_kind_name(change.symbol_kind)} | \{optional_name(change.old_name)} | \{optional_name(change.new_name)} | \{change.reason} |\n",
)
}
}
if !plan.diagnostics.is_empty() {
builder.write_string("\n## Suggestions\n\n")
for diagnostic in plan.diagnostics {
builder.write_string("- \{diagnostic}\n")
}
}
if !plan.shim_sources.is_empty() {
builder.write_string("\n## Generated shims\n\n")
builder.write_string("```mbt\n")
for shim in plan.shim_sources {
builder.write_string(shim)
}
builder.write_string("```\n")
}
builder.to_string()
}
///|
/// Merges all generated declarations into one compilable source file.
pub fn render_shim_source(plan : MigrationPlan) -> String {
let builder = StringBuilder()
builder.write_string(
"// Generated by MoonShim. Review before committing.\n\n",
)
for shim in plan.shim_sources {
builder.write_string(shim)
builder.write_char('\n')
}
builder.to_string()
}
///|
/// Renders a stable machine-readable report suitable for CI.
pub fn render_json(
plan : MigrationPlan,
old_name : StringView,
new_name : StringView,
) -> String {
let builder = StringBuilder()
builder.write_string("{\n")
builder.write_string(" \"schema_version\": 1,\n")
builder.write_string(" \"old_interface\": \"\{json_escape(old_name)}\",\n")
builder.write_string(" \"new_interface\": \"\{json_escape(new_name)}\",\n")
builder.write_string(
" \"review_required\": \{has_unresolved_breaking_changes(plan)},\n",
)
builder.write_string(" \"changes\": [")
for index, change in plan.changes {
if index > 0 {
builder.write_char(',')
}
builder.write_string("\n {")
builder.write_string("\"status\": \"\{change_marker(change)}\", ")
builder.write_string(
"\"change\": \"\{change_kind_name(change.change_kind)}\", ",
)
builder.write_string(
"\"kind\": \"\{symbol_kind_name(change.symbol_kind)}\", ",
)
builder.write_string("\"old_name\": ")
match change.old_name {
Some(name) => builder.write_string("\"\{json_escape(name)}\"")
None => builder.write_string("null")
}
builder.write_string(", \"new_name\": ")
match change.new_name {
Some(name) => builder.write_string("\"\{json_escape(name)}\"")
None => builder.write_string("null")
}
builder.write_string(", \"reason\": \"\{json_escape(change.reason)}\"}")
}
if !plan.changes.is_empty() {
builder.write_string("\n ")
}
builder.write_string("],\n")
builder.write_string(" \"diagnostics\": [")
for index, diagnostic in plan.diagnostics {
if index > 0 {
builder.write_char(',')
}
builder.write_string("\n \"\{json_escape(diagnostic)}\"")
}
if !plan.diagnostics.is_empty() {
builder.write_string("\n ")
}
builder.write_string("],\n")
builder.write_string(" \"shim_count\": \{plan.shim_sources.length()}\n")
builder.write_string("}\n")
builder.to_string()
}