1use std::cell::RefCell;
4use std::cmp::Ordering;
5use std::fs::{read, File};
6use std::io::{stdout, Write};
7use std::mem::take;
8use std::ops::{Bound, RangeBounds};
9use std::path::{Path, PathBuf};
10use std::sync::{LazyLock, Mutex, MutexGuard};
11
12use anyhow::Result;
13use encoding_rs::{UTF_8, WINDOWS_1252};
14
15use crate::helpers::{TigerHashMap, TigerHashSet};
16use crate::macros::MACRO_MAP;
17use crate::parse::ignore::IgnoreFilter;
18use crate::report::error_loc::ErrorLoc;
19use crate::report::filter::ReportFilter;
20use crate::report::suppress::{Suppression, SuppressionKey};
21use crate::report::writer::log_report;
22use crate::report::writer_json::log_report_json;
23use crate::report::{ErrorKey, FilterRule, LogReport, OutputStyle, PointedMessage};
24use crate::token::{leak, Loc};
25
26static ERRORS: LazyLock<Mutex<Errors>> = LazyLock::new(|| Mutex::new(Errors::default()));
27
28#[allow(missing_debug_implementations)]
29pub struct Errors {
30 pub(crate) output: RefCell<Box<dyn Write + Send>>,
31
32 pub(crate) loaded_mods_labels: Vec<String>,
34
35 pub(crate) loaded_dlcs_labels: Vec<String>,
37
38 pub(crate) cache: Cache,
39
40 pub(crate) filter: ReportFilter,
42 pub(crate) styles: OutputStyle,
44
45 pub(crate) suppress: TigerHashMap<SuppressionKey, Vec<Suppression>>,
46 ignore: TigerHashMap<PathBuf, Vec<IgnoreEntry>>,
49
50 storage: TigerHashSet<LogReport>,
54}
55
56impl Default for Errors {
57 fn default() -> Self {
58 Errors {
59 output: RefCell::new(Box::new(stdout())),
60 loaded_mods_labels: Vec::default(),
61 loaded_dlcs_labels: Vec::default(),
62 cache: Cache::default(),
63 filter: ReportFilter::default(),
64 styles: OutputStyle::default(),
65 storage: TigerHashSet::default(),
66 suppress: TigerHashMap::default(),
67 ignore: TigerHashMap::default(),
68 }
69 }
70}
71
72impl Errors {
73 fn should_suppress(&mut self, report: &LogReport) -> bool {
74 let key = SuppressionKey { key: report.key, message: report.msg.clone() };
76 if let Some(v) = self.suppress.get(&key) {
77 for suppression in v {
78 if suppression.len() != report.pointers.len() {
79 continue;
80 }
81 for (s, p) in suppression.iter().zip(report.pointers.iter()) {
82 if s.path == p.loc.pathname().to_string_lossy()
83 && s.tag == p.msg
84 && s.line.as_deref() == self.cache.get_line(p.loc)
85 {
86 return true;
87 }
88 }
89 }
90 }
91 false
92 }
93
94 fn should_ignore(&self, report: &LogReport) -> bool {
95 for p in &report.pointers {
96 if let Some(vec) = self.ignore.get(p.loc.pathname()) {
97 for entry in vec {
98 if (entry.start, entry.end).contains(&p.loc.line)
99 && entry.filter.matches(report.key, &report.msg)
100 {
101 return true;
102 }
103 }
104 }
105 }
106 false
107 }
108
109 fn push_report(&mut self, report: LogReport) {
112 if !self.filter.should_print_report(&report) || self.should_suppress(&report) {
113 return;
114 }
115 self.storage.insert(report);
116 }
117
118 pub fn push_abbreviated<E: ErrorLoc>(&mut self, eloc: E, key: ErrorKey) {
124 let loc = eloc.into_loc();
125 if self.filter.should_maybe_print(key, loc) {
126 if loc.line == 0 {
127 _ = writeln!(self.output.get_mut(), "({key}) {}", loc.pathname().to_string_lossy());
128 } else if let Some(line) = self.cache.get_line(loc) {
129 _ = writeln!(self.output.get_mut(), "({key}) {line}");
130 }
131 }
132 }
133
134 pub fn push_header(&mut self, _key: ErrorKey, msg: &str) {
138 _ = writeln!(self.output.get_mut(), "{msg}");
139 }
140
141 pub fn take_reports(&mut self) -> Vec<LogReport> {
144 let mut reports: Vec<LogReport> = take(&mut self.storage).into_iter().collect();
145 reports.sort_unstable_by(|a, b| {
146 let mut cmp = b.severity.cmp(&a.severity);
148 if cmp != Ordering::Equal {
149 return cmp;
150 }
151 cmp = b.confidence.cmp(&a.confidence);
153 if cmp != Ordering::Equal {
154 return cmp;
155 }
156 for (a, b) in a.pointers.iter().zip(b.pointers.iter()) {
158 cmp = a.loc.cmp(&b.loc);
159 if cmp != Ordering::Equal {
160 return cmp;
161 }
162 }
163 cmp = b.pointers.len().cmp(&a.pointers.len());
165 if cmp != Ordering::Equal {
166 return cmp;
167 }
168 if cmp == Ordering::Equal {
170 cmp = a.msg.cmp(&b.msg);
171 }
172 cmp
173 });
174 reports
175 }
176
177 pub fn emit_reports(&mut self, json: bool) {
186 let reports = self.take_reports();
187 if json {
188 _ = writeln!(self.output.get_mut(), "[");
189 let mut first = true;
190 for report in &reports {
191 if self.should_ignore(report) {
192 continue;
193 }
194 if !first {
195 _ = writeln!(self.output.get_mut(), ",");
196 }
197 first = false;
198 log_report_json(self, report);
199 }
200 _ = writeln!(self.output.get_mut(), "\n]");
201 } else {
202 for report in &reports {
203 if self.should_ignore(report) {
204 continue;
205 }
206 log_report(self, report);
207 }
208 }
209 }
210
211 pub fn store_source_file(&mut self, fullpath: PathBuf, source: &'static str) {
212 self.cache.filecache.insert(fullpath, source);
213 }
214
215 pub fn get_mut() -> MutexGuard<'static, Errors> {
220 ERRORS.lock().unwrap()
221 }
222
223 pub fn get() -> MutexGuard<'static, Errors> {
231 ERRORS.lock().unwrap()
232 }
233}
234
235#[derive(Debug, Default)]
236pub(crate) struct Cache {
237 filecache: TigerHashMap<PathBuf, &'static str>,
240
241 linecache: TigerHashMap<PathBuf, Vec<&'static str>>,
243}
244
245impl Cache {
246 pub(crate) fn get_line(&mut self, loc: Loc) -> Option<&'static str> {
248 if loc.line == 0 {
249 return None;
250 }
251 let fullpath = loc.fullpath();
252 if let Some(lines) = self.linecache.get(fullpath) {
253 return lines.get(loc.line as usize - 1).copied();
254 }
255 if let Some(contents) = self.filecache.get(fullpath) {
256 let lines: Vec<_> = contents.lines().collect();
257 let line = lines.get(loc.line as usize - 1).copied();
258 self.linecache.insert(fullpath.to_path_buf(), lines);
259 return line;
260 }
261 let bytes = read(fullpath).ok()?;
262 let contents = match UTF_8.decode(&bytes) {
265 (contents, _, false) => contents,
266 (_, _, true) => WINDOWS_1252.decode(&bytes).0,
267 };
268 let contents = leak(contents.into_owned());
269 self.filecache.insert(fullpath.to_path_buf(), contents);
270
271 let lines: Vec<_> = contents.lines().collect();
272 let line = lines.get(loc.line as usize - 1).copied();
273 self.linecache.insert(fullpath.to_path_buf(), lines);
274 line
275 }
276}
277
278#[derive(Debug, Clone)]
279struct IgnoreEntry {
280 start: Bound<u32>,
281 end: Bound<u32>,
282 filter: IgnoreFilter,
283}
284
285pub fn add_loaded_mod_root(label: String) {
288 let mut errors = Errors::get_mut();
289 errors.loaded_mods_labels.push(label);
290}
291
292pub fn add_loaded_dlc_root(label: String) {
295 let mut errors = Errors::get_mut();
296 errors.loaded_dlcs_labels.push(label);
297}
298
299pub fn set_output_file(file: &Path) -> Result<()> {
301 let file = File::create(file)?;
302 Errors::get_mut().output = RefCell::new(Box::new(file));
303 Ok(())
304}
305
306pub fn log(mut report: LogReport) {
308 let mut vec = Vec::new();
309 report.pointers.drain(..).for_each(|pointer| {
310 let index = vec.len();
311 recursive_pointed_msg_expansion(&mut vec, &pointer);
312 vec.insert(index, pointer);
313 });
314 report.pointers.extend(vec);
315 Errors::get_mut().push_report(report);
316}
317
318fn recursive_pointed_msg_expansion(vec: &mut Vec<PointedMessage>, pointer: &PointedMessage) {
323 if let Some(link) = pointer.loc.link_idx {
324 let from_here = PointedMessage {
325 loc: MACRO_MAP.get_loc(link).unwrap(),
326 length: 0,
327 msg: Some("from here".to_owned()),
328 };
329 let index = vec.len();
330 recursive_pointed_msg_expansion(vec, &from_here);
331 vec.insert(index, from_here);
332 }
333}
334
335pub fn will_maybe_log<E: ErrorLoc>(eloc: E, key: ErrorKey) -> bool {
337 Errors::get().filter.should_maybe_print(key, eloc.into_loc())
338}
339
340pub fn emit_reports(json: bool) {
347 Errors::get_mut().emit_reports(json);
348}
349
350pub fn take_reports() -> Vec<LogReport> {
353 Errors::get_mut().take_reports()
354}
355
356pub fn store_source_file(fullpath: PathBuf, source: &'static str) {
357 Errors::get_mut().store_source_file(fullpath, source);
358}
359
360pub fn register_ignore_filter<R>(pathname: PathBuf, lines: R, filter: IgnoreFilter)
361where
362 R: RangeBounds<u32>,
363{
364 let start = lines.start_bound().cloned();
365 let end = lines.end_bound().cloned();
366 let entry = IgnoreEntry { start, end, filter };
367 Errors::get_mut().ignore.entry(pathname).or_default().push(entry);
368}
369
370pub(crate) fn warn_header(key: ErrorKey, msg: &str) {
377 Errors::get_mut().push_header(key, msg);
378}
379
380pub(crate) fn warn_abbreviated<E: ErrorLoc>(eloc: E, key: ErrorKey) {
385 Errors::get_mut().push_abbreviated(eloc, key);
386}
387
388pub fn set_output_style(style: OutputStyle) {
394 Errors::get_mut().styles = style;
395}
396
397pub fn disable_ansi_colors() {
399 Errors::get_mut().styles = OutputStyle::no_color();
400}
401
402pub fn set_show_vanilla(v: bool) {
409 Errors::get_mut().filter.show_vanilla = v;
410}
411
412pub fn set_show_loaded_mods(v: bool) {
415 Errors::get_mut().filter.show_loaded_mods = v;
416}
417
418pub(crate) fn set_predicate(predicate: FilterRule) {
420 Errors::get_mut().filter.predicate = predicate;
421}