diff options
| author | 魏曹先生 <1992414357@qq.com> | 2026-08-04 20:39:00 +0800 |
|---|---|---|
| committer | 魏曹先生 <1992414357@qq.com> | 2026-08-07 16:57:24 +0800 |
| commit | fb8d3d2a3984bc90ddbde8aa2f3c88158653deb3 (patch) | |
| tree | d8a72e0ec74b7a1ff15acf9574f6edcca91cea36 /mingling_cli/src/linter/cmd_lint.rs | |
| parent | a3621adc074c850fea4e457982f20ae2116a3f96 (diff) | |
refactor(linter): rename cmd_mlint to cmd_lint
Diffstat (limited to 'mingling_cli/src/linter/cmd_lint.rs')
| -rw-r--r-- | mingling_cli/src/linter/cmd_lint.rs | 138 |
1 files changed, 138 insertions, 0 deletions
diff --git a/mingling_cli/src/linter/cmd_lint.rs b/mingling_cli/src/linter/cmd_lint.rs new file mode 100644 index 0000000..57a78ad --- /dev/null +++ b/mingling_cli/src/linter/cmd_lint.rs @@ -0,0 +1,138 @@ +use crate::linter::mlint_report::{MlintReport, StateLintReports}; +use cargo_metadata::Metadata; +use mingling::LazyRes; +use mingling::consts::REMAINS; +use mingling::macros::{arg, chain, dispatcher, metadata, pack}; +use mingling::metadata::Description; +use mingling::picker::EntryPicker; +use tokio::task::JoinSet; + +dispatcher!("lint", CMDLint => EntryLint); + +#[metadata(EntryLint)] +pub fn desc_lint() -> Description { + "Mingling Linter".to_string().into() +} + +/// 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) +} |
