diff --git a/Documentation/Configuration File.md b/Documentation/Configuration File.md index fad3c396b..4210621ba 100644 --- a/Documentation/Configuration File.md +++ b/Documentation/Configuration File.md @@ -3,7 +3,8 @@ `.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/org.swift.sourcekit-lsp/config.json` +- 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 The structure of the file is currently not guaranteed to be stable. Options may be removed or renamed. diff --git a/Sources/SKCore/SourceKitLSPOptions.swift b/Sources/SKCore/SourceKitLSPOptions.swift index 0a9ad011a..ab4ccec73 100644 --- a/Sources/SKCore/SourceKitLSPOptions.swift +++ b/Sources/SKCore/SourceKitLSPOptions.swift @@ -12,7 +12,7 @@ import Foundation import LSPLogging -import SKCore +import LanguageServerProtocol import SKSupport import struct TSCBasic.AbsolutePath @@ -200,6 +200,21 @@ public struct SourceKitLSPOptions: Sendable, Codable { self.workDoneProgressDebounce = workDoneProgressDebounce } + public init?(fromLSPAny lspAny: LSPAny?) throws { + guard let lspAny else { + return nil + } + let jsonEncoded = try JSONEncoder().encode(lspAny) + self = try JSONDecoder().decode(Self.self, from: jsonEncoded) + } + + public var asLSPAny: LSPAny { + get throws { + let jsonEncoded = try JSONEncoder().encode(self) + return try JSONDecoder().decode(LSPAny.self, from: jsonEncoded) + } + } + public init?(path: URL?) { guard let path, let contents = try? String(contentsOf: path, encoding: .utf8) else { return nil diff --git a/Sources/SKTestSupport/MultiFileTestProject.swift b/Sources/SKTestSupport/MultiFileTestProject.swift index b25fa27f6..5303cf46a 100644 --- a/Sources/SKTestSupport/MultiFileTestProject.swift +++ b/Sources/SKTestSupport/MultiFileTestProject.swift @@ -80,6 +80,7 @@ public class MultiFileTestProject { public init( files: [RelativeFileLocation: String], workspaces: (URL) async throws -> [WorkspaceFolder] = { [WorkspaceFolder(uri: DocumentURI($0))] }, + initializationOptions: LSPAny? = nil, capabilities: ClientCapabilities = ClientCapabilities(), options: SourceKitLSPOptions = .testDefault(), testHooks: TestHooks = TestHooks(), @@ -118,6 +119,7 @@ public class MultiFileTestProject { self.testClient = try await TestSourceKitLSPClient( options: options, testHooks: testHooks, + initializationOptions: initializationOptions, capabilities: capabilities, usePullDiagnostics: usePullDiagnostics, enableBackgroundIndexing: enableBackgroundIndexing, diff --git a/Sources/SKTestSupport/SwiftPMTestProject.swift b/Sources/SKTestSupport/SwiftPMTestProject.swift index 024fa6fff..fe43b3407 100644 --- a/Sources/SKTestSupport/SwiftPMTestProject.swift +++ b/Sources/SKTestSupport/SwiftPMTestProject.swift @@ -150,6 +150,7 @@ public class SwiftPMTestProject: MultiFileTestProject { files: [RelativeFileLocation: String], manifest: String = SwiftPMTestProject.defaultPackageManifest, workspaces: (URL) async throws -> [WorkspaceFolder] = { [WorkspaceFolder(uri: DocumentURI($0))] }, + initializationOptions: LSPAny? = nil, capabilities: ClientCapabilities = ClientCapabilities(), options: SourceKitLSPOptions = .testDefault(), testHooks: TestHooks = TestHooks(), @@ -190,6 +191,7 @@ public class SwiftPMTestProject: MultiFileTestProject { try await super.init( files: filesByPath, workspaces: workspaces, + initializationOptions: initializationOptions, capabilities: capabilities, options: options, testHooks: testHooks, diff --git a/Sources/SourceKitLSP/SourceKitLSPServer.swift b/Sources/SourceKitLSP/SourceKitLSPServer.swift index fe41305f2..5c87e6d67 100644 --- a/Sources/SourceKitLSP/SourceKitLSPServer.swift +++ b/Sources/SourceKitLSP/SourceKitLSPServer.swift @@ -104,7 +104,7 @@ public actor SourceKitLSPServer { /// This ensures that we only inform the user about background indexing not being supported for these projects once. private var didSendBackgroundIndexingNotSupportedNotification = false - let options: SourceKitLSPOptions + var options: SourceKitLSPOptions let testHooks: TestHooks @@ -959,6 +959,10 @@ extension SourceKitLSPServer { func initialize(_ req: InitializeRequest) async throws -> InitializeResult { capabilityRegistry = CapabilityRegistry(clientCapabilities: req.capabilities) + self.options = SourceKitLSPOptions.merging( + base: self.options, + override: orLog("Parsing SourceKitLSPOptions", { try SourceKitLSPOptions(fromLSPAny: req.initializationOptions) }) + ) await workspaceQueue.async { [testHooks] in if let workspaceFolders = req.workspaceFolders { @@ -983,12 +987,13 @@ extension SourceKitLSPServer { if self.workspaces.isEmpty { logger.error("No workspace found") + let options = self.options let workspace = await Workspace( documentManager: self.documentManager, rootUri: req.rootURI, capabilityRegistry: self.capabilityRegistry!, toolchainRegistry: self.toolchainRegistry, - options: self.options, + options: options, testHooks: testHooks, underlyingBuildSystem: nil, index: nil, diff --git a/Sources/SourceKitLSP/Swift/SwiftLanguageService.swift b/Sources/SourceKitLSP/Swift/SwiftLanguageService.swift index 1f8414b1f..c5816d654 100644 --- a/Sources/SourceKitLSP/Swift/SwiftLanguageService.swift +++ b/Sources/SourceKitLSP/Swift/SwiftLanguageService.swift @@ -809,7 +809,7 @@ extension SwiftLanguageService { var canInlineMacro = false let showMacroExpansionsIsEnabled = - self.sourceKitLSPServer?.options.hasExperimentalFeature(.showMacroExpansions) ?? false + await self.sourceKitLSPServer?.options.hasExperimentalFeature(.showMacroExpansions) ?? false var refactorActions = cursorInfoResponse.refactorActions.compactMap { let lspCommand = $0.asCommand() diff --git a/Sources/sourcekit-lsp/SourceKitLSP.swift b/Sources/sourcekit-lsp/SourceKitLSP.swift index 8b3c2f26a..50da731de 100644 --- a/Sources/sourcekit-lsp/SourceKitLSP.swift +++ b/Sources/sourcekit-lsp/SourceKitLSP.swift @@ -263,7 +263,7 @@ struct SourceKitLSP: AsyncParsableCommand { override: SourceKitLSPOptions( path: URL(fileURLWithPath: xdgConfigHome) - .appendingPathComponent("org.swift.sourcekit-lsp") + .appendingPathComponent("sourcekit-lsp") .appendingPathComponent("config.json") ) ) diff --git a/Tests/SourceKitLSPTests/WorkspaceTests.swift b/Tests/SourceKitLSPTests/WorkspaceTests.swift index 5cccfbbfa..984c51161 100644 --- a/Tests/SourceKitLSPTests/WorkspaceTests.swift +++ b/Tests/SourceKitLSPTests/WorkspaceTests.swift @@ -861,4 +861,68 @@ final class WorkspaceTests: XCTestCase { } XCTAssertEqual(diagnostics.items.map(\.message), ["Cannot convert value of type 'Int' to specified type 'String'"]) } + + func testOptionsInInitializeRequest() async throws { + let project = try await SwiftPMTestProject( + files: [ + "Test.swift": """ + func test() { + #if TEST + let x: String = 1 + #endif + } + """ + ], + initializationOptions: SourceKitLSPOptions( + swiftPM: SourceKitLSPOptions.SwiftPMOptions(swiftCompilerFlags: ["-D", "TEST"]) + ).asLSPAny + ) + + let (uri, _) = try project.openDocument("Test.swift") + let diagnostics = try await project.testClient.send( + DocumentDiagnosticsRequest(textDocument: TextDocumentIdentifier(uri)) + ) + guard case .full(let diagnostics) = diagnostics else { + XCTFail("Expected full diagnostics") + return + } + XCTAssertEqual(diagnostics.items.map(\.message), ["Cannot convert value of type 'Int' to specified type 'String'"]) + } + + func testWorkspaceOptionsOverrideGlobalOptions() async throws { + let project = try await SwiftPMTestProject( + files: [ + "/.sourcekit-lsp/config.json": """ + { + "swiftPM": { + "swiftCompilerFlags": ["-D", "TEST"] + } + } + """, + "Test.swift": """ + func test() { + #if TEST + let x: String = 1 + #endif + #if OTHER + let x: String = 1.0 + #endif + } + """, + ], + initializationOptions: SourceKitLSPOptions( + swiftPM: SourceKitLSPOptions.SwiftPMOptions(swiftCompilerFlags: ["-D", "OTHER"]) + ).asLSPAny + ) + + let (uri, _) = try project.openDocument("Test.swift") + let diagnostics = try await project.testClient.send( + DocumentDiagnosticsRequest(textDocument: TextDocumentIdentifier(uri)) + ) + guard case .full(let diagnostics) = diagnostics else { + XCTFail("Expected full diagnostics") + return + } + XCTAssertEqual(diagnostics.items.map(\.message), ["Cannot convert value of type 'Int' to specified type 'String'"]) + } }