1use serde_json::{Value, json};
8
9use crate::Diagnostic;
10
11pub fn to_json(file_path: &str, diagnostics: &[Diagnostic]) -> String {
13 serde_json::to_string_pretty(
14 &json!({ "diagnostics": diagnostics_value(file_path, diagnostics) }),
15 )
16 .expect("a serde_json::Value is always serializable")
17}
18
19pub fn diagnostics_value(file_path: &str, diagnostics: &[Diagnostic]) -> Value {
22 Value::Array(
23 diagnostics
24 .iter()
25 .map(|d| diagnostic_value(file_path, d))
26 .collect(),
27 )
28}
29
30fn diagnostic_value(file_path: &str, d: &Diagnostic) -> Value {
31 json!({
32 "code": d.code.as_str(),
33 "severity": d.severity.as_str(),
34 "message": d.message,
35 "primary": {
36 "file": file_path,
37 "span": [d.primary.start, d.primary.end],
38 },
39 "labels": d
40 .labels
41 .iter()
42 .map(|label| json!({
43 "span": [label.span.start, label.span.end],
44 "text": label.text,
45 }))
46 .collect::<Vec<_>>(),
47 "help": d.help,
48 "fixes": d
49 .fixes
50 .iter()
51 .map(|fix| json!({
52 "title": fix.title,
53 "edits": fix
54 .edits
55 .iter()
56 .map(|edit| json!({
57 "span": [edit.span.start, edit.span.end],
58 "text": edit.text,
59 }))
60 .collect::<Vec<_>>(),
61 }))
62 .collect::<Vec<_>>(),
63 })
64}
65
66#[cfg(test)]
67mod tests {
68 use super::*;
69 use crate::{DiagCode, Edit, Fix, Label, Severity};
70
71 #[test]
72 fn the_document_has_the_documented_shape() {
73 let d = Diagnostic::error(
74 DiagCode::new("E0701"),
75 4..8,
76 "pin conflict",
77 "GPIO18 is taken",
78 )
79 .with_help("pick another pin");
80
81 let json = to_json("blink.espf", &[d]);
82 let value: Value = serde_json::from_str(&json).unwrap();
83 let diag = &value["diagnostics"][0];
84
85 assert_eq!(diag["code"], "E0701");
86 assert_eq!(diag["severity"], "error");
87 assert_eq!(diag["message"], "pin conflict");
88 assert_eq!(diag["primary"]["file"], "blink.espf");
89 assert_eq!(diag["primary"]["span"], json!([4, 8]));
90 assert_eq!(diag["labels"][0]["text"], "GPIO18 is taken");
91 assert_eq!(diag["help"], "pick another pin");
92 assert_eq!(diag["fixes"], json!([]));
93 }
94
95 #[test]
96 fn every_diagnostic_carries_a_code() {
97 let d = Diagnostic::error(DiagCode::new("E0101"), 0..1, "m", "l");
98 let json = to_json("x.espf", &[d]);
99 let value: Value = serde_json::from_str(&json).unwrap();
100 assert_eq!(value["diagnostics"][0]["code"], "E0101");
101 }
102
103 #[test]
104 fn fixes_and_context_labels_are_preserved() {
105 let d = Diagnostic {
106 code: DiagCode::new("E0701"),
107 severity: Severity::Warning,
108 message: "pin conflict".into(),
109 primary: 4..8,
110 labels: vec![
111 Label {
112 span: 4..8,
113 text: "claimed here".into(),
114 },
115 Label {
116 span: 20..24,
117 text: "first claimed here".into(),
118 },
119 ],
120 help: None,
121 fixes: vec![Fix {
122 title: "rename the second led".into(),
123 edits: vec![Edit {
124 span: 24..27,
125 text: "red-led-2".into(),
126 }],
127 }],
128 };
129
130 let json = to_json("blink.espf", &[d]);
131 let value: Value = serde_json::from_str(&json).unwrap();
132 let diag = &value["diagnostics"][0];
133
134 assert_eq!(diag["severity"], "warning");
135 assert_eq!(diag["labels"][1]["span"], json!([20, 24]));
136 assert_eq!(diag["fixes"][0]["title"], "rename the second led");
137 assert_eq!(diag["fixes"][0]["edits"][0]["span"], json!([24, 27]));
138 assert_eq!(diag["fixes"][0]["edits"][0]["text"], "red-led-2");
139 assert!(diag["help"].is_null());
140 }
141
142 #[test]
143 fn the_pretty_shape_is_pinned() {
144 let d = Diagnostic::error(DiagCode::new("E0701"), 0..1, "boom", "here");
145 assert_eq!(
146 to_json("x.espf", &[d]),
147 "{
148 \"diagnostics\": [
149 {
150 \"code\": \"E0701\",
151 \"fixes\": [],
152 \"help\": null,
153 \"labels\": [
154 {
155 \"span\": [
156 0,
157 1
158 ],
159 \"text\": \"here\"
160 }
161 ],
162 \"message\": \"boom\",
163 \"primary\": {
164 \"file\": \"x.espf\",
165 \"span\": [
166 0,
167 1
168 ]
169 },
170 \"severity\": \"error\"
171 }
172 ]
173}"
174 );
175 }
176}