mod config;
mod connection;
mod document;
mod error;
mod lint;
mod protocol;
mod server;
use clap::{Parser, Subcommand};
use config::LspSettings;
use lint::{DryRunFormat, LintConfig, LintEngine, OutputFormat};
use server::FstarServer;
use std::path::PathBuf;
use tower_lsp::{LspService, Server};
use tracing::{info, Level};
use tracing_subscriber::FmtSubscriber;
#[derive(Parser, Debug)]
#[command(name = "brrr-lint")]
#[command(author)]
#[command(version)]
#[command(about = "A fast linter and language server for F* (FStar)", long_about = None)]
struct Cli {
#[arg(short, long, global = true)]
debug: bool,
#[arg(long, global = true)]
fstar_exe: Option<String>,
#[command(subcommand)]
command: Option<Commands>,
}
#[derive(Subcommand, Debug)]
enum Commands {
Serve {
#[arg(long)]
verify_on_open: bool,
#[arg(long, default_value = "true")]
verify_on_save: bool,
#[arg(long, default_value = "true")]
fly_check: bool,
#[arg(long, default_value = "30000")]
timeout_ms: u64,
#[arg(long, default_value = "4")]
max_processes: usize,
#[arg(long)]
log_file: Option<String>,
},
Check {
#[arg(required = true)]
paths: Vec<PathBuf>,
#[arg(long)]
select: Option<String>,
#[arg(long)]
ignore: Option<String>,
#[arg(long, value_enum, default_value = "text")]
format: OutputFormat,
#[arg(long)]
show_fixes: bool,
#[arg(long)]
exit_zero: bool,
},
Fix {
#[arg(required = true)]
paths: Vec<PathBuf>,
#[arg(long)]
select: Option<String>,
#[arg(long)]
ignore: Option<String>,
#[arg(long)]
apply: bool,
#[arg(long)]
force: bool,
#[arg(long, value_enum, default_value = "text")]
format: OutputFormat,
#[arg(long, value_enum, default_value = "full")]
dry_run_format: DryRunFormat,
},
Rules,
}
#[tokio::main]
async fn main() {
let cli = Cli::parse();
let log_level = if cli.debug { Level::DEBUG } else { Level::INFO };
let subscriber = FmtSubscriber::builder()
.with_max_level(log_level)
.with_writer(std::io::stderr)
.with_ansi(true)
.finish();
tracing::subscriber::set_global_default(subscriber).expect("Failed to set tracing subscriber");
match cli.command {
None | Some(Commands::Serve { .. }) => {
run_server(cli).await;
}
Some(Commands::Check {
paths,
select,
ignore,
format,
show_fixes,
exit_zero,
}) => {
let config = LintConfig::new(select, ignore, cli.fstar_exe);
let engine = LintEngine::new(config);
let exit_code = engine.check(&paths, format, show_fixes).await;
if !exit_zero {
std::process::exit(exit_code);
}
}
Some(Commands::Fix {
paths,
select,
ignore,
apply,
force,
format,
dry_run_format,
}) => {
let config = LintConfig::new(select, ignore, cli.fstar_exe);
let engine = LintEngine::new(config);
let dry_run = !apply;
let force_apply = force && apply;
let exit_code = engine.fix(&paths, format, dry_run, dry_run_format, force_apply).await;
std::process::exit(exit_code);
}
Some(Commands::Rules) => {
lint::print_rules();
}
}
}
async fn run_server(cli: Cli) {
let (verify_on_open, verify_on_save, fly_check, timeout_ms, max_processes) = match cli.command {
Some(Commands::Serve {
verify_on_open,
verify_on_save,
fly_check,
timeout_ms,
max_processes,
..
}) => (
verify_on_open,
verify_on_save,
fly_check,
timeout_ms,
max_processes,
),
_ => (false, true, true, 30000, 4),
};
info!("brrr-lint LSP server starting");
info!("Version: {}", env!("CARGO_PKG_VERSION"));
info!("Debug: {}", cli.debug);
let cli_settings = LspSettings {
verify_on_open,
verify_on_save,
fly_check,
debug: cli.debug,
timeout_ms,
max_processes,
fstar_exe: cli.fstar_exe,
};
let (service, socket) = LspService::build(|client| FstarServer::new(client, cli_settings))
.custom_method(
"$/fstar/verifyToPosition",
FstarServer::handle_verify_to_position,
)
.custom_method("$/fstar/restart", FstarServer::handle_restart)
.custom_method(
"$/fstar/killAndRestartSolver",
FstarServer::handle_kill_and_restart_solver,
)
.custom_method("$/fstar/killAll", FstarServer::handle_kill_all)
.custom_method(
"$/fstar/getTranslatedFst",
FstarServer::handle_get_translated_fst,
)
.custom_method(
"$/fstar/getDiagnostics",
FstarServer::handle_get_diagnostics,
)
.custom_method("$/fstar/getFragments", FstarServer::handle_get_fragments)
.finish();
let stdin = tokio::io::stdin();
let stdout = tokio::io::stdout();
Server::new(stdin, stdout, socket).serve(service).await;
info!("brrr-lint LSP server shutting down");
}