//===----------------------------------------------------------------------===// // // 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 = """ # 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 } } } }