///|
fn html_escape(text : String) -> String {
text
.replace(old="&", new="&")
.replace(old="<", new="<")
.replace(old=">", new=">")
.replace(old="\"", new=""")
}
///|
fn status_class(value : Bool) -> String {
if value {
"ok"
} else {
"bad"
}
}
///|
fn status_text(value : Bool, lang : String) -> String {
if value {
if is_zh(lang) {
"通过"
} else {
"OK"
}
} else if is_zh(lang) {
"缺失"
} else {
"Missing"
}
}
///|
fn push_metric(lines : Array[String], label : String, value : String) -> Unit {
lines.push(
"" +
html_escape(label) +
"" +
html_escape(value) +
"
",
)
}
///|
fn push_signal(
lines : Array[String],
label : String,
value : Bool,
lang : String,
) -> Unit {
lines.push(
"" +
html_escape(label) +
"" +
status_text(value, lang) +
"",
)
}
///|
fn coverage_class(value : Int) -> String {
if value >= 90 {
"ok"
} else if value >= 70 {
"warn"
} else {
"bad"
}
}
///|
fn issue_kind(issue : DocIssue, lang : String) -> String {
if issue.message.contains("Missing documentation") {
if is_zh(lang) {
"缺少 API 文档"
} else {
"Missing API docs"
}
} else if issue.message.contains("Weak documentation") {
if is_zh(lang) {
"弱文档"
} else {
"Weak docs"
}
} else if issue.message.contains("README") {
"README"
} else if issue.message.contains("moon.mod") {
"moon.mod"
} else if issue.message.contains("interface summary") ||
issue.message.contains("moon info") {
"pkg.generated.mbti"
} else if issue.message.contains("CI") ||
issue.message.contains("GitHub Actions") {
"CI"
} else if is_zh(lang) {
"项目检查"
} else {
"Project check"
}
}
///|
fn issue_kind_key(issue : DocIssue) -> String {
if issue.message.contains("Missing documentation") {
"api-missing"
} else if issue.message.contains("Weak documentation") {
"api-weak"
} else if issue.message.contains("README") {
"readme"
} else if issue.message.contains("moon.mod") {
"moon-mod"
} else if issue.message.contains("interface summary") ||
issue.message.contains("moon info") {
"mbti"
} else if issue.message.contains("CI") ||
issue.message.contains("GitHub Actions") {
"ci"
} else {
"project"
}
}
///|
fn issue_explanation(issue : DocIssue, lang : String) -> String {
let key = issue_kind_key(issue)
if key == "api-missing" {
if is_zh(lang) {
"公开 API 是外部用户最先接触的接口,缺少 `///` 文档会降低可用性,也会影响后续生成文档。"
} else {
"Public APIs are the first surface users rely on; missing `///` docs reduce usability and generated documentation quality."
}
} else if key == "api-weak" {
if is_zh(lang) {
"占位式注释虽然能通过形式检查,但不能说明用途、参数含义或使用边界。"
} else {
"Placeholder comments pass a shape check but do not explain purpose, parameters, or boundaries."
}
} else if key == "readme" {
if is_zh(lang) {
"README 是用户进入项目后的第一份说明,缺少使用信息会让项目难以快速试用。"
} else {
"The README is the entry document; missing usage information makes the project harder to try."
}
} else if key == "moon-mod" {
if is_zh(lang) {
"Moon 元数据会影响发布、检索和评审,描述、仓库地址、许可证等字段应尽量完整。"
} else {
"Moon metadata supports publishing, discovery, and review; description, repository, and license should be complete."
}
} else if key == "mbti" {
if is_zh(lang) {
"`pkg.generated.mbti` 是 `moon info` 生成的公开接口摘要,适合作为发布前 API 审查依据。"
} else {
"`pkg.generated.mbti` is generated by `moon info` and helps review the public API surface before release."
}
} else if key == "ci" {
if is_zh(lang) {
"CI 信号说明项目是否在提交后自动执行基础检查;脚本式验证会单独标记。"
} else {
"CI signals show whether basic checks run after commits; script-based validation is tracked separately."
}
} else if is_zh(lang) {
"该问题来自项目级文档准备度检查。"
} else {
"This issue comes from project-level documentation readiness checks."
}
}
///|
fn push_html_head(lines : Array[String], lang : String) -> Unit {
lines.push("")
lines.push(
"",
)
lines.push("")
lines.push("")
lines.push(
"",
)
lines.push(
"" +
(if is_zh(lang) { "MoonDocCheck 报告" } else { "MoonDocCheck Report" }) +
"",
)
lines.push(
(
#|
),
)
lines.push("")
lines.push("")
}
///|
fn push_html_signals(
lines : Array[String],
report : ProjectReport,
lang : String,
) -> Unit {
lines.push("")
lines.push("README
")
push_signal(
lines,
if is_zh(lang) {
"找到"
} else {
"Found"
},
report.readme.found,
lang,
)
push_signal(
lines,
if is_zh(lang) {
"项目介绍"
} else {
"Overview"
},
report.readme.has_overview,
lang,
)
push_signal(
lines,
if is_zh(lang) {
"文档入口"
} else {
"Docs entry"
},
report.readme.has_usage,
lang,
)
push_signal(
lines,
if is_zh(lang) {
"开发命令"
} else {
"Development commands"
},
report.readme.has_development_commands,
lang,
)
lines.push("
")
lines.push(
"" +
(if is_zh(lang) { "项目元数据与 CI" } else { "Metadata and CI" }) +
"
",
)
push_signal(lines, "moon.mod", report.moon_mod.found, lang)
push_signal(lines, "moon.mod.json", report.moon_mod.legacy_json_found, lang)
push_signal(lines, "pkg.generated.mbti", report.has_generated_mbti, lang)
push_signal(lines, "moon check", report.ci.has_moon_check, lang)
push_signal(lines, "moon test", report.ci.has_moon_test, lang)
push_signal(
lines,
if is_zh(lang) {
"脚本式验证"
} else {
"Script validation"
},
report.ci.has_script_validation,
lang,
)
lines.push("
")
}
///|
fn push_html_top_files(
lines : Array[String],
report : ProjectReport,
lang : String,
) -> Unit {
let top_files = top_missing_files(report, 8)
if !top_files.is_empty() {
lines.push(
"" +
(if is_zh(lang) { "缺文档重灾区" } else { "Top Missing-Doc Files" }) +
"
",
)
lines.push(
"| " +
(if is_zh(lang) { "文件" } else { "File" }) +
" | " +
(if is_zh(lang) { "缺失" } else { "Missing" }) +
" | " +
(if is_zh(lang) { "覆盖率" } else { "Coverage" }) +
" |
",
)
for item in top_files {
lines.push(
"| " +
html_escape(display_path(report, item.file)) +
" | " +
item.public_api_missing_docs.to_string() +
" | " +
item.documentation_coverage.to_string() +
"% |
",
)
}
lines.push("
")
}
}
///|
fn push_html_file_coverage(
lines : Array[String],
report : ProjectReport,
lang : String,
) -> Unit {
if !report.file_coverage.is_empty() {
lines.push(
"",
)
}
}
///|
fn push_html_issues(
lines : Array[String],
report : ProjectReport,
all_issues : Bool,
lang : String,
) -> Unit {
lines.push(
"" +
(if is_zh(lang) { "问题清单" } else { "Issues" }) +
"
" +
(if is_zh(lang) {
"可按问题类型或路径搜索,展开单项可查看判定依据。"
} else {
"Filter by issue type or path. Expand an item to inspect the rationale."
}) +
"
",
)
if report.issues.is_empty() {
lines.push(
"" +
(if is_zh(lang) { "未发现问题。" } else { "No issues found." }) +
"
",
)
} else {
lines.push(
"",
)
let limit = if all_issues {
report.issues.length()
} else if report.issues.length() > 80 {
80
} else {
report.issues.length()
}
for index in 0.. display_path(report, issue.file) + ":" + line.to_string()
None => display_path(report, issue.file)
}
let search_text = (location +
" " +
issue_message(issue, lang) +
" " +
issue_kind(issue, lang)).to_lower()
lines.push(
"" +
html_escape(location) +
"
" +
html_escape(issue_kind(issue, lang)) +
"
" +
html_escape(issue_message(issue, lang)) +
"" +
html_escape(fix_hint(issue, lang)) +
"
" +
(if is_zh(lang) { "查看判定依据" } else { "View rationale" }) +
"
" +
html_escape(issue_explanation(issue, lang)) +
"
",
)
}
if report.issues.length() > limit {
lines.push(
"... " +
(report.issues.length() - limit).to_string() +
(if is_zh(lang) {
" 个问题未显示。使用 --all-issues 查看全部。"
} else {
" more issues hidden. Use --all-issues to show everything."
}) +
"
",
)
}
lines.push(
"" +
(if is_zh(lang) {
"没有匹配的问题。"
} else {
"No matching issues."
}) +
"
",
)
}
lines.push("")
}
///|
fn push_html_script(lines : Array[String]) -> Unit {
lines.push(
(
#|
),
)
}
///|
/// Render a project report as a standalone HTML preview.
pub fn render_html(
report : ProjectReport,
all_issues? : Bool = true,
lang? : String = "zh",
) -> String {
let lines : Array[String] = []
push_html_head(lines, lang)
lines.push(
"" +
(if is_zh(lang) { "MoonDocCheck 报告" } else { "MoonDocCheck Report" }) +
"
" +
html_escape(report.project_name) +
"
" +
html_escape(readiness_level(report, lang)) +
"
",
)
lines.push("")
push_metric(
lines,
if is_zh(lang) {
"扫描文件数"
} else {
"Files scanned"
},
report.files_scanned.to_string(),
)
push_metric(
lines,
if is_zh(lang) {
"MoonBit 源文件数"
} else {
"MoonBit source files"
},
report.moonbit_source_files.to_string(),
)
push_metric(
lines,
if is_zh(lang) {
"公开 API"
} else {
"Public APIs"
},
report.public_api_total.to_string(),
)
push_metric(
lines,
if is_zh(lang) {
"文档覆盖率"
} else {
"Coverage"
},
report.documentation_coverage.to_string() + "%",
)
lines.push("")
lines.push(
"" +
(if is_zh(lang) { "总体评价" } else { "Overall Assessment" }) +
"
" +
html_escape(readiness_comment(report, lang)) +
"
" +
html_escape(
if is_zh(lang) {
report.public_api_total.to_string() +
" 个公开 API 中有 " +
report.public_api_missing_docs.to_string() +
" 个缺少文档。"
} else {
report.public_api_missing_docs.to_string() +
" of " +
report.public_api_total.to_string() +
" public APIs are missing docs."
},
) +
"
",
)
lines.push(
"" +
(if is_zh(lang) { "下一步" } else { "Next Steps" }) +
"
",
)
for step in next_steps(report, lang) {
lines.push("- " + html_escape(step) + "
")
}
lines.push("
")
push_html_top_files(lines, report, lang)
push_html_signals(lines, report, lang)
push_html_file_coverage(lines, report, lang)
push_html_issues(lines, report, all_issues, lang)
push_html_script(lines)
lines.push("")
lines.join("\n")
}