aboutsummaryrefslogtreecommitdiff
path: root/mingling_cli/src/linter/cmd_mlint.rs
diff options
context:
space:
mode:
Diffstat (limited to 'mingling_cli/src/linter/cmd_mlint.rs')
-rw-r--r--mingling_cli/src/linter/cmd_mlint.rs132
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)
+}