Skip to content

Commit 9e798fa

Browse files
authored
Merge pull request #2143 from DougGregor/diagnostic-format-improvements
2 parents 13f113e + 9ee9ac3 commit 9e798fa

File tree

8 files changed

+122
-40
lines changed

8 files changed

+122
-40
lines changed

Release Notes/510.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@
1717
- Issue: https://github.com/apple/swift-syntax/issues/1984
1818
- Pull Request: https://github.com/apple/swift-syntax/pull/2112
1919

20+
- `DiagnosticSeverity` and `PluginMessage.Diagnostic.Severity` now have new case named `remark`
21+
- Description: Remarks are used by the Swift compiler and other tools to describe some aspect of translation that doesn't reflect correctness, but may be useful for the user. Remarks have been added to the diagnostic severity enums to align with the Swift compiler.
22+
- Pull Request: https://github.com/apple/swift-syntax/pull/2143
23+
2024
## API Behavior Changes
2125

2226
## Deprecations

Sources/SwiftCompilerPluginMessageHandling/Diagnostics.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ extension PluginMessage.Diagnostic.Severity {
5353
case .error: self = .error
5454
case .warning: self = .warning
5555
case .note: self = .note
56+
case .remark: self = .remark
5657
}
5758
}
5859
}

Sources/SwiftCompilerPluginMessageHandling/PluginMessages.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ public enum PluginMessage {
155155
case error
156156
case warning
157157
case note
158+
case remark
158159
}
159160
public struct Position: Codable {
160161
public var fileName: String

Sources/SwiftDiagnostics/DiagnosticsFormatter.swift

Lines changed: 46 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -182,14 +182,13 @@ public struct DiagnosticsFormatter {
182182
/// - suffixTexts: suffix text to be printed at the given absolute
183183
/// locations within the source file.
184184
func annotatedSource(
185-
fileName: String?,
186185
tree: some SyntaxProtocol,
187186
diags: [Diagnostic],
188187
indentString: String,
189188
suffixTexts: [AbsolutePosition: String],
190189
sourceLocationConverter: SourceLocationConverter? = nil
191190
) -> String {
192-
let slc = sourceLocationConverter ?? SourceLocationConverter(fileName: fileName ?? "", tree: tree)
191+
let slc = sourceLocationConverter ?? SourceLocationConverter(fileName: "<unknown>", tree: tree)
193192

194193
// First, we need to put each line and its diagnostics together
195194
var annotatedSourceLines = [AnnotatedSourceLine]()
@@ -218,20 +217,9 @@ public struct DiagnosticsFormatter {
218217
return nil
219218
}
220219

220+
// Accumulate the fully annotated source files here.
221221
var annotatedSource = ""
222222

223-
// If there was a filename, add it first.
224-
if let fileName {
225-
let header = colorizeBufferOutline("===")
226-
let firstLine =
227-
1
228-
+ (annotatedSourceLines.enumerated().first { (lineIndex, sourceLine) in
229-
!sourceLine.isFreeOfAnnotations
230-
}?.offset ?? 0)
231-
232-
annotatedSource.append("\(indentString)\(header) \(fileName):\(firstLine) \(header)\n")
233-
}
234-
235223
/// Keep track if a line missing char should be printed
236224
var hasLineBeenSkipped = false
237225

@@ -318,7 +306,6 @@ public struct DiagnosticsFormatter {
318306
diags: [Diagnostic]
319307
) -> String {
320308
return annotatedSource(
321-
fileName: nil,
322309
tree: tree,
323310
diags: diags,
324311
indentString: "",
@@ -328,22 +315,36 @@ public struct DiagnosticsFormatter {
328315

329316
/// Annotates the given ``DiagnosticMessage`` with an appropriate ANSI color code (if the value of the `colorize`
330317
/// property is `true`) and returns the result as a printable string.
331-
private func colorizeIfRequested(_ message: DiagnosticMessage) -> String {
332-
switch message.severity {
318+
func colorizeIfRequested(_ message: DiagnosticMessage) -> String {
319+
colorizeIfRequested(severity: message.severity, message: message.message)
320+
}
321+
322+
/// Annotates a diagnostic message with the given severity and text with an appropriate ANSI color code.
323+
func colorizeIfRequested(severity: DiagnosticSeverity, message: String) -> String {
324+
let severityText: String
325+
let severityAnnotation: ANSIAnnotation
326+
327+
switch severity {
333328
case .error:
334-
let annotation = ANSIAnnotation(color: .red, trait: .bold)
335-
return colorizeIfRequested("error: \(message.message)", annotation: annotation)
329+
severityText = "error"
330+
severityAnnotation = .errorText
336331

337332
case .warning:
338-
let color = ANSIAnnotation(color: .yellow)
339-
let prefix = colorizeIfRequested("warning: ", annotation: color.withTrait(.bold))
333+
severityText = "warning"
334+
severityAnnotation = .warningText
340335

341-
return prefix + colorizeIfRequested(message.message, annotation: color);
342336
case .note:
343-
let color = ANSIAnnotation(color: .default, trait: .bold)
344-
let prefix = colorizeIfRequested("note: ", annotation: color)
345-
return prefix + message.message
337+
severityText = "note"
338+
severityAnnotation = .noteText
339+
340+
case .remark:
341+
severityText = "remark"
342+
severityAnnotation = .remarkText
346343
}
344+
345+
let prefix = colorizeIfRequested("\(severityText): ", annotation: severityAnnotation)
346+
347+
return prefix + colorizeIfRequested(message, annotation: .diagnosticText);
347348
}
348349

349350
/// Apply the given color and trait to the specified text, when we are
@@ -421,4 +422,24 @@ struct ANSIAnnotation {
421422
static var sourceHighlight: ANSIAnnotation {
422423
ANSIAnnotation(color: .default, trait: .underline)
423424
}
425+
426+
static var diagnosticText: ANSIAnnotation {
427+
ANSIAnnotation(color: .default, trait: .bold)
428+
}
429+
430+
static var errorText: ANSIAnnotation {
431+
ANSIAnnotation(color: .red, trait: .bold)
432+
}
433+
434+
static var warningText: ANSIAnnotation {
435+
ANSIAnnotation(color: .yellow, trait: .bold)
436+
}
437+
438+
static var noteText: ANSIAnnotation {
439+
ANSIAnnotation(color: .default, trait: .bold)
440+
}
441+
442+
static var remarkText: ANSIAnnotation {
443+
ANSIAnnotation(color: .blue, trait: .bold)
444+
}
424445
}

Sources/SwiftDiagnostics/GroupedDiagnostics.swift

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,32 @@ extension GroupedDiagnostics {
133133
}
134134
}
135135

136+
// Find the "primary" diagnostic that will be shown at the top of the diagnostic
137+
// message. This is typically the error, warning, or remark.
138+
private func findPrimaryDiagnostic(in sourceFile: SourceFile) -> (SourceFile, Diagnostic)? {
139+
// If there is a non-note diagnostic, it's the primary diagnostic.
140+
if let primaryDiag = sourceFile.diagnostics.first(where: { $0.diagMessage.severity != .note }) {
141+
return (sourceFile, primaryDiag)
142+
}
143+
144+
// If one of our child source files has a primary diagnostic, return that.
145+
for childID in sourceFile.children {
146+
if let foundInChild = findPrimaryDiagnostic(in: sourceFiles[childID.id]) {
147+
return foundInChild
148+
}
149+
}
150+
151+
// If this is a root note, take the first note.
152+
if sourceFile.parent == nil,
153+
let note = sourceFile.diagnostics.first
154+
{
155+
return (sourceFile, note)
156+
}
157+
158+
// There is no primary diagnostic.
159+
return nil
160+
}
161+
136162
/// Annotate the source for a given source file ID, embedding its child
137163
/// source files.
138164
func annotateSource(
@@ -164,11 +190,41 @@ extension GroupedDiagnostics {
164190

165191
// If this is a nested source file, draw a box around it.
166192
let isRoot = sourceFile.parent == nil
167-
let prefixString: String
193+
var prefixString: String
168194
let suffixString: String
169195

170196
if isRoot {
171-
prefixString = ""
197+
// If there's a primary diagnostic, print it first.
198+
if let (primaryDiagSourceFile, primaryDiag) = findPrimaryDiagnostic(in: sourceFile) {
199+
let primaryDiagSLC = SourceLocationConverter(fileName: primaryDiagSourceFile.displayName, tree: primaryDiagSourceFile.tree)
200+
let location = primaryDiag.location(converter: primaryDiagSLC)
201+
202+
// Display file/line/column and diagnostic text for the primary diagnostic.
203+
prefixString = "\(location.file):\(location.line):\(location.column): \(formatter.colorizeIfRequested(primaryDiag.diagMessage))\n"
204+
205+
// If the primary diagnostic source file is not the same as the root source file, we're pointing into a generated buffer.
206+
// Provide a link back to the original source file where this generated buffer occurred, so it's easy to find if
207+
// (for example) the generated buffer is no longer available.
208+
if sourceFile.id != primaryDiagSourceFile.id,
209+
var (rootSourceID, rootPosition) = primaryDiagSourceFile.parent
210+
{
211+
// Go all the way up to the root to find the absolute position of the outermost generated buffer within the
212+
// root source file.
213+
while let parent = sourceFiles[rootSourceID.id].parent {
214+
(rootSourceID, rootPosition) = parent
215+
}
216+
217+
if rootSourceID == sourceFileID {
218+
let bufferLoc = slc.location(for: rootPosition)
219+
let coloredMessage = formatter.colorizeIfRequested(severity: .note, message: "expanded code originates here")
220+
prefixString += "╰─ \(bufferLoc.file):\(bufferLoc.line):\(bufferLoc.column): \(coloredMessage)\n"
221+
}
222+
}
223+
} else {
224+
let firstLine = sourceFile.diagnostics.first.map { $0.location(converter: slc).line } ?? 0
225+
prefixString = "\(sourceFile.displayName): \(firstLine):"
226+
}
227+
172228
suffixString = ""
173229
} else {
174230
let padding = indentString.dropLast(1)
@@ -190,7 +246,6 @@ extension GroupedDiagnostics {
190246
// Render the buffer.
191247
return prefixString
192248
+ formatter.annotatedSource(
193-
fileName: isRoot ? sourceFile.displayName : nil,
194249
tree: sourceFile.tree,
195250
diags: sourceFile.diagnostics,
196251
indentString: colorizeBufferOutline(indentString),

Sources/SwiftDiagnostics/Message.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ public enum DiagnosticSeverity {
3030
case error
3131
case warning
3232
case note
33+
case remark
3334
}
3435

3536
/// Types conforming to this protocol represent diagnostic messages that can be

Tests/SwiftDiagnosticsTest/DiagnosticsFormatterTests.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ final class DiagnosticsFormatterTests: XCTestCase {
101101

102102
let expectedOutput = """
103103
\u{001B}[0;36m1 │\u{001B}[0;0m var foo = bar +
104-
\u{001B}[0;36m│\u{001B}[0;0m ╰─ \u{001B}[1;31merror: expected expression after operator\u{001B}[0;0m
104+
\u{001B}[0;36m│\u{001B}[0;0m ╰─ \u{001B}[1;31merror: \u{001B}[0;0m\u{001B}[1;39mexpected expression after operator\u{001B}[0;0m
105105
106106
"""
107107
assertStringsEqualWithDiff(annotate(source: source, colorize: true), expectedOutput)
@@ -113,9 +113,9 @@ final class DiagnosticsFormatterTests: XCTestCase {
113113
"""
114114
let expectedOutput = """
115115
\u{001B}[0;36m1 │\u{001B}[0;0m foo.[].[].[]
116-
\u{001B}[0;36m│\u{001B}[0;0m │ │ ╰─ \u{001B}[1;31merror: expected name in member access\u{001B}[0;0m
117-
\u{001B}[0;36m│\u{001B}[0;0m │ ╰─ \u{001B}[1;31merror: expected name in member access\u{001B}[0;0m
118-
\u{001B}[0;36m│\u{001B}[0;0m ╰─ \u{001B}[1;31merror: expected name in member access\u{001B}[0;0m
116+
\u{001B}[0;36m│\u{001B}[0;0m │ │ ╰─ \u{001B}[1;31merror: \u{001B}[0;0m\u{001B}[1;39mexpected name in member access\u{001B}[0;0m
117+
\u{001B}[0;36m│\u{001B}[0;0m │ ╰─ \u{001B}[1;31merror: \u{001B}[0;0m\u{001B}[1;39mexpected name in member access\u{001B}[0;0m
118+
\u{001B}[0;36m│\u{001B}[0;0m ╰─ \u{001B}[1;31merror: \u{001B}[0;0m\u{001B}[1;39mexpected name in member access\u{001B}[0;0m
119119
120120
"""
121121
assertStringsEqualWithDiff(annotate(source: source, colorize: true), expectedOutput)
@@ -128,8 +128,8 @@ final class DiagnosticsFormatterTests: XCTestCase {
128128

129129
let expectedOutput = """
130130
\u{001B}[0;36m1 │\u{001B}[0;0m for \u{001B}[4;39m(i\u{001B}[0;0m \u{001B}[4;39m= 🐮; i != 👩‍👩‍👦‍👦; i += 1)\u{001B}[0;0m { }
131-
\u{001B}[0;36m│\u{001B}[0;0m │ ╰─ \u{001B}[1;31merror: expected ')' to end tuple pattern\u{001B}[0;0m
132-
\u{001B}[0;36m│\u{001B}[0;0m ╰─ \u{001B}[1;31merror: C-style for statement has been removed in Swift 3\u{001B}[0;0m
131+
\u{001B}[0;36m│\u{001B}[0;0m │ ╰─ \u{001B}[1;31merror: \u{001B}[0;0m\u{001B}[1;39mexpected ')' to end tuple pattern\u{001B}[0;0m
132+
\u{001B}[0;36m│\u{001B}[0;0m ╰─ \u{001B}[1;31merror: \u{001B}[0;0m\u{001B}[1;39mC-style for statement has been removed in Swift 3\u{001B}[0;0m
133133
134134
"""
135135

Tests/SwiftDiagnosticsTest/GroupDiagnosticsFormatterTests.swift

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -112,8 +112,7 @@ final class GroupedDiagnosticsFormatterTests: XCTestCase {
112112
assertStringsEqualWithDiff(
113113
annotated,
114114
"""
115-
=== main.swift:5 ===
116-
115+
main.swift:6:14: error: expected ')' to end function call
117116
3 │ // test
118117
4 │ let pi = 3.14159
119118
5 │ #myAssert(pi == 3)
@@ -141,7 +140,7 @@ final class GroupedDiagnosticsFormatterTests: XCTestCase {
141140
"""
142141
let pi = 3.14159
143142
1️⃣#myAssert(pi == 3)
144-
print("hello"
143+
print("hello")
145144
""",
146145
displayName: "main.swift",
147146
extraDiagnostics: ["1️⃣": ("in expansion of macro 'myAssert' here", .note)]
@@ -181,7 +180,8 @@ final class GroupedDiagnosticsFormatterTests: XCTestCase {
181180
assertStringsEqualWithDiff(
182181
annotated,
183182
"""
184-
=== main.swift:2 ===
183+
#invertedEqualityCheck:1:7: error: no matching operator '==' for types 'Double' and 'Int'
184+
╰─ main.swift:2:1: note: expanded code originates here
185185
1 │ let pi = 3.14159
186186
2 │ #myAssert(pi == 3)
187187
│ ╰─ note: in expansion of macro 'myAssert' here
@@ -197,8 +197,7 @@ final class GroupedDiagnosticsFormatterTests: XCTestCase {
197197
│4 │ fatalError("assertion failed: pi != 3")
198198
│5 │ }
199199
╰─────────────────────────────────────────────────────────────────────
200-
3 │ print("hello"
201-
│ ╰─ error: expected ')' to end function call
200+
3 │ print("hello")
202201
203202
"""
204203
)

0 commit comments

Comments
 (0)