perspt_coding/diag/
cargo.rs1use perspt_sdk::ResidualClass;
8
9use super::types::StructuredDiagnostic;
10use crate::lang::classify_rust_code;
11
12pub const PARSER_ID: &str = "cargo-json-v1";
14
15pub fn looks_like_stream(raw: &str) -> bool {
17 raw.lines()
18 .any(|line| line.starts_with('{') && line.contains("\"reason\""))
19}
20
21pub fn parse(raw: &str) -> Vec<StructuredDiagnostic> {
23 let mut diagnostics = Vec::new();
24 for line in raw.lines() {
25 if !line.starts_with('{') {
26 continue;
27 }
28 let Ok(value) = serde_json::from_str::<serde_json::Value>(line) else {
29 continue;
30 };
31 if value.get("reason").and_then(|r| r.as_str()) != Some("compiler-message") {
32 continue;
33 }
34 let Some(message) = value.get("message") else {
35 continue;
36 };
37 if message.get("level").and_then(|l| l.as_str()) != Some("error") {
38 continue;
39 }
40 diagnostics.push(from_message(message));
41 }
42 diagnostics
43}
44
45fn from_message(message: &serde_json::Value) -> StructuredDiagnostic {
46 let code = message
47 .get("code")
48 .and_then(|code| code.get("code"))
49 .and_then(|code| code.as_str())
50 .map(str::to_string);
51 let class = code
52 .as_deref()
53 .map(classify_rust_code)
54 .unwrap_or(ResidualClass::Build);
55 let span = message
56 .get("spans")
57 .and_then(|spans| spans.as_array())
58 .and_then(|spans| {
59 spans
60 .iter()
61 .find(|span| span.get("is_primary").and_then(|p| p.as_bool()) == Some(true))
62 .or_else(|| spans.first())
63 });
64 let suggestion = span
65 .and_then(|span| span.get("suggested_replacement"))
66 .and_then(|value| value.as_str())
67 .map(str::to_string)
68 .or_else(|| {
69 message
70 .get("children")
71 .and_then(|children| children.as_array())
72 .and_then(|children| {
73 children.iter().find_map(|child| {
74 child
75 .get("spans")?
76 .as_array()?
77 .iter()
78 .find_map(|span| span.get("suggested_replacement")?.as_str())
79 .map(str::to_string)
80 })
81 })
82 });
83 StructuredDiagnostic {
84 class,
85 code,
86 message: message
87 .get("message")
88 .and_then(|m| m.as_str())
89 .unwrap_or_default()
90 .to_string(),
91 path: span
92 .and_then(|span| span.get("file_name"))
93 .and_then(|f| f.as_str())
94 .map(str::to_string),
95 line: span
96 .and_then(|span| span.get("line_start"))
97 .and_then(|l| l.as_u64())
98 .and_then(|l| u32::try_from(l).ok()),
99 column: span
100 .and_then(|span| span.get("column_start"))
101 .and_then(|c| c.as_u64())
102 .and_then(|c| u32::try_from(c).ok()),
103 primary: true,
104 suggestion,
105 }
106}