diff options
Diffstat (limited to 'mingling_cli/src/linter/cmd_mlint.rs')
| -rw-r--r-- | mingling_cli/src/linter/cmd_mlint.rs | 132 |
1 files changed, 132 insertions, 0 deletions
diff --git a/mingling_cli/src/linter/cmd_mlint.rs b/mingling_cli/src/linter/cmd_mlint.rs new file mode 100644 index 0000000..1dfd02b --- /dev/null +++ b/mingling_cli/src/linter/cmd_mlint.rs @@ -0,0 +1,132 @@ +use crate::linter::mlint_report::{MlintReport, StateLintReports}; +use cargo_metadata::Metadata; +use mingling::LazyRes; +use mingling::consts::REMAINS; +use mingling::macros::{arg, chain, dispatcher, pack}; +use mingling::picker::EntryPicker; +use tokio::task::JoinSet; + +dispatcher!("lint", CMDLint => EntryLint); + +/// Main linting function that processes all packages in the metadata. +/// +/// Iterates through all packages and their targets (e.g., binaries, libraries, tests), +/// reads Rust source files (`.rs`), parses them into ASTs, runs lint checks, +/// and enriches each report with metadata information. +async fn linter_main(metadata: &Metadata) -> Vec<MlintReport> { + let mut join_set = JoinSet::new(); + + for package in &metadata.packages { + for target in &package.targets { + let path = &target.src_path; + // Only process Rust source files (with `.rs` extension) + if !path.as_str().ends_with(".rs") { + continue; + } + + // Clone/move all data needed inside the blocking closure + let path_str = path.as_str().to_string(); + let package_id = package.id.to_string(); + let target_name = target.name.clone(); + let target_kind = target.kind.first().map(|k| k.to_string()); + + join_set.spawn_blocking(move || { + // Read the source file content + let source = std::fs::read_to_string(&path_str).ok()?; + // Parse the source file into an AST + let ast = syn::parse_file(&source).ok()?; + // Run all lint checks and collect reports + let reports = crate::lints::run_all_lints(&ast, &source); + + // Enrich each report with metadata information + let enriched: Vec<MlintReport> = reports + .into_iter() + .map(|mut r| { + r.file_name = path_str.clone(); + r.source_code = source.clone(); + r.package_id = Some(package_id.clone()); + r.target_name = Some(target_name.clone()); + r.target_kind = target_kind.clone(); + r.target_src_path = Some(path_str.clone()); + r + }) + .collect(); + + Some(enriched) + }); + } + } + + let mut all_reports = Vec::new(); + while let Some(res) = join_set.join_next().await { + // `spawn_blocking` panics are propagated, `None` means task skipped (read/parse failure) + if let Ok(Some(reports)) = res { + all_reports.extend(reports); + } + } + + all_reports +} + +pack!(StateBeginLinter = ()); + +#[chain] +pub fn handle_lint(args: EntryLint) -> StateBeginLinter { + let (with_checker, checker_args) = args + .pick_or(&arg![with_checker: Option<String>], || { + Some("cargo,check".to_string()) + }) + .pick(&REMAINS) + .unwrap(); + + // If with_checker is not set, proceed directly to the mingling lint phase + let Some(with_checker) = with_checker else { + return StateBeginLinter::new(()); + }; + + let with_checker: Vec<&str> = with_checker.split(',').collect(); + let checker_args: Vec<String> = checker_args.into(); + + // Run the outer checker (e.g. cargo check) with output passed through directly + execute_checker(&with_checker, checker_args.as_slice()); + + StateBeginLinter::new(()) +} + +/// Run the outer checker (e.g. cargo check) with output passed through directly. +fn execute_checker(with_checker: &[&str], checker_args: &[String]) { + if with_checker.is_empty() { + return; + } + + let checker_str = with_checker.join(" "); + let args_str = checker_args.join(" "); + let full_cmd = if args_str.is_empty() { + checker_str + } else { + format!("{} {}", checker_str, args_str) + }; + + let mut cmd = if cfg!(target_os = "windows") { + let mut c = std::process::Command::new("cmd"); + c.args(["/C", &full_cmd]); + c + } else { + let mut c = std::process::Command::new("sh"); + c.args(["-c", &full_cmd]); + c + }; + + // Pass through stdin/stdout/stderr so the user sees everything + let _ = cmd.status(); +} + +#[chain] +pub async fn handle_state_begin_linter( + _: StateBeginLinter, + metadata: &mut LazyRes<crate::metadata::setup::ResMetadata>, +) -> StateLintReports { + let metadata = metadata.get_ref().data(); + let reports = linter_main(metadata).await; + StateLintReports::new(reports) +} |
