use std::path::PathBuf; use std::process::ExitCode; use crate::cli::ParseError; use crate::fragments::{Arg, Entry, Parser, reject}; pub struct Args { pub typescript: PathBuf, pub entry: Entry, } /// `avril check` argument parsing failed. #[derive(Debug, thiserror::Error)] pub enum CheckParseError { #[error(transparent)] Generic(#[from] ParseError), #[error("check requires --typescript=PATH to the native backend")] MissingBackend, } pub fn parse(args: &[String]) -> Result { let cwd = crate::fragments::working_dir()?; let mut parser = Parser::new("check", args.to_vec(), &cwd)?; let (mut typescript, mut raw) = (None, None); while let Some(arg) = parser.next()? { match arg { Arg::Positional(arg) if raw.is_none() => raw = Some(arg), Arg::Positional(_) => return Err(parser.usage().into()), Arg::Flag(arg) if parser.path_once(&arg, "--typescript", &mut typescript)? => {} Arg::Flag(arg) => return Err(reject(&arg, &[]).into()), } } let typescript = typescript.ok_or(CheckParseError::MissingBackend)?; let raw = raw.ok_or_else(|| parser.usage())?; let entry = parser.entry(&raw)?; Ok(Args { typescript: PathBuf::from(typescript), entry, }) } fn display_uri(uri: &str) -> String { if let Some(path) = url::Url::parse(uri) .ok() .and_then(|url| url.to_file_path().ok()) { return path.display().to_string(); } for (editor, original) in [ ("avril-https:", "https:"), ("avril-http:", "http:"), ("avril-builtin:", "avril:"), ] { if let Some(rest) = uri.strip_prefix(editor) { return format!("{original}{rest}"); } } uri.to_owned() } pub fn run(args: Args) -> Result> { let Args { typescript, entry } = args; let (_authority, root, packages) = entry.project(false)?; let runtime = tokio::runtime::Builder::new_multi_thread() .worker_threads(2) .enable_all() .build()?; let caps = entry.caps; let explicit_lock = entry.explicit_lock; let module = entry.module; let diagnostics = runtime.block_on(async move { let input = crate::lsp::check_input( root, caps, packages, explicit_lock, entry.automatic_types, &module, ) .await?; avril_lsp::check(&typescript, input).await }); let diagnostics = match diagnostics { Ok(diagnostics) => diagnostics, Err(error) => { runtime.shutdown_background(); return Err(error); } }; runtime.shutdown_background(); let mut failed = false; for diagnostic in &diagnostics { let severity = if diagnostic.severity == 1 { failed = true; "error" } else { "warning" }; let code = diagnostic .code .map(|code| format!(" TS{code}")) .unwrap_or_default(); println!( "{}:{}:{}: {severity}{code}: {}", display_uri(&diagnostic.uri), diagnostic.line + 1, diagnostic.character + 1, diagnostic.message ); } Ok(if failed { ExitCode::FAILURE } else { ExitCode::SUCCESS }) }