1use std::cell::RefCell;
4use std::cmp::Ordering;
5use std::fs::{read, File};
6use std::io::{stdout, Write};
7use std::mem::take;
8use std::path::{Path, PathBuf};
9use std::sync::{Mutex, MutexGuard};
10
11use anyhow::Result;
12use encoding_rs::{UTF_8, WINDOWS_1252};
13use once_cell::sync::Lazy;
14
15use crate::helpers::{TigerHashMap, TigerHashSet};
16use crate::macros::MACRO_MAP;
17use crate::report::error_loc::ErrorLoc;
18use crate::report::filter::ReportFilter;
19use crate::report::suppress::{Suppression, SuppressionKey};
20use crate::report::writer::log_report;
21use crate::report::writer_json::log_report_json;
22use crate::report::{ErrorKey, FilterRule, LogReport, OutputStyle, PointedMessage};
23use crate::token::{leak, Loc};
24
25static ERRORS: Lazy<Mutex<Errors>> = Lazy::new(|| Mutex::new(Errors::default()));
26
27#[allow(missing_debug_implementations)]
28pub struct Errors {
29 pub(crate) output: RefCell<Box<dyn Write + Send>>,
30
31 pub(crate) loaded_mods_labels: Vec<String>,
33
34 pub(crate) loaded_dlcs_labels: Vec<String>,
36
37 pub(crate) cache: Cache,
38
39 pub(crate) filter: ReportFilter,
41 pub(crate) styles: OutputStyle,
43
44 pub(crate) suppress: TigerHashMap<SuppressionKey, Vec<Suppression>>,
45
46 storage: TigerHashSet<LogReport>,
50}
51
52impl Default for Errors {
53 fn default() -> Self {
54 Errors {
55 output: RefCell::new(Box::new(stdout())),
56 loaded_mods_labels: Vec::default(),
57 loaded_dlcs_labels: Vec::default(),
58 cache: Cache::default(),
59 filter: ReportFilter::default(),
60 styles: OutputStyle::default(),
61 storage: TigerHashSet::default(),
62 suppress: TigerHashMap::default(),
63 }
64 }
65}
66
67impl Errors {
68 fn should_suppress(&mut self, report: &LogReport) -> bool {
69 let key = SuppressionKey { key: report.key, message: report.msg.clone() };
71 if let Some(v) = self.suppress.get(&key) {
72 for suppression in v {
73 if suppression.len() != report.pointers.len() {
74 continue;
75 }
76 for (s, p) in suppression.iter().zip(report.pointers.iter()) {
77 if s.path == p.loc.pathname().to_string_lossy()
78 && s.tag == p.msg
79 && s.line.as_deref() == self.cache.get_line(p.loc)
80 {
81 return true;
82 }
83 }
84 }
85 }
86 false
87 }
88
89 fn push_report(&mut self, report: LogReport) {
92 if !self.filter.should_print_report(&report) || self.should_suppress(&report) {
93 return;
94 }
95 self.storage.insert(report);
96 }
97
98 pub fn push_abbreviated<E: ErrorLoc>(&mut self, eloc: E, key: ErrorKey) {
104 let loc = eloc.into_loc();
105 if self.filter.should_maybe_print(key, loc) {
106 if loc.line == 0 {
107 _ = writeln!(self.output.get_mut(), "({key}) {}", loc.pathname().to_string_lossy());
108 } else if let Some(line) = self.cache.get_line(loc) {
109 _ = writeln!(self.output.get_mut(), "({key}) {line}");
110 }
111 }
112 }
113
114 pub fn push_header(&mut self, _key: ErrorKey, msg: &str) {
118 _ = writeln!(self.output.get_mut(), "{msg}");
119 }
120
121 pub fn take_reports(&mut self) -> Vec<LogReport> {
124 let mut reports: Vec<LogReport> = take(&mut self.storage).into_iter().collect();
125 reports.sort_unstable_by(|a, b| {
126 let mut cmp = b.severity.cmp(&a.severity);
128 if cmp != Ordering::Equal {
129 return cmp;
130 }
131 cmp = b.confidence.cmp(&a.confidence);
133 if cmp != Ordering::Equal {
134 return cmp;
135 }
136 for (a, b) in a.pointers.iter().zip(b.pointers.iter()) {
138 cmp = a.loc.cmp(&b.loc);
139 if cmp != Ordering::Equal {
140 return cmp;
141 }
142 }
143 cmp = b.pointers.len().cmp(&a.pointers.len());
145 if cmp != Ordering::Equal {
146 return cmp;
147 }
148 if cmp == Ordering::Equal {
150 cmp = a.msg.cmp(&b.msg);
151 }
152 cmp
153 });
154 reports
155 }
156
157 pub fn emit_reports(&mut self, json: bool) {
164 let reports = self.take_reports();
165 if json {
166 _ = writeln!(self.output.get_mut(), "[");
167 let mut first = true;
168 for report in &reports {
169 if !first {
170 _ = writeln!(self.output.get_mut(), ",");
171 }
172 first = false;
173 log_report_json(self, report);
174 }
175 _ = writeln!(self.output.get_mut(), "\n]");
176 } else {
177 for report in &reports {
178 log_report(self, report);
179 }
180 }
181 }
182
183 pub fn store_source_file(&mut self, fullpath: PathBuf, source: &'static str) {
184 self.cache.filecache.insert(fullpath, source);
185 }
186
187 pub fn get_mut() -> MutexGuard<'static, Errors> {
192 ERRORS.lock().unwrap()
193 }
194
195 pub fn get() -> MutexGuard<'static, Errors> {
203 ERRORS.lock().unwrap()
204 }
205}
206
207#[derive(Debug, Default)]
208pub(crate) struct Cache {
209 filecache: TigerHashMap<PathBuf, &'static str>,
212
213 linecache: TigerHashMap<PathBuf, Vec<&'static str>>,
215}
216
217impl Cache {
218 pub(crate) fn get_line(&mut self, loc: Loc) -> Option<&'static str> {
220 if loc.line == 0 {
221 return None;
222 }
223 let fullpath = loc.fullpath();
224 if let Some(lines) = self.linecache.get(fullpath) {
225 return lines.get(loc.line as usize - 1).copied();
226 }
227 if let Some(contents) = self.filecache.get(fullpath) {
228 let lines: Vec<_> = contents.lines().collect();
229 let line = lines.get(loc.line as usize - 1).copied();
230 self.linecache.insert(fullpath.to_path_buf(), lines);
231 return line;
232 }
233 let bytes = read(fullpath).ok()?;
234 let contents = match UTF_8.decode(&bytes) {
237 (contents, _, false) => contents,
238 (_, _, true) => WINDOWS_1252.decode(&bytes).0,
239 };
240 let contents = leak(contents.into_owned());
241 self.filecache.insert(fullpath.to_path_buf(), contents);
242
243 let lines: Vec<_> = contents.lines().collect();
244 let line = lines.get(loc.line as usize - 1).copied();
245 self.linecache.insert(fullpath.to_path_buf(), lines);
246 line
247 }
248}
249
250pub fn add_loaded_mod_root(label: String) {
253 let mut errors = Errors::get_mut();
254 errors.loaded_mods_labels.push(label);
255}
256
257pub fn add_loaded_dlc_root(label: String) {
260 let mut errors = Errors::get_mut();
261 errors.loaded_dlcs_labels.push(label);
262}
263
264pub fn set_output_file(file: &Path) -> Result<()> {
266 let file = File::create(file)?;
267 Errors::get_mut().output = RefCell::new(Box::new(file));
268 Ok(())
269}
270
271pub fn log(mut report: LogReport) {
273 let mut vec = Vec::new();
274 report.pointers.drain(..).for_each(|pointer| {
275 let index = vec.len();
276 recursive_pointed_msg_expansion(&mut vec, &pointer);
277 vec.insert(index, pointer);
278 });
279 report.pointers.extend(vec);
280 Errors::get_mut().push_report(report);
281}
282
283fn recursive_pointed_msg_expansion(vec: &mut Vec<PointedMessage>, pointer: &PointedMessage) {
288 if let Some(link) = pointer.loc.link_idx {
289 let from_here = PointedMessage {
290 loc: MACRO_MAP.get_loc(link).unwrap(),
291 length: 0,
292 msg: Some("from here".to_owned()),
293 };
294 let index = vec.len();
295 recursive_pointed_msg_expansion(vec, &from_here);
296 vec.insert(index, from_here);
297 }
298}
299
300pub fn will_maybe_log<E: ErrorLoc>(eloc: E, key: ErrorKey) -> bool {
302 Errors::get().filter.should_maybe_print(key, eloc.into_loc())
303}
304
305pub fn emit_reports(json: bool) {
312 Errors::get_mut().emit_reports(json);
313}
314
315pub fn take_reports() -> Vec<LogReport> {
318 Errors::get_mut().take_reports()
319}
320
321pub fn store_source_file(fullpath: PathBuf, source: &'static str) {
322 Errors::get_mut().store_source_file(fullpath, source);
323}
324
325pub(crate) fn warn_header(key: ErrorKey, msg: &str) {
332 Errors::get_mut().push_header(key, msg);
333}
334
335pub(crate) fn warn_abbreviated<E: ErrorLoc>(eloc: E, key: ErrorKey) {
340 Errors::get_mut().push_abbreviated(eloc, key);
341}
342
343pub fn set_output_style(style: OutputStyle) {
349 Errors::get_mut().styles = style;
350}
351
352pub fn disable_ansi_colors() {
354 Errors::get_mut().styles = OutputStyle::no_color();
355}
356
357pub fn set_show_vanilla(v: bool) {
364 Errors::get_mut().filter.show_vanilla = v;
365}
366
367pub fn set_show_loaded_mods(v: bool) {
370 Errors::get_mut().filter.show_loaded_mods = v;
371}
372
373pub(crate) fn set_predicate(predicate: FilterRule) {
375 Errors::get_mut().filter.predicate = predicate;
376}