pdftract/scripts/count_rustdoc_coverage.rs
jedarden 1c6f26ecaa fix(bf-4mkhv): clean up unused imports in hash.rs
The bead description mentioned compile errors in hash.rs from API drift,
but those errors were either already fixed or misattributed. The API usage
was already correct:
- compute_fingerprint already takes 3 arguments with source
- len() already propagates Result with ?
- read_at method already used correctly
- Catalog fields accessed via trailer correctly

Only cleanup: removed unused std::fs::File and std::io imports.

Verification: notes/bf-4mkhv.md
2026-06-01 09:43:48 -04:00

120 lines
3.7 KiB
Rust
Executable file

#!/usr/bin/env rust-script
//! Measure rustdoc coverage for pdftract-core public API.
use std::fs;
use std::path::Path;
#[derive(Default)]
struct DocStats {
total_items: usize,
with_docs: usize,
with_examples: usize,
modules: usize,
structs: usize,
enums: usize,
traits: usize,
functions: usize,
types: usize,
}
impl DocStats {
fn coverage(&self) -> f64 {
if self.total_items == 0 {
0.0
} else {
(self.with_examples as f64 / self.total_items as f64) * 100.0
}
}
}
fn scan_file(path: &Path, stats: &mut DocStats) {
let content = match fs::read_to_string(path) {
Ok(c) => c,
Err(_) => return,
};
let lines: Vec<&str> = content.lines().collect();
for (i, line) in lines.iter().enumerate() {
let line = line.trim();
// Look for doc comments before public items
let mut has_doc = false;
let mut has_example = false;
// Scan backward for doc comments
if i > 0 {
for j in (0..i).rev() {
let prev_line = lines[j].trim();
if prev_line.starts_with("///") || prev_line.starts_with("//!") {
has_doc = true;
if prev_line.contains("```") && (prev_line.contains("rust") || prev_line.contains("no_run")) {
has_example = true;
}
} else if !prev_line.is_empty() && !prev_line.starts_with("//") && !prev_line.starts_with("#[") {
break;
}
}
}
// Count public items
if line.starts_with("pub ") && !line.starts_with("pub(crate)") {
if line.contains("fn ") {
stats.functions += 1;
} else if line.contains("struct ") {
stats.structs += 1;
} else if line.contains("enum ") {
stats.enums += 1;
} else if line.contains("trait ") {
stats.traits += 1;
} else if line.contains("type ") {
stats.types += 1;
} else if line.contains("mod ") {
stats.modules += 1;
}
stats.total_items += 1;
if has_doc {
stats.with_docs += 1;
}
if has_example {
stats.with_examples += 1;
}
}
}
}
fn scan_directory(dir: &Path, stats: &mut DocStats) {
if let Ok(entries) = fs::read_dir(dir) {
for entry in entries.flatten() {
let path = entry.path();
if path.is_dir() {
scan_directory(&path, stats);
} else if path.extension().map(|e| e == "rs").unwrap_or(false) {
scan_file(&path, stats);
}
}
}
}
fn main() {
let mut stats = DocStats::default();
scan_directory(Path::new("crates/pdftract-core/src"), &mut stats);
println!("\n=== Rustdoc Coverage Report ===\n");
println!("Total public items: {}", stats.total_items);
println!("With docs: {} ({:.1}%)", stats.with_docs,
(stats.with_docs as f64 / stats.total_items as f64) * 100.0);
println!("With examples: {} ({:.1}%)", stats.with_examples,
(stats.with_examples as f64 / stats.total_items as f64) * 100.0);
println!("\nBy type:");
println!(" Modules: {}", stats.modules);
println!(" Structs: {}", stats.structs);
println!(" Enums: {}", stats.enums);
println!(" Traits: {}", stats.traits);
println!(" Functions: {}", stats.functions);
println!(" Types: {}", stats.types);
println!("\nTarget: 80%+ coverage");
println!("Status: {}", if stats.coverage() >= 80.0 { "✓ PASS" } else { "✗ FAIL" });
println!("Current: {:.1}%", stats.coverage());
}