mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
We have two similar objects for source location. AbsoluteLocation calculates the offset of a syntax node on the fly. SourceLocation is designed to serialize a Swift syntax diagnostics to the driver. The only difference is AbsoluteLocation doesn't contain source file name however SourceLocation does. This patch bridges them by making AbsoluteLocation a private member of SourceLocation. We also expect Swift syntax to be file-name agnostic. The clients should keep track of the file name when emitting diagnostics.
295 lines
10 KiB
Swift
295 lines
10 KiB
Swift
//===--------------- 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
|
|
}
|
|
}
|