1//===-- DiagnosticsRendering.cpp ------------------------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
9#include "lldb/Utility/DiagnosticsRendering.h"
10#include <cstdint>
11
12using namespace lldb_private;
13using namespace lldb;
14
15namespace lldb_private {
16
17char DiagnosticError::ID;
18
19lldb::ErrorType DiagnosticError::GetErrorType() const {
20 return lldb::eErrorTypeExpression;
21}
22
23StructuredData::ObjectSP Serialize(llvm::ArrayRef<DiagnosticDetail> details) {
24 auto make_array = []() { return std::make_unique<StructuredData::Array>(); };
25 auto make_dict = []() {
26 return std::make_unique<StructuredData::Dictionary>();
27 };
28 auto dict_up = make_dict();
29 dict_up->AddIntegerItem(key: "version", value: 1u);
30 auto array_up = make_array();
31 for (const DiagnosticDetail &diag : details) {
32 auto detail_up = make_dict();
33 if (auto &sloc = diag.source_location) {
34 auto sloc_up = make_dict();
35 sloc_up->AddStringItem(key: "file", value: sloc->file.GetPath());
36 sloc_up->AddIntegerItem(key: "line", value: sloc->line);
37 sloc_up->AddIntegerItem(key: "length", value: sloc->length);
38 sloc_up->AddBooleanItem(key: "hidden", value: sloc->hidden);
39 sloc_up->AddBooleanItem(key: "in_user_input", value: sloc->in_user_input);
40 detail_up->AddItem(key: "source_location", value_sp: std::move(sloc_up));
41 }
42 llvm::StringRef severity = "unknown";
43 switch (diag.severity) {
44 case lldb::eSeverityError:
45 severity = "error";
46 break;
47 case lldb::eSeverityWarning:
48 severity = "warning";
49 break;
50 case lldb::eSeverityInfo:
51 severity = "note";
52 break;
53 }
54 detail_up->AddStringItem(key: "severity", value: severity);
55 detail_up->AddStringItem(key: "message", value: diag.message);
56 detail_up->AddStringItem(key: "rendered", value: diag.rendered);
57 array_up->AddItem(item: std::move(detail_up));
58 }
59 dict_up->AddItem(key: "details", value_sp: std::move(array_up));
60 return dict_up;
61}
62
63static llvm::raw_ostream &PrintSeverity(Stream &stream,
64 lldb::Severity severity) {
65 llvm::HighlightColor color;
66 llvm::StringRef text;
67 switch (severity) {
68 case lldb::eSeverityError:
69 color = llvm::HighlightColor::Error;
70 text = "error: ";
71 break;
72 case lldb::eSeverityWarning:
73 color = llvm::HighlightColor::Warning;
74 text = "warning: ";
75 break;
76 case lldb::eSeverityInfo:
77 color = llvm::HighlightColor::Remark;
78 text = "note: ";
79 break;
80 }
81 return llvm::WithColor(stream.AsRawOstream(), color, llvm::ColorMode::Enable)
82 << text;
83}
84
85void RenderDiagnosticDetails(Stream &stream,
86 std::optional<uint16_t> offset_in_command,
87 bool show_inline,
88 llvm::ArrayRef<DiagnosticDetail> details) {
89 if (details.empty())
90 return;
91
92 if (!offset_in_command) {
93 for (const DiagnosticDetail &detail : details) {
94 PrintSeverity(stream, severity: detail.severity);
95 stream << detail.rendered << '\n';
96 }
97 return;
98 }
99
100 // Since there is no other way to find this out, use the color
101 // attribute as a proxy for whether the terminal supports Unicode
102 // characters. In the future it might make sense to move this into
103 // Host so it can be customized for a specific platform.
104 llvm::StringRef cursor, underline, vbar, joint, hbar, spacer;
105 if (stream.AsRawOstream().colors_enabled()) {
106 cursor = "˄";
107 underline = "˜";
108 vbar = "│";
109 joint = "╰";
110 hbar = "─";
111 spacer = " ";
112 } else {
113 cursor = "^";
114 underline = "~";
115 vbar = "|";
116 joint = "";
117 hbar = "";
118 spacer = "";
119 }
120
121 // Partition the diagnostics.
122 std::vector<DiagnosticDetail> remaining_details, other_details,
123 hidden_details;
124 for (const DiagnosticDetail &detail : details) {
125 if (!show_inline || !detail.source_location) {
126 other_details.push_back(x: detail);
127 continue;
128 }
129 if (detail.source_location->hidden) {
130 hidden_details.push_back(x: detail);
131 continue;
132 }
133 if (!detail.source_location->in_user_input) {
134 other_details.push_back(x: detail);
135 continue;
136 }
137
138 remaining_details.push_back(x: detail);
139 }
140
141 // Sort the diagnostics.
142 auto sort = [](std::vector<DiagnosticDetail> &ds) {
143 llvm::stable_sort(Range&: ds, C: [](auto &d1, auto &d2) {
144 auto l1 = d1.source_location.value_or(DiagnosticDetail::SourceLocation{});
145 auto l2 = d2.source_location.value_or(DiagnosticDetail::SourceLocation{});
146 return std::tie(l1.line, l1.column) < std::tie(l2.line, l2.column);
147 });
148 };
149 sort(remaining_details);
150 sort(other_details);
151 sort(hidden_details);
152
153 // Print a line with caret indicator(s) below the lldb prompt + command.
154 const size_t padding = *offset_in_command;
155 stream << std::string(padding, ' ');
156 {
157 size_t x_pos = 1;
158 for (const DiagnosticDetail &detail : remaining_details) {
159 auto &loc = *detail.source_location;
160
161 if (x_pos > loc.column)
162 continue;
163
164 stream << std::string(loc.column - x_pos, ' ') << cursor;
165 x_pos = loc.column + 1;
166 for (unsigned i = 0; i + 1 < loc.length; ++i) {
167 stream << underline;
168 x_pos += 1;
169 }
170 }
171 }
172 stream << '\n';
173
174 // Reverse the order within groups of diagnostics that are on the same column.
175 auto group = [](std::vector<DiagnosticDetail> &details) {
176 for (auto it = details.begin(), end = details.end(); it != end;) {
177 auto eq_end = std::find_if(first: it, last: end, pred: [&](const DiagnosticDetail &d) {
178 return d.source_location->column != it->source_location->column;
179 });
180 std::reverse(first: it, last: eq_end);
181 it = eq_end;
182 }
183 };
184 group(remaining_details);
185
186 // Work through each detail in reverse order using the vector/stack.
187 bool did_print = false;
188 for (; !remaining_details.empty(); remaining_details.pop_back()) {
189 const auto &detail = remaining_details.back();
190 // Get the information to print this detail and remove it from the stack.
191 // Print all the lines for all the other messages first.
192 stream << std::string(padding, ' ');
193 size_t x_pos = 1;
194 for (auto &remaining_detail :
195 llvm::ArrayRef(remaining_details).drop_back(N: 1)) {
196 uint16_t column = remaining_detail.source_location->column;
197 // Is this a note with the same column as another diagnostic?
198 if (column == detail.source_location->column)
199 continue;
200
201 if (column >= x_pos) {
202 stream << std::string(column - x_pos, ' ') << vbar;
203 x_pos = column + 1;
204 }
205 }
206
207 uint16_t column = detail.source_location->column;
208 // Print the line connecting the ^ with the error message.
209 if (column >= x_pos)
210 stream << std::string(column - x_pos, ' ') << joint << hbar << spacer;
211
212 // Print a colorized string based on the message's severity type.
213 PrintSeverity(stream, severity: detail.severity);
214
215 // Finally, print the message and start a new line.
216 stream << detail.message << '\n';
217 did_print = true;
218 }
219
220 // Print the non-located details.
221 for (const DiagnosticDetail &detail : other_details) {
222 PrintSeverity(stream, severity: detail.severity);
223 stream << detail.rendered << '\n';
224 did_print = true;
225 }
226
227 // Print the hidden details as a last resort.
228 if (!did_print)
229 for (const DiagnosticDetail &detail : hidden_details) {
230 PrintSeverity(stream, severity: detail.severity);
231 stream << detail.rendered << '\n';
232 }
233}
234
235} // namespace lldb_private
236

source code of lldb/source/Utility/DiagnosticsRendering.cpp