File: Diagnostic.swift

package info (click to toggle)
swiftlang 6.1.3-1
  • links: PTS, VCS
  • area: main
  • in suites: forky
  • size: 2,791,532 kB
  • sloc: cpp: 9,901,743; ansic: 2,201,431; asm: 1,091,827; python: 308,252; objc: 82,166; f90: 80,126; lisp: 38,358; pascal: 25,559; sh: 20,429; ml: 5,058; perl: 4,745; makefile: 4,484; awk: 3,535; javascript: 3,018; xml: 918; fortran: 664; cs: 573; ruby: 396
file content (129 lines) | stat: -rw-r--r-- 4,352 bytes parent folder | download | duplicates (2)
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
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2023 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 the list of Swift project authors
//
//===----------------------------------------------------------------------===//

#if swift(>=6)
public import SwiftSyntax
#else
import SwiftSyntax
#endif

public struct Diagnostic: CustomDebugStringConvertible, Sendable {
  /// The message that should be displayed to the user
  public let diagMessage: DiagnosticMessage

  /// The node at whose start location the message should be displayed.
  public let node: Syntax

  /// The position at which the location should be anchored.
  /// By default, this is the start location of `node`.
  public let position: AbsolutePosition

  /// Nodes that should be highlighted in the source code.
  public let highlights: [Syntax]

  /// Notes that point to additional locations which are relevant for this diagnostic.
  public let notes: [Note]

  /// Fix-Its that can be applied to resolve this diagnostic.
  /// Each Fix-It offers a different way to resolve the diagnostic. Usually, there's only one.
  public let fixIts: [FixIt]

  /// If `highlights` is `nil` then `node` will be highlighted. This is a
  /// reasonable default for almost all diagnostics.
  public init(
    node: some SyntaxProtocol,
    position: AbsolutePosition? = nil,
    message: DiagnosticMessage,
    highlights: [Syntax]? = nil,
    notes: [Note] = [],
    fixIts: [FixIt] = []
  ) {
    self.node = Syntax(node)
    self.position = position ?? node.positionAfterSkippingLeadingTrivia
    self.diagMessage = message
    self.highlights = highlights ?? [Syntax(node)]
    self.notes = notes
    self.fixIts = fixIts
  }

  /// The message that should be displayed to the user.
  public var message: String {
    return diagMessage.message
  }

  /// An ID that identifies the diagnostic's message.
  /// See ``MessageID``.
  public var diagnosticID: MessageID {
    return diagMessage.diagnosticID
  }

  /// The location at which the diagnostic should be displayed.
  public func location(converter: SourceLocationConverter) -> SourceLocation {
    return converter.location(for: position)
  }

  public var debugDescription: String {
    let locationConverter = SourceLocationConverter(fileName: "", tree: node.root)
    let location = location(converter: locationConverter)
    return "\(location.line):\(location.column): \(message)"
  }
}

public struct DiagnosticsError: Error, Sendable {
  public var diagnostics: [Diagnostic]

  /// The diagnostics must contain at least one with severity == `.error`.
  /// Asserts if this condition is not satisfied.
  public init(diagnostics: [Diagnostic]) {
    self.diagnostics = diagnostics

    precondition(
      diagnostics.contains(where: { $0.diagMessage.severity == .error }),
      "at least one diagnostic must have severity == .error"
    )
  }
}

/// Diagnostic message used for thrown errors.
private struct DiagnosticFromError: DiagnosticMessage {
  let error: Error
  let severity: DiagnosticSeverity = .error

  var message: String {
    return String(describing: error)
  }

  var diagnosticID: MessageID {
    .init(domain: "SwiftDiagnostics", id: "\(type(of: error))")
  }
}

extension Error {
  /// Given an error, produce an array of diagnostics reporting the error,
  /// using the given syntax node as the location if it wasn't otherwise known.
  ///
  /// This operation will look for diagnostics of known type, such as
  /// `DiagnosticsError` and `DiagnosticMessage` to retain information. If
  /// none of those apply, it will produce an `error` diagnostic whose message
  /// comes from rendering the error as a string.
  public func asDiagnostics(at node: some SyntaxProtocol) -> [Diagnostic] {
    if let diagnosticsError = self as? DiagnosticsError {
      return diagnosticsError.diagnostics
    }

    if let message = self as? DiagnosticMessage {
      return [Diagnostic(node: Syntax(node), message: message)]
    }

    return [Diagnostic(node: Syntax(node), message: DiagnosticFromError(error: self))]
  }
}