aboutsummaryrefslogtreecommitdiff
path: root/mingling_cli/src/linter/cmd_mlint.rs
blob: 90871afe3c957e595db24774751ce7b736003c59 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
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;

dispatcher!("lint", CMDMinglingLinter => EntryMinglingLinter);

/// 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> {
    // Vector to accumulate all lint reports
    let mut all_reports = Vec::new();

    // Iterate over all packages in the metadata
    for package in &metadata.packages {
        // Iterate over all targets within a package (e.g., bin, lib, test, etc.)
        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;
            }
            // Read the source file content
            let source = match std::fs::read_to_string(path.as_str()) {
                Ok(s) => s,
                Err(_) => continue,
            };
            // Parse the source file into an AST (Abstract Syntax Tree)
            let ast = match syn::parse_file(&source) {
                Ok(f) => f,
                Err(_) => continue,
            };

            // Run all lint checks and collect reports
            let reports = crate::lints::run_all_lints(&ast, &source);
            //
            for mut r in reports {
                // Enrich report with metadata information
                r.file_name = path.as_str().to_string();
                r.source_code = source.clone();
                r.package_id = Some(package.id.to_string());
                r.target_name = Some(target.name.clone());
                r.target_kind = target.kind.first().map(|k| k.to_string());
                r.target_src_path = Some(path.as_str().to_string());
                all_reports.push(r);
            }
        }
    }

    all_reports
}

pack!(StateBeginLinter = ());

#[chain]
pub fn handle_lint(args: EntryMinglingLinter) -> 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)
}