mirror of
https://github.com/apple/sourcekit-lsp.git
synced 2025-12-17 12:03:01 +01:00
142 lines
5.4 KiB
Swift
142 lines
5.4 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
|
||
//
|
||
//===----------------------------------------------------------------------===//
|
||
|
||
/// Generates a markdown document for the configuration file based on the schema.
|
||
struct OptionDocumentBuilder {
|
||
static let preamble = """
|
||
<!-- DO NOT EDIT THIS FILE. This file is generated by \(#fileID). -->
|
||
|
||
# Configuration File
|
||
|
||
`.sourcekit-lsp/config.json` configuration files can be used to modify the behavior of SourceKit-LSP in various ways. The following locations are checked. Settings in later configuration files override settings in earlier configuration files
|
||
- `~/.sourcekit-lsp/config.json`
|
||
- On macOS: `~/Library/Application Support/org.swift.sourcekit-lsp/config.json` from the various `Library` folders on the system
|
||
- If the `XDG_CONFIG_HOME` environment variable is set: `$XDG_CONFIG_HOME/sourcekit-lsp/config.json`
|
||
- Initialization options passed in the initialize request
|
||
- A `.sourcekit-lsp/config.json` file in a workspace’s root
|
||
|
||
Modifying the configuration file requires SourceKit-LSP to be restarted for the new options to take effect.
|
||
|
||
The structure of the file is currently not guaranteed to be stable. Options may be removed or renamed.
|
||
|
||
## Structure
|
||
|
||
`config.json` is a JSON file with the following structure. All keys are optional and unknown keys are ignored.
|
||
|
||
"""
|
||
|
||
let context: OptionSchemaContext
|
||
|
||
/// Builds a markdown document for the configuration file based on the schema.
|
||
func build(from schema: OptionTypeSchama) throws -> String {
|
||
var doc = Self.preamble
|
||
|
||
func appendProperty(_ property: OptionTypeSchama.Property, indentLevel: Int) throws {
|
||
let indent = String(repeating: " ", count: indentLevel)
|
||
let name = property.name
|
||
doc += "\(indent)- `\(name)"
|
||
let type = property.type
|
||
let typeDescription: String?
|
||
switch type.kind {
|
||
case .struct:
|
||
// Skip struct type as we describe its properties in the next level
|
||
typeDescription = nil
|
||
default:
|
||
typeDescription = Self.typeToDisplay(type)
|
||
}
|
||
if let typeDescription {
|
||
doc += ": \(typeDescription)`:"
|
||
} else {
|
||
doc += "`:"
|
||
}
|
||
if let description = property.description {
|
||
doc += " " + description.split(separator: "\n").joined(separator: "\n\(indent) ")
|
||
}
|
||
doc += "\n"
|
||
switch type.kind {
|
||
case .struct(let schema):
|
||
for property in schema.properties {
|
||
try appendProperty(property, indentLevel: indentLevel + 1)
|
||
}
|
||
case .enum(let schema):
|
||
let hasAssociatedTypes = schema.cases.contains {
|
||
$0.associatedProperties != nil && !$0.associatedProperties!.isEmpty
|
||
}
|
||
|
||
if hasAssociatedTypes {
|
||
let discriminatorFieldName = schema.discriminatorFieldName ?? "type"
|
||
doc +=
|
||
"\(indent) - This is a tagged union discriminated by the `\(discriminatorFieldName)` field. Each case has the following structure:\n"
|
||
|
||
for caseInfo in schema.cases {
|
||
doc += """
|
||
\(indent) - `\(discriminatorFieldName): "\(caseInfo.name)"`
|
||
"""
|
||
if let description = caseInfo.description {
|
||
doc += ": " + description.split(separator: "\n").joined(separator: "\n\(indent) ")
|
||
}
|
||
doc += "\n"
|
||
|
||
if let associatedProperties = caseInfo.associatedProperties {
|
||
for assocProp in associatedProperties {
|
||
try appendProperty(assocProp, indentLevel: indentLevel + 2)
|
||
}
|
||
}
|
||
}
|
||
} else {
|
||
for caseInfo in schema.cases {
|
||
guard let description = caseInfo.description else {
|
||
continue
|
||
}
|
||
doc += "\(indent) - `\(caseInfo.name)`"
|
||
doc += ": " + description.split(separator: "\n").joined(separator: "\n\(indent) ")
|
||
doc += "\n"
|
||
}
|
||
}
|
||
default: break
|
||
}
|
||
}
|
||
guard case .struct(let schema) = schema.kind else {
|
||
throw ConfigSchemaGenError("Root schema must be a struct")
|
||
}
|
||
for property in schema.properties {
|
||
try appendProperty(property, indentLevel: 0)
|
||
}
|
||
return doc
|
||
}
|
||
|
||
static func typeToDisplay(_ type: OptionTypeSchama, shouldWrap: Bool = false) -> String {
|
||
switch type.kind {
|
||
case .boolean: return "boolean"
|
||
case .integer: return "integer"
|
||
case .number: return "number"
|
||
case .string: return "string"
|
||
case .array(let value):
|
||
return "\(typeToDisplay(value, shouldWrap: true))[]"
|
||
case .dictionary(let value):
|
||
return "[string: \(typeToDisplay(value))]"
|
||
case .struct(let structInfo):
|
||
return structInfo.name
|
||
case .enum(let enumInfo):
|
||
let hasAssociatedTypes = enumInfo.cases.contains {
|
||
$0.associatedProperties != nil && !$0.associatedProperties!.isEmpty
|
||
}
|
||
if hasAssociatedTypes {
|
||
return "object"
|
||
} else {
|
||
let cases = enumInfo.cases.map { "\"\($0.name)\"" }.joined(separator: "|")
|
||
return shouldWrap ? "(\(cases))" : cases
|
||
}
|
||
}
|
||
}
|
||
}
|