blob: 1fd286927a5af0b1e1202d088f3a14412e4c993a [file] [log] [blame]
//===--------------- 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
}
}