/Users/buildslave/jenkins/workspace/coverage/llvm-project/clang/lib/Frontend/TextDiagnostic.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | //===--- TextDiagnostic.cpp - Text Diagnostic Pretty-Printing -------------===// |
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 "clang/Frontend/TextDiagnostic.h" |
10 | | #include "clang/Basic/CharInfo.h" |
11 | | #include "clang/Basic/DiagnosticOptions.h" |
12 | | #include "clang/Basic/FileManager.h" |
13 | | #include "clang/Basic/SourceManager.h" |
14 | | #include "clang/Lex/Lexer.h" |
15 | | #include "llvm/ADT/SmallString.h" |
16 | | #include "llvm/ADT/StringExtras.h" |
17 | | #include "llvm/Support/ConvertUTF.h" |
18 | | #include "llvm/Support/ErrorHandling.h" |
19 | | #include "llvm/Support/Locale.h" |
20 | | #include "llvm/Support/Path.h" |
21 | | #include "llvm/Support/raw_ostream.h" |
22 | | #include <algorithm> |
23 | | |
24 | | using namespace clang; |
25 | | |
26 | | static const enum raw_ostream::Colors noteColor = |
27 | | raw_ostream::BLACK; |
28 | | static const enum raw_ostream::Colors remarkColor = |
29 | | raw_ostream::BLUE; |
30 | | static const enum raw_ostream::Colors fixitColor = |
31 | | raw_ostream::GREEN; |
32 | | static const enum raw_ostream::Colors caretColor = |
33 | | raw_ostream::GREEN; |
34 | | static const enum raw_ostream::Colors warningColor = |
35 | | raw_ostream::MAGENTA; |
36 | | static const enum raw_ostream::Colors templateColor = |
37 | | raw_ostream::CYAN; |
38 | | static const enum raw_ostream::Colors errorColor = raw_ostream::RED; |
39 | | static const enum raw_ostream::Colors fatalColor = raw_ostream::RED; |
40 | | // Used for changing only the bold attribute. |
41 | | static const enum raw_ostream::Colors savedColor = |
42 | | raw_ostream::SAVEDCOLOR; |
43 | | |
44 | | /// Add highlights to differences in template strings. |
45 | | static void applyTemplateHighlighting(raw_ostream &OS, StringRef Str, |
46 | 33.2k | bool &Normal, bool Bold) { |
47 | 33.3k | while (true) { |
48 | 33.3k | size_t Pos = Str.find(ToggleHighlight); |
49 | 33.3k | OS << Str.slice(0, Pos); |
50 | 33.3k | if (Pos == StringRef::npos) |
51 | 33.2k | break; |
52 | | |
53 | 110 | Str = Str.substr(Pos + 1); |
54 | 110 | if (Normal) |
55 | 55 | OS.changeColor(templateColor, true); |
56 | 55 | else { |
57 | 55 | OS.resetColor(); |
58 | 55 | if (Bold) |
59 | 36 | OS.changeColor(savedColor, true); |
60 | 55 | } |
61 | 110 | Normal = !Normal; |
62 | 110 | } |
63 | 33.2k | } |
64 | | |
65 | | /// Number of spaces to indent when word-wrapping. |
66 | | const unsigned WordWrapIndentation = 6; |
67 | | |
68 | 1.00k | static int bytesSincePreviousTabOrLineBegin(StringRef SourceLine, size_t i) { |
69 | 1.00k | int bytes = 0; |
70 | 2.23k | while (0<i) { |
71 | 1.35k | if (SourceLine[--i]=='\t') |
72 | 120 | break; |
73 | 1.23k | ++bytes; |
74 | 1.23k | } |
75 | 1.00k | return bytes; |
76 | 1.00k | } |
77 | | |
78 | | /// returns a printable representation of first item from input range |
79 | | /// |
80 | | /// This function returns a printable representation of the next item in a line |
81 | | /// of source. If the next byte begins a valid and printable character, that |
82 | | /// character is returned along with 'true'. |
83 | | /// |
84 | | /// Otherwise, if the next byte begins a valid, but unprintable character, a |
85 | | /// printable, escaped representation of the character is returned, along with |
86 | | /// 'false'. Otherwise a printable, escaped representation of the next byte |
87 | | /// is returned along with 'false'. |
88 | | /// |
89 | | /// \note The index is updated to be used with a subsequent call to |
90 | | /// printableTextForNextCharacter. |
91 | | /// |
92 | | /// \param SourceLine The line of source |
93 | | /// \param i Pointer to byte index, |
94 | | /// \param TabStop used to expand tabs |
95 | | /// \return pair(printable text, 'true' iff original text was printable) |
96 | | /// |
97 | | static std::pair<SmallString<16>, bool> |
98 | | printableTextForNextCharacter(StringRef SourceLine, size_t *i, |
99 | 2.81M | unsigned TabStop) { |
100 | 2.81M | assert(i && "i must not be null"); |
101 | 0 | assert(*i<SourceLine.size() && "must point to a valid index"); |
102 | | |
103 | 2.81M | if (SourceLine[*i]=='\t') { |
104 | 1.00k | assert(0 < TabStop && TabStop <= DiagnosticOptions::MaxTabStop && |
105 | 1.00k | "Invalid -ftabstop value"); |
106 | 0 | unsigned col = bytesSincePreviousTabOrLineBegin(SourceLine, *i); |
107 | 1.00k | unsigned NumSpaces = TabStop - col%TabStop; |
108 | 1.00k | assert(0 < NumSpaces && NumSpaces <= TabStop |
109 | 1.00k | && "Invalid computation of space amt"); |
110 | 0 | ++(*i); |
111 | | |
112 | 1.00k | SmallString<16> expandedTab; |
113 | 1.00k | expandedTab.assign(NumSpaces, ' '); |
114 | 1.00k | return std::make_pair(expandedTab, true); |
115 | 1.00k | } |
116 | | |
117 | 2.81M | unsigned char const *begin, *end; |
118 | 2.81M | begin = reinterpret_cast<unsigned char const *>(&*(SourceLine.begin() + *i)); |
119 | 2.81M | end = begin + (SourceLine.size() - *i); |
120 | | |
121 | 2.81M | if (llvm::isLegalUTF8Sequence(begin, end)) { |
122 | 2.81M | llvm::UTF32 c; |
123 | 2.81M | llvm::UTF32 *cptr = &c; |
124 | 2.81M | unsigned char const *original_begin = begin; |
125 | 2.81M | unsigned char const *cp_end = |
126 | 2.81M | begin + llvm::getNumBytesForUTF8(SourceLine[*i]); |
127 | | |
128 | 2.81M | llvm::ConversionResult res = llvm::ConvertUTF8toUTF32( |
129 | 2.81M | &begin, cp_end, &cptr, cptr + 1, llvm::strictConversion); |
130 | 2.81M | (void)res; |
131 | 2.81M | assert(llvm::conversionOK == res); |
132 | 0 | assert(0 < begin-original_begin |
133 | 2.81M | && "we must be further along in the string now"); |
134 | 0 | *i += begin-original_begin; |
135 | | |
136 | 2.81M | if (!llvm::sys::locale::isPrint(c)) { |
137 | | // If next character is valid UTF-8, but not printable |
138 | 449 | SmallString<16> expandedCP("<U+>"); |
139 | 842 | while (c) { |
140 | 393 | expandedCP.insert(expandedCP.begin()+3, llvm::hexdigit(c%16)); |
141 | 393 | c/=16; |
142 | 393 | } |
143 | 1.85k | while (expandedCP.size() < 8) |
144 | 1.40k | expandedCP.insert(expandedCP.begin()+3, llvm::hexdigit(0)); |
145 | 449 | return std::make_pair(expandedCP, false); |
146 | 449 | } |
147 | | |
148 | | // If next character is valid UTF-8, and printable |
149 | 2.80M | return std::make_pair(SmallString<16>(original_begin, cp_end), true); |
150 | | |
151 | 2.81M | } |
152 | | |
153 | | // If next byte is not valid UTF-8 (and therefore not printable) |
154 | 486 | SmallString<16> expandedByte("<XX>"); |
155 | 486 | unsigned char byte = SourceLine[*i]; |
156 | 486 | expandedByte[1] = llvm::hexdigit(byte / 16); |
157 | 486 | expandedByte[2] = llvm::hexdigit(byte % 16); |
158 | 486 | ++(*i); |
159 | 486 | return std::make_pair(expandedByte, false); |
160 | 2.81M | } |
161 | | |
162 | 3.08k | static void expandTabs(std::string &SourceLine, unsigned TabStop) { |
163 | 3.08k | size_t i = SourceLine.size(); |
164 | 55.2k | while (i>0) { |
165 | 52.2k | i--; |
166 | 52.2k | if (SourceLine[i]!='\t') |
167 | 52.2k | continue; |
168 | 0 | size_t tmp_i = i; |
169 | 0 | std::pair<SmallString<16>,bool> res |
170 | 0 | = printableTextForNextCharacter(SourceLine, &tmp_i, TabStop); |
171 | 0 | SourceLine.replace(i, 1, res.first.c_str()); |
172 | 0 | } |
173 | 3.08k | } |
174 | | |
175 | | /// This function takes a raw source line and produces a mapping from the bytes |
176 | | /// of the printable representation of the line to the columns those printable |
177 | | /// characters will appear at (numbering the first column as 0). |
178 | | /// |
179 | | /// If a byte 'i' corresponds to multiple columns (e.g. the byte contains a tab |
180 | | /// character) then the array will map that byte to the first column the |
181 | | /// tab appears at and the next value in the map will have been incremented |
182 | | /// more than once. |
183 | | /// |
184 | | /// If a byte is the first in a sequence of bytes that together map to a single |
185 | | /// entity in the output, then the array will map that byte to the appropriate |
186 | | /// column while the subsequent bytes will be -1. |
187 | | /// |
188 | | /// The last element in the array does not correspond to any byte in the input |
189 | | /// and instead is the number of columns needed to display the source |
190 | | /// |
191 | | /// example: (given a tabstop of 8) |
192 | | /// |
193 | | /// "a \t \u3042" -> {0,1,2,8,9,-1,-1,11} |
194 | | /// |
195 | | /// (\\u3042 is represented in UTF-8 by three bytes and takes two columns to |
196 | | /// display) |
197 | | static void byteToColumn(StringRef SourceLine, unsigned TabStop, |
198 | 20.4k | SmallVectorImpl<int> &out) { |
199 | 20.4k | out.clear(); |
200 | | |
201 | 20.4k | if (SourceLine.empty()) { |
202 | 1 | out.resize(1u,0); |
203 | 1 | return; |
204 | 1 | } |
205 | | |
206 | 20.4k | out.resize(SourceLine.size()+1, -1); |
207 | | |
208 | 20.4k | int columns = 0; |
209 | 20.4k | size_t i = 0; |
210 | 958k | while (i<SourceLine.size()) { |
211 | 937k | out[i] = columns; |
212 | 937k | std::pair<SmallString<16>,bool> res |
213 | 937k | = printableTextForNextCharacter(SourceLine, &i, TabStop); |
214 | 937k | columns += llvm::sys::locale::columnWidth(res.first); |
215 | 937k | } |
216 | 20.4k | out.back() = columns; |
217 | 20.4k | } |
218 | | |
219 | | /// This function takes a raw source line and produces a mapping from columns |
220 | | /// to the byte of the source line that produced the character displaying at |
221 | | /// that column. This is the inverse of the mapping produced by byteToColumn() |
222 | | /// |
223 | | /// The last element in the array is the number of bytes in the source string |
224 | | /// |
225 | | /// example: (given a tabstop of 8) |
226 | | /// |
227 | | /// "a \t \u3042" -> {0,1,2,-1,-1,-1,-1,-1,3,4,-1,7} |
228 | | /// |
229 | | /// (\\u3042 is represented in UTF-8 by three bytes and takes two columns to |
230 | | /// display) |
231 | | static void columnToByte(StringRef SourceLine, unsigned TabStop, |
232 | 20.4k | SmallVectorImpl<int> &out) { |
233 | 20.4k | out.clear(); |
234 | | |
235 | 20.4k | if (SourceLine.empty()) { |
236 | 1 | out.resize(1u, 0); |
237 | 1 | return; |
238 | 1 | } |
239 | | |
240 | 20.4k | int columns = 0; |
241 | 20.4k | size_t i = 0; |
242 | 958k | while (i<SourceLine.size()) { |
243 | 937k | out.resize(columns+1, -1); |
244 | 937k | out.back() = i; |
245 | 937k | std::pair<SmallString<16>,bool> res |
246 | 937k | = printableTextForNextCharacter(SourceLine, &i, TabStop); |
247 | 937k | columns += llvm::sys::locale::columnWidth(res.first); |
248 | 937k | } |
249 | 20.4k | out.resize(columns+1, -1); |
250 | 20.4k | out.back() = i; |
251 | 20.4k | } |
252 | | |
253 | | namespace { |
254 | | struct SourceColumnMap { |
255 | | SourceColumnMap(StringRef SourceLine, unsigned TabStop) |
256 | 20.4k | : m_SourceLine(SourceLine) { |
257 | | |
258 | 20.4k | ::byteToColumn(SourceLine, TabStop, m_byteToColumn); |
259 | 20.4k | ::columnToByte(SourceLine, TabStop, m_columnToByte); |
260 | | |
261 | 20.4k | assert(m_byteToColumn.size()==SourceLine.size()+1); |
262 | 0 | assert(0 < m_byteToColumn.size() && 0 < m_columnToByte.size()); |
263 | 0 | assert(m_byteToColumn.size() |
264 | 20.4k | == static_cast<unsigned>(m_columnToByte.back()+1)); |
265 | 0 | assert(static_cast<unsigned>(m_byteToColumn.back()+1) |
266 | 20.4k | == m_columnToByte.size()); |
267 | 20.4k | } |
268 | 20.5k | int columns() const { return m_byteToColumn.back(); } |
269 | 3.01k | int bytes() const { return m_columnToByte.back(); } |
270 | | |
271 | | /// Map a byte to the column which it is at the start of, or return -1 |
272 | | /// if it is not at the start of a column (for a UTF-8 trailing byte). |
273 | 2.18k | int byteToColumn(int n) const { |
274 | 2.18k | assert(0<=n && n<static_cast<int>(m_byteToColumn.size())); |
275 | 0 | return m_byteToColumn[n]; |
276 | 2.18k | } |
277 | | |
278 | | /// Map a byte to the first column which contains it. |
279 | 50.1k | int byteToContainingColumn(int N) const { |
280 | 50.1k | assert(0 <= N && N < static_cast<int>(m_byteToColumn.size())); |
281 | 50.1k | while (m_byteToColumn[N] == -1) |
282 | 0 | --N; |
283 | 50.1k | return m_byteToColumn[N]; |
284 | 50.1k | } |
285 | | |
286 | | /// Map a column to the byte which starts the column, or return -1 if |
287 | | /// the column the second or subsequent column of an expanded tab or similar |
288 | | /// multi-column entity. |
289 | 132 | int columnToByte(int n) const { |
290 | 132 | assert(0<=n && n<static_cast<int>(m_columnToByte.size())); |
291 | 0 | return m_columnToByte[n]; |
292 | 132 | } |
293 | | |
294 | | /// Map from a byte index to the next byte which starts a column. |
295 | 179 | int startOfNextColumn(int N) const { |
296 | 179 | assert(0 <= N && N < static_cast<int>(m_byteToColumn.size() - 1)); |
297 | 211 | while (byteToColumn(++N) == -1) {}32 |
298 | 179 | return N; |
299 | 179 | } |
300 | | |
301 | | /// Map from a byte index to the previous byte which starts a column. |
302 | 1.15k | int startOfPreviousColumn(int N) const { |
303 | 1.15k | assert(0 < N && N < static_cast<int>(m_byteToColumn.size())); |
304 | 1.21k | while (byteToColumn(--N) == -1) {}66 |
305 | 1.15k | return N; |
306 | 1.15k | } |
307 | | |
308 | 95.5k | StringRef getSourceLine() const { |
309 | 95.5k | return m_SourceLine; |
310 | 95.5k | } |
311 | | |
312 | | private: |
313 | | const std::string m_SourceLine; |
314 | | SmallVector<int,200> m_byteToColumn; |
315 | | SmallVector<int,200> m_columnToByte; |
316 | | }; |
317 | | } // end anonymous namespace |
318 | | |
319 | | /// When the source code line we want to print is too long for |
320 | | /// the terminal, select the "interesting" region. |
321 | | static void selectInterestingSourceRegion(std::string &SourceLine, |
322 | | std::string &CaretLine, |
323 | | std::string &FixItInsertionLine, |
324 | | unsigned Columns, |
325 | 42 | const SourceColumnMap &map) { |
326 | 42 | unsigned CaretColumns = CaretLine.size(); |
327 | 42 | unsigned FixItColumns = llvm::sys::locale::columnWidth(FixItInsertionLine); |
328 | 42 | unsigned MaxColumns = std::max(static_cast<unsigned>(map.columns()), |
329 | 42 | std::max(CaretColumns, FixItColumns)); |
330 | | // if the number of columns is less than the desired number we're done |
331 | 42 | if (MaxColumns <= Columns) |
332 | 13 | return; |
333 | | |
334 | | // No special characters are allowed in CaretLine. |
335 | 29 | assert(CaretLine.end() == |
336 | 29 | llvm::find_if(CaretLine, [](char c) { return c < ' ' || '~' < c; })); |
337 | | |
338 | | // Find the slice that we need to display the full caret line |
339 | | // correctly. |
340 | 0 | unsigned CaretStart = 0, CaretEnd = CaretLine.size(); |
341 | 1.55k | for (; CaretStart != CaretEnd; ++CaretStart1.52k ) |
342 | 1.55k | if (!isWhitespace(CaretLine[CaretStart])) |
343 | 29 | break; |
344 | | |
345 | 1.12k | for (; CaretEnd != CaretStart; --CaretEnd1.09k ) |
346 | 1.12k | if (!isWhitespace(CaretLine[CaretEnd - 1])) |
347 | 29 | break; |
348 | | |
349 | | // caret has already been inserted into CaretLine so the above whitespace |
350 | | // check is guaranteed to include the caret |
351 | | |
352 | | // If we have a fix-it line, make sure the slice includes all of the |
353 | | // fix-it information. |
354 | 29 | if (!FixItInsertionLine.empty()) { |
355 | 7 | unsigned FixItStart = 0, FixItEnd = FixItInsertionLine.size(); |
356 | 622 | for (; FixItStart != FixItEnd; ++FixItStart615 ) |
357 | 622 | if (!isWhitespace(FixItInsertionLine[FixItStart])) |
358 | 7 | break; |
359 | | |
360 | 10 | for (; FixItEnd != FixItStart; --FixItEnd3 ) |
361 | 10 | if (!isWhitespace(FixItInsertionLine[FixItEnd - 1])) |
362 | 7 | break; |
363 | | |
364 | | // We can safely use the byte offset FixItStart as the column offset |
365 | | // because the characters up until FixItStart are all ASCII whitespace |
366 | | // characters. |
367 | 7 | unsigned FixItStartCol = FixItStart; |
368 | 7 | unsigned FixItEndCol |
369 | 7 | = llvm::sys::locale::columnWidth(FixItInsertionLine.substr(0, FixItEnd)); |
370 | | |
371 | 7 | CaretStart = std::min(FixItStartCol, CaretStart); |
372 | 7 | CaretEnd = std::max(FixItEndCol, CaretEnd); |
373 | 7 | } |
374 | | |
375 | | // CaretEnd may have been set at the middle of a character |
376 | | // If it's not at a character's first column then advance it past the current |
377 | | // character. |
378 | 29 | while (static_cast<int>(CaretEnd) < map.columns() && |
379 | 29 | -1 == map.columnToByte(CaretEnd)22 ) |
380 | 0 | ++CaretEnd; |
381 | | |
382 | 29 | assert((static_cast<int>(CaretStart) > map.columns() || |
383 | 29 | -1!=map.columnToByte(CaretStart)) && |
384 | 29 | "CaretStart must not point to a column in the middle of a source" |
385 | 29 | " line character"); |
386 | 0 | assert((static_cast<int>(CaretEnd) > map.columns() || |
387 | 29 | -1!=map.columnToByte(CaretEnd)) && |
388 | 29 | "CaretEnd must not point to a column in the middle of a source line" |
389 | 29 | " character"); |
390 | | |
391 | | // CaretLine[CaretStart, CaretEnd) contains all of the interesting |
392 | | // parts of the caret line. While this slice is smaller than the |
393 | | // number of columns we have, try to grow the slice to encompass |
394 | | // more context. |
395 | | |
396 | 0 | unsigned SourceStart = map.columnToByte(std::min<unsigned>(CaretStart, |
397 | 29 | map.columns())); |
398 | 29 | unsigned SourceEnd = map.columnToByte(std::min<unsigned>(CaretEnd, |
399 | 29 | map.columns())); |
400 | | |
401 | 29 | unsigned CaretColumnsOutsideSource = CaretEnd-CaretStart |
402 | 29 | - (map.byteToColumn(SourceEnd)-map.byteToColumn(SourceStart)); |
403 | | |
404 | 29 | char const *front_ellipse = " ..."; |
405 | 29 | char const *front_space = " "; |
406 | 29 | char const *back_ellipse = "..."; |
407 | 29 | unsigned ellipses_space = strlen(front_ellipse) + strlen(back_ellipse); |
408 | | |
409 | 29 | unsigned TargetColumns = Columns; |
410 | | // Give us extra room for the ellipses |
411 | | // and any of the caret line that extends past the source |
412 | 29 | if (TargetColumns > ellipses_space+CaretColumnsOutsideSource) |
413 | 17 | TargetColumns -= ellipses_space+CaretColumnsOutsideSource; |
414 | | |
415 | 155 | while (SourceStart>0 || SourceEnd<SourceLine.size()85 ) { |
416 | 155 | bool ExpandedRegion = false; |
417 | | |
418 | 155 | if (SourceStart>0) { |
419 | 70 | unsigned NewStart = map.startOfPreviousColumn(SourceStart); |
420 | | |
421 | | // Skip over any whitespace we see here; we're looking for |
422 | | // another bit of interesting text. |
423 | | // FIXME: Detect non-ASCII whitespace characters too. |
424 | 346 | while (NewStart && isWhitespace(SourceLine[NewStart])336 ) |
425 | 276 | NewStart = map.startOfPreviousColumn(NewStart); |
426 | | |
427 | | // Skip over this bit of "interesting" text. |
428 | 817 | while (NewStart) { |
429 | 806 | unsigned Prev = map.startOfPreviousColumn(NewStart); |
430 | 806 | if (isWhitespace(SourceLine[Prev])) |
431 | 59 | break; |
432 | 747 | NewStart = Prev; |
433 | 747 | } |
434 | | |
435 | 70 | assert(map.byteToColumn(NewStart) != -1); |
436 | 0 | unsigned NewColumns = map.byteToColumn(SourceEnd) - |
437 | 70 | map.byteToColumn(NewStart); |
438 | 70 | if (NewColumns <= TargetColumns) { |
439 | 38 | SourceStart = NewStart; |
440 | 38 | ExpandedRegion = true; |
441 | 38 | } |
442 | 70 | } |
443 | | |
444 | 155 | if (SourceEnd<SourceLine.size()) { |
445 | 125 | unsigned NewEnd = map.startOfNextColumn(SourceEnd); |
446 | | |
447 | | // Skip over any whitespace we see here; we're looking for |
448 | | // another bit of interesting text. |
449 | | // FIXME: Detect non-ASCII whitespace characters too. |
450 | 171 | while (NewEnd < SourceLine.size() && isWhitespace(SourceLine[NewEnd])160 ) |
451 | 46 | NewEnd = map.startOfNextColumn(NewEnd); |
452 | | |
453 | | // Skip over this bit of "interesting" text. |
454 | 125 | while (NewEnd < SourceLine.size() && isWhitespace(SourceLine[NewEnd])114 ) |
455 | 0 | NewEnd = map.startOfNextColumn(NewEnd); |
456 | | |
457 | 125 | assert(map.byteToColumn(NewEnd) != -1); |
458 | 0 | unsigned NewColumns = map.byteToColumn(NewEnd) - |
459 | 125 | map.byteToColumn(SourceStart); |
460 | 125 | if (NewColumns <= TargetColumns) { |
461 | 104 | SourceEnd = NewEnd; |
462 | 104 | ExpandedRegion = true; |
463 | 104 | } |
464 | 125 | } |
465 | | |
466 | 155 | if (!ExpandedRegion) |
467 | 29 | break; |
468 | 155 | } |
469 | | |
470 | 29 | CaretStart = map.byteToColumn(SourceStart); |
471 | 29 | CaretEnd = map.byteToColumn(SourceEnd) + CaretColumnsOutsideSource; |
472 | | |
473 | | // [CaretStart, CaretEnd) is the slice we want. Update the various |
474 | | // output lines to show only this slice, with two-space padding |
475 | | // before the lines so that it looks nicer. |
476 | | |
477 | 29 | assert(CaretStart!=(unsigned)-1 && CaretEnd!=(unsigned)-1 && |
478 | 29 | SourceStart!=(unsigned)-1 && SourceEnd!=(unsigned)-1); |
479 | 0 | assert(SourceStart <= SourceEnd); |
480 | 0 | assert(CaretStart <= CaretEnd); |
481 | | |
482 | 0 | unsigned BackColumnsRemoved |
483 | 29 | = map.byteToColumn(SourceLine.size())-map.byteToColumn(SourceEnd); |
484 | 29 | unsigned FrontColumnsRemoved = CaretStart; |
485 | 29 | unsigned ColumnsKept = CaretEnd-CaretStart; |
486 | | |
487 | | // We checked up front that the line needed truncation |
488 | 29 | assert(FrontColumnsRemoved+ColumnsKept+BackColumnsRemoved > Columns); |
489 | | |
490 | | // The line needs some truncation, and we'd prefer to keep the front |
491 | | // if possible, so remove the back |
492 | 29 | if (BackColumnsRemoved > strlen(back_ellipse)) |
493 | 11 | SourceLine.replace(SourceEnd, std::string::npos, back_ellipse); |
494 | | |
495 | | // If that's enough then we're done |
496 | 29 | if (FrontColumnsRemoved+ColumnsKept <= Columns) |
497 | 4 | return; |
498 | | |
499 | | // Otherwise remove the front as well |
500 | 25 | if (FrontColumnsRemoved > strlen(front_ellipse)) { |
501 | 20 | SourceLine.replace(0, SourceStart, front_ellipse); |
502 | 20 | CaretLine.replace(0, CaretStart, front_space); |
503 | 20 | if (!FixItInsertionLine.empty()) |
504 | 6 | FixItInsertionLine.replace(0, CaretStart, front_space); |
505 | 20 | } |
506 | 25 | } |
507 | | |
508 | | /// Skip over whitespace in the string, starting at the given |
509 | | /// index. |
510 | | /// |
511 | | /// \returns The index of the first non-whitespace character that is |
512 | | /// greater than or equal to Idx or, if no such character exists, |
513 | | /// returns the end of the string. |
514 | 2.75k | static unsigned skipWhitespace(unsigned Idx, StringRef Str, unsigned Length) { |
515 | 5.20k | while (Idx < Length && isWhitespace(Str[Idx])) |
516 | 2.45k | ++Idx; |
517 | 2.75k | return Idx; |
518 | 2.75k | } |
519 | | |
520 | | /// If the given character is the start of some kind of |
521 | | /// balanced punctuation (e.g., quotes or parentheses), return the |
522 | | /// character that will terminate the punctuation. |
523 | | /// |
524 | | /// \returns The ending punctuation character, if any, or the NULL |
525 | | /// character if the input character does not start any punctuation. |
526 | 15.7k | static inline char findMatchingPunctuation(char c) { |
527 | 15.7k | switch (c) { |
528 | 573 | case '\'': return '\''; |
529 | 0 | case '`': return '\''; |
530 | 0 | case '"': return '"'; |
531 | 33 | case '(': return ')'; |
532 | 294 | case '[': return ']'; |
533 | 0 | case '{': return '}'; |
534 | 14.8k | default: break; |
535 | 15.7k | } |
536 | | |
537 | 14.8k | return 0; |
538 | 15.7k | } |
539 | | |
540 | | /// Find the end of the word starting at the given offset |
541 | | /// within a string. |
542 | | /// |
543 | | /// \returns the index pointing one character past the end of the |
544 | | /// word. |
545 | | static unsigned findEndOfWord(unsigned Start, StringRef Str, |
546 | | unsigned Length, unsigned Column, |
547 | 2.81k | unsigned Columns) { |
548 | 2.81k | assert(Start < Str.size() && "Invalid start position!"); |
549 | 0 | unsigned End = Start + 1; |
550 | | |
551 | | // If we are already at the end of the string, take that as the word. |
552 | 2.81k | if (End == Str.size()) |
553 | 0 | return End; |
554 | | |
555 | | // Determine if the start of the string is actually opening |
556 | | // punctuation, e.g., a quote or parentheses. |
557 | 2.81k | char EndPunct = findMatchingPunctuation(Str[Start]); |
558 | 2.81k | if (!EndPunct) { |
559 | | // This is a normal word. Just find the first space character. |
560 | 15.3k | while (End < Length && !isWhitespace(Str[End])15.2k ) |
561 | 13.3k | ++End; |
562 | 1.93k | return End; |
563 | 1.93k | } |
564 | | |
565 | | // We have the start of a balanced punctuation sequence (quotes, |
566 | | // parentheses, etc.). Determine the full sequence is. |
567 | 876 | SmallString<16> PunctuationEndStack; |
568 | 876 | PunctuationEndStack.push_back(EndPunct); |
569 | 14.7k | while (End < Length && !PunctuationEndStack.empty()14.4k ) { |
570 | 13.8k | if (Str[End] == PunctuationEndStack.back()) |
571 | 900 | PunctuationEndStack.pop_back(); |
572 | 12.9k | else if (char SubEndPunct = findMatchingPunctuation(Str[End])) |
573 | 24 | PunctuationEndStack.push_back(SubEndPunct); |
574 | | |
575 | 13.8k | ++End; |
576 | 13.8k | } |
577 | | |
578 | | // Find the first space character after the punctuation ended. |
579 | 918 | while (End < Length && !isWhitespace(Str[End])618 ) |
580 | 42 | ++End; |
581 | | |
582 | 876 | unsigned PunctWordLength = End - Start; |
583 | 876 | if (// If the word fits on this line |
584 | 876 | Column + PunctWordLength <= Columns || |
585 | | // ... or the word is "short enough" to take up the next line |
586 | | // without too much ugly white space |
587 | 876 | PunctWordLength < Columns/3323 ) |
588 | 818 | return End; // Take the whole thing as a single "word". |
589 | | |
590 | | // The whole quoted/parenthesized string is too long to print as a |
591 | | // single "word". Instead, find the "word" that starts just after |
592 | | // the punctuation and use that end-point instead. This will recurse |
593 | | // until it finds something small enough to consider a word. |
594 | 58 | return findEndOfWord(Start + 1, Str, Length, Column + 1, Columns); |
595 | 876 | } |
596 | | |
597 | | /// Print the given string to a stream, word-wrapping it to |
598 | | /// some number of columns in the process. |
599 | | /// |
600 | | /// \param OS the stream to which the word-wrapping string will be |
601 | | /// emitted. |
602 | | /// \param Str the string to word-wrap and output. |
603 | | /// \param Columns the number of columns to word-wrap to. |
604 | | /// \param Column the column number at which the first character of \p |
605 | | /// Str will be printed. This will be non-zero when part of the first |
606 | | /// line has already been printed. |
607 | | /// \param Bold if the current text should be bold |
608 | | /// \param Indentation the number of spaces to indent any lines beyond |
609 | | /// the first line. |
610 | | /// \returns true if word-wrapping was required, or false if the |
611 | | /// string fit on the first line. |
612 | | static bool printWordWrapped(raw_ostream &OS, StringRef Str, |
613 | | unsigned Columns, |
614 | | unsigned Column = 0, |
615 | | bool Bold = false, |
616 | 305 | unsigned Indentation = WordWrapIndentation) { |
617 | 305 | const unsigned Length = std::min(Str.find('\n'), Str.size()); |
618 | 305 | bool TextNormal = true; |
619 | | |
620 | | // The string used to indent each line. |
621 | 305 | SmallString<16> IndentStr; |
622 | 305 | IndentStr.assign(Indentation, ' '); |
623 | 305 | bool Wrapped = false; |
624 | 3.06k | for (unsigned WordStart = 0, WordEnd; WordStart < Length; |
625 | 2.75k | WordStart = WordEnd) { |
626 | | // Find the beginning of the next word. |
627 | 2.75k | WordStart = skipWhitespace(WordStart, Str, Length); |
628 | 2.75k | if (WordStart == Length) |
629 | 0 | break; |
630 | | |
631 | | // Find the end of this word. |
632 | 2.75k | WordEnd = findEndOfWord(WordStart, Str, Length, Column, Columns); |
633 | | |
634 | | // Does this word fit on the current line? |
635 | 2.75k | unsigned WordLength = WordEnd - WordStart; |
636 | 2.75k | if (Column + WordLength < Columns) { |
637 | | // This word fits on the current line; print it there. |
638 | 1.74k | if (WordStart) { |
639 | 1.74k | OS << ' '; |
640 | 1.74k | Column += 1; |
641 | 1.74k | } |
642 | 1.74k | applyTemplateHighlighting(OS, Str.substr(WordStart, WordLength), |
643 | 1.74k | TextNormal, Bold); |
644 | 1.74k | Column += WordLength; |
645 | 1.74k | continue; |
646 | 1.74k | } |
647 | | |
648 | | // This word does not fit on the current line, so wrap to the next |
649 | | // line. |
650 | 1.00k | OS << '\n'; |
651 | 1.00k | OS.write(&IndentStr[0], Indentation); |
652 | 1.00k | applyTemplateHighlighting(OS, Str.substr(WordStart, WordLength), |
653 | 1.00k | TextNormal, Bold); |
654 | 1.00k | Column = Indentation + WordLength; |
655 | 1.00k | Wrapped = true; |
656 | 1.00k | } |
657 | | |
658 | | // Append any remaning text from the message with its existing formatting. |
659 | 305 | applyTemplateHighlighting(OS, Str.substr(Length), TextNormal, Bold); |
660 | | |
661 | 305 | assert(TextNormal && "Text highlighted at end of diagnostic message."); |
662 | | |
663 | 0 | return Wrapped; |
664 | 305 | } |
665 | | |
666 | | TextDiagnostic::TextDiagnostic(raw_ostream &OS, |
667 | | const LangOptions &LangOpts, |
668 | | DiagnosticOptions *DiagOpts) |
669 | 91.3k | : DiagnosticRenderer(LangOpts, DiagOpts), OS(OS) {} |
670 | | |
671 | 91.2k | TextDiagnostic::~TextDiagnostic() {} |
672 | | |
673 | | void TextDiagnostic::emitDiagnosticMessage( |
674 | | FullSourceLoc Loc, PresumedLoc PLoc, DiagnosticsEngine::Level Level, |
675 | | StringRef Message, ArrayRef<clang::CharSourceRange> Ranges, |
676 | 22.3k | DiagOrStoredDiag D) { |
677 | 22.3k | uint64_t StartOfLocationInfo = OS.tell(); |
678 | | |
679 | | // Emit the location of this particular diagnostic. |
680 | 22.3k | if (Loc.isValid()) |
681 | 22.3k | emitDiagnosticLoc(Loc, PLoc, Level, Ranges); |
682 | | |
683 | 22.3k | if (DiagOpts->ShowColors) |
684 | 51 | OS.resetColor(); |
685 | | |
686 | 22.3k | if (DiagOpts->ShowLevel) |
687 | 21.8k | printDiagnosticLevel(OS, Level, DiagOpts->ShowColors); |
688 | 22.3k | printDiagnosticMessage(OS, |
689 | 22.3k | /*IsSupplemental*/ Level == DiagnosticsEngine::Note, |
690 | 22.3k | Message, OS.tell() - StartOfLocationInfo, |
691 | 22.3k | DiagOpts->MessageLength, DiagOpts->ShowColors); |
692 | 22.3k | } |
693 | | |
694 | | /*static*/ void |
695 | | TextDiagnostic::printDiagnosticLevel(raw_ostream &OS, |
696 | | DiagnosticsEngine::Level Level, |
697 | 29.9k | bool ShowColors) { |
698 | 29.9k | if (ShowColors) { |
699 | | // Print diagnostic category in bold and color |
700 | 52 | switch (Level) { |
701 | 0 | case DiagnosticsEngine::Ignored: |
702 | 0 | llvm_unreachable("Invalid diagnostic type"); |
703 | 28 | case DiagnosticsEngine::Note: OS.changeColor(noteColor, true); break; |
704 | 0 | case DiagnosticsEngine::Remark: OS.changeColor(remarkColor, true); break; |
705 | 0 | case DiagnosticsEngine::Warning: OS.changeColor(warningColor, true); break; |
706 | 23 | case DiagnosticsEngine::Error: OS.changeColor(errorColor, true); break; |
707 | 1 | case DiagnosticsEngine::Fatal: OS.changeColor(fatalColor, true); break; |
708 | 52 | } |
709 | 52 | } |
710 | | |
711 | 29.9k | switch (Level) { |
712 | 0 | case DiagnosticsEngine::Ignored: |
713 | 0 | llvm_unreachable("Invalid diagnostic type"); |
714 | 7.31k | case DiagnosticsEngine::Note: OS << "note: "; break; |
715 | 245 | case DiagnosticsEngine::Remark: OS << "remark: "; break; |
716 | 16.0k | case DiagnosticsEngine::Warning: OS << "warning: "; break; |
717 | 6.20k | case DiagnosticsEngine::Error: OS << "error: "; break; |
718 | 128 | case DiagnosticsEngine::Fatal: OS << "fatal error: "; break; |
719 | 29.9k | } |
720 | | |
721 | 29.9k | if (ShowColors) |
722 | 52 | OS.resetColor(); |
723 | 29.9k | } |
724 | | |
725 | | /*static*/ |
726 | | void TextDiagnostic::printDiagnosticMessage(raw_ostream &OS, |
727 | | bool IsSupplemental, |
728 | | StringRef Message, |
729 | | unsigned CurrentColumn, |
730 | 30.4k | unsigned Columns, bool ShowColors) { |
731 | 30.4k | bool Bold = false; |
732 | 30.4k | if (ShowColors && !IsSupplemental52 ) { |
733 | | // Print primary diagnostic messages in bold and without color, to visually |
734 | | // indicate the transition from continuation notes and other output. |
735 | 24 | OS.changeColor(savedColor, true); |
736 | 24 | Bold = true; |
737 | 24 | } |
738 | | |
739 | 30.4k | if (Columns) |
740 | 305 | printWordWrapped(OS, Message, Columns, CurrentColumn, Bold); |
741 | 30.1k | else { |
742 | 30.1k | bool Normal = true; |
743 | 30.1k | applyTemplateHighlighting(OS, Message, Normal, Bold); |
744 | 30.1k | assert(Normal && "Formatting should have returned to normal"); |
745 | 30.1k | } |
746 | | |
747 | 30.4k | if (ShowColors) |
748 | 52 | OS.resetColor(); |
749 | 30.4k | OS << '\n'; |
750 | 30.4k | } |
751 | | |
752 | 22.3k | void TextDiagnostic::emitFilename(StringRef Filename, const SourceManager &SM) { |
753 | | #ifdef _WIN32 |
754 | | SmallString<4096> TmpFilename; |
755 | | #endif |
756 | 22.3k | if (DiagOpts->AbsolutePath) { |
757 | 4 | auto File = SM.getFileManager().getFile(Filename); |
758 | 4 | if (File) { |
759 | | // We want to print a simplified absolute path, i. e. without "dots". |
760 | | // |
761 | | // The hardest part here are the paths like "<part1>/<link>/../<part2>". |
762 | | // On Unix-like systems, we cannot just collapse "<link>/..", because |
763 | | // paths are resolved sequentially, and, thereby, the path |
764 | | // "<part1>/<part2>" may point to a different location. That is why |
765 | | // we use FileManager::getCanonicalName(), which expands all indirections |
766 | | // with llvm::sys::fs::real_path() and caches the result. |
767 | | // |
768 | | // On the other hand, it would be better to preserve as much of the |
769 | | // original path as possible, because that helps a user to recognize it. |
770 | | // real_path() expands all links, which sometimes too much. Luckily, |
771 | | // on Windows we can just use llvm::sys::path::remove_dots(), because, |
772 | | // on that system, both aforementioned paths point to the same place. |
773 | | #ifdef _WIN32 |
774 | | TmpFilename = (*File)->getName(); |
775 | | llvm::sys::fs::make_absolute(TmpFilename); |
776 | | llvm::sys::path::native(TmpFilename); |
777 | | llvm::sys::path::remove_dots(TmpFilename, /* remove_dot_dot */ true); |
778 | | Filename = StringRef(TmpFilename.data(), TmpFilename.size()); |
779 | | #else |
780 | 3 | Filename = SM.getFileManager().getCanonicalName(*File); |
781 | 3 | #endif |
782 | 3 | } |
783 | 4 | } |
784 | | |
785 | 22.3k | OS << Filename; |
786 | 22.3k | } |
787 | | |
788 | | /// Print out the file/line/column information and include trace. |
789 | | /// |
790 | | /// This method handlen the emission of the diagnostic location information. |
791 | | /// This includes extracting as much location information as is present for |
792 | | /// the diagnostic and printing it, as well as any include stack or source |
793 | | /// ranges necessary. |
794 | | void TextDiagnostic::emitDiagnosticLoc(FullSourceLoc Loc, PresumedLoc PLoc, |
795 | | DiagnosticsEngine::Level Level, |
796 | 22.3k | ArrayRef<CharSourceRange> Ranges) { |
797 | 22.3k | if (PLoc.isInvalid()) { |
798 | | // At least print the file name if available: |
799 | 2 | FileID FID = Loc.getFileID(); |
800 | 2 | if (FID.isValid()) { |
801 | 2 | if (const FileEntry *FE = Loc.getFileEntry()) { |
802 | 0 | emitFilename(FE->getName(), Loc.getManager()); |
803 | 0 | OS << ": "; |
804 | 0 | } |
805 | 2 | } |
806 | 2 | return; |
807 | 2 | } |
808 | 22.3k | unsigned LineNo = PLoc.getLine(); |
809 | | |
810 | 22.3k | if (!DiagOpts->ShowLocation) |
811 | 0 | return; |
812 | | |
813 | 22.3k | if (DiagOpts->ShowColors) |
814 | 51 | OS.changeColor(savedColor, true); |
815 | | |
816 | 22.3k | emitFilename(PLoc.getFilename(), Loc.getManager()); |
817 | 22.3k | switch (DiagOpts->getFormat()) { |
818 | 22.2k | case DiagnosticOptions::Clang: |
819 | 22.2k | if (DiagOpts->ShowLine) |
820 | 22.2k | OS << ':' << LineNo; |
821 | 22.2k | break; |
822 | 21 | case DiagnosticOptions::MSVC: OS << '(' << LineNo; break; |
823 | 9 | case DiagnosticOptions::Vi: OS << " +" << LineNo; break; |
824 | 22.3k | } |
825 | | |
826 | 22.3k | if (DiagOpts->ShowColumn) |
827 | | // Compute the column number. |
828 | 22.3k | if (unsigned ColNo = PLoc.getColumn()) { |
829 | 22.3k | if (DiagOpts->getFormat() == DiagnosticOptions::MSVC) { |
830 | 19 | OS << ','; |
831 | | // Visual Studio 2010 or earlier expects column number to be off by one |
832 | 19 | if (LangOpts.MSCompatibilityVersion && |
833 | 19 | !LangOpts.isCompatibleWithMSVC(LangOptions::MSVC2012)17 ) |
834 | 6 | ColNo--; |
835 | 19 | } else |
836 | 22.3k | OS << ':'; |
837 | 22.3k | OS << ColNo; |
838 | 22.3k | } |
839 | 22.3k | switch (DiagOpts->getFormat()) { |
840 | 22.2k | case DiagnosticOptions::Clang: |
841 | 22.3k | case DiagnosticOptions::Vi: OS << ':'; break; |
842 | 21 | case DiagnosticOptions::MSVC: |
843 | | // MSVC2013 and before print 'file(4) : error'. MSVC2015 gets rid of the |
844 | | // space and prints 'file(4): error'. |
845 | 21 | OS << ')'; |
846 | 21 | if (LangOpts.MSCompatibilityVersion && |
847 | 21 | !LangOpts.isCompatibleWithMSVC(LangOptions::MSVC2015)19 ) |
848 | 8 | OS << ' '; |
849 | 21 | OS << ':'; |
850 | 21 | break; |
851 | 22.3k | } |
852 | | |
853 | 22.3k | if (DiagOpts->ShowSourceRanges && !Ranges.empty()17 ) { |
854 | 9 | FileID CaretFileID = Loc.getExpansionLoc().getFileID(); |
855 | 9 | bool PrintedRange = false; |
856 | | |
857 | 9 | for (ArrayRef<CharSourceRange>::const_iterator RI = Ranges.begin(), |
858 | 9 | RE = Ranges.end(); |
859 | 24 | RI != RE; ++RI15 ) { |
860 | | // Ignore invalid ranges. |
861 | 15 | if (!RI->isValid()) continue2 ; |
862 | | |
863 | 13 | auto &SM = Loc.getManager(); |
864 | 13 | SourceLocation B = SM.getExpansionLoc(RI->getBegin()); |
865 | 13 | CharSourceRange ERange = SM.getExpansionRange(RI->getEnd()); |
866 | 13 | SourceLocation E = ERange.getEnd(); |
867 | 13 | bool IsTokenRange = ERange.isTokenRange(); |
868 | | |
869 | 13 | std::pair<FileID, unsigned> BInfo = SM.getDecomposedLoc(B); |
870 | 13 | std::pair<FileID, unsigned> EInfo = SM.getDecomposedLoc(E); |
871 | | |
872 | | // If the start or end of the range is in another file, just discard |
873 | | // it. |
874 | 13 | if (BInfo.first != CaretFileID || EInfo.first != CaretFileID) |
875 | 0 | continue; |
876 | | |
877 | | // Add in the length of the token, so that we cover multi-char |
878 | | // tokens. |
879 | 13 | unsigned TokSize = 0; |
880 | 13 | if (IsTokenRange) |
881 | 9 | TokSize = Lexer::MeasureTokenLength(E, SM, LangOpts); |
882 | | |
883 | 13 | FullSourceLoc BF(B, SM), EF(E, SM); |
884 | 13 | OS << '{' |
885 | 13 | << BF.getLineNumber() << ':' << BF.getColumnNumber() << '-' |
886 | 13 | << EF.getLineNumber() << ':' << (EF.getColumnNumber() + TokSize) |
887 | 13 | << '}'; |
888 | 13 | PrintedRange = true; |
889 | 13 | } |
890 | | |
891 | 9 | if (PrintedRange) |
892 | 9 | OS << ':'; |
893 | 9 | } |
894 | 22.3k | OS << ' '; |
895 | 22.3k | } |
896 | | |
897 | 551 | void TextDiagnostic::emitIncludeLocation(FullSourceLoc Loc, PresumedLoc PLoc) { |
898 | 551 | if (DiagOpts->ShowLocation && PLoc.isValid()) |
899 | 551 | OS << "In file included from " << PLoc.getFilename() << ':' |
900 | 551 | << PLoc.getLine() << ":\n"; |
901 | 0 | else |
902 | 0 | OS << "In included file:\n"; |
903 | 551 | } |
904 | | |
905 | | void TextDiagnostic::emitImportLocation(FullSourceLoc Loc, PresumedLoc PLoc, |
906 | 27 | StringRef ModuleName) { |
907 | 27 | if (DiagOpts->ShowLocation && PLoc.isValid()) |
908 | 22 | OS << "In module '" << ModuleName << "' imported from " |
909 | 22 | << PLoc.getFilename() << ':' << PLoc.getLine() << ":\n"; |
910 | 5 | else |
911 | 5 | OS << "In module '" << ModuleName << "':\n"; |
912 | 27 | } |
913 | | |
914 | | void TextDiagnostic::emitBuildingModuleLocation(FullSourceLoc Loc, |
915 | | PresumedLoc PLoc, |
916 | 107 | StringRef ModuleName) { |
917 | 107 | if (DiagOpts->ShowLocation && PLoc.isValid()) |
918 | 96 | OS << "While building module '" << ModuleName << "' imported from " |
919 | 96 | << PLoc.getFilename() << ':' << PLoc.getLine() << ":\n"; |
920 | 11 | else |
921 | 11 | OS << "While building module '" << ModuleName << "':\n"; |
922 | 107 | } |
923 | | |
924 | | /// Find the suitable set of lines to show to include a set of ranges. |
925 | | static llvm::Optional<std::pair<unsigned, unsigned>> |
926 | | findLinesForRange(const CharSourceRange &R, FileID FID, |
927 | 15.2k | const SourceManager &SM) { |
928 | 15.2k | if (!R.isValid()) return None0 ; |
929 | | |
930 | 15.2k | SourceLocation Begin = R.getBegin(); |
931 | 15.2k | SourceLocation End = R.getEnd(); |
932 | 15.2k | if (SM.getFileID(Begin) != FID || SM.getFileID(End) != FID) |
933 | 0 | return None; |
934 | | |
935 | 15.2k | return std::make_pair(SM.getExpansionLineNumber(Begin), |
936 | 15.2k | SM.getExpansionLineNumber(End)); |
937 | 15.2k | } |
938 | | |
939 | | /// Add as much of range B into range A as possible without exceeding a maximum |
940 | | /// size of MaxRange. Ranges are inclusive. |
941 | | static std::pair<unsigned, unsigned> |
942 | | maybeAddRange(std::pair<unsigned, unsigned> A, std::pair<unsigned, unsigned> B, |
943 | 15.2k | unsigned MaxRange) { |
944 | | // If A is already the maximum size, we're done. |
945 | 15.2k | unsigned Slack = MaxRange - (A.second - A.first + 1); |
946 | 15.2k | if (Slack == 0) |
947 | 15.2k | return A; |
948 | | |
949 | | // Easy case: merge succeeds within MaxRange. |
950 | 29 | unsigned Min = std::min(A.first, B.first); |
951 | 29 | unsigned Max = std::max(A.second, B.second); |
952 | 29 | if (Max - Min + 1 <= MaxRange) |
953 | 24 | return {Min, Max}; |
954 | | |
955 | | // If we can't reach B from A within MaxRange, there's nothing to do. |
956 | | // Don't add lines to the range that contain nothing interesting. |
957 | 5 | if ((B.first > A.first && B.first - A.first + 1 > MaxRange2 ) || |
958 | 5 | (B.second < A.second && A.second - B.second + 1 > MaxRange1 )) |
959 | 1 | return A; |
960 | | |
961 | | // Otherwise, expand A towards B to produce a range of size MaxRange. We |
962 | | // attempt to expand by the same amount in both directions if B strictly |
963 | | // contains A. |
964 | | |
965 | | // Expand downwards by up to half the available amount, then upwards as |
966 | | // much as possible, then downwards as much as possible. |
967 | 4 | A.second = std::min(A.second + (Slack + 1) / 2, Max); |
968 | 4 | Slack = MaxRange - (A.second - A.first + 1); |
969 | 4 | A.first = std::max(Min + Slack, A.first) - Slack; |
970 | 4 | A.second = std::min(A.first + MaxRange - 1, Max); |
971 | 4 | return A; |
972 | 5 | } |
973 | | |
974 | | /// Highlight a SourceRange (with ~'s) for any characters on LineNo. |
975 | | static void highlightRange(const CharSourceRange &R, |
976 | | unsigned LineNo, FileID FID, |
977 | | const SourceColumnMap &map, |
978 | | std::string &CaretLine, |
979 | | const SourceManager &SM, |
980 | 14.4k | const LangOptions &LangOpts) { |
981 | 14.4k | if (!R.isValid()) return0 ; |
982 | | |
983 | 14.4k | SourceLocation Begin = R.getBegin(); |
984 | 14.4k | SourceLocation End = R.getEnd(); |
985 | | |
986 | 14.4k | unsigned StartLineNo = SM.getExpansionLineNumber(Begin); |
987 | 14.4k | if (StartLineNo > LineNo || SM.getFileID(Begin) != FID13.4k ) |
988 | 1.07k | return; // No intersection. |
989 | | |
990 | 13.4k | unsigned EndLineNo = SM.getExpansionLineNumber(End); |
991 | 13.4k | if (EndLineNo < LineNo || SM.getFileID(End) != FID13.3k ) |
992 | 42 | return; // No intersection. |
993 | | |
994 | | // Compute the column number of the start. |
995 | 13.3k | unsigned StartColNo = 0; |
996 | 13.3k | if (StartLineNo == LineNo) { |
997 | 13.3k | StartColNo = SM.getExpansionColumnNumber(Begin); |
998 | 13.3k | if (StartColNo) --StartColNo; // Zero base the col #. |
999 | 13.3k | } |
1000 | | |
1001 | | // Compute the column number of the end. |
1002 | 13.3k | unsigned EndColNo = map.getSourceLine().size(); |
1003 | 13.3k | if (EndLineNo == LineNo) { |
1004 | 13.2k | EndColNo = SM.getExpansionColumnNumber(End); |
1005 | 13.2k | if (EndColNo) { |
1006 | 13.2k | --EndColNo; // Zero base the col #. |
1007 | | |
1008 | | // Add in the length of the token, so that we cover multi-char tokens if |
1009 | | // this is a token range. |
1010 | 13.2k | if (R.isTokenRange()) |
1011 | 9.19k | EndColNo += Lexer::MeasureTokenLength(End, SM, LangOpts); |
1012 | 13.2k | } else { |
1013 | 0 | EndColNo = CaretLine.size(); |
1014 | 0 | } |
1015 | 13.2k | } |
1016 | | |
1017 | 13.3k | assert(StartColNo <= EndColNo && "Invalid range!"); |
1018 | | |
1019 | | // Check that a token range does not highlight only whitespace. |
1020 | 13.3k | if (R.isTokenRange()) { |
1021 | | // Pick the first non-whitespace column. |
1022 | 9.24k | while (StartColNo < map.getSourceLine().size() && |
1023 | 9.24k | (map.getSourceLine()[StartColNo] == ' ' || |
1024 | 9.24k | map.getSourceLine()[StartColNo] == '\t'9.23k )) |
1025 | 8 | StartColNo = map.startOfNextColumn(StartColNo); |
1026 | | |
1027 | | // Pick the last non-whitespace column. |
1028 | 9.23k | if (EndColNo > map.getSourceLine().size()) |
1029 | 1 | EndColNo = map.getSourceLine().size(); |
1030 | 9.23k | while (EndColNo && |
1031 | 9.23k | (map.getSourceLine()[EndColNo-1] == ' ' || |
1032 | 9.23k | map.getSourceLine()[EndColNo-1] == '\t')) |
1033 | 0 | EndColNo = map.startOfPreviousColumn(EndColNo); |
1034 | | |
1035 | | // If the start/end passed each other, then we are trying to highlight a |
1036 | | // range that just exists in whitespace. That most likely means we have |
1037 | | // a multi-line highlighting range that covers a blank line. |
1038 | 9.23k | if (StartColNo > EndColNo) { |
1039 | 0 | assert(StartLineNo != EndLineNo && "trying to highlight whitespace"); |
1040 | 0 | StartColNo = EndColNo; |
1041 | 0 | } |
1042 | 9.23k | } |
1043 | | |
1044 | 0 | assert(StartColNo <= map.getSourceLine().size() && "Invalid range!"); |
1045 | 0 | assert(EndColNo <= map.getSourceLine().size() && "Invalid range!"); |
1046 | | |
1047 | | // Fill the range with ~'s. |
1048 | 0 | StartColNo = map.byteToContainingColumn(StartColNo); |
1049 | 13.3k | EndColNo = map.byteToContainingColumn(EndColNo); |
1050 | | |
1051 | 13.3k | assert(StartColNo <= EndColNo && "Invalid range!"); |
1052 | 13.3k | if (CaretLine.size() < EndColNo) |
1053 | 0 | CaretLine.resize(EndColNo,' '); |
1054 | 13.3k | std::fill(CaretLine.begin()+StartColNo,CaretLine.begin()+EndColNo,'~'); |
1055 | 13.3k | } |
1056 | | |
1057 | | static std::string buildFixItInsertionLine(FileID FID, |
1058 | | unsigned LineNo, |
1059 | | const SourceColumnMap &map, |
1060 | | ArrayRef<FixItHint> Hints, |
1061 | | const SourceManager &SM, |
1062 | 20.4k | const DiagnosticOptions *DiagOpts) { |
1063 | 20.4k | std::string FixItInsertionLine; |
1064 | 20.4k | if (Hints.empty() || !DiagOpts->ShowFixits3.08k ) |
1065 | 17.3k | return FixItInsertionLine; |
1066 | 3.08k | unsigned PrevHintEndCol = 0; |
1067 | | |
1068 | 3.08k | for (ArrayRef<FixItHint>::iterator I = Hints.begin(), E = Hints.end(); |
1069 | 6.84k | I != E; ++I3.76k ) { |
1070 | 3.76k | if (!I->CodeToInsert.empty()) { |
1071 | | // We have an insertion hint. Determine whether the inserted |
1072 | | // code contains no newlines and is on the same line as the caret. |
1073 | 3.18k | std::pair<FileID, unsigned> HintLocInfo |
1074 | 3.18k | = SM.getDecomposedExpansionLoc(I->RemoveRange.getBegin()); |
1075 | 3.18k | if (FID == HintLocInfo.first && |
1076 | 3.18k | LineNo == SM.getLineNumber(HintLocInfo.first, HintLocInfo.second)3.18k && |
1077 | 3.18k | StringRef(I->CodeToInsert).find_first_of("\n\r") == StringRef::npos3.07k ) { |
1078 | | // Insert the new code into the line just below the code |
1079 | | // that the user wrote. |
1080 | | // Note: When modifying this function, be very careful about what is a |
1081 | | // "column" (printed width, platform-dependent) and what is a |
1082 | | // "byte offset" (SourceManager "column"). |
1083 | 3.01k | unsigned HintByteOffset |
1084 | 3.01k | = SM.getColumnNumber(HintLocInfo.first, HintLocInfo.second) - 1; |
1085 | | |
1086 | | // The hint must start inside the source or right at the end |
1087 | 3.01k | assert(HintByteOffset < static_cast<unsigned>(map.bytes())+1); |
1088 | 0 | unsigned HintCol = map.byteToContainingColumn(HintByteOffset); |
1089 | | |
1090 | | // If we inserted a long previous hint, push this one forwards, and add |
1091 | | // an extra space to show that this is not part of the previous |
1092 | | // completion. This is sort of the best we can do when two hints appear |
1093 | | // to overlap. |
1094 | | // |
1095 | | // Note that if this hint is located immediately after the previous |
1096 | | // hint, no space will be added, since the location is more important. |
1097 | 3.01k | if (HintCol < PrevHintEndCol) |
1098 | 85 | HintCol = PrevHintEndCol + 1; |
1099 | | |
1100 | | // This should NOT use HintByteOffset, because the source might have |
1101 | | // Unicode characters in earlier columns. |
1102 | 3.01k | unsigned NewFixItLineSize = FixItInsertionLine.size() + |
1103 | 3.01k | (HintCol - PrevHintEndCol) + I->CodeToInsert.size(); |
1104 | 3.01k | if (NewFixItLineSize > FixItInsertionLine.size()) |
1105 | 3.01k | FixItInsertionLine.resize(NewFixItLineSize, ' '); |
1106 | | |
1107 | 3.01k | std::copy(I->CodeToInsert.begin(), I->CodeToInsert.end(), |
1108 | 3.01k | FixItInsertionLine.end() - I->CodeToInsert.size()); |
1109 | | |
1110 | 3.01k | PrevHintEndCol = |
1111 | 3.01k | HintCol + llvm::sys::locale::columnWidth(I->CodeToInsert); |
1112 | 3.01k | } |
1113 | 3.18k | } |
1114 | 3.76k | } |
1115 | | |
1116 | 3.08k | expandTabs(FixItInsertionLine, DiagOpts->TabStop); |
1117 | | |
1118 | 3.08k | return FixItInsertionLine; |
1119 | 20.4k | } |
1120 | | |
1121 | | /// Emit a code snippet and caret line. |
1122 | | /// |
1123 | | /// This routine emits a single line's code snippet and caret line.. |
1124 | | /// |
1125 | | /// \param Loc The location for the caret. |
1126 | | /// \param Ranges The underlined ranges for this code snippet. |
1127 | | /// \param Hints The FixIt hints active for this diagnostic. |
1128 | | void TextDiagnostic::emitSnippetAndCaret( |
1129 | | FullSourceLoc Loc, DiagnosticsEngine::Level Level, |
1130 | 22.3k | SmallVectorImpl<CharSourceRange> &Ranges, ArrayRef<FixItHint> Hints) { |
1131 | 22.3k | assert(Loc.isValid() && "must have a valid source location here"); |
1132 | 0 | assert(Loc.isFileID() && "must have a file location here"); |
1133 | | |
1134 | | // If caret diagnostics are enabled and we have location, we want to |
1135 | | // emit the caret. However, we only do this if the location moved |
1136 | | // from the last diagnostic, if the last diagnostic was a note that |
1137 | | // was part of a different warning or error diagnostic, or if the |
1138 | | // diagnostic has ranges. We don't want to emit the same caret |
1139 | | // multiple times if one loc has multiple diagnostics. |
1140 | 22.3k | if (!DiagOpts->ShowCarets) |
1141 | 77 | return; |
1142 | 22.2k | if (Loc == LastLoc && Ranges.empty()2.72k && Hints.empty()1.34k && |
1143 | 22.2k | (1.34k LastLevel != DiagnosticsEngine::Note1.34k || Level == LastLevel68 )) |
1144 | 1.33k | return; |
1145 | | |
1146 | | // Decompose the location into a FID/Offset pair. |
1147 | 20.9k | std::pair<FileID, unsigned> LocInfo = Loc.getDecomposedLoc(); |
1148 | 20.9k | FileID FID = LocInfo.first; |
1149 | 20.9k | const SourceManager &SM = Loc.getManager(); |
1150 | | |
1151 | | // Get information about the buffer it points into. |
1152 | 20.9k | bool Invalid = false; |
1153 | 20.9k | StringRef BufData = Loc.getBufferData(&Invalid); |
1154 | 20.9k | if (Invalid) |
1155 | 2 | return; |
1156 | | |
1157 | 20.9k | unsigned CaretLineNo = Loc.getLineNumber(); |
1158 | 20.9k | unsigned CaretColNo = Loc.getColumnNumber(); |
1159 | | |
1160 | | // Arbitrarily stop showing snippets when the line is too long. |
1161 | 20.9k | static const size_t MaxLineLengthToPrint = 4096; |
1162 | 20.9k | if (CaretColNo > MaxLineLengthToPrint) |
1163 | 96 | return; |
1164 | | |
1165 | | // Find the set of lines to include. |
1166 | 20.8k | const unsigned MaxLines = DiagOpts->SnippetLineLimit; |
1167 | 20.8k | std::pair<unsigned, unsigned> Lines = {CaretLineNo, CaretLineNo}; |
1168 | 20.8k | for (SmallVectorImpl<CharSourceRange>::iterator I = Ranges.begin(), |
1169 | 20.8k | E = Ranges.end(); |
1170 | 36.1k | I != E; ++I15.2k ) |
1171 | 15.2k | if (auto OptionalRange = findLinesForRange(*I, FID, SM)) |
1172 | 15.2k | Lines = maybeAddRange(Lines, *OptionalRange, MaxLines); |
1173 | | |
1174 | 41.2k | for (unsigned LineNo = Lines.first; LineNo != Lines.second + 1; ++LineNo20.4k ) { |
1175 | 20.8k | const char *BufStart = BufData.data(); |
1176 | 20.8k | const char *BufEnd = BufStart + BufData.size(); |
1177 | | |
1178 | | // Rewind from the current position to the start of the line. |
1179 | 20.8k | const char *LineStart = |
1180 | 20.8k | BufStart + |
1181 | 20.8k | SM.getDecomposedLoc(SM.translateLineCol(FID, LineNo, 1)).second; |
1182 | 20.8k | if (LineStart == BufEnd) |
1183 | 19 | break; |
1184 | | |
1185 | | // Compute the line end. |
1186 | 20.8k | const char *LineEnd = LineStart; |
1187 | 3.19M | while (*LineEnd != '\n' && *LineEnd != '\r'3.17M && LineEnd != BufEnd3.17M ) |
1188 | 3.17M | ++LineEnd; |
1189 | | |
1190 | | // Arbitrarily stop showing snippets when the line is too long. |
1191 | | // FIXME: Don't print any lines in this case. |
1192 | 20.8k | if (size_t(LineEnd - LineStart) > MaxLineLengthToPrint) |
1193 | 429 | return; |
1194 | | |
1195 | | // Trim trailing null-bytes. |
1196 | 20.4k | StringRef Line(LineStart, LineEnd - LineStart); |
1197 | 76.5k | while (!Line.empty() && Line.back() == '\0'76.5k && |
1198 | 76.5k | (56.1k LineNo != CaretLineNo56.1k || Line.size() > CaretColNo56.1k )) |
1199 | 56.1k | Line = Line.drop_back(); |
1200 | | |
1201 | | // Copy the line of code into an std::string for ease of manipulation. |
1202 | 20.4k | std::string SourceLine(Line.begin(), Line.end()); |
1203 | | |
1204 | | // Build the byte to column map. |
1205 | 20.4k | const SourceColumnMap sourceColMap(SourceLine, DiagOpts->TabStop); |
1206 | | |
1207 | | // Create a line for the caret that is filled with spaces that is the same |
1208 | | // number of columns as the line of source code. |
1209 | 20.4k | std::string CaretLine(sourceColMap.columns(), ' '); |
1210 | | |
1211 | | // Highlight all of the characters covered by Ranges with ~ characters. |
1212 | 20.4k | for (SmallVectorImpl<CharSourceRange>::iterator I = Ranges.begin(), |
1213 | 20.4k | E = Ranges.end(); |
1214 | 34.8k | I != E; ++I14.4k ) |
1215 | 14.4k | highlightRange(*I, LineNo, FID, sourceColMap, CaretLine, SM, LangOpts); |
1216 | | |
1217 | | // Next, insert the caret itself. |
1218 | 20.4k | if (CaretLineNo == LineNo) { |
1219 | 20.3k | CaretColNo = sourceColMap.byteToContainingColumn(CaretColNo - 1); |
1220 | 20.3k | if (CaretLine.size() < CaretColNo + 1) |
1221 | 376 | CaretLine.resize(CaretColNo + 1, ' '); |
1222 | 20.3k | CaretLine[CaretColNo] = '^'; |
1223 | 20.3k | } |
1224 | | |
1225 | 20.4k | std::string FixItInsertionLine = buildFixItInsertionLine( |
1226 | 20.4k | FID, LineNo, sourceColMap, Hints, SM, DiagOpts.get()); |
1227 | | |
1228 | | // If the source line is too long for our terminal, select only the |
1229 | | // "interesting" source region within that line. |
1230 | 20.4k | unsigned Columns = DiagOpts->MessageLength; |
1231 | 20.4k | if (Columns) |
1232 | 42 | selectInterestingSourceRegion(SourceLine, CaretLine, FixItInsertionLine, |
1233 | 42 | Columns, sourceColMap); |
1234 | | |
1235 | | // If we are in -fdiagnostics-print-source-range-info mode, we are trying |
1236 | | // to produce easily machine parsable output. Add a space before the |
1237 | | // source line and the caret to make it trivial to tell the main diagnostic |
1238 | | // line from what the user is intended to see. |
1239 | 20.4k | if (DiagOpts->ShowSourceRanges) { |
1240 | 16 | SourceLine = ' ' + SourceLine; |
1241 | 16 | CaretLine = ' ' + CaretLine; |
1242 | 16 | } |
1243 | | |
1244 | | // Finally, remove any blank spaces from the end of CaretLine. |
1245 | 523k | while (!CaretLine.empty() && CaretLine[CaretLine.size() - 1] == ' '523k ) |
1246 | 502k | CaretLine.erase(CaretLine.end() - 1); |
1247 | | |
1248 | | // Emit what we have computed. |
1249 | 20.4k | emitSnippet(SourceLine); |
1250 | | |
1251 | 20.4k | if (!CaretLine.empty()) { |
1252 | 20.3k | if (DiagOpts->ShowColors) |
1253 | 51 | OS.changeColor(caretColor, true); |
1254 | 20.3k | OS << CaretLine << '\n'; |
1255 | 20.3k | if (DiagOpts->ShowColors) |
1256 | 51 | OS.resetColor(); |
1257 | 20.3k | } |
1258 | | |
1259 | 20.4k | if (!FixItInsertionLine.empty()) { |
1260 | 2.51k | if (DiagOpts->ShowColors) |
1261 | | // Print fixit line in color |
1262 | 0 | OS.changeColor(fixitColor, false); |
1263 | 2.51k | if (DiagOpts->ShowSourceRanges) |
1264 | 4 | OS << ' '; |
1265 | 2.51k | OS << FixItInsertionLine << '\n'; |
1266 | 2.51k | if (DiagOpts->ShowColors) |
1267 | 0 | OS.resetColor(); |
1268 | 2.51k | } |
1269 | 20.4k | } |
1270 | | |
1271 | | // Print out any parseable fixit information requested by the options. |
1272 | 20.3k | emitParseableFixits(Hints, SM); |
1273 | 20.3k | } |
1274 | | |
1275 | 20.4k | void TextDiagnostic::emitSnippet(StringRef line) { |
1276 | 20.4k | if (line.empty()) |
1277 | 1 | return; |
1278 | | |
1279 | 20.4k | size_t i = 0; |
1280 | | |
1281 | 20.4k | std::string to_print; |
1282 | 20.4k | bool print_reversed = false; |
1283 | | |
1284 | 956k | while (i<line.size()) { |
1285 | 935k | std::pair<SmallString<16>,bool> res |
1286 | 935k | = printableTextForNextCharacter(line, &i, DiagOpts->TabStop); |
1287 | 935k | bool was_printable = res.second; |
1288 | | |
1289 | 935k | if (DiagOpts->ShowColors && was_printable == print_reversed1.62k ) { |
1290 | 0 | if (print_reversed) |
1291 | 0 | OS.reverseColor(); |
1292 | 0 | OS << to_print; |
1293 | 0 | to_print.clear(); |
1294 | 0 | if (DiagOpts->ShowColors) |
1295 | 0 | OS.resetColor(); |
1296 | 0 | } |
1297 | | |
1298 | 935k | print_reversed = !was_printable; |
1299 | 935k | to_print += res.first.str(); |
1300 | 935k | } |
1301 | | |
1302 | 20.4k | if (print_reversed && DiagOpts->ShowColors1 ) |
1303 | 0 | OS.reverseColor(); |
1304 | 20.4k | OS << to_print; |
1305 | 20.4k | if (print_reversed && DiagOpts->ShowColors1 ) |
1306 | 0 | OS.resetColor(); |
1307 | | |
1308 | 20.4k | OS << '\n'; |
1309 | 20.4k | } |
1310 | | |
1311 | | void TextDiagnostic::emitParseableFixits(ArrayRef<FixItHint> Hints, |
1312 | 20.3k | const SourceManager &SM) { |
1313 | 20.3k | if (!DiagOpts->ShowParseableFixits) |
1314 | 17.3k | return; |
1315 | | |
1316 | | // We follow FixItRewriter's example in not (yet) handling |
1317 | | // fix-its in macros. |
1318 | 3.00k | for (ArrayRef<FixItHint>::iterator I = Hints.begin(), E = Hints.end(); |
1319 | 4.87k | I != E; ++I1.87k ) { |
1320 | 1.87k | if (I->RemoveRange.isInvalid() || |
1321 | 1.87k | I->RemoveRange.getBegin().isMacroID() || |
1322 | 1.87k | I->RemoveRange.getEnd().isMacroID()) |
1323 | 0 | return; |
1324 | 1.87k | } |
1325 | | |
1326 | 3.00k | for (ArrayRef<FixItHint>::iterator I = Hints.begin(), E = Hints.end(); |
1327 | 4.87k | I != E; ++I1.87k ) { |
1328 | 1.87k | SourceLocation BLoc = I->RemoveRange.getBegin(); |
1329 | 1.87k | SourceLocation ELoc = I->RemoveRange.getEnd(); |
1330 | | |
1331 | 1.87k | std::pair<FileID, unsigned> BInfo = SM.getDecomposedLoc(BLoc); |
1332 | 1.87k | std::pair<FileID, unsigned> EInfo = SM.getDecomposedLoc(ELoc); |
1333 | | |
1334 | | // Adjust for token ranges. |
1335 | 1.87k | if (I->RemoveRange.isTokenRange()) |
1336 | 0 | EInfo.second += Lexer::MeasureTokenLength(ELoc, SM, LangOpts); |
1337 | | |
1338 | | // We specifically do not do word-wrapping or tab-expansion here, |
1339 | | // because this is supposed to be easy to parse. |
1340 | 1.87k | PresumedLoc PLoc = SM.getPresumedLoc(BLoc); |
1341 | 1.87k | if (PLoc.isInvalid()) |
1342 | 0 | break; |
1343 | | |
1344 | 1.87k | OS << "fix-it:\""; |
1345 | 1.87k | OS.write_escaped(PLoc.getFilename()); |
1346 | 1.87k | OS << "\":{" << SM.getLineNumber(BInfo.first, BInfo.second) |
1347 | 1.87k | << ':' << SM.getColumnNumber(BInfo.first, BInfo.second) |
1348 | 1.87k | << '-' << SM.getLineNumber(EInfo.first, EInfo.second) |
1349 | 1.87k | << ':' << SM.getColumnNumber(EInfo.first, EInfo.second) |
1350 | 1.87k | << "}:\""; |
1351 | 1.87k | OS.write_escaped(I->CodeToInsert); |
1352 | 1.87k | OS << "\"\n"; |
1353 | 1.87k | } |
1354 | 3.00k | } |