fn apply_filters(
mut table: SymbolTable,
filter: Option<crate::cli::SymbolTypeFilter>,
query: Option<String>,
top_files: usize,
) -> Result<SymbolTable> {
if let Some(type_filter) = filter {
table.symbols.retain(|s| match type_filter {
crate::cli::SymbolTypeFilter::Functions => {
s.kind == SymbolKind::Function || s.kind == SymbolKind::Method
}
crate::cli::SymbolTypeFilter::Classes => s.kind == SymbolKind::Class,
crate::cli::SymbolTypeFilter::Types => {
s.kind == SymbolKind::Type
|| s.kind == SymbolKind::Interface
|| s.kind == SymbolKind::Enum
}
crate::cli::SymbolTypeFilter::Variables => {
s.kind == SymbolKind::Variable || s.kind == SymbolKind::Constant
}
crate::cli::SymbolTypeFilter::Modules => s.kind == SymbolKind::Module,
crate::cli::SymbolTypeFilter::All => true,
});
}
if let Some(q) = query {
let q_lower = q.to_lowercase();
table
.symbols
.retain(|s| s.name.to_lowercase().contains(&q_lower));
}
Ok(rederive_summary(table, top_files))
}
fn rederive_summary(mut table: SymbolTable, top_files: usize) -> SymbolTable {
let retained: std::collections::HashSet<String> =
table.symbols.iter().map(|s| s.name.clone()).collect();
table.total_symbols = table.symbols.len();
table
.unreferenced_symbols
.retain(|name| retained.contains(name));
let (most_referenced, referenced_symbol_count) =
find_most_referenced(&table.symbols, top_files);
table.most_referenced = most_referenced;
table.referenced_symbol_count = referenced_symbol_count;
table
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn format_output(
table: SymbolTable,
format: crate::cli::SymbolTableOutputFormat,
show_unreferenced: bool,
show_references: bool,
top_files: usize,
) -> Result<String> {
match format {
crate::cli::SymbolTableOutputFormat::Json => format_json_output(&table),
crate::cli::SymbolTableOutputFormat::Human
| crate::cli::SymbolTableOutputFormat::Summary => format_human_output(
table,
HumanRender {
show_unreferenced,
show_references,
max_per_group: SYMBOLS_PER_GROUP_IN_SUMMARY,
top_files,
},
),
crate::cli::SymbolTableOutputFormat::Detailed => format_human_output(
table,
HumanRender {
show_unreferenced,
show_references: true,
max_per_group: usize::MAX,
top_files,
},
),
crate::cli::SymbolTableOutputFormat::Csv => format_csv_output(table),
}
}
struct HumanRender {
show_unreferenced: bool,
show_references: bool,
max_per_group: usize,
top_files: usize,
}
fn format_json_output(table: &SymbolTable) -> Result<String> {
Ok(serde_json::to_string_pretty(table)?)
}
fn format_human_output(table: SymbolTable, opts: HumanRender) -> Result<String> {
let mut output = String::new();
write_header(&mut output, table.total_symbols)?;
write_symbols_by_type(&mut output, &table.symbols, &opts)?;
if opts.show_unreferenced {
write_unreferenced_symbols(&mut output, &table.unreferenced_symbols)?;
}
write_most_referenced(
&mut output,
&table.most_referenced,
table.referenced_symbol_count,
)?;
write_top_files_by_count(&mut output, &table.symbols, opts.top_files)?;
Ok(output)
}
fn write_header(output: &mut String, total_symbols: usize) -> Result<()> {
use crate::cli::colors as c;
use std::fmt::Write;
writeln!(output, "{}\n", c::header("Symbol Table Analysis"))?;
writeln!(
output,
" {} {}",
c::label("Total symbols:"),
c::number(&total_symbols.to_string())
)?;
writeln!(output, "\n{}\n", c::subheader("Symbols by Type"))?;
Ok(())
}
fn write_symbols_by_type(
output: &mut String,
symbols: &[Symbol],
opts: &HumanRender,
) -> Result<()> {
for (kind, syms) in group_symbols_by_type(symbols) {
write_symbol_group(output, &kind, &syms, opts)?;
}
Ok(())
}
fn group_symbols_by_type(symbols: &[Symbol]) -> BTreeMap<SymbolKind, Vec<&Symbol>> {
let mut by_type: BTreeMap<SymbolKind, Vec<&Symbol>> = BTreeMap::new();
for symbol in symbols {
by_type.entry(symbol.kind.clone()).or_default().push(symbol);
}
by_type
}
fn write_symbol_group(
output: &mut String,
kind: &SymbolKind,
syms: &[&Symbol],
opts: &HumanRender,
) -> Result<()> {
use crate::cli::colors as c;
use std::fmt::Write;
writeln!(
output,
"{} ({})",
c::subheader(&format!("{kind:?}")),
c::number(&syms.len().to_string())
)?;
for sym in syms.iter().take(opts.max_per_group) {
writeln!(
output,
" - {} {}",
c::number(&sym.name),
c::path(&format!("{}:{}", sym.file, sym.line))
)?;
if opts.show_references {
write_reference_sites(output, sym)?;
}
}
if syms.len() > opts.max_per_group {
writeln!(
output,
" {}",
c::dim(&format!("... and {} more", syms.len() - opts.max_per_group))
)?;
}
writeln!(output)?;
Ok(())
}
fn write_reference_sites(output: &mut String, sym: &Symbol) -> Result<()> {
use crate::cli::colors as c;
use std::fmt::Write;
let sites: Vec<String> = sym
.references
.iter()
.filter(|r| !matches!(r.kind, ReferenceKind::Definition))
.take(REFERENCE_SITES_SHOWN)
.map(|r| format!("{}:{}", extract_filename(&r.file), r.line))
.collect();
if sites.is_empty() {
writeln!(output, " {}", c::dim("used at: none resolved"))?;
return Ok(());
}
let total = usage_count(sym);
let suffix = if total > sites.len() {
format!(" (+{} more)", total - sites.len())
} else {
String::new()
};
writeln!(
output,
" {}",
c::dim(&format!("used at: {}{}", sites.join(", "), suffix))
)?;
Ok(())
}
fn write_unreferenced_symbols(output: &mut String, unreferenced: &[String]) -> Result<()> {
use crate::cli::colors as c;
use std::fmt::Write;
if unreferenced.is_empty() {
return Ok(());
}
writeln!(output, "\n{}\n", c::subheader("Unreferenced Symbols"))?;
for name in unreferenced {
writeln!(output, " - {}", c::colored(c::YELLOW, name))?;
}
Ok(())
}
fn truncated_heading(title: &str, shown: usize, total: usize) -> String {
use crate::cli::colors as c;
if shown >= total {
return c::subheader(&format!("{title} ({total})"));
}
c::subheader(&format!("{title} (top {shown} of {total})"))
}
fn write_most_referenced(
output: &mut String,
most_referenced: &[(String, usize)],
referenced_total: usize,
) -> Result<()> {
use crate::cli::colors as c;
use std::fmt::Write;
if most_referenced.is_empty() {
return Ok(());
}
writeln!(
output,
"\n{}\n",
truncated_heading(
"Most Referenced Symbols",
most_referenced.len(),
referenced_total
)
)?;
for (name, count) in most_referenced {
writeln!(
output,
" - {}: {} references",
c::number(name),
c::number(&count.to_string())
)?;
}
Ok(())
}
fn write_top_files_by_count(
output: &mut String,
symbols: &[Symbol],
top_files: usize,
) -> Result<()> {
use crate::cli::colors as c;
use std::fmt::Write;
if symbols.is_empty() {
return Ok(());
}
let sorted_files = get_sorted_file_counts(symbols);
let shown = if top_files == 0 {
sorted_files.len()
} else {
top_files.min(sorted_files.len())
};
writeln!(
output,
"\n{}\n",
truncated_heading("Top Files by Symbol Count", shown, sorted_files.len())
)?;
for (i, (file_path, count)) in sorted_files.iter().take(shown).enumerate() {
let filename = extract_filename(file_path);
writeln!(
output,
"{}. {} - {} symbols",
c::subheader(&(i + 1).to_string()),
c::path(filename),
c::number(&count.to_string())
)?;
}
Ok(())
}
fn get_sorted_file_counts(symbols: &[Symbol]) -> Vec<(&str, usize)> {
let mut file_counts: HashMap<&str, usize> = HashMap::new();
for symbol in symbols {
*file_counts.entry(&symbol.file).or_insert(0) += 1;
}
let mut sorted_files: Vec<_> = file_counts.into_iter().collect();
sorted_files.sort_by(|a, b| b.1.cmp(&a.1).then_with(|| a.0.cmp(b.0)));
sorted_files
}
fn extract_filename(file_path: &str) -> &str {
Path::new(file_path)
.file_name()
.and_then(|n| n.to_str())
.unwrap_or(file_path)
}
fn format_csv_output(table: SymbolTable) -> Result<String> {
use std::fmt::Write;
let mut output = String::new();
writeln!(&mut output, "name,kind,file,line,column,visibility")?;
for sym in table.symbols {
writeln!(
&mut output,
"{},{:?},{},{},{},{:?}",
sym.name, sym.kind, sym.file, sym.line, sym.column, sym.visibility
)?;
}
Ok(output)
}