Files
sourcekit-lsp/Sources/SourceKitLSP/ReferenceDocumentURL.swift
2025-12-02 12:27:27 +00:00

174 lines
6.2 KiB
Swift

//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2024 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
//
//===----------------------------------------------------------------------===//
import Foundation
@_spi(SourceKitLSP) package import LanguageServerProtocol
protocol ReferenceURLData {
static var documentType: String { get }
var displayName: String { get }
var queryItems: [URLQueryItem] { get }
}
/// A Reference Document is a document whose url scheme is `sourcekit-lsp:` and whose content can only be retrieved
/// using `GetReferenceDocumentRequest`. The enum represents a specific type of reference document and its
/// associated value represents the data necessary to generate the document's contents and its url
///
/// The `url` will be of the form: `sourcekit-lsp://<document-type>/<display-name>?<parameters>`
/// Here,
/// - The `<document-type>` denotes the kind of the content present in the reference document
/// - The `<parameters>` denotes the parameter-value pairs such as "p1=v1&p2=v2&..." needed to generate
/// the content of the reference document.
/// - The `<display-name>` is the displayed file name of the reference document. It doesn't involve in generating
/// the content of the reference document.
package enum ReferenceDocumentURL {
package static let scheme = "sourcekit-lsp"
case macroExpansion(MacroExpansionReferenceDocumentURLData)
case generatedInterface(GeneratedInterfaceDocumentURLData)
var url: URL {
get throws {
let data: any ReferenceURLData =
switch self {
case .macroExpansion(let data): data
case .generatedInterface(let data): data
}
var components = URLComponents()
components.scheme = Self.scheme
components.host = type(of: data).documentType
components.path = "/\(data.displayName)"
components.queryItems = data.queryItems
guard let url = components.url else {
throw ReferenceDocumentURLError(description: "Unable to create URL for reference document")
}
return url
}
}
package var uri: DocumentURI {
get throws {
DocumentURI(try url)
}
}
package init(from uri: DocumentURI) throws {
try self.init(from: uri.arbitrarySchemeURL)
}
init(from url: URL) throws {
guard url.scheme == Self.scheme else {
throw ReferenceDocumentURLError(description: "Invalid Scheme for reference document")
}
let documentType = url.host
switch documentType {
case MacroExpansionReferenceDocumentURLData.documentType:
guard let queryItems = URLComponents(string: url.absoluteString)?.queryItems else {
throw ReferenceDocumentURLError(
description: "No queryItems passed for macro expansion reference document: \(url)"
)
}
let macroExpansionURLData = try MacroExpansionReferenceDocumentURLData(
displayName: url.lastPathComponent,
queryItems: queryItems
)
self = .macroExpansion(macroExpansionURLData)
case GeneratedInterfaceDocumentURLData.documentType:
guard let queryItems = URLComponents(string: url.absoluteString)?.queryItems else {
throw ReferenceDocumentURLError(
description: "No queryItems passed for generated interface reference document: \(url)"
)
}
let macroExpansionURLData = try GeneratedInterfaceDocumentURLData(queryItems: queryItems)
self = .generatedInterface(macroExpansionURLData)
case nil:
throw ReferenceDocumentURLError(
description: "Bad URL for reference document: \(url)"
)
case let documentType?:
throw ReferenceDocumentURLError(
description: "Invalid document type in URL for reference document: \(documentType)"
)
}
}
/// The path that should be passed as `keys.sourcefile` to sourcekitd in conjunction with a `keys.primaryFile`.
///
/// For macro expansions, this is the buffer name that the URI references.
var sourcekitdSourceFile: String {
switch self {
case .macroExpansion(let data): return data.bufferName
case .generatedInterface(let data): return data.sourcekitdDocumentName
}
}
/// The file that should be used to retrieve build settings for this reference document.
var buildSettingsFile: DocumentURI {
switch self {
case .macroExpansion(let data): return data.primaryFile
case .generatedInterface(let data): return data.buildSettingsFrom
}
}
var primaryFile: DocumentURI? {
switch self {
case .macroExpansion(let data): return data.primaryFile
case .generatedInterface(let data): return data.buildSettingsFrom.primaryFile
}
}
}
extension DocumentURI {
/// The path that should be passed as `keys.sourcefile` to sourcekitd in conjunction with a `keys.primaryFile`.
///
/// For normal document URIs, this is the pseudo path of this URI. For macro expansions, this is the buffer name
/// that the URI references.
package var sourcekitdSourceFile: String {
if let referenceDocument = try? ReferenceDocumentURL(from: self) {
referenceDocument.sourcekitdSourceFile
} else {
self.pseudoPath
}
}
/// If this is a URI to a reference document, the URI of the source file from which this reference document was
/// derived.
///
/// The primary file is used to determine the workspace and language service that is used to generate the reference
/// document as well as getting the reference document's build settings.
package var primaryFile: DocumentURI? {
if let referenceDocument = try? ReferenceDocumentURL(from: self) {
return referenceDocument.primaryFile
}
return nil
}
/// The file that should be used to retrieve build settings for this reference document.
package var buildSettingsFile: DocumentURI {
if let referenceDocument = try? ReferenceDocumentURL(from: self) {
return referenceDocument.buildSettingsFile
}
return self
}
}
package struct ReferenceDocumentURLError: Error, CustomStringConvertible {
package var description: String
}