1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213
|
/*
This source file is part of the Swift.org open source project
Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors
Licensed under Apache License v2.0 with Runtime Library Exception
See http://swift.org/LICENSE.txt for license information
See http://swift.org/CONTRIBUTORS.txt for Swift project authors
*/
import Dispatch
/// The payload of a diagnostic.
public protocol DiagnosticData: Sendable, CustomStringConvertible {
}
extension DiagnosticData {
public var localizedDescription: String { self.description }
}
public protocol DiagnosticLocation: Sendable, CustomStringConvertible {
}
public struct Diagnostic: CustomStringConvertible, Sendable {
/// The behavior associated with this diagnostic.
public enum Behavior: Sendable {
/// An error which will halt the operation.
case error
/// A warning, but which will not halt the operation.
case warning
case note
case remark
// FIXME: Kill this
case ignored
}
public struct Message: Sendable {
/// The diagnostic's behavior.
public let behavior: Behavior
/// The information on the actual diagnostic.
public let data: DiagnosticData
/// The textual representation of the diagnostic data.
public var text: String { data.description }
fileprivate init(data: DiagnosticData, behavior: Behavior) {
self.data = data
self.behavior = behavior
}
}
/// The message in this diagnostic.
public let message: Message
/// The conceptual location of this diagnostic.
///
/// This could refer to a concrete location in a file, for example, but it
/// could also refer to an abstract location such as "the Git repository at
/// this URL".
public let location: DiagnosticLocation
public var data: DiagnosticData { message.data }
public var behavior: Behavior { message.behavior }
public init(
message: Message,
location: DiagnosticLocation = UnknownLocation.location
) {
self.message = message
self.location = location
}
public var description: String { message.text }
public var localizedDescription: String { message.text }
}
public final class DiagnosticsEngine: CustomStringConvertible {
public typealias DiagnosticsHandler = (Diagnostic) -> Void
/// Queue to protect concurrent mutations to the diagnostics engine.
private let queue = DispatchQueue(label: "\(DiagnosticsEngine.self)")
/// Queue for dispatching handlers.
private let handlerQueue = DispatchQueue(label: "\(DiagnosticsEngine.self)-callback")
/// The diagnostics produced by the engine.
public var diagnostics: [Diagnostic] {
return queue.sync { _diagnostics }
}
private var _diagnostics: [Diagnostic] = []
/// The list of handlers to run when a diagnostic is emitted.
///
/// The handler will be called on an unknown queue.
private let handlers: [DiagnosticsHandler]
/// The default location to apply to location-less diagnostics.
public let defaultLocation: DiagnosticLocation
/// Returns true if there is an error diagnostics in the engine.
public var hasErrors: Bool {
return diagnostics.contains(where: { $0.message.behavior == .error })
}
public init(handlers: [DiagnosticsHandler] = [], defaultLocation: DiagnosticLocation = UnknownLocation.location) {
self.handlers = handlers
self.defaultLocation = defaultLocation
}
public func emit(
_ message: Diagnostic.Message,
location: DiagnosticLocation? = nil
) {
emit(Diagnostic(message: message, location: location ?? defaultLocation))
}
public func emit(_ diagnostic: Diagnostic) {
queue.sync {
_diagnostics.append(diagnostic)
}
// Call the handlers on the background queue, if we have any.
if !handlers.isEmpty {
// FIXME: We should probably do this async but then we need
// a way for clients to be able to wait until all handlers
// are called.
handlerQueue.sync {
for handler in self.handlers {
handler(diagnostic)
}
}
}
}
/// Merges contents of given engine.
public func merge(_ engine: DiagnosticsEngine) {
for diagnostic in engine.diagnostics {
emit(diagnostic.message, location: diagnostic.location)
}
}
public var description: String {
let stream = BufferedOutputByteStream()
stream.send("[")
for diag in diagnostics {
stream.send(diag.description).send(", ")
}
stream.send("]")
return stream.bytes.description
}
}
extension Diagnostic.Message {
public static func error(_ data: DiagnosticData) -> Diagnostic.Message {
.init(data: data, behavior: .error)
}
public static func warning(_ data: DiagnosticData) -> Diagnostic.Message {
.init(data: data, behavior: .warning)
}
public static func note(_ data: DiagnosticData) -> Diagnostic.Message {
.init(data: data, behavior: .note)
}
public static func remark(_ data: DiagnosticData) -> Diagnostic.Message {
.init(data: data, behavior: .remark)
}
public static func error(_ str: String) -> Diagnostic.Message {
.error(StringDiagnostic(str))
}
public static func warning(_ str: String) -> Diagnostic.Message {
.warning(StringDiagnostic(str))
}
public static func note(_ str: String) -> Diagnostic.Message {
.note(StringDiagnostic(str))
}
public static func remark(_ str: String) -> Diagnostic.Message {
.remark(StringDiagnostic(str))
}
}
public struct StringDiagnostic: DiagnosticData, Sendable {
/// The diagnostic description.
public let description: String
public init(_ description: String) {
self.description = description
}
}
/// Represents a diagnostic location which is unknown.
public final class UnknownLocation: DiagnosticLocation {
/// The singleton instance.
public static let location = UnknownLocation()
private init() {}
public var description: String {
return "<unknown>"
}
}
|