rslint_errors/
formatters.rs

1use crate::termcolor::{ColorChoice, StandardStream, WriteColor};
2use crate::*;
3use codespan::files::Error;
4use colored::*;
5use file::Files;
6use std::collections::HashSet;
7use std::io;
8
9/// A trait describing a struct which can render diagnostics to a writer such as stderr.
10///
11/// Each formatter may rely on behavior specific to a batch of diagnostics, therefore
12/// you should collect all diagnostics and then call the appropriate formatter
13pub trait Formatter {
14    fn emit_stdout(&mut self, diagnostics: &[Diagnostic], files: &dyn Files) -> io::Result<()> {
15        let stderr = StandardStream::stderr(ColorChoice::Always);
16        let mut out = stderr.lock();
17        self.emit_with_writer(diagnostics, files, &mut out)
18    }
19
20    fn emit_stderr(&mut self, diagnostics: &[Diagnostic], files: &dyn Files) -> io::Result<()> {
21        let stderr = StandardStream::stderr(ColorChoice::Always);
22        let mut out = stderr.lock();
23        self.emit_with_writer(diagnostics, files, &mut out)
24    }
25
26    fn emit_with_writer(
27        &mut self,
28        diagnostics: &[Diagnostic],
29        files: &dyn Files,
30        writer: &mut dyn WriteColor,
31    ) -> io::Result<()>;
32}
33
34#[derive(Debug, Copy, Clone)]
35pub struct ShortFormatter;
36
37impl Formatter for ShortFormatter {
38    fn emit_with_writer(
39        &mut self,
40        diagnostics: &[Diagnostic],
41        files: &dyn Files,
42        writer: &mut dyn WriteColor,
43    ) -> io::Result<()> {
44        let mut ids = HashSet::new();
45        diagnostics.iter().for_each(|d| {
46            ids.insert(d.file_id);
47        });
48        for id in ids {
49            let cur_diags = diagnostics
50                .iter()
51                .filter(|x| x.file_id == id && x.primary.is_some());
52            if cur_diags.clone().count() == 0 {
53                continue;
54            }
55
56            let name = files.name(id).expect("Invalid file id");
57            writeln!(writer, "{}", name.white().underline())?;
58            let mut line_starts = vec![];
59
60            for diag in cur_diags.clone() {
61                let line_index = files
62                    .line_index(id, diag.primary.as_ref().unwrap().span.range.start)
63                    .expect("Line index out of bounds");
64                let line_span = files.line_range(id, line_index).unwrap();
65                let column = diag.primary.as_ref().unwrap().span.range.start - line_span.start;
66                line_starts.push((line_index, column));
67            }
68            let max_msg_len = cur_diags
69                .clone()
70                .max_by_key(|x| x.title.trim().len())
71                .map(|x| x.title.trim().len())
72                .unwrap();
73
74            let max_severity_len = cur_diags
75                .clone()
76                .map(|x| format!("{:?}", x.severity).len())
77                .max()
78                .unwrap();
79
80            let max_loc = line_starts
81                .iter()
82                .map(|x| x.0.to_string().len() + x.1.to_string().len() + 1)
83                .max()
84                .unwrap();
85            for (diag, (line, column)) in cur_diags.zip(line_starts) {
86                write!(writer, "  ")?;
87                write!(
88                    writer,
89                    "{} ",
90                    " ".repeat(max_loc - (line.to_string().len() + column.to_string().len() + 1))
91                )?;
92                write!(
93                    writer,
94                    "{}{}{}  ",
95                    line.to_string().truecolor(140, 140, 140),
96                    ":".truecolor(140, 140, 140),
97                    column.to_string().truecolor(140, 140, 140)
98                )?;
99                let color = match diag.severity {
100                    Severity::Bug | Severity::Error => Color::BrightRed,
101                    Severity::Note => Color::BrightCyan,
102                    Severity::Warning => Color::BrightYellow,
103                    Severity::Help => Color::BrightGreen,
104                };
105                let severity_string = format!("{:?}", diag.severity).to_ascii_lowercase();
106                write!(
107                    writer,
108                    "{}{}  ",
109                    " ".repeat(max_severity_len - severity_string.len()),
110                    severity_string.color(color)
111                )?;
112                write!(
113                    writer,
114                    "{}{}  ",
115                    diag.title.trim(),
116                    " ".repeat(max_msg_len - diag.title.trim().len())
117                )?;
118                if let Some(code) = diag.code.clone() {
119                    write!(writer, "{}", code.white())?;
120                }
121                writeln!(writer)?;
122            }
123            writeln!(writer)?;
124        }
125        Ok(())
126    }
127}
128
129#[derive(Debug, Copy, Clone)]
130pub struct LongFormatter;
131
132impl Formatter for LongFormatter {
133    fn emit_with_writer(
134        &mut self,
135        diagnostics: &[Diagnostic],
136        files: &dyn Files,
137        writer: &mut dyn WriteColor,
138    ) -> io::Result<()> {
139        for diag in diagnostics {
140            match Emitter::new(files).emit_with_writer(diag, writer) {
141                Ok(_) => {}
142                Err(err) => {
143                    if let Error::Io(io_err) = err {
144                        return Err(io_err);
145                    } else {
146                        panic!("{}", err)
147                    }
148                }
149            }
150        }
151        Ok(())
152    }
153}