1use std::collections::HashMap;
2
3use serde::Serialize;
4
5use super::{Summary, calculate_line_column, read_source_code};
6use crate::{config::LintLevel, violation::Violation};
7
8fn lint_level_to_severity(lint_level: LintLevel) -> u8 {
9 match lint_level {
10 LintLevel::Deny => 1,
11 LintLevel::Warn => 2,
12 LintLevel::Allow => unreachable!("Allow level violations should never be created"),
13 }
14}
15
16#[must_use]
17pub fn format_vscode_json(violations: &[Violation]) -> String {
18 let mut diagnostics_by_file: HashMap<String, Vec<VsCodeDiagnostic>> = HashMap::new();
19
20 for violation in violations {
21 let file_path = violation
22 .file
23 .as_ref()
24 .map_or_else(|| "unknown".to_string(), ToString::to_string);
25 diagnostics_by_file
26 .entry(file_path)
27 .or_default()
28 .push(violation_to_vscode_diagnostic(violation));
29 }
30
31 let summary = Summary::from_violations(violations);
32 let output = VsCodeJsonOutput {
33 diagnostics: diagnostics_by_file,
34 summary,
35 };
36
37 serde_json::to_string_pretty(&output).unwrap_or_default()
38}
39
40fn violation_to_vscode_diagnostic(violation: &Violation) -> VsCodeDiagnostic {
41 let source_code = read_source_code(violation.file.as_ref());
42
43 let (line_start, column_start) = calculate_line_column(&source_code, violation.span.start);
44 let (line_end, column_end) = calculate_line_column(&source_code, violation.span.end);
45
46 let line_start_zero = line_start.saturating_sub(1);
47 let column_start_zero = column_start.saturating_sub(1);
48 let line_end_zero = line_end.saturating_sub(1);
49 let column_end_zero = column_end.saturating_sub(1);
50
51 VsCodeDiagnostic {
52 range: VsCodeRange {
53 start: VsCodePosition {
54 line: line_start_zero,
55 character: column_start_zero,
56 },
57 end: VsCodePosition {
58 line: line_end_zero,
59 character: column_end_zero,
60 },
61 },
62 severity: lint_level_to_severity(violation.lint_level),
63 code: violation.rule_id.to_string(),
64 source: "nu-lint".to_string(),
65 message: violation.message.to_string(),
66 related_information: violation.help.as_ref().map(|suggestion| {
67 vec![VsCodeRelatedInformation {
68 location: VsCodeLocation {
69 uri: violation
70 .file
71 .as_ref()
72 .map_or_else(|| "unknown".to_string(), ToString::to_string),
73 range: VsCodeRange {
74 start: VsCodePosition {
75 line: line_start_zero,
76 character: column_start_zero,
77 },
78 end: VsCodePosition {
79 line: line_end_zero,
80 character: column_end_zero,
81 },
82 },
83 },
84 message: suggestion.to_string(),
85 }]
86 }),
87 code_action: violation.fix.as_ref().map(|fix| VsCodeCodeAction {
88 title: fix.explanation.to_string(),
89 edits: fix
90 .replacements
91 .iter()
92 .map(|r| {
93 let (r_line_start, r_col_start) =
94 calculate_line_column(&source_code, r.span.start);
95 let (r_line_end, r_col_end) = calculate_line_column(&source_code, r.span.end);
96 VsCodeTextEdit {
97 range: VsCodeRange {
98 start: VsCodePosition {
99 line: r_line_start.saturating_sub(1),
100 character: r_col_start.saturating_sub(1),
101 },
102 end: VsCodePosition {
103 line: r_line_end.saturating_sub(1),
104 character: r_col_end.saturating_sub(1),
105 },
106 },
107 replacement_text: r.replacement_text.to_string(),
108 }
109 })
110 .collect(),
111 }),
112 }
113}
114
115#[derive(Serialize)]
116pub struct VsCodeJsonOutput {
117 pub diagnostics: HashMap<String, Vec<VsCodeDiagnostic>>,
118 pub summary: Summary,
119}
120
121#[derive(Serialize)]
122pub struct VsCodeDiagnostic {
123 pub range: VsCodeRange,
124 pub severity: u8,
125 pub code: String,
126 pub source: String,
127 pub message: String,
128 #[serde(skip_serializing_if = "Option::is_none")]
129 pub related_information: Option<Vec<VsCodeRelatedInformation>>,
130 #[serde(skip_serializing_if = "Option::is_none")]
131 pub code_action: Option<VsCodeCodeAction>,
132}
133
134#[derive(Serialize)]
135pub struct VsCodeRange {
136 pub start: VsCodePosition,
137 pub end: VsCodePosition,
138}
139
140#[derive(Serialize)]
141pub struct VsCodePosition {
142 pub line: usize,
143 pub character: usize,
144}
145
146#[derive(Serialize)]
147pub struct VsCodeRelatedInformation {
148 pub location: VsCodeLocation,
149 pub message: String,
150}
151
152#[derive(Serialize)]
153pub struct VsCodeLocation {
154 pub uri: String,
155 pub range: VsCodeRange,
156}
157
158#[derive(Serialize)]
159pub struct VsCodeCodeAction {
160 pub title: String,
161 pub edits: Vec<VsCodeTextEdit>,
162}
163
164#[derive(Serialize)]
165pub struct VsCodeTextEdit {
166 pub range: VsCodeRange,
167 pub replacement_text: String,
168}