Skip to content

Diagnostics: teach the parser API to listen to diagnostics emitted from the compiler(parser). #102

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Mar 6, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 24 additions & 3 deletions Sources/SwiftSyntax/Diagnostic.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@

/// A FixIt represents a change to source code in order to "correct" a
/// diagnostic.
public enum FixIt: Codable {
public enum FixIt: Codable, CustomDebugStringConvertible {

/// Remove the characters from the source file over the provided source range.
case remove(SourceRange)

Expand All @@ -32,6 +33,10 @@ public enum FixIt: Codable {
case string
}

public var debugDescription: String {
return "Fixit: \(range.debugDescription) Text: \"\(text)\""
}

public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let type = try container.decode(String.self, forKey: .type)
Expand Down Expand Up @@ -119,8 +124,14 @@ public struct Note: Codable {
}

/// A Diagnostic message that can be emitted regarding some piece of code.
public struct Diagnostic: Codable {
public struct Message: Codable {
public struct Diagnostic: Codable, CustomDebugStringConvertible {

public struct Message: Codable, CustomDebugStringConvertible {

public var debugDescription: String {
return "\(severity): \(text)"
}

/// The severity of diagnostic. This can be note, error, or warning.
public let severity: Severity

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

public var debugDescription: String {
var lines: [String] = []
let loc = location == nil ? "" : "\(location!) "
lines.append("\(loc)\(message.debugDescription)")
fixIts.forEach { lines.append("\($0.debugDescription)") }
highlights.forEach { lines.append("Highlight: \($0.debugDescription)") }
lines.append(contentsOf: notes.map({ loc + $0.asDiagnostic().debugDescription}))
return lines.joined(separator: "\n")
}

/// A diagnostic builder that exposes mutating operations for notes,
/// highlights, and FixIts. When a Diagnostic is created, a builder
/// will be provided in a closure where the user can conditionally
Expand Down
9 changes: 9 additions & 0 deletions Sources/SwiftSyntax/DiagnosticConsumer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,19 @@
/// An object that intends to receive notifications when diagnostics are
/// emitted.
public protocol DiagnosticConsumer {

/// Whether the collected diagnostics should calculate line:column pair; true
/// by default.
var needsLineColumn: Bool { get }

/// Handle the provided diagnostic which has just been registered with the
/// DiagnosticEngine.
func handle(_ diagnostic: Diagnostic)

/// Finalize the consumption of diagnostics, flushing to disk if necessary.
func finalize()
}

public extension DiagnosticConsumer {
var needsLineColumn: Bool { return true }
}
19 changes: 13 additions & 6 deletions Sources/SwiftSyntax/DiagnosticEngine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ public class DiagnosticEngine {

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

internal var needsLineColumn: Bool {
// Check if any consumer is interested in line and column.
return consumers.first { $0.needsLineColumn } != nil
}

/// Adds the provided consumer to the consumers list.
public func addConsumer(_ consumer: DiagnosticConsumer) {
consumers.append(consumer)
Expand All @@ -36,6 +41,13 @@ public class DiagnosticEngine {
}
}

internal func diagnose(_ diagnostic: Diagnostic) {
diagnostics.append(diagnostic)
for consumer in consumers {
consumer.handle(diagnostic)
}
}

/// Registers a diagnostic with the diagnostic engine.
/// - parameters:
/// - message: The message for the diagnostic. This message includes
Expand All @@ -44,12 +56,7 @@ public class DiagnosticEngine {
public func diagnose(_ message: Diagnostic.Message,
location: SourceLocation? = nil,
actions: ((inout Diagnostic.Builder) -> Void)? = nil) {
let diagnostic = Diagnostic(message: message, location: location,
actions: actions)
diagnostics.append(diagnostic)
for consumer in consumers {
consumer.handle(diagnostic)
}
diagnose(Diagnostic(message: message, location: location, actions: actions))
}

/// If any of the diagnostics in this engine have the `error` severity.
Expand Down
48 changes: 45 additions & 3 deletions Sources/SwiftSyntax/SourceLocation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

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

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

var debugDescription: String {
// Print file name?
return "\(line):\(column)"
}

init(line: Int, column: Int, file: String) {
self.line = line
self.column = column
Expand All @@ -38,7 +43,7 @@ struct ComputedLocation: Codable {
}

/// Represents a source location in a Swift file.
public struct SourceLocation: Codable {
public struct SourceLocation: Codable, CustomDebugStringConvertible {

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

public var debugDescription: String {
guard let compLoc = compLoc else {
return "\(offset)"
}
return compLoc.debugDescription
}

public init(line: Int, column: Int, offset: Int, file: String) {
self.offset = offset
self.compLoc = ComputedLocation(line: line, column: column, file: file)
Expand All @@ -79,13 +91,18 @@ public struct SourceLocation: Codable {
}

/// Represents a start and end location in a Swift file.
public struct SourceRange: Codable {
public struct SourceRange: Codable, CustomDebugStringConvertible {

/// The beginning location in the source range.
public let start: SourceLocation

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

public var debugDescription: String {
return "(\(start.debugDescription),\(end.debugDescription))"
}

public init(start: SourceLocation, end: SourceLocation) {
self.start = start
self.end = end
Expand All @@ -111,6 +128,15 @@ public final class SourceLocationConverter {
assert(tree.byteSize == endOfFile.utf8Offset)
}

/// - Parameters:
/// - file: The file path associated with the syntax tree.
/// - source: The source code to convert positions to line/columns for.
public init(file: String, source: String) {
self.file = file
(self.lines, endOfFile) = computeLines(source)
assert(source.lengthOfBytes(using: .utf8) == endOfFile.utf8Offset)
}

/// Convert a `AbsolutePosition` to a `SourceLocation`. If the position is
/// exceeding the file length then the `SourceLocation` for the end of file
/// is returned. If position is negative the location for start of file is
Expand Down Expand Up @@ -305,6 +331,22 @@ fileprivate func computeLines(
return (lines, position)
}

fileprivate func computeLines(_ source: String) ->
([AbsolutePosition], AbsolutePosition) {
var lines: [AbsolutePosition] = []
// First line starts from the beginning.
lines.append(.startOfFile)
var position: AbsolutePosition = .startOfFile
let addLine = { (lineLength: SourceLength) in
position += lineLength
lines.append(position)
}
var curPrefix: SourceLength = .zero
curPrefix = source.forEachLineLength(prefix: curPrefix, body: addLine)
position += curPrefix
return (lines, position)
}

fileprivate extension String {
/// Walks and passes to `body` the `SourceLength` for every detected line,
/// with the newline character included.
Expand Down
Loading