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}