Skip to main content

mist_api/
builder.rs

1use std::{
2    collections::HashMap,
3    env, fs,
4    path::{MAIN_SEPARATOR, PathBuf},
5    process::{Command, Stdio},
6};
7
8use cargo_metadata::{CompilerMessage, Message};
9use mist_parser::rev_mapper::{RustMap, find_mapping, get_mapping};
10
11#[derive(Debug, Clone)]
12pub struct MistDiagnosticMessage {
13    pub message: String,
14    pub file_path: PathBuf,
15    pub file_name: String,
16    pub line: usize,
17    pub column: usize,
18}
19
20#[derive(Debug, Clone)]
21pub enum MistDiagnostic {
22    Error(MistDiagnosticMessage),
23    Warning(MistDiagnosticMessage),
24    Rust(CompilerMessage),
25}
26
27pub fn build(mut args: Vec<String>, root: PathBuf) -> bool {
28    args.remove(0);
29    args.insert(1, "--message-format=json".to_string());
30
31    let is_root = root == env::current_dir().expect("Failed getting env");
32
33    let mut command = Command::new("cargo")
34        .args(args)
35        .stdout(Stdio::piped())
36        .stderr(Stdio::inherit())
37        .stdin(Stdio::inherit())
38        .spawn()
39        .expect("Failed to run cargo");
40
41    let mut reader = std::io::BufReader::new(command.stdout.take().expect("Failed to get reader"));
42
43    let mut diagnostics = Vec::new();
44
45    let mut mapping = HashMap::new();
46
47    let mist_src = format!(".mist{MAIN_SEPARATOR}src");
48
49    for message in cargo_metadata::Message::parse_stream(&mut reader) {
50        match message {
51            Ok(Message::CompilerMessage(msg)) => {
52                for span in &msg.message.spans {
53                    if span.is_primary {
54                        let rust_path = root.join(&span.file_name);
55
56                        let mist_file = span
57                            .file_name
58                            .replacen(&mist_src, "src", 1)
59                            .trim_end_matches(".rs")
60                            .to_string()
61                            + ".mist";
62
63                        let mist_path = root.join(&mist_file);
64
65                        if !fs::exists(&mist_path).expect("Unable to check if mist file exists") {
66                            diagnostics.push(MistDiagnostic::Rust(msg));
67                            break;
68                        }
69
70                        let map = mapping.entry(rust_path.clone()).or_insert_with(|| {
71                            get_mapping(
72                                &fs::read_to_string(rust_path)
73                                    .expect("Failed to read file for mapping"),
74                            )
75                        });
76
77                        let mist_span =
78                            find_mapping(&map, &RustMap(span.line_end, span.column_start))
79                                .expect("Unable to find mapping");
80
81                        let mist_msg = MistDiagnosticMessage {
82                            message: format!(
83                                "{}: {}",
84                                msg.message.message,
85                                span.label.clone().unwrap_or_default()
86                            ),
87                            file_name: if is_root {
88                                mist_file
89                            } else {
90                                mist_path.to_string_lossy().to_string()
91                            },
92                            file_path: mist_path,
93                            line: mist_span.1.0,
94                            column: mist_span.1.1,
95                        };
96
97                        match msg.message.level {
98                            cargo_metadata::diagnostic::DiagnosticLevel::Error => {
99                                diagnostics.push(MistDiagnostic::Error(mist_msg))
100                            }
101
102                            cargo_metadata::diagnostic::DiagnosticLevel::Warning => {
103                                diagnostics.push(MistDiagnostic::Warning(mist_msg))
104                            }
105
106                            _ => {}
107                        }
108                    }
109                }
110            }
111
112            Ok(Message::BuildFinished(finish)) => {
113                print_diagnostics(&diagnostics);
114
115                let mut raw_reader = reader.into_inner();
116                let mut stdout = std::io::stdout();
117
118                std::io::copy(&mut raw_reader, &mut stdout)
119                    .map_err(|v| v.to_string())
120                    .expect("(Mist) Failed to copy IO");
121
122                command.wait().unwrap();
123
124                return finish.success;
125            }
126
127            Ok(Message::TextLine(text)) => println!("{text}"),
128            _ => {}
129        }
130    }
131
132    false
133}
134
135pub fn print_diagnostics(diagnostics: &Vec<MistDiagnostic>) {
136    let mut files = HashMap::new();
137
138    for diag in diagnostics {
139        match diag {
140            MistDiagnostic::Error(msg) => {
141                let line = get_line(&mut files, &msg);
142
143                println!(
144                    "\n{}:{}:{}\n \x1b[31mError\x1b[0m: {}\n\t{}",
145                    msg.file_name,
146                    msg.line,
147                    msg.column,
148                    msg.message,
149                    line.unwrap_or_default(),
150                )
151            }
152
153            MistDiagnostic::Warning(msg) => {
154                let line = get_line(&mut files, &msg);
155
156                println!(
157                    "\n{}:{}:{}\n \x1b[33mWarning\x1b[0m: {}\n\t{}",
158                    msg.file_name,
159                    msg.line,
160                    msg.column,
161                    msg.message,
162                    line.unwrap_or_default(),
163                )
164            }
165
166            MistDiagnostic::Rust(rs) => println!("{rs}"),
167        }
168    }
169}
170
171pub fn get_line(
172    files: &mut HashMap<PathBuf, Vec<String>>,
173    msg: &MistDiagnosticMessage,
174) -> Option<String> {
175    let src_path = msg.file_path.clone();
176
177    let lines = files.entry(src_path.clone()).or_insert_with(|| {
178        fs::read_to_string(src_path)
179            .expect("Unable to read mist file")
180            .lines()
181            .into_iter()
182            .map(String::from)
183            .collect()
184    });
185
186    lines.get(msg.line - 1).map(|v| v.trim().to_string())
187}