#![deny(warnings)]
mod cmd;
mod output;
mod util;
use clap::{Parser, Subcommand};
use cmd::{analyze::AnalyzeArgs, config::ConfigAction, diff::DiffArgs};
use std::path::PathBuf;
#[derive(Parser)]
#[command(name = "hotspots")]
#[command(
about = "Multi-language static analysis tool (TypeScript, JavaScript, Go, Java, Python, Rust)"
)]
#[command(version = env!("HOTSPOTS_VERSION"))]
struct Cli {
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
Analyze {
path: PathBuf,
#[arg(long, default_value = "text")]
format: OutputFormat,
#[arg(long)]
mode: Option<OutputMode>,
#[arg(long)]
policy: bool,
#[arg(long)]
top: Option<usize>,
#[arg(long)]
min_lrs: Option<f64>,
#[arg(long)]
config: Option<PathBuf>,
#[arg(long)]
output: Option<PathBuf>,
#[arg(long)]
explain: bool,
#[arg(short = 'f', long)]
force: bool,
#[arg(long)]
no_persist: bool,
#[arg(long, value_name = "LEVEL")]
level: Option<OutputLevel>,
#[arg(long)]
per_function_touches: bool,
#[arg(long)]
all_functions: bool,
#[arg(long)]
explain_patterns: bool,
#[arg(long, value_name = "URL")]
source_url: Option<String>,
},
Prune {
#[arg(long)]
unreachable: bool,
#[arg(long)]
older_than: Option<u64>,
#[arg(long)]
dry_run: bool,
},
Compact {
#[arg(long)]
level: u32,
},
Trends {
path: PathBuf,
#[arg(long, default_value = "json")]
format: OutputFormat,
#[arg(long, default_value = "10")]
window: usize,
#[arg(long, default_value = "5")]
top: usize,
},
#[command(name = "config")]
Config {
#[command(subcommand)]
action: ConfigAction,
},
Init {
#[arg(long)]
hooks: bool,
},
Diff {
base: String,
head: String,
#[arg(long, default_value = "text")]
format: OutputFormat,
#[arg(long)]
output: Option<PathBuf>,
#[arg(long)]
policy: bool,
#[arg(long)]
top: Option<usize>,
#[arg(long)]
config: Option<PathBuf>,
#[arg(long)]
auto_analyze: bool,
},
}
#[derive(Clone, Copy, clap::ValueEnum)]
pub(crate) enum OutputFormat {
Text,
Json,
Html,
Jsonl,
Sarif,
}
#[derive(Clone, Copy, PartialEq, clap::ValueEnum)]
pub(crate) enum OutputMode {
Snapshot,
Delta,
}
#[derive(Clone, Copy, PartialEq, clap::ValueEnum)]
pub(crate) enum OutputLevel {
File,
Module,
}
fn main() -> anyhow::Result<()> {
let cli = Cli::parse();
match cli.command {
Commands::Analyze {
path,
format,
mode,
policy,
top,
min_lrs,
config: config_path,
output,
explain,
force,
no_persist,
level,
per_function_touches,
all_functions,
explain_patterns,
source_url,
} => cmd::analyze::handle_analyze(AnalyzeArgs {
path,
format,
mode,
policy,
top,
min_lrs,
config_path,
output,
explain,
force,
no_persist,
level,
per_function_touches,
all_functions,
explain_patterns,
source_url,
})?,
Commands::Prune {
unreachable,
older_than,
dry_run,
} => cmd::prune::handle_prune(unreachable, older_than, dry_run)?,
Commands::Compact { level } => cmd::compact::handle_compact(level)?,
Commands::Config { action } => cmd::config::handle_config(action)?,
Commands::Trends {
path,
format,
window,
top,
} => cmd::trends::handle_trends(path, format, window, top)?,
Commands::Init { hooks } => cmd::init::handle_init(hooks)?,
Commands::Diff {
base,
head,
format,
output,
policy,
top,
config,
auto_analyze,
} => cmd::diff::handle_diff(DiffArgs {
base,
head,
format,
output,
policy,
top,
config_path: config,
auto_analyze,
})?,
}
Ok(())
}