mod cmd;
mod config;
mod debug;
mod file;
mod file_finding;
mod github;
mod reporter;
use crate::cmd::Cmd;
use crate::reporter::LintArgs;
use anyhow::{Context, Result};
use clap::{CommandFactory, Parser, Subcommand, ValueEnum};
use debug::debug;
use reporter::lint_and_report;
use simplelog::CombinedLogger;
use squawk_linter::{Rule, Version};
use squawk_thread::ThreadIntent;
use std::io;
use std::panic;
use std::path::PathBuf;
use std::process::ExitCode;
#[derive(Parser, Debug)]
pub struct UploadToGithubArgs {
paths: Vec<String>,
#[arg(long)]
fail_on_violations: bool,
#[arg(long, env = "SQUAWK_GITHUB_PRIVATE_KEY")]
github_private_key: Option<String>,
#[arg(long, env = "SQUAWK_GITHUB_PRIVATE_KEY_BASE64")]
github_private_key_base64: Option<String>,
#[arg(long, env = "SQUAWK_GITHUB_API_URL")]
github_api_url: Option<String>,
#[arg(long, env = "SQUAWK_GITHUB_TOKEN")]
github_token: Option<String>,
#[arg(long, env = "SQUAWK_GITHUB_APP_ID")]
github_app_id: Option<i64>,
#[arg(long, env = "SQUAWK_GITHUB_INSTALL_ID")]
github_install_id: Option<i64>,
#[arg(long, env = "SQUAWK_GITHUB_REPO_OWNER")]
github_repo_owner: String,
#[arg(long, env = "SQUAWK_GITHUB_REPO_NAME")]
github_repo_name: String,
#[arg(long, env = "SQUAWK_GITHUB_PR_NUMBER")]
github_pr_number: i64,
}
#[derive(Subcommand, Debug)]
pub enum Command {
Server,
UploadToGithub(Box<UploadToGithubArgs>),
}
#[derive(Debug, ValueEnum, Clone)]
pub enum DebugOption {
Lex,
Parse,
Ast,
}
#[derive(Debug, ValueEnum, Clone, Default)]
pub enum Reporter {
#[default]
Tty,
Gcc,
Json,
Gitlab,
}
#[allow(clippy::struct_excessive_bools)]
#[derive(Parser, Debug)]
#[command(version)]
struct Opts {
#[arg(value_name = "path")]
path_patterns: Vec<String>,
#[arg(long = "exclude-path", global = true)]
excluded_path: Option<Vec<String>>,
#[arg(
short = 'e',
long = "exclude",
value_name = "rule",
value_delimiter = ',',
global = true
)]
excluded_rules: Option<Vec<Rule>>,
#[arg(long, global = true)]
pg_version: Option<Version>,
#[arg(long, value_name = "format", ignore_case = true)]
debug: Option<DebugOption>,
#[arg(long, ignore_case = true)]
reporter: Option<Reporter>,
#[arg(long, value_name = "filepath")]
stdin_filepath: Option<String>,
#[command(subcommand)]
cmd: Option<Command>,
#[arg(long, global = true)]
verbose: bool,
#[arg(short = 'c', long = "config", global = true)]
config_path: Option<PathBuf>,
#[arg(long, global = true)]
assume_in_transaction: bool,
#[arg(
long,
hide = true,
conflicts_with = "assume_in_transaction",
global = true
)]
no_assume_in_transaction: bool,
#[arg(long = "no-error-on-unmatched-pattern", global = true)]
no_error_on_unmatched_pattern: bool,
}
const STACK_SIZE: usize = 1024 * 1024 * 8;
fn with_extra_thread(
thread_name: impl Into<String>,
thread_intent: ThreadIntent,
f: impl FnOnce() -> Result<()> + Send + 'static,
) -> Result<()> {
let handle = squawk_thread::Builder::new(thread_intent, thread_name)
.stack_size(STACK_SIZE)
.spawn(f)?;
handle.join()
}
fn main() -> Result<ExitCode> {
let version = env!("CARGO_PKG_VERSION");
panic::set_hook(Box::new(move |panic_info| {
use std::io::Write;
let backtrace = std::backtrace::Backtrace::force_capture();
let mut stderr = std::io::stderr().lock();
let open_an_issue = format!(
r#"An internal error has occured with Squawk v{version}!
Please open an issue at https://github.com/sbdchd/squawk/issues/new with the logs above!
"#
);
writeln!(stderr, "{panic_info}\n{backtrace}\n{open_an_issue}").ok();
}));
let opts = Opts::parse();
if opts.verbose {
let color_choice = if matches!(opts.cmd, Some(Command::Server)) {
simplelog::ColorChoice::Never
} else {
simplelog::ColorChoice::Auto
};
CombinedLogger::init(vec![simplelog::TermLogger::new(
simplelog::LevelFilter::Info,
simplelog::Config::default(),
simplelog::TerminalMode::Stderr,
color_choice,
)])
.expect("problem creating logger");
}
match Cmd::from(opts) {
Cmd::Server => {
with_extra_thread(
"LspServer",
ThreadIntent::LatencySensitive,
squawk_server::run,
)
.context("language server failed")?;
}
Cmd::UploadToGithub(config) => {
github::check_and_comment_on_pr(*config).context("Upload to GitHub failed")?;
}
Cmd::Debug(debug_args) => {
let stdout = io::stdout();
let mut handle = stdout.lock();
debug(&mut handle, debug_args)?;
}
Cmd::Lint(lint_args) => {
let stdout = io::stdout();
let mut handle = stdout.lock();
return lint_and_report(&mut handle, lint_args);
}
Cmd::Help => {
Opts::command().print_long_help()?;
println!();
}
Cmd::None => (),
}
Ok(ExitCode::SUCCESS)
}