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
|
/*
This source file is part of the Swift.org open source project
Copyright (c) 2021-2022 Apple Inc. and the Swift project authors
Licensed under Apache License v2.0 with Runtime Library Exception
See https://swift.org/LICENSE.txt for license information
See https://swift.org/CONTRIBUTORS.txt for Swift project authors
*/
import Foundation
/// A type that collects and dispatches diagnostics during compilation.
public final class DiagnosticEngine {
/// The queue on which diagnostics are dispatched to consumers.
private let workQueue = DispatchQueue(label: "org.swift.docc.DiagnosticsEngine.work-queue")
/// The diagnostic consumers currently subscribed to this engine.
let consumers: Synchronized<[ObjectIdentifier: DiagnosticConsumer]> = .init([:])
/// The diagnostics encountered by this engine.
let diagnostics: Synchronized<[Problem]> = .init([])
/// A flag that indicates whether this engine has emitted a diagnostics with a severity level of ``DiagnosticSeverity/error``.
var didEncounterError: Synchronized<Bool> = .init(false)
/// Determines which problems will be emitted to consumers.
///
/// This filter level is inclusive, i.e. if a level of ``DiagnosticSeverity/information`` is specified,
/// diagnostics with a severity up to and including `.information` will be printed.
public var filterLevel: DiagnosticSeverity {
didSet {
self.filter = { $0.diagnostic.severity.rawValue <= self.filterLevel.rawValue }
}
}
/// Returns a Boolean value indicating whether the engine contains a consumer that satisfies the given predicate.
/// - Parameter predicate: A closure that takes one of the engine's consumers as its argument and returns a Boolean value that indicates whether the passed consumer represents a match.
/// - Returns: `true` if the engine contains a consumer that satisfies predicate; otherwise, `false`.
public func hasConsumer(matching predicate: (DiagnosticConsumer) throws -> Bool) rethrows -> Bool {
try consumers.sync {
try $0.values.contains(where: predicate)
}
}
/// Determines whether warnings will be treated as errors.
private let treatWarningsAsErrors: Bool
/// Determines which problems should be emitted.
private var filter: (Problem) -> Bool
/// A convenience accessor for retrieving all of the diagnostics this engine currently holds.
public var problems: [Problem] {
return diagnostics.sync { $0 }
}
/// Creates a new diagnostic engine instance with no consumers.
public init(filterLevel: DiagnosticSeverity = .warning, treatWarningsAsErrors: Bool = false) {
self.filterLevel = filterLevel
self.treatWarningsAsErrors = treatWarningsAsErrors
self.filter = { $0.diagnostic.severity.rawValue <= filterLevel.rawValue }
}
/// Removes all of the encountered diagnostics from this engine.
public func clearDiagnostics() {
diagnostics.sync {
$0.removeAll()
}
didEncounterError.sync { $0 = false }
}
/// Dispatches a diagnostic to all subscribed consumers.
/// - Parameter problem: The diagnostic to dispatch to this engine's currently subscribed consumers.
public func emit(_ problem: Problem) {
emit([problem])
}
/// Dispatches multiple diagnostics to consumers.
/// - Parameter problems: The array of diagnostics to dispatch to this engine's currently subscribed consumers.
/// > Note: Diagnostics are dispatched asynchronously.
public func emit(_ problems: [Problem]) {
let mappedProblems = problems.map { problem -> Problem in
var problem = problem
if treatWarningsAsErrors, problem.diagnostic.severity == .warning {
problem.diagnostic.severity = .error
}
return problem
}
let filteredProblems = mappedProblems.filter(filter)
guard !filteredProblems.isEmpty else { return }
if filteredProblems.containsErrors {
didEncounterError.sync { $0 = true }
}
diagnostics.sync {
$0.append(contentsOf: filteredProblems)
}
workQueue.async { [weak self] in
// If the engine isn't around then return early
guard let self else { return }
for consumer in self.consumers.sync({ $0.values }) {
consumer.receive(filteredProblems)
}
}
}
@available(*, deprecated, renamed: "flush()", message: "Use 'flush()' instead. This deprecated API will be removed after 5.11 is released")
public func finalize() {
flush()
}
public func flush() {
workQueue.sync {
for consumer in self.consumers.sync({ $0.values }) {
try? consumer.flush()
}
}
}
/// Subscribes a given consumer to the diagnostics emitted by this engine.
/// - Parameter consumer: The consumer to subscribe to this engine.
public func add(_ consumer: DiagnosticConsumer) {
consumers.sync {
$0[ObjectIdentifier(consumer)] = consumer
}
}
/// Unsubscribes a given consumer
/// - Parameter consumer: The consumer to remove from this engine.
public func remove(_ consumer: DiagnosticConsumer) {
consumers.sync {
$0.removeValue(forKey: ObjectIdentifier(consumer))
}
}
}
|