mirror of
https://github.com/apple/sourcekit-lsp.git
synced 2026-03-02 18:23:24 +01:00
210 lines
8.1 KiB
Swift
210 lines
8.1 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
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
package import Foundation
|
|
@_spi(SourceKitLSP) package import LanguageServerProtocol
|
|
import RegexBuilder
|
|
|
|
/// Represents url of macro expansion reference document as follows:
|
|
/// `sourcekit-lsp://swift-macro-expansion/LaCb-LcCd.swift?fromLine=&fromColumn=&toLine=&toColumn=&bufferName=&parent=`
|
|
///
|
|
/// Here,
|
|
/// - `LaCb-LcCd.swift`, the `displayName`, represents where the macro will expand to or
|
|
/// replace in the source file (i.e. `macroExpansionEditRange`)
|
|
/// - `fromLine`, `fromColumn`, `toLine`, `toColumn` represents the cursor's selection range in
|
|
/// its `parent` (i.e. `parentSelectionRange`)
|
|
/// - `bufferName` denotes the buffer name of the specific macro expansion edit
|
|
/// - `parent` denoting the URI of the document from which the macro was expanded. For a first-level macro expansion,
|
|
/// this is a file URI. For nested macro expansions, this is a `sourcekit-lsp://swift-macro-expansion` URL.
|
|
package struct MacroExpansionReferenceDocumentURLData: ReferenceURLData {
|
|
package static let documentType = "swift-macro-expansion"
|
|
|
|
/// The document from which this macro was expanded. For first-level macro expansions, this is a file URL. For
|
|
/// second-level macro expansions, this is a `sourcekit-lsp://swift-macro-expansion/` URL, third-level macro
|
|
/// expansions are a `sourcekit-lsp:` URL that themselves have a `sourcekit-lsp:` URL as their parent.
|
|
package var parent: DocumentURI
|
|
|
|
/// The range that was selected in `parent` when the macro was expanded.
|
|
package var parentSelectionRange: Range<Position>
|
|
|
|
/// ## Example
|
|
///
|
|
/// User's source File:
|
|
/// URL: `file:///path/to/swift_file.swift`
|
|
/// ```swift
|
|
/// let a = 10
|
|
/// let b = 5
|
|
/// print(#stringify(a + b))
|
|
/// ```
|
|
///
|
|
/// Generated content of reference document url:
|
|
/// URL:
|
|
/// `sourcekit-lsp://swift-macro-expansion/L3C7-L3C23.swift?fromLine=3&fromColumn=8&toLine=3&toColumn=8&bufferName=@__swift_macro_..._Stringify_.swift&parent=/path/to/swift_file.swift`
|
|
/// ```swift
|
|
/// (a + b, "a + b")
|
|
/// ```
|
|
///
|
|
/// Here the `bufferName` of the reference document url is `@__swift_macro_..._Stringify_.swift`
|
|
package var bufferName: String
|
|
|
|
/// The range at which the expanded macro should be inserted. For freestanding macros, this will be the full range of
|
|
/// the macro expansion expr/decl.
|
|
/// For attached macros, this is the position at which the buffer should be inserted, which could be at a different
|
|
/// location than the macro attribute (eg. attached member macros).
|
|
package var macroExpansionEditRange: Range<Position>
|
|
|
|
package init(
|
|
macroExpansionEditRange: Range<Position>,
|
|
parent: DocumentURI,
|
|
parentSelectionRange: Range<Position>,
|
|
bufferName: String
|
|
) {
|
|
self.macroExpansionEditRange = macroExpansionEditRange
|
|
self.parent = parent
|
|
self.parentSelectionRange = parentSelectionRange
|
|
self.bufferName = bufferName
|
|
}
|
|
|
|
package var displayName: String {
|
|
"L\(macroExpansionEditRange.lowerBound.line + 1)C\(macroExpansionEditRange.lowerBound.utf16index + 1)-L\(macroExpansionEditRange.upperBound.line + 1)C\(macroExpansionEditRange.upperBound.utf16index + 1).swift"
|
|
}
|
|
|
|
package var queryItems: [URLQueryItem] {
|
|
[
|
|
URLQueryItem(name: Parameters.fromLine, value: String(parentSelectionRange.lowerBound.line)),
|
|
URLQueryItem(name: Parameters.fromColumn, value: String(parentSelectionRange.lowerBound.utf16index)),
|
|
URLQueryItem(name: Parameters.toLine, value: String(parentSelectionRange.upperBound.line)),
|
|
URLQueryItem(name: Parameters.toColumn, value: String(parentSelectionRange.upperBound.utf16index)),
|
|
URLQueryItem(name: Parameters.bufferName, value: bufferName),
|
|
|
|
// *Note*: Having `parent` as the last parameter will ensure that the url's parameters aren't mistaken to be its
|
|
// `parent`'s parameters in certain environments where percent encoding gets removed or added
|
|
// unnecessarily (for example: VS Code)
|
|
URLQueryItem(name: Parameters.parent, value: parent.stringValue),
|
|
]
|
|
}
|
|
|
|
package init(displayName: String, queryItems: [URLQueryItem]) throws {
|
|
guard let parent = queryItems.last(where: { $0.name == Parameters.parent })?.value,
|
|
let fromLine = Int(queryItems.last(where: { $0.name == Parameters.fromLine })?.value ?? ""),
|
|
let fromColumn = Int(queryItems.last(where: { $0.name == Parameters.fromColumn })?.value ?? ""),
|
|
let toLine = Int(queryItems.last(where: { $0.name == Parameters.toLine })?.value ?? ""),
|
|
let toColumn = Int(queryItems.last(where: { $0.name == Parameters.toColumn })?.value ?? ""),
|
|
let bufferName = queryItems.last(where: { $0.name == Parameters.bufferName })?.value
|
|
else {
|
|
throw ReferenceDocumentURLError(description: "Invalid queryItems for macro expansion reference document url")
|
|
}
|
|
|
|
self.parent = try DocumentURI(string: parent)
|
|
self.parentSelectionRange =
|
|
Position(line: fromLine, utf16index: fromColumn)..<Position(line: toLine, utf16index: toColumn)
|
|
self.bufferName = bufferName
|
|
self.macroExpansionEditRange = try Self.parse(displayName: displayName)
|
|
}
|
|
|
|
/// The URI of the document from which this reference document was derived.
|
|
/// This is used to determine the workspace and language service that is used to generate the reference document.
|
|
///
|
|
/// ## Example
|
|
///
|
|
/// User's source File:
|
|
/// URL: `file://path/to/swift_file.swift`
|
|
/// ```swift
|
|
/// let a = 10
|
|
/// let b = 5
|
|
/// print(#stringify(a + b))
|
|
/// ```
|
|
///
|
|
/// Generated content of reference document url:
|
|
/// URL:
|
|
/// `sourcekit-lsp://swift-macro-expansion/L3C7-L3C23.swift?fromLine=3&fromColumn=8&toLine=3&toColumn=8&bufferName=@__swift_macro_..._Stringify_.swift&parent=/path/to/swift_file.swift`
|
|
/// ```swift
|
|
/// (a + b, "a + b")
|
|
/// ```
|
|
///
|
|
/// Here the `primaryFile` of the reference document url is a `DocumentURI`
|
|
/// with the following url: `file:///path/to/swift_file.swift`
|
|
///
|
|
/// - Note: In case of nested macro expansion reference documents, they all will have the same `primaryFile`
|
|
/// as that of the first macro expansion reference document i.e. `primaryFile` doesn't change.
|
|
package var primaryFile: DocumentURI {
|
|
switch try? ReferenceDocumentURL(from: parent) {
|
|
case .macroExpansion(let data):
|
|
data.primaryFile
|
|
case .generatedInterface, nil:
|
|
parent
|
|
}
|
|
}
|
|
|
|
package var primaryFileSelectionRange: Range<Position> {
|
|
switch try? ReferenceDocumentURL(from: parent) {
|
|
case .macroExpansion(let data):
|
|
data.primaryFileSelectionRange
|
|
case .generatedInterface, nil:
|
|
self.parentSelectionRange
|
|
}
|
|
}
|
|
|
|
private struct Parameters {
|
|
static let parent = "parent"
|
|
static let fromLine = "fromLine"
|
|
static let fromColumn = "fromColumn"
|
|
static let toLine = "toLine"
|
|
static let toColumn = "toColumn"
|
|
static let bufferName = "bufferName"
|
|
}
|
|
|
|
private static func parse(displayName: String) throws -> Range<Position> {
|
|
let regex = Regex {
|
|
"L"
|
|
TryCapture {
|
|
OneOrMore(.digit)
|
|
} transform: {
|
|
Int($0)
|
|
}
|
|
"C"
|
|
TryCapture {
|
|
OneOrMore(.digit)
|
|
} transform: {
|
|
Int($0)
|
|
}
|
|
"-L"
|
|
TryCapture {
|
|
OneOrMore(.digit)
|
|
} transform: {
|
|
Int($0)
|
|
}
|
|
"C"
|
|
TryCapture {
|
|
OneOrMore(.digit)
|
|
} transform: {
|
|
Int($0)
|
|
}
|
|
".swift"
|
|
}
|
|
|
|
guard let match = try? regex.wholeMatch(in: displayName) else {
|
|
throw ReferenceDocumentURLError(
|
|
description: "Wrong format of display name of macro expansion reference document: '\(displayName)'"
|
|
)
|
|
}
|
|
|
|
return Position(
|
|
line: match.1 - 1,
|
|
utf16index: match.2 - 1
|
|
)..<Position(
|
|
line: match.3 - 1,
|
|
utf16index: match.4 - 1
|
|
)
|
|
}
|
|
}
|