mirror of
https://github.com/apple/sourcekit-lsp.git
synced 2026-03-02 18:23:24 +01:00
178 lines
4.6 KiB
Swift
178 lines
4.6 KiB
Swift
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2014 - 2018 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) import LanguageServerProtocolExtensions
|
|
|
|
#if canImport(FoundationXML)
|
|
import FoundationXML
|
|
#endif
|
|
|
|
enum CommentXMLError: Error {
|
|
case noRootElement
|
|
}
|
|
|
|
/// Converts from sourcekit's XML documentation format to Markdown.
|
|
///
|
|
/// This code should go away and sourcekitd should return the Markdown directly.
|
|
package func xmlDocumentationToMarkdown(_ xmlString: String) throws -> String {
|
|
let xml = try XMLDocument(xmlString: xmlString)
|
|
guard let root = xml.rootElement() else {
|
|
throw CommentXMLError.noRootElement
|
|
}
|
|
|
|
var convert = XMLToMarkdown()
|
|
convert.out.reserveCapacity(xmlString.utf16.count)
|
|
convert.toMarkdown(root)
|
|
return convert.out
|
|
}
|
|
|
|
private struct XMLToMarkdown {
|
|
var out: String = ""
|
|
var indentCount: Int = 0
|
|
let indentWidth: Int = 4
|
|
var lineNumber: Int = 0
|
|
var inParam: Bool = false
|
|
|
|
mutating func newlineIfNeeded(count: Int = 1) {
|
|
if !out.isEmpty && out.last! != "\n" {
|
|
newline(count: count)
|
|
}
|
|
}
|
|
|
|
mutating func newline(count: Int = 1) {
|
|
out += String(repeating: "\n", count: count)
|
|
out += String(repeating: " ", count: indentWidth * indentCount)
|
|
}
|
|
|
|
mutating func toMarkdown(_ node: XMLNode) {
|
|
switch node.kind {
|
|
case .element:
|
|
toMarkdown(node as! XMLElement)
|
|
default:
|
|
out += node.stringValue ?? ""
|
|
}
|
|
}
|
|
|
|
// [XMLNode]? is the type of XMLNode.children.
|
|
mutating func toMarkdown(_ nodes: [XMLNode]?, separator: String = "") {
|
|
guard let nodes else {
|
|
return
|
|
}
|
|
for node in nodes {
|
|
toMarkdown(node)
|
|
out += separator
|
|
}
|
|
}
|
|
|
|
mutating func toMarkdown(_ node: XMLElement) {
|
|
switch node.name {
|
|
case "Declaration":
|
|
newlineIfNeeded(count: 2)
|
|
out += "```swift\n"
|
|
toMarkdown(node.children)
|
|
out += "\n```\n"
|
|
|
|
case "Name", "USR", "Direction":
|
|
break
|
|
|
|
case "Abstract", "Para":
|
|
if !inParam {
|
|
newlineIfNeeded(count: 2)
|
|
}
|
|
toMarkdown(node.children)
|
|
|
|
case "Discussion", "ResultDiscussion", "ThrowsDiscussion":
|
|
if !inParam {
|
|
newlineIfNeeded(count: 2)
|
|
}
|
|
out += "### "
|
|
switch node.name {
|
|
case "Discussion": out += "Discussion"
|
|
case "ResultDiscussion": out += "Returns"
|
|
case "ThrowsDiscussion": out += "Throws"
|
|
default: fatalError("handled in outer switch")
|
|
}
|
|
newline(count: 2)
|
|
toMarkdown(node.children)
|
|
|
|
case "Parameters":
|
|
newlineIfNeeded(count: 2)
|
|
out += "- Parameters:"
|
|
indentCount += 1
|
|
toMarkdown(node.children)
|
|
indentCount -= 1
|
|
|
|
case "Parameter":
|
|
guard let name = node.elements(forName: "Name").first else { break }
|
|
newlineIfNeeded()
|
|
out += "- "
|
|
toMarkdown(name.children)
|
|
if let discussion = node.elements(forName: "Discussion").first {
|
|
out += ": "
|
|
inParam = true
|
|
toMarkdown(discussion.children)
|
|
inParam = false
|
|
}
|
|
|
|
case "CodeListing":
|
|
lineNumber = 0
|
|
newlineIfNeeded(count: 2)
|
|
out += "```\(node.attributes?.first(where: { $0.name == "language" })?.stringValue ?? "")\n"
|
|
toMarkdown(node.children, separator: "\n")
|
|
out += "```"
|
|
newlineIfNeeded(count: 2)
|
|
|
|
case "zCodeLineNumbered":
|
|
lineNumber += 1
|
|
out += "\(lineNumber).\t"
|
|
toMarkdown(node.children)
|
|
|
|
case "codeVoice":
|
|
out += "`"
|
|
toMarkdown(node.children)
|
|
out += "`"
|
|
|
|
case "emphasis":
|
|
out += "*"
|
|
toMarkdown(node.children)
|
|
out += "*"
|
|
|
|
case "bold":
|
|
out += "**"
|
|
toMarkdown(node.children)
|
|
out += "**"
|
|
|
|
case "h1", "h2", "h3", "h4", "h5", "h6":
|
|
newlineIfNeeded(count: 2)
|
|
let n = Int(node.name!.dropFirst())
|
|
out += String(repeating: "#", count: n!)
|
|
out += " "
|
|
toMarkdown(node.children)
|
|
out += "\n\n"
|
|
|
|
case "Link":
|
|
if let href = node.attributes?.first(where: { $0.name == "href" })?.stringValue {
|
|
out += "["
|
|
toMarkdown(node.children)
|
|
out += "](\(href))"
|
|
} else {
|
|
// Not a valid link.
|
|
toMarkdown(node.children)
|
|
}
|
|
|
|
default:
|
|
toMarkdown(node.children)
|
|
}
|
|
}
|
|
}
|