| //===--------------- Diagnostics.swift - Diagnostic Emitter ---------------===// |
| // |
| // This source file is part of the Swift.org open source project |
| // |
| // Copyright (c) 2014 - 2017 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 |
| // |
| //===----------------------------------------------------------------------===// |
| // This file provides the Diagnostic, Note, and FixIt types. |
| //===----------------------------------------------------------------------===// |
| |
| import Foundation |
| |
| /// Represents a source location in a Swift file. |
| public struct SourceLocation: Codable { |
| /// The line in the file where this location resides. |
| public let line: Int |
| |
| /// The UTF-8 byte offset from the beginning of the line where this location |
| /// resides. |
| public let column: Int |
| |
| /// The UTF-8 byte offset into the file where this location resides. |
| public let offset: Int |
| |
| /// The file in which this location resides. |
| public let file: String |
| |
| public init(file: String, position: AbsolutePosition) { |
| assert(position is UTF8Position, "must be utf8 position") |
| self.init(line: position.line, column: position.column, |
| offset: position.byteOffset, file: file) |
| } |
| |
| public init(line: Int, column: Int, offset: Int, file: String) { |
| self.line = line |
| self.column = column |
| self.offset = offset |
| self.file = file |
| } |
| } |
| |
| /// Represents a start and end location in a Swift file. |
| public struct SourceRange: Codable { |
| /// The beginning location in the source range. |
| public let start: SourceLocation |
| |
| /// The beginning location in the source range. |
| public let end: SourceLocation |
| |
| public init(start: SourceLocation, end: SourceLocation) { |
| self.start = start |
| self.end = end |
| } |
| } |
| |
| /// A FixIt represents a change to source code in order to "correct" a |
| /// diagnostic. |
| public enum FixIt: Codable { |
| /// Remove the characters from the source file over the provided source range. |
| case remove(SourceRange) |
| |
| /// Insert, at the provided source location, the provided string. |
| case insert(SourceLocation, String) |
| |
| /// Replace the characters at the provided source range with the provided |
| /// string. |
| case replace(SourceRange, String) |
| |
| enum CodingKeys: String, CodingKey { |
| case type |
| case range |
| case location |
| case string |
| } |
| |
| public init(from decoder: Decoder) throws { |
| let container = try decoder.container(keyedBy: CodingKeys.self) |
| let type = try container.decode(String.self, forKey: .type) |
| switch type { |
| case "remove": |
| let range = try container.decode(SourceRange.self, forKey: .range) |
| self = .remove(range) |
| case "insert": |
| let string = try container.decode(String.self, forKey: .string) |
| let loc = try container.decode(SourceLocation.self, forKey: .location) |
| self = .insert(loc, string) |
| case "replace": |
| let string = try container.decode(String.self, forKey: .string) |
| let range = try container.decode(SourceRange.self, forKey: .range) |
| self = .replace(range, string) |
| default: |
| fatalError("unknown FixIt type \(type)") |
| } |
| } |
| |
| public func encode(to encoder: Encoder) throws { |
| var container = encoder.container(keyedBy: CodingKeys.self) |
| switch self { |
| case let .remove(range): |
| try container.encode(range, forKey: .range) |
| case let .insert(location, string): |
| try container.encode(location, forKey: .location) |
| try container.encode(string, forKey: .string) |
| case let .replace(range, string): |
| try container.encode(range, forKey: .range) |
| try container.encode(string, forKey: .string) |
| } |
| } |
| |
| /// The source range associated with a FixIt. If this is an insertion, |
| /// it is a range with the same start and end location. |
| public var range: SourceRange { |
| switch self { |
| case .remove(let range), .replace(let range, _): return range |
| case .insert(let loc, _): return SourceRange(start: loc, end: loc) |
| } |
| } |
| |
| /// The text associated with this FixIt. If this is a removal, the text is |
| /// the empty string. |
| public var text: String { |
| switch self { |
| case .remove(_): return "" |
| case .insert(_, let text), .replace(_, let text): return text |
| } |
| } |
| } |
| |
| /// A Note attached to a Diagnostic. This provides more context for a specific |
| /// error, and optionally allows for FixIts. |
| public struct Note: Codable { |
| /// The note's message. |
| public let message: Diagnostic.Message |
| |
| /// The source location where the note should point. |
| public let location: SourceLocation? |
| |
| /// An array of source ranges that should be highlighted. |
| public let highlights: [SourceRange] |
| |
| /// An array of FixIts that apply to this note. |
| public let fixIts: [FixIt] |
| |
| /// Constructs a new Note from the constituent parts. |
| internal init(message: Diagnostic.Message, location: SourceLocation?, |
| highlights: [SourceRange], fixIts: [FixIt]) { |
| precondition(message.severity == .note, |
| "notes can only have the `note` severity") |
| self.message = message |
| self.location = location |
| self.highlights = highlights |
| self.fixIts = fixIts |
| } |
| |
| /// Converts this Note to a Diagnostic for serialization. |
| func asDiagnostic() -> Diagnostic { |
| return Diagnostic(message: message, location: location, notes: [], |
| highlights: highlights, fixIts: fixIts) |
| } |
| } |
| |
| /// A Diagnostic message that can be emitted regarding some piece of code. |
| public struct Diagnostic: Codable { |
| public struct Message: Codable { |
| /// The severity of diagnostic. This can be note, error, or warning. |
| public let severity: Severity |
| |
| /// A string containing the contents of the diagnostic. |
| public let text: String |
| |
| /// Creates a diagnostic message with the provided severity and text. |
| public init(_ severity: Severity, _ text: String) { |
| self.severity = severity |
| self.text = text |
| } |
| } |
| |
| // These values must match clang/Frontend/SerializedDiagnostics.h |
| /// The severity of the diagnostic. |
| public enum Severity: UInt8, Codable { |
| case note = 1 |
| case warning = 2 |
| case error = 3 |
| } |
| |
| /// The diagnostic's message. |
| public let message: Message |
| |
| /// The location the diagnostic should point. |
| public let location: SourceLocation? |
| |
| /// An array of notes providing more context for this diagnostic. |
| public let notes: [Note] |
| |
| /// An array of source ranges to highlight. |
| public let highlights: [SourceRange] |
| |
| /// An array of possible FixIts to apply to this diagnostic. |
| public let fixIts: [FixIt] |
| |
| /// A diagnostic builder that |
| public struct Builder { |
| /// An in-flight array of notes. |
| internal var notes = [Note]() |
| |
| /// An in-flight array of highlighted source ranges. |
| internal var highlights = [SourceRange]() |
| |
| /// An in-flight array of FixIts. |
| internal var fixIts = [FixIt]() |
| |
| internal init() {} |
| |
| /// Adds a Note to the diagnostic builder. |
| /// - parameters: |
| /// - message: The message associated with the note. This must have the |
| /// `.note` severity. |
| /// - location: The source location to which this note is attached. |
| /// - highlights: Any source ranges that should be highlighted by this |
| /// note. |
| /// - fixIts: Any FixIts that should be attached to this note. |
| public mutating func note(_ message: Message, |
| location: SourceLocation? = nil, |
| highlights: [SourceRange] = [], |
| fixIts: [FixIt] = []) { |
| self.notes.append(Note(message: message, location: location, |
| highlights: highlights, fixIts: fixIts)) |
| } |
| |
| /// Adds the provided source ranges as highlights of this diagnostic. |
| public mutating func highlight(_ ranges: SourceRange...) { |
| self.highlights += ranges |
| } |
| |
| /// Adds a FixIt to remove the contents of the provided SourceRange. |
| /// When applied, this FixIt will delete the characters corresponding to |
| /// this range in the original source file. |
| public mutating func fixItRemove(_ sourceRange: SourceRange) { |
| fixIts.append(.remove(sourceRange)) |
| } |
| |
| /// Adds a FixIt to insert the provided text at the provided SourceLocation |
| /// in the file where the location resides. |
| public mutating |
| func fixItInsert(_ text: String, at sourceLocation: SourceLocation) { |
| fixIts.append(.insert(sourceLocation, text)) |
| } |
| |
| /// Adds a FixIt to replace the contents of the source file corresponding |
| /// to the provided SourceRange with the provided text. |
| public mutating |
| func fixItReplace(_ sourceRange: SourceRange, with text: String) { |
| fixIts.append(.replace(sourceRange, text)) |
| } |
| } |
| |
| /// Creates a new Diagnostic with the provided message, pointing to the |
| /// provided location (if any). |
| /// This initializer also takes a closure that will be passed a Diagnostic |
| /// Builder as an inout parameter. Use this closure to add notes, highlights, |
| /// and FixIts to the diagnostic through the Builder's API. |
| /// - parameters: |
| /// - message: The diagnostic's message. |
| /// - location: The location the diagnostic is attached to. |
| /// - actions: A closure that's used to attach notes and highlights to |
| /// diagnostics. |
| init(message: Message, location: SourceLocation?, |
| actions: ((inout Builder) -> Void)?) { |
| var builder = Builder() |
| actions?(&builder) |
| self.init(message: message, location: location, notes: builder.notes, |
| highlights: builder.highlights, fixIts: builder.fixIts) |
| } |
| |
| /// Creates a new Diagnostic with the provided message, pointing to the |
| /// provided location (if any). |
| /// - parameters: |
| /// - message: The diagnostic's message. |
| /// - location: The location the diagnostic is attached to. |
| /// - highlights: An array of SourceRanges which will be highlighted when |
| /// the diagnostic is presented. |
| init(message: Message, location: SourceLocation?, notes: [Note], |
| highlights: [SourceRange], fixIts: [FixIt]) { |
| self.message = message |
| self.location = location |
| self.notes = notes |
| self.highlights = highlights |
| self.fixIts = fixIts |
| } |
| } |