Skip to content

Commit b8a87f6

Browse files
authored
Merge pull request swiftlang#30206 from owenv/format-multiline-fixits
[Diag-Experimental-Formatting] Better rendering of multi-line fix-its
2 parents 0dd56a9 + d220ac8 commit b8a87f6

File tree

2 files changed

+104
-14
lines changed

2 files changed

+104
-14
lines changed

lib/Frontend/PrintingDiagnosticConsumer.cpp

Lines changed: 55 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -124,16 +124,31 @@ namespace {
124124
Out.resetColor();
125125
}
126126

127+
static void printStringAsSingleQuotedLine(StringRef str, raw_ostream &Out) {
128+
Out << "'";
129+
for (auto character : str) {
130+
if (character == '\n')
131+
Out << "\\n";
132+
else
133+
Out << character;
134+
}
135+
Out << "'";
136+
}
137+
127138
// Describe a fix-it out-of-line.
128139
static void describeFixIt(SourceManager &SM, DiagnosticInfo::FixIt fixIt,
129140
raw_ostream &Out) {
130141
if (fixIt.getRange().getByteLength() == 0) {
131-
Out << "insert '" << fixIt.getText() << "'";
142+
Out << "insert ";
143+
printStringAsSingleQuotedLine(fixIt.getText(), Out);
132144
} else if (fixIt.getText().empty()) {
133-
Out << "remove '" << SM.extractText(fixIt.getRange()) << "'";
145+
Out << "remove ";
146+
printStringAsSingleQuotedLine(SM.extractText(fixIt.getRange()), Out);
134147
} else {
135-
Out << "replace '" << SM.extractText(fixIt.getRange()) << "' with '"
136-
<< fixIt.getText() << "'";
148+
Out << "replace ";
149+
printStringAsSingleQuotedLine(SM.extractText(fixIt.getRange()), Out);
150+
Out << " with ";
151+
printStringAsSingleQuotedLine(fixIt.getText(), Out);
137152
}
138153
}
139154

@@ -280,6 +295,11 @@ namespace {
280295
// line.
281296
unsigned *byteToColumnMap = new unsigned[LineText.size() + 1];
282297
unsigned extraColumns = 0;
298+
// Track the location of the first character in the line that is not a
299+
// whitespace character. This can be used to avoid underlining leading
300+
// whitespace, which looks weird even though it is technically accurate.
301+
unsigned firstNonWhitespaceColumn = 0;
302+
bool seenNonWhitespaceCharacter = false;
283303
// We count one past the end of LineText here to handle trailing fix-it
284304
// insertions.
285305
for (unsigned i = 0; i < LineText.size() + 1; ++i) {
@@ -294,9 +314,17 @@ namespace {
294314
}
295315
}
296316
}
297-
// Tabs are mapped to 2 spaces so they have a known column width.
298-
if (i < LineText.size() && LineText[i] == '\t')
299-
extraColumns += 1;
317+
318+
if (i < LineText.size()) {
319+
// Tabs are mapped to 2 spaces so they have a known column width.
320+
if (LineText[i] == '\t')
321+
extraColumns += 1;
322+
323+
if (!seenNonWhitespaceCharacter && !isspace(LineText[i])) {
324+
firstNonWhitespaceColumn = i + extraColumns;
325+
seenNonWhitespaceCharacter = true;
326+
}
327+
}
300328

301329
byteToColumnMap[i] = i + extraColumns;
302330
}
@@ -325,18 +353,28 @@ namespace {
325353
if (isASCII) {
326354
auto highlightLine = std::string(byteToColumnMap[LineText.size()], ' ');
327355
for (auto highlight : Highlights) {
328-
for (unsigned i = highlight.StartByte; i < highlight.EndByte; ++i)
356+
for (unsigned i =
357+
std::max(highlight.StartByte, firstNonWhitespaceColumn);
358+
i < highlight.EndByte; ++i)
329359
highlightLine[byteToColumnMap[i]] = '~';
330360
}
331361

332362
for (auto fixIt : FixIts) {
333363
// Mark deletions.
334-
for (unsigned i = fixIt.StartByte; i < fixIt.EndByte; ++i)
364+
for (unsigned i = std::max(fixIt.StartByte, firstNonWhitespaceColumn);
365+
i < fixIt.EndByte; ++i)
335366
highlightLine[byteToColumnMap[i]] = '-';
336-
337-
// Mark insertions.
338-
for (unsigned i = byteToColumnMap[fixIt.StartByte - 1] + 1;
339-
i < byteToColumnMap[fixIt.StartByte]; ++i)
367+
// Mark insertions. If the fix-it starts at the beginning of the line,
368+
// highlight from column zero to the end column. Otherwise, find the
369+
// column which immediately precedes the insertion. Then, highlight
370+
// from the column after that to the end column. The end column in
371+
// this case is obtained from the fix-it's starting byte, because
372+
// insertions are printed before the deleted range.
373+
unsigned startColumn = fixIt.StartByte == 0
374+
? 0
375+
: byteToColumnMap[fixIt.StartByte - 1] + 1;
376+
for (unsigned i = startColumn; i < byteToColumnMap[fixIt.StartByte];
377+
++i)
340378
highlightLine[i] = '+';
341379
}
342380

@@ -640,7 +678,10 @@ static void annotateSnippetWithInfo(SourceManager &SM,
640678
// Don't print inline fix-its for notes.
641679
if (Info.Kind != DiagnosticKind::Note) {
642680
for (auto fixIt : Info.FixIts) {
643-
Snippet.addFixIt(fixIt.getRange(), fixIt.getText());
681+
// Don't print multi-line fix-its inline, only include them at the end of
682+
// the message.
683+
if (fixIt.getText().find("\n") == std::string::npos)
684+
Snippet.addFixIt(fixIt.getRange(), fixIt.getText());
644685
}
645686
}
646687
// Add any explicitly grouped notes to the snippet.

test/diagnostics/pretty-printed-diagnostics.swift

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,20 @@ let 👍👍👍 = {
5151
return y
5252
}
5353
54+
// Multi-line fix-its
55+
foo(b: 1,
56+
a: 2)
57+
58+
foo(b:
59+
1,
60+
a:
61+
2)
62+
63+
foo(b:
64+
1,
65+
a: 2)
66+
67+
5468
// Test fallback for non-ASCII characters.
5569
// CHECK: SOURCE_DIR{{[/\]+}}test{{[/\]+}}diagnostics{{[/\]+}}pretty-printed-diagnostics.swift:[[#LINE:]]:11
5670
// CHECK: [[#LINE-1]] |
@@ -143,6 +157,41 @@ let 👍👍👍 = {
143157
// CHECK: | --> error: unable to infer complex closure return type; add explicit type to disambiguate [insert ' () -> <#Result#> in ']
144158
// CHECK: [[#LINE+1]] | let y = 1
145159

160+
// CHECK: SOURCE_DIR{{[/\]+}}test{{[/\]+}}diagnostics{{[/\]+}}pretty-printed-diagnostics.swift:[[#LINE:]]:5
161+
// CHECK: [[#LINE-2]] | // Multi-line fix-its
162+
// CHECK: [[#LINE-1]] | foo(a: 2, b: 1,
163+
// CHECK: | ++++++~~~~-
164+
// CHECK: [[#LINE]] | a: 2)
165+
// CHECK: | ----
166+
// CHECK: | ^ error: argument 'a' must precede argument 'b' [remove ',\n a: 2' and insert 'a: 2, ']
167+
// CHECK: [[#LINE+1]] |
168+
169+
170+
// CHECK: SOURCE_DIR{{[/\]+}}test{{[/\]+}}diagnostics{{[/\]+}}pretty-printed-diagnostics.swift:[[#LINE:]]:5
171+
// CHECK: [[#LINE-3]] |
172+
// CHECK: [[#LINE-2]] | foo(b:
173+
// CHECK: | ~~
174+
// CHECK: [[#LINE-1]] | 1,
175+
// CHECK: | ~-
176+
// CHECK: [[#LINE]] | a:
177+
// CHECK: | --
178+
// CHECK: | ^ error: argument 'a' must precede argument 'b' [remove ',\n a:\n 2' and insert 'a:\n 2, ']
179+
// CHECK: [[#LINE+1]] | 2)
180+
// CHECK: | -
181+
// CHECK: [[#LINE+2]] |
182+
183+
184+
// CHECK: SOURCE_DIR{{[/\]+}}test{{[/\]+}}diagnostics{{[/\]+}}pretty-printed-diagnostics.swift:[[#LINE:]]:5
185+
// CHECK: [[#LINE-3]] |
186+
// CHECK: [[#LINE-2]] | foo(a: 2, b:
187+
// CHECK: | ++++++~~
188+
// CHECK: [[#LINE-1]] | 1,
189+
// CHECK: | ~-
190+
// CHECK: [[#LINE]] | a: 2)
191+
// CHECK: | ----
192+
// CHECK: | ^ error: argument 'a' must precede argument 'b' [remove ',\n a: 2' and insert 'a: 2, ']
193+
// CHECK: [[#LINE+1]] |
194+
146195
// CHECK: SOURCE_DIR{{[/\]+}}test{{[/\]+}}diagnostics{{[/\]+}}pretty-printed-diagnostics.swift:[[#LINE:]]:5
147196
// CHECK: [[#LINE-1]] | func foo(a: Int, b: Int) {
148197
// CHECK: [[#LINE]] | a + b

0 commit comments

Comments
 (0)