Skip to content

Commit 4e09ab4

Browse files
authored
Merge pull request #102 from nkcsgexi/diag-parser
Diagnostics: teach the parser API to listen to diagnostics emitted from the compiler(parser).
2 parents 6ba1a44 + 1b8d84d commit 4e09ab4

File tree

7 files changed

+275
-17
lines changed

7 files changed

+275
-17
lines changed

Sources/SwiftSyntax/Diagnostic.swift

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414

1515
/// A FixIt represents a change to source code in order to "correct" a
1616
/// diagnostic.
17-
public enum FixIt: Codable {
17+
public enum FixIt: Codable, CustomDebugStringConvertible {
18+
1819
/// Remove the characters from the source file over the provided source range.
1920
case remove(SourceRange)
2021

@@ -32,6 +33,10 @@ public enum FixIt: Codable {
3233
case string
3334
}
3435

36+
public var debugDescription: String {
37+
return "Fixit: \(range.debugDescription) Text: \"\(text)\""
38+
}
39+
3540
public init(from decoder: Decoder) throws {
3641
let container = try decoder.container(keyedBy: CodingKeys.self)
3742
let type = try container.decode(String.self, forKey: .type)
@@ -119,8 +124,14 @@ public struct Note: Codable {
119124
}
120125

121126
/// A Diagnostic message that can be emitted regarding some piece of code.
122-
public struct Diagnostic: Codable {
123-
public struct Message: Codable {
127+
public struct Diagnostic: Codable, CustomDebugStringConvertible {
128+
129+
public struct Message: Codable, CustomDebugStringConvertible {
130+
131+
public var debugDescription: String {
132+
return "\(severity): \(text)"
133+
}
134+
124135
/// The severity of diagnostic. This can be note, error, or warning.
125136
public let severity: Severity
126137

@@ -157,6 +168,16 @@ public struct Diagnostic: Codable {
157168
/// An array of possible FixIts to apply to this diagnostic.
158169
public let fixIts: [FixIt]
159170

171+
public var debugDescription: String {
172+
var lines: [String] = []
173+
let loc = location == nil ? "" : "\(location!) "
174+
lines.append("\(loc)\(message.debugDescription)")
175+
fixIts.forEach { lines.append("\($0.debugDescription)") }
176+
highlights.forEach { lines.append("Highlight: \($0.debugDescription)") }
177+
lines.append(contentsOf: notes.map({ loc + $0.asDiagnostic().debugDescription}))
178+
return lines.joined(separator: "\n")
179+
}
180+
160181
/// A diagnostic builder that exposes mutating operations for notes,
161182
/// highlights, and FixIts. When a Diagnostic is created, a builder
162183
/// will be provided in a closure where the user can conditionally

Sources/SwiftSyntax/DiagnosticConsumer.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,19 @@
1515
/// An object that intends to receive notifications when diagnostics are
1616
/// emitted.
1717
public protocol DiagnosticConsumer {
18+
19+
/// Whether the collected diagnostics should calculate line:column pair; true
20+
/// by default.
21+
var needsLineColumn: Bool { get }
22+
1823
/// Handle the provided diagnostic which has just been registered with the
1924
/// DiagnosticEngine.
2025
func handle(_ diagnostic: Diagnostic)
2126

2227
/// Finalize the consumption of diagnostics, flushing to disk if necessary.
2328
func finalize()
2429
}
30+
31+
public extension DiagnosticConsumer {
32+
var needsLineColumn: Bool { return true }
33+
}

Sources/SwiftSyntax/DiagnosticEngine.swift

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@ public class DiagnosticEngine {
2626

2727
public private(set) var diagnostics = [Diagnostic]()
2828

29+
internal var needsLineColumn: Bool {
30+
// Check if any consumer is interested in line and column.
31+
return consumers.first { $0.needsLineColumn } != nil
32+
}
33+
2934
/// Adds the provided consumer to the consumers list.
3035
public func addConsumer(_ consumer: DiagnosticConsumer) {
3136
consumers.append(consumer)
@@ -36,6 +41,13 @@ public class DiagnosticEngine {
3641
}
3742
}
3843

44+
internal func diagnose(_ diagnostic: Diagnostic) {
45+
diagnostics.append(diagnostic)
46+
for consumer in consumers {
47+
consumer.handle(diagnostic)
48+
}
49+
}
50+
3951
/// Registers a diagnostic with the diagnostic engine.
4052
/// - parameters:
4153
/// - message: The message for the diagnostic. This message includes
@@ -44,12 +56,7 @@ public class DiagnosticEngine {
4456
public func diagnose(_ message: Diagnostic.Message,
4557
location: SourceLocation? = nil,
4658
actions: ((inout Diagnostic.Builder) -> Void)? = nil) {
47-
let diagnostic = Diagnostic(message: message, location: location,
48-
actions: actions)
49-
diagnostics.append(diagnostic)
50-
for consumer in consumers {
51-
consumer.handle(diagnostic)
52-
}
59+
diagnose(Diagnostic(message: message, location: location, actions: actions))
5360
}
5461

5562
/// If any of the diagnostics in this engine have the `error` severity.

Sources/SwiftSyntax/SourceLocation.swift

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
/// Represent the user-facing part of SourceLocation that can be calculated
1414
/// on demand.
15-
struct ComputedLocation: Codable {
15+
struct ComputedLocation: Codable, CustomDebugStringConvertible {
1616
/// The line in the file where this location resides. 1-based.
1717
let line: Int
1818

@@ -23,6 +23,11 @@ struct ComputedLocation: Codable {
2323
/// The file in which this location resides.
2424
let file: String
2525

26+
var debugDescription: String {
27+
// Print file name?
28+
return "\(line):\(column)"
29+
}
30+
2631
init(line: Int, column: Int, file: String) {
2732
self.line = line
2833
self.column = column
@@ -38,7 +43,7 @@ struct ComputedLocation: Codable {
3843
}
3944

4045
/// Represents a source location in a Swift file.
41-
public struct SourceLocation: Codable {
46+
public struct SourceLocation: Codable, CustomDebugStringConvertible {
4247

4348
/// Line and column that can be computed on demand.
4449
private var compLoc: ComputedLocation?
@@ -62,6 +67,13 @@ public struct SourceLocation: Codable {
6267
return compLoc?.file
6368
}
6469

70+
public var debugDescription: String {
71+
guard let compLoc = compLoc else {
72+
return "\(offset)"
73+
}
74+
return compLoc.debugDescription
75+
}
76+
6577
public init(line: Int, column: Int, offset: Int, file: String) {
6678
self.offset = offset
6779
self.compLoc = ComputedLocation(line: line, column: column, file: file)
@@ -79,13 +91,18 @@ public struct SourceLocation: Codable {
7991
}
8092

8193
/// Represents a start and end location in a Swift file.
82-
public struct SourceRange: Codable {
94+
public struct SourceRange: Codable, CustomDebugStringConvertible {
95+
8396
/// The beginning location in the source range.
8497
public let start: SourceLocation
8598

8699
/// The beginning location in the source range.
87100
public let end: SourceLocation
88101

102+
public var debugDescription: String {
103+
return "(\(start.debugDescription),\(end.debugDescription))"
104+
}
105+
89106
public init(start: SourceLocation, end: SourceLocation) {
90107
self.start = start
91108
self.end = end
@@ -111,6 +128,15 @@ public final class SourceLocationConverter {
111128
assert(tree.byteSize == endOfFile.utf8Offset)
112129
}
113130

131+
/// - Parameters:
132+
/// - file: The file path associated with the syntax tree.
133+
/// - source: The source code to convert positions to line/columns for.
134+
public init(file: String, source: String) {
135+
self.file = file
136+
(self.lines, endOfFile) = computeLines(source)
137+
assert(source.lengthOfBytes(using: .utf8) == endOfFile.utf8Offset)
138+
}
139+
114140
/// Convert a `AbsolutePosition` to a `SourceLocation`. If the position is
115141
/// exceeding the file length then the `SourceLocation` for the end of file
116142
/// is returned. If position is negative the location for start of file is
@@ -305,6 +331,22 @@ fileprivate func computeLines(
305331
return (lines, position)
306332
}
307333

334+
fileprivate func computeLines(_ source: String) ->
335+
([AbsolutePosition], AbsolutePosition) {
336+
var lines: [AbsolutePosition] = []
337+
// First line starts from the beginning.
338+
lines.append(.startOfFile)
339+
var position: AbsolutePosition = .startOfFile
340+
let addLine = { (lineLength: SourceLength) in
341+
position += lineLength
342+
lines.append(position)
343+
}
344+
var curPrefix: SourceLength = .zero
345+
curPrefix = source.forEachLineLength(prefix: curPrefix, body: addLine)
346+
position += curPrefix
347+
return (lines, position)
348+
}
349+
308350
fileprivate extension String {
309351
/// Walks and passes to `body` the `SourceLength` for every detected line,
310352
/// with the newline character included.

0 commit comments

Comments
 (0)