Skip to content

Commit d36f0c1

Browse files
authored
Merge pull request #2512 from ahoppen/ahoppen/character-column
Use character columns instead of UTF-8 columns in the diagnostics printer
2 parents 84a4bed + 7676255 commit d36f0c1

File tree

2 files changed

+56
-3
lines changed

2 files changed

+56
-3
lines changed

Sources/SwiftDiagnostics/DiagnosticsFormatter.swift

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,22 @@ public struct DiagnosticsFormatter {
6868
var isFreeOfAnnotations: Bool {
6969
return diagnostics.isEmpty && suffixText.isEmpty
7070
}
71+
72+
/// Converts a UTF-8 column index into an index that considers each character as a single column, not each UTF-8
73+
/// byte.
74+
///
75+
/// For example the 👨‍👩‍👧‍👦 character is considered as a single character, not 25 bytes.
76+
///
77+
/// Both the input and the output column are 1-based.
78+
func characterColumn(ofUtf8Column utf8Column: Int) -> Int {
79+
let index =
80+
sourceString.utf8.index(
81+
sourceString.utf8.startIndex,
82+
offsetBy: utf8Column - 1,
83+
limitedBy: sourceString.utf8.endIndex
84+
) ?? sourceString.utf8.endIndex
85+
return sourceString.distance(from: sourceString.startIndex, to: index) + 1
86+
}
7187
}
7288

7389
/// Number of lines which should be printed before and after the diagnostic message
@@ -139,7 +155,7 @@ public struct DiagnosticsFormatter {
139155

140156
let endColumn: Int
141157
if endLine > lineNumber {
142-
endColumn = annotatedLine.sourceString.count
158+
endColumn = annotatedLine.sourceString.utf8.count
143159
} else if endLine == lineNumber {
144160
endColumn = endLoc.column
145161
} else {
@@ -274,9 +290,13 @@ public struct DiagnosticsFormatter {
274290
annotatedSource.append("\n")
275291
}
276292

277-
let columnsWithDiagnostics = Set(annotatedLine.diagnostics.map { $0.location(converter: slc).column })
293+
let columnsWithDiagnostics = Set(
294+
annotatedLine.diagnostics.map {
295+
annotatedLine.characterColumn(ofUtf8Column: $0.location(converter: slc).column)
296+
}
297+
)
278298
let diagsPerColumn = Dictionary(grouping: annotatedLine.diagnostics) { diag in
279-
diag.location(converter: slc).column
299+
annotatedLine.characterColumn(ofUtf8Column: diag.location(converter: slc).column)
280300
}.sorted { lhs, rhs in
281301
lhs.key > rhs.key
282302
}

Tests/SwiftDiagnosticsTest/ParserDiagnosticsFormatterIntegrationTests.swift

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,4 +150,37 @@ final class ParserDiagnosticsFormatterIntegrationTests: XCTestCase {
150150

151151
assertStringsEqualWithDiff(annotate(source: source), expectedOutput)
152152
}
153+
154+
func testDontCrashIfFullLineHighlightContainsEmoji() {
155+
let source = """
156+
func o() {
157+
}👨‍👩‍👧‍👦}
158+
}
159+
"""
160+
161+
let expectedOutput = """
162+
1 │ func o() {
163+
2 │ }👨‍👩‍👧‍👦}
164+
│ │╰─ error: extraneous braces at top level
165+
│ ╰─ error: consecutive statements on a line must be separated by newline or ';'
166+
3 │ }
167+
168+
"""
169+
170+
assertStringsEqualWithDiff(annotate(source: source), expectedOutput)
171+
}
172+
173+
func testEmojiInSourceCode() {
174+
let source = """
175+
let 👨‍👩‍👧‍👦 = ;
176+
"""
177+
178+
let expectedOutput = """
179+
1 │ let 👨‍👩‍👧‍👦 = ;
180+
│ ╰─ error: expected expression in variable
181+
182+
"""
183+
184+
assertStringsEqualWithDiff(annotate(source: source), expectedOutput)
185+
}
153186
}

0 commit comments

Comments
 (0)