swc_error_reporters/
json_emitter.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
use std::fmt::Write;

use serde_derive::Serialize;
use swc_common::{
    errors::{DiagnosticBuilder, DiagnosticId, Emitter},
    sync::Lrc,
    SourceMap, SourceMapper,
};

use crate::WriterWrapper;

pub struct JsonEmitter {
    cm: Lrc<SourceMap>,

    wr: WriterWrapper,

    config: JsonEmitterConfig,

    diagnostics: Vec<String>,
}

impl JsonEmitter {
    pub fn new(
        cm: Lrc<SourceMap>,
        wr: Box<dyn Write + Send + Sync>,
        config: JsonEmitterConfig,
    ) -> Self {
        Self {
            cm,
            wr: WriterWrapper(wr),
            config,
            diagnostics: vec![],
        }
    }
}

#[derive(Debug, Clone, Default)]
pub struct JsonEmitterConfig {
    pub skip_filename: bool,
}

impl Emitter for JsonEmitter {
    fn emit(&mut self, db: &DiagnosticBuilder) {
        let d = &**db;

        let children = d
            .children
            .iter()
            .map(|d| todo!("json subdiagnostic: {d:?}"))
            .collect::<Vec<_>>();

        let error_code = match &d.code {
            Some(DiagnosticId::Error(s)) => Some(&**s),
            Some(DiagnosticId::Lint(s)) => Some(&**s),
            None => None,
        };

        let loc = d
            .span
            .primary_span()
            .and_then(|span| self.cm.try_lookup_char_pos(span.lo()).ok());

        let snippet = d
            .span
            .primary_span()
            .and_then(|span| self.cm.span_to_snippet(span).ok());

        let filename = if self.config.skip_filename {
            None
        } else {
            loc.as_ref().map(|loc| loc.file.name.to_string())
        };

        let error = JsonDiagnostic {
            code: error_code,
            message: &d.message[0].0,
            snippet: snippet.as_deref(),
            filename: filename.as_deref(),
            line: loc.as_ref().map(|loc| loc.line),
            column: loc.as_ref().map(|loc| loc.col_display),
            children,
        };

        let result = serde_json::to_string(&error).unwrap();

        self.wr.write_str(&result).unwrap();
        writeln!(self.wr).unwrap();

        self.diagnostics.push(result);
    }

    fn take_diagnostics(&mut self) -> Vec<String> {
        std::mem::take(&mut self.diagnostics)
    }
}

#[derive(Serialize)]
struct JsonDiagnostic<'a> {
    /// Error code
    #[serde(skip_serializing_if = "Option::is_none")]
    code: Option<&'a str>,
    message: &'a str,

    #[serde(skip_serializing_if = "Option::is_none")]
    snippet: Option<&'a str>,

    #[serde(skip_serializing_if = "Option::is_none")]
    filename: Option<&'a str>,

    #[serde(skip_serializing_if = "Option::is_none")]
    line: Option<usize>,
    #[serde(skip_serializing_if = "Option::is_none")]
    column: Option<usize>,

    #[serde(skip_serializing_if = "Vec::is_empty")]
    children: Vec<JsonSubdiagnostic<'a>>,
}

#[derive(Serialize)]
struct JsonSubdiagnostic<'a> {
    message: &'a str,
    snippet: Option<&'a str>,
    filename: &'a str,
    line: usize,
}