use crate::cli::pipeline::helpers::{check_precondition, run_before, run_command_streamed};
use crate::core::config::{Language, ResolvedCrateConfig};
use rayon::prelude::*;
use tracing::warn;
enum LintPhase {
Format,
Check,
Typecheck,
}
fn prepare_lint_languages(config: &ResolvedCrateConfig, languages: &[Language]) -> anyhow::Result<Vec<Language>> {
let mut ready = Vec::with_capacity(languages.len());
for &lang in languages {
let lang_lint = config.lint_config_for_language(lang);
if !check_precondition(lang, lang_lint.precondition.as_deref()) {
continue;
}
run_before(lang, lang_lint.before.as_ref())?;
ready.push(lang);
}
Ok(ready)
}
fn run_phase(config: &ResolvedCrateConfig, languages: &[Language], phase: LintPhase) -> anyhow::Result<()> {
let tasks: Vec<(&Language, String)> = languages
.iter()
.filter_map(|lang| {
let lang_lint = config.lint_config_for_language(*lang);
let cmds = match phase {
LintPhase::Format => lang_lint.format,
LintPhase::Check => lang_lint.check,
LintPhase::Typecheck => lang_lint.typecheck,
};
cmds.map(|cmd_list| {
cmd_list
.commands()
.into_iter()
.map(|c| (lang, c.to_string()))
.collect::<Vec<_>>()
})
})
.flatten()
.collect();
let results: Vec<anyhow::Result<()>> = tasks
.par_iter()
.map(|(lang, cmd)| run_command_streamed(cmd, Some(&lang.to_string())))
.collect();
let mut first_error: Option<anyhow::Error> = None;
for result in results {
if let Err(e) = result {
if first_error.is_none() {
first_error = Some(e);
}
}
}
if let Some(e) = first_error {
return Err(e);
}
Ok(())
}
pub fn lint(config: &ResolvedCrateConfig, languages: &[Language]) -> anyhow::Result<()> {
let ready = prepare_lint_languages(config, languages)?;
run_phase(config, &ready, LintPhase::Format)?;
run_phase(config, &ready, LintPhase::Check)?;
run_phase(config, &ready, LintPhase::Typecheck)?;
Ok(())
}
pub fn fmt(config: &ResolvedCrateConfig, languages: &[Language]) -> anyhow::Result<()> {
let ready = prepare_lint_languages(config, languages)?;
run_phase(config, &ready, LintPhase::Format)
}
pub fn fmt_post_generate(config: &ResolvedCrateConfig, languages: &[Language]) {
for &lang in languages {
let lang_lint = config.lint_config_for_language(lang);
if !check_precondition(lang, lang_lint.precondition.as_deref()) {
continue;
}
if let Err(e) = run_before(lang, lang_lint.before.as_ref()) {
warn!("[{lang}] post-generation `before` hook failed: {e:#}");
continue;
}
let Some(cmd_list) = lang_lint.format else {
continue;
};
for cmd in cmd_list.commands() {
if let Err(e) = run_command_streamed(cmd, Some(&lang.to_string())) {
warn!("[{lang}] post-generation format command failed (continuing): {e:#}");
}
}
}
}
#[cfg(all(test, unix))]
mod fmt_post_generate_tests {
use super::*;
use crate::core::config::output::{LintConfig, StringOrVec};
fn config_with_lint(lang: Language, cfg: LintConfig) -> ResolvedCrateConfig {
let alef_cfg: crate::core::config::NewAlefConfig = toml::from_str(
r#"
[workspace]
languages = ["python"]
[[crates]]
name = "test-lib"
sources = ["src/lib.rs"]
"#,
)
.unwrap();
let mut c = alef_cfg.resolve().unwrap().remove(0);
c.lint.insert(lang.to_string(), cfg);
c
}
#[test]
fn fmt_post_generate_swallows_failing_format_command() {
let lint = LintConfig {
precondition: Some("true".to_string()),
before: None,
format: Some(StringOrVec::Single("false".to_string())),
check: None,
typecheck: None,
};
let cfg = config_with_lint(Language::Python, lint);
fmt_post_generate(&cfg, &[Language::Python]);
}
#[test]
fn fmt_post_generate_swallows_failing_before_hook() {
let lint = LintConfig {
precondition: Some("true".to_string()),
before: Some(StringOrVec::Single("false".to_string())),
format: Some(StringOrVec::Single("true".to_string())),
check: None,
typecheck: None,
};
let cfg = config_with_lint(Language::Python, lint);
fmt_post_generate(&cfg, &[Language::Python]);
}
#[test]
fn fmt_post_generate_skips_when_precondition_fails() {
let lint = LintConfig {
precondition: Some("false".to_string()),
before: None,
format: Some(StringOrVec::Single("false".to_string())),
check: None,
typecheck: None,
};
let cfg = config_with_lint(Language::Python, lint);
fmt_post_generate(&cfg, &[Language::Python]);
}
}