From 12142c024c8381196bc45c0c353348b5100e6272 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Wed, 26 Jun 2024 14:37:29 +0200 Subject: [PATCH 1/2] Remove configuration options from `WorkspaceFolder` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This extension was added for VS Code but never used. Let’s remove it in favor of workspace-specific configuration files. --- Documentation/LSP Extensions.md | 74 ------------------ .../SupportTypes/WorkspaceFolder.swift | 75 +------------------ Sources/SourceKitLSP/SourceKitLSPServer.swift | 47 +----------- Sources/SourceKitLSP/Workspace.swift | 42 ----------- Tests/SourceKitLSPTests/WorkspaceTests.swift | 40 ---------- 5 files changed, 2 insertions(+), 276 deletions(-) diff --git a/Documentation/LSP Extensions.md b/Documentation/LSP Extensions.md index 3ace18a9b..24fda628b 100644 --- a/Documentation/LSP Extensions.md +++ b/Documentation/LSP Extensions.md @@ -73,80 +73,6 @@ Added case identifier = 'identifier' ``` -## `WorkspaceFolder` - -Added field: - -```ts -/** - * Build settings that should be used for this workspace. - * - * For arguments that have a single value (like the build configuration), this takes precedence over the global - * options set when launching sourcekit-lsp. For all other options, the values specified in the workspace-specific - * build setup are appended to the global options. - */ -var buildSetup?: WorkspaceBuildSetup -``` - -with - -```ts -/** - * The configuration to build a workspace in. - */ -export enum BuildConfiguration { - case debug = 'debug' - case release = 'release' -} - -/** - * The type of workspace; default workspace type selection logic can be overridden. - */ -export enum WorkspaceType { - buildServer = 'buildServer' - compilationDatabase = 'compilationDatabase' - swiftPM = 'swiftPM' -} - -/// Build settings that should be used for a workspace. -interface WorkspaceBuildSetup { - /** - * The configuration that the workspace should be built in. - */ - buildConfiguration?: BuildConfiguration - - /** - * The default workspace type to use for this workspace. - */ - defaultWorkspaceType?: WorkspaceType - - /** - * The build directory for the workspace. - */ - scratchPath?: DocumentURI - - /** - * Arguments to be passed to any C compiler invocations. - */ - cFlags?: string[] - - /** - * Arguments to be passed to any C++ compiler invocations. - */ - cxxFlags?: string[] - - /** - * Arguments to be passed to any linker invocations. - */ - linkerFlags?: string[] - - /** - * Arguments to be passed to any Swift compiler invocations. - */ - swiftFlags?: string[] -} -``` - ## `textDocument/completion` Added field: diff --git a/Sources/LanguageServerProtocol/SupportTypes/WorkspaceFolder.swift b/Sources/LanguageServerProtocol/SupportTypes/WorkspaceFolder.swift index 96292c5d4..c1ef2a404 100644 --- a/Sources/LanguageServerProtocol/SupportTypes/WorkspaceFolder.swift +++ b/Sources/LanguageServerProtocol/SupportTypes/WorkspaceFolder.swift @@ -10,65 +10,6 @@ // //===----------------------------------------------------------------------===// -/// The configuration to build a workspace in. -/// -/// **(LSP Extension)** -public enum BuildConfiguration: Hashable, Codable, Sendable { - case debug - case release -} - -/// The type of workspace; default workspace type selection logic can be overridden. -/// -/// **(LSP Extension)** -public enum WorkspaceType: Hashable, Codable, Sendable { - case buildServer, compilationDatabase, swiftPM -} - -/// Build settings that should be used for a workspace. -/// -/// **(LSP Extension)** -public struct WorkspaceBuildSetup: Hashable, Codable, Sendable { - /// The configuration that the workspace should be built in. - public let buildConfiguration: BuildConfiguration? - - /// The default workspace type to use for this workspace. - public let defaultWorkspaceType: WorkspaceType? - - /// The build directory for the workspace. - public let scratchPath: DocumentURI? - - /// Arguments to be passed to any C compiler invocations. - public let cFlags: [String]? - - /// Arguments to be passed to any C++ compiler invocations. - public let cxxFlags: [String]? - - /// Arguments to be passed to any linker invocations. - public let linkerFlags: [String]? - - /// Arguments to be passed to any Swift compiler invocations. - public let swiftFlags: [String]? - - public init( - buildConfiguration: BuildConfiguration? = nil, - defaultWorkspaceType: WorkspaceType? = nil, - scratchPath: DocumentURI? = nil, - cFlags: [String]? = nil, - cxxFlags: [String]? = nil, - linkerFlags: [String]? = nil, - swiftFlags: [String]? = nil - ) { - self.buildConfiguration = buildConfiguration - self.defaultWorkspaceType = defaultWorkspaceType - self.scratchPath = scratchPath - self.cFlags = cFlags - self.cxxFlags = cxxFlags - self.linkerFlags = linkerFlags - self.swiftFlags = swiftFlags - } -} - /// Unique identifier for a document. public struct WorkspaceFolder: ResponseType, Hashable, Codable, Sendable { @@ -78,20 +19,7 @@ public struct WorkspaceFolder: ResponseType, Hashable, Codable, Sendable { /// The name of the workspace (default: basename of url). public var name: String - /// Build settings that should be used for this workspace. - /// - /// For arguments that have a single value (like the build configuration), this takes precedence over the global - /// options set when launching sourcekit-lsp. For all other options, the values specified in the workspace-specific - /// build setup are appended to the global options. - /// - /// **(LSP Extension)** - public var buildSetup: WorkspaceBuildSetup? - - public init( - uri: DocumentURI, - name: String? = nil, - buildSetup: WorkspaceBuildSetup? = nil - ) { + public init(uri: DocumentURI, name: String? = nil) { self.uri = uri self.name = name ?? uri.fileURL?.lastPathComponent ?? "unknown_workspace" @@ -99,6 +27,5 @@ public struct WorkspaceFolder: ResponseType, Hashable, Codable, Sendable { if self.name.isEmpty { self.name = "unknown_workspace" } - self.buildSetup = buildSetup } } diff --git a/Sources/SourceKitLSP/SourceKitLSPServer.swift b/Sources/SourceKitLSP/SourceKitLSPServer.swift index 4f272f6af..500f67e8c 100644 --- a/Sources/SourceKitLSP/SourceKitLSPServer.swift +++ b/Sources/SourceKitLSP/SourceKitLSPServer.swift @@ -838,27 +838,6 @@ extension SourceKitLSPServer: BuildSystemDelegate { } } -extension LanguageServerProtocol.BuildConfiguration { - /// Convert `LanguageServerProtocol.BuildConfiguration` to `SKSupport.BuildConfiguration`. - var configuration: SKCore.BuildConfiguration { - switch self { - case .debug: return .debug - case .release: return .release - } - } -} - -private extension LanguageServerProtocol.WorkspaceType { - /// Convert `LanguageServerProtocol.WorkspaceType` to `SkSupport.WorkspaceType`. - var workspaceType: SKCore.WorkspaceType { - switch self { - case .buildServer: return .buildServer - case .compilationDatabase: return .compilationDatabase - case .swiftPM: return .swiftPM - } - } -} - extension SourceKitLSPServer { nonisolated func logMessageToIndexLog(taskID: IndexTaskID, message: String) { var message: Substring = message[...] @@ -884,29 +863,6 @@ extension SourceKitLSPServer { // MARK: - General - /// Returns the build setup for the parameters specified for the given `WorkspaceFolder`. - private func buildSetup(for workspaceFolder: WorkspaceFolder) -> BuildSetup { - let buildParams = workspaceFolder.buildSetup - let scratchPath: AbsolutePath? - if let scratchPathParam = buildParams?.scratchPath { - scratchPath = try? AbsolutePath(validating: scratchPathParam.pseudoPath) - } else { - scratchPath = nil - } - return SKCore.BuildSetup( - configuration: buildParams?.buildConfiguration?.configuration, - defaultWorkspaceType: buildParams?.defaultWorkspaceType?.workspaceType, - path: scratchPath, - flags: BuildFlags( - cCompilerFlags: buildParams?.cFlags ?? [], - cxxCompilerFlags: buildParams?.cxxFlags ?? [], - swiftCompilerFlags: buildParams?.swiftFlags ?? [], - linkerFlags: buildParams?.linkerFlags ?? [], - xcbuildFlags: [] - ) - ) - } - private func reloadPackageStatusCallback(_ status: ReloadPackageStatus) async { switch status { case .start: @@ -927,8 +883,7 @@ extension SourceKitLSPServer { logger.log("Cannot open workspace before server is initialized") return nil } - var options = self.options - options.buildSetup = self.options.buildSetup.merging(buildSetup(for: workspaceFolder)) + let options = self.options let buildSystem = await createBuildSystem( rootUri: workspaceFolder.uri, options: options, diff --git a/Sources/SourceKitLSP/Workspace.swift b/Sources/SourceKitLSP/Workspace.swift index a5a6b7374..91bfa53cf 100644 --- a/Sources/SourceKitLSP/Workspace.swift +++ b/Sources/SourceKitLSP/Workspace.swift @@ -255,45 +255,3 @@ struct WeakWorkspace { self.value = value } } - -public struct IndexOptions: Sendable { - - /// Override the index-store-path provided by the build system. - public var indexStorePath: AbsolutePath? - - /// Override the index-database-path provided by the build system. - public var indexDatabasePath: AbsolutePath? - - /// Override the index prefix mappings provided by the build system. - public var indexPrefixMappings: [PathPrefixMapping]? - - /// *For Testing* Whether the index should listen to unit events, or wait for - /// explicit calls to pollForUnitChangesAndWait(). - public var listenToUnitEvents: Bool - - /// The percentage of the machine's cores that should at most be used for background indexing. - /// - /// Setting this to a value < 1 ensures that background indexing doesn't use all CPU resources. - public var maxCoresPercentageToUseForBackgroundIndexing: Double - - /// How long to wait until we cancel an update indexstore task. This timeout should be long enough that all - /// `swift-frontend` tasks finish within it. It prevents us from blocking the index if the type checker gets stuck on - /// an expression for a long time. - public var updateIndexStoreTimeout: Duration - - public init( - indexStorePath: AbsolutePath? = nil, - indexDatabasePath: AbsolutePath? = nil, - indexPrefixMappings: [PathPrefixMapping]? = nil, - listenToUnitEvents: Bool = true, - maxCoresPercentageToUseForBackgroundIndexing: Double = 1, - updateIndexStoreTimeout: Duration = .seconds(120) - ) { - self.indexStorePath = indexStorePath - self.indexDatabasePath = indexDatabasePath - self.indexPrefixMappings = indexPrefixMappings - self.listenToUnitEvents = listenToUnitEvents - self.maxCoresPercentageToUseForBackgroundIndexing = maxCoresPercentageToUseForBackgroundIndexing - self.updateIndexStoreTimeout = updateIndexStoreTimeout - } -} diff --git a/Tests/SourceKitLSPTests/WorkspaceTests.swift b/Tests/SourceKitLSPTests/WorkspaceTests.swift index f1ad94616..0eb2176df 100644 --- a/Tests/SourceKitLSPTests/WorkspaceTests.swift +++ b/Tests/SourceKitLSPTests/WorkspaceTests.swift @@ -723,46 +723,6 @@ final class WorkspaceTests: XCTestCase { ] ) } - - public func testWorkspaceSpecificBuildSettings() async throws { - let project = try await SwiftPMTestProject( - files: [ - "test.swift": """ - #if MY_FLAG - let a: Int = "" - #endif - """ - ], - workspaces: { - [ - WorkspaceFolder( - uri: DocumentURI($0), - buildSetup: WorkspaceBuildSetup( - buildConfiguration: nil, - scratchPath: nil, - cFlags: nil, - cxxFlags: nil, - linkerFlags: nil, - swiftFlags: ["-DMY_FLAG"] - ) - ) - ] - } - ) - - _ = try project.openDocument("test.swift") - let report = try await project.testClient.send( - DocumentDiagnosticsRequest(textDocument: TextDocumentIdentifier(project.uri(for: "test.swift"))) - ) - guard case .full(let fullReport) = report else { - XCTFail("Expected full diagnostics report") - return - } - XCTAssertEqual(fullReport.items.count, 1) - let diag = try XCTUnwrap(fullReport.items.first) - XCTAssertEqual(diag.message, "Cannot convert value of type 'String' to specified type 'Int'") - } - func testIntegrationTest() async throws { // This test is doing the same as `test-sourcekit-lsp` in the `swift-integration-tests` repo. From d1cddb8c417dee1753aaa5fb76912ff82f13e691 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Wed, 26 Jun 2024 18:57:12 +0200 Subject: [PATCH 2/2] =?UTF-8?q?Allow=20configuring=20of=20SourceKit-LSP?= =?UTF-8?q?=E2=80=99s=20options=20using=20`.sourcekit-lsp`=20configuration?= =?UTF-8?q?=20files?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The idea here is to unify the different ways in which we can currently set options on SourceKit-LSP in a scalable way: Environment variables, command line arguments to `sourcekit-lsp` and initialization options. The idea is that a user can define a `~/.sourcekit-lsp/.sourcekit-lsp` file (we store logs in `~/.sourcekit-lsp/logs` on non-Darwin platforms), which will be used as the default configuration for all SourceKit-LSP instances. They can also place a `.sourcekit-lsp` file in the root of a workspace to configure SourceKit-LSP for that project specifically, eg. setting arguments that need to be passed to `swift build` for that project and which thus also need to be set on SourceKit-LSP. For compatibility reasons, I’m mapping the existing command line options into the new options structure for now. I hope to delete the command line arguments in the future and solely rely on `.sourcekit-lsp` configuration files. Environment variable will be migrated to `.sourcekit-lsp` in a follow-up commit. --- Documentation/Configuration File.md | 38 +++ Documentation/LSP Extensions.md | 22 -- Sources/Diagnose/IndexCommand.swift | 6 +- .../InProcessSourceKitLSPClient.swift | 6 +- Sources/LanguageServerProtocol/CMakeLists.txt | 1 - .../Requests/CompletionRequest.swift | 7 +- .../Requests/PollIndexRequest.swift | 3 - .../SupportTypes/SKCompletionOptions.swift | 31 --- Sources/SKCore/BuildConfiguration.swift | 2 +- Sources/SKCore/BuildServerBuildSystem.swift | 9 +- Sources/SKCore/BuildSetup.swift | 67 ----- Sources/SKCore/CMakeLists.txt | 2 +- Sources/SKCore/FallbackBuildSystem.swift | 13 +- Sources/SKCore/SourceKitLSPOptions.swift | 253 ++++++++++++++++++ Sources/SKCore/WorkspaceType.swift | 2 +- Sources/SKSupport/AbsolutePath+Init.swift | 24 ++ Sources/SKSupport/CMakeLists.txt | 1 + .../SwiftPMBuildSystem.swift | 42 +-- .../IndexedSingleSwiftFileTestProject.swift | 9 +- .../SKTestSupport/MultiFileTestProject.swift | 6 +- .../SKTestSupport/SwiftPMTestProject.swift | 8 +- .../TestSourceKitLSPClient.swift | 21 +- Sources/SourceKitLSP/CMakeLists.txt | 2 +- .../Clang/ClangLanguageService.swift | 5 +- Sources/SourceKitLSP/CreateBuildSystem.swift | 15 +- Sources/SourceKitLSP/LanguageService.swift | 3 +- .../SourceKitLSPServer+Options.swift | 102 ------- Sources/SourceKitLSP/SourceKitLSPServer.swift | 51 ++-- .../SourceKitLSP/Swift/CodeCompletion.swift | 3 - .../Swift/CodeCompletionSession.swift | 21 +- .../Swift/SwiftLanguageService.swift | 13 +- Sources/SourceKitLSP/TestHooks.swift | 36 +++ Sources/SourceKitLSP/Workspace.swift | 49 ++-- Sources/sourcekit-lsp/SourceKitLSP.swift | 109 +++++--- .../BuildServerBuildSystemTests.swift | 6 +- .../SKCoreTests/BuildSystemManagerTests.swift | 4 +- .../FallbackBuildSystemTests.swift | 67 ++--- .../SwiftPMBuildSystemTests.swift | 50 ++-- .../BackgroundIndexingTests.swift | 70 ++--- .../SourceKitLSPTests/BuildSystemTests.swift | 8 +- .../ExecuteCommandTests.swift | 6 +- Tests/SourceKitLSPTests/LocalSwiftTests.swift | 5 +- .../PullDiagnosticsTests.swift | 12 +- .../SwiftCompletionTests.swift | 155 +---------- Tests/SourceKitLSPTests/WorkspaceTests.swift | 31 +++ 45 files changed, 722 insertions(+), 674 deletions(-) create mode 100644 Documentation/Configuration File.md delete mode 100644 Sources/LanguageServerProtocol/SupportTypes/SKCompletionOptions.swift delete mode 100644 Sources/SKCore/BuildSetup.swift create mode 100644 Sources/SKCore/SourceKitLSPOptions.swift create mode 100644 Sources/SKSupport/AbsolutePath+Init.swift delete mode 100644 Sources/SourceKitLSP/SourceKitLSPServer+Options.swift create mode 100644 Sources/SourceKitLSP/TestHooks.swift diff --git a/Documentation/Configuration File.md b/Documentation/Configuration File.md new file mode 100644 index 000000000..fad3c396b --- /dev/null +++ b/Documentation/Configuration File.md @@ -0,0 +1,38 @@ +# 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/org.swift.sourcekit-lsp/config.json` +- 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. + +## Structure + +`config.json` is a JSON file with the following structure. All keys are optional and unknown keys are ignored. + +- `swiftPM`: Dictionary with the following keys, defining options for SwiftPM workspaces + - `configuration: "debug"|"release"`: The configuration to build the project for during background indexing and the configuration whose build folder should be used for Swift modules if background indexing is disabled + - `scratchPath: string`: Build artifacts directory path. If nil, the build system may choose a default value + - `cCompilerFlags: string[]`: Extra arguments passed to the compiler for C files + - `cxxCompilerFlags: string[]`: Extra arguments passed to the compiler for C++ files + - `swiftCompilerFlags: string[]`: Extra arguments passed to the compiler for Swift files + - `linkerFlags: string[]`: Extra arguments passed to the linker +- `compilationDatabase`: Dictionary with the following keys, defining options for workspaces with a compilation database + - `searchPaths: string[]`: Additional paths to search for a compilation database, relative to a workspace root. +- `fallbackBuildSystem`: Dictionary with the following keys, defining options for files that aren't managed by any build system + - `cCompilerFlags: string[]`: Extra arguments passed to the compiler for C files + - `cxxCompilerFlags: string[]`: Extra arguments passed to the compiler for C++ files + - `swiftCompilerFlags: string[]`: Extra arguments passed to the compiler for Swift files +- `clangdOptions: string[]`: Extra command line arguments passed to `clangd` when launching it +- `index`: Dictionary with the following keys, defining options related to indexing + - `indexStorePath: string`: Directory in which a separate compilation stores the index store. By default, inferred from the build system. + - `indexDatabasePath: string`: Directory in which the indexstore-db should be stored. By default, inferred from the build system. + - `indexPrefixMap: [string: string]`: Path remappings for remapping index data for local use. + - `maxCoresPercentageToUseForBackgroundIndexing: double`: A hint indicating how many cores background indexing should use at most (value between 0 and 1). Background indexing is not required to honor this setting + - `updateIndexStoreTimeout: int`: Number of seconds to wait for an update index store task to finish before killing it. +- `defaultWorkspaceType: "buildserver"|"compdb"|"swiftpm"`: Overrides workspace type selection logic. +- `generatedFilesPath: string`: Directory in which generated interfaces and macro expansions should be stored. +- `experimentalFeatures: string[]`: Experimental features to enable +- `swiftPublishDiagnosticsDebounce`: The time that `SwiftLanguageService` should wait after an edit before starting to compute diagnostics and sending a `PublishDiagnosticsNotification`. diff --git a/Documentation/LSP Extensions.md b/Documentation/LSP Extensions.md index 24fda628b..fb6c5a97f 100644 --- a/Documentation/LSP Extensions.md +++ b/Documentation/LSP Extensions.md @@ -73,28 +73,6 @@ Added case identifier = 'identifier' ``` -## `textDocument/completion` - -Added field: - -```ts -/** - * Options to control code completion behavior in Swift - */ -sourcekitlspOptions?: SKCompletionOptions -``` - -with - -```ts -interface SKCompletionOptions { - /** - * The maximum number of completion results to return, or `null` for unlimited. - */ - maxResults?: int -} -``` - ## `textDocument/interface` New request that request a textual interface of a module to display in the IDE. Used internally within SourceKit-LSP diff --git a/Sources/Diagnose/IndexCommand.swift b/Sources/Diagnose/IndexCommand.swift index dc36688ec..8e556581c 100644 --- a/Sources/Diagnose/IndexCommand.swift +++ b/Sources/Diagnose/IndexCommand.swift @@ -82,9 +82,7 @@ public struct IndexCommand: AsyncParsableCommand { public init() {} public func run() async throws { - var serverOptions = SourceKitLSPServer.Options() - serverOptions.experimentalFeatures = Set(experimentalFeatures) - serverOptions.experimentalFeatures.insert(.backgroundIndexing) + let options = SourceKitLSPOptions(experimentalFeatures: Set(experimentalFeatures).union([.backgroundIndexing])) let installPath = if let toolchainOverride, let toolchain = Toolchain(try AbsolutePath(validating: toolchainOverride)) { @@ -96,7 +94,7 @@ public struct IndexCommand: AsyncParsableCommand { let messageHandler = IndexLogMessageHandler() let inProcessClient = try await InProcessSourceKitLSPClient( toolchainRegistry: ToolchainRegistry(installPath: installPath), - serverOptions: serverOptions, + options: options, workspaceFolders: [WorkspaceFolder(uri: DocumentURI(URL(fileURLWithPath: project)))], messageHandler: messageHandler ) diff --git a/Sources/InProcessClient/InProcessSourceKitLSPClient.swift b/Sources/InProcessClient/InProcessSourceKitLSPClient.swift index fed141572..9f23052c6 100644 --- a/Sources/InProcessClient/InProcessSourceKitLSPClient.swift +++ b/Sources/InProcessClient/InProcessSourceKitLSPClient.swift @@ -26,7 +26,8 @@ public final class InProcessSourceKitLSPClient: Sendable { /// `messageHandler` handles notifications and requests sent from the SourceKit-LSP server to the client. public init( toolchainRegistry: ToolchainRegistry, - serverOptions: SourceKitLSPServer.Options = SourceKitLSPServer.Options(), + options: SourceKitLSPOptions = SourceKitLSPOptions(), + testHooks: TestHooks = TestHooks(), capabilities: ClientCapabilities = ClientCapabilities(), workspaceFolders: [WorkspaceFolder], messageHandler: any MessageHandler @@ -35,7 +36,8 @@ public final class InProcessSourceKitLSPClient: Sendable { self.server = SourceKitLSPServer( client: serverToClientConnection, toolchainRegistry: toolchainRegistry, - options: serverOptions, + options: options, + testHooks: testHooks, onExit: { serverToClientConnection.close() } diff --git a/Sources/LanguageServerProtocol/CMakeLists.txt b/Sources/LanguageServerProtocol/CMakeLists.txt index bd06c0f5e..e3f16ce7d 100644 --- a/Sources/LanguageServerProtocol/CMakeLists.txt +++ b/Sources/LanguageServerProtocol/CMakeLists.txt @@ -124,7 +124,6 @@ add_library(LanguageServerProtocol STATIC SupportTypes/SemanticTokens.swift SupportTypes/SemanticTokenTypes.swift SupportTypes/ServerCapabilities.swift - SupportTypes/SKCompletionOptions.swift SupportTypes/StringOrMarkupContent.swift SupportTypes/SymbolKind.swift SupportTypes/TestItem.swift diff --git a/Sources/LanguageServerProtocol/Requests/CompletionRequest.swift b/Sources/LanguageServerProtocol/Requests/CompletionRequest.swift index 921d29fb2..3d995a728 100644 --- a/Sources/LanguageServerProtocol/Requests/CompletionRequest.swift +++ b/Sources/LanguageServerProtocol/Requests/CompletionRequest.swift @@ -23,7 +23,6 @@ /// - textDocument: The document to perform completion in. /// - position: The location to perform completion at. /// - context: Optional code-completion context. -/// - sourcekitlspOptions: **(LSP Extension)** code-completion options for sourcekit-lsp. /// /// - Returns: A list of completion items to complete the code at the given position. public struct CompletionRequest: TextDocumentRequest, Hashable { @@ -36,18 +35,14 @@ public struct CompletionRequest: TextDocumentRequest, Hashable { public var context: CompletionContext? - public var sourcekitlspOptions: SKCompletionOptions? - public init( textDocument: TextDocumentIdentifier, position: Position, - context: CompletionContext? = nil, - sourcekitlspOptions: SKCompletionOptions? = nil + context: CompletionContext? = nil ) { self.textDocument = textDocument self.position = position self.context = context - self.sourcekitlspOptions = sourcekitlspOptions } } diff --git a/Sources/LanguageServerProtocol/Requests/PollIndexRequest.swift b/Sources/LanguageServerProtocol/Requests/PollIndexRequest.swift index d3ee4e2d8..52f18a737 100644 --- a/Sources/LanguageServerProtocol/Requests/PollIndexRequest.swift +++ b/Sources/LanguageServerProtocol/Requests/PollIndexRequest.swift @@ -12,9 +12,6 @@ /// Poll the index for unit changes and wait for them to be registered. /// **LSP Extension, For Testing**. -/// -/// Users of PollIndex should set `"initializationOptions": { "listenToUnitEvents": false }` during -/// the `initialize` request. public struct PollIndexRequest: RequestType { public static let method: String = "workspace/_pollIndex" public typealias Response = VoidResponse diff --git a/Sources/LanguageServerProtocol/SupportTypes/SKCompletionOptions.swift b/Sources/LanguageServerProtocol/SupportTypes/SKCompletionOptions.swift deleted file mode 100644 index 4434c6413..000000000 --- a/Sources/LanguageServerProtocol/SupportTypes/SKCompletionOptions.swift +++ /dev/null @@ -1,31 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2014 - 2020 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 -// -//===----------------------------------------------------------------------===// - -/// Code-completion configuration. -/// -/// **(LSP Extension)**: This is used as part of an extension to the -/// code-completion request. -public struct SKCompletionOptions: Codable, Hashable, Sendable { - /// The maximum number of completion results to return, or `nil` for unlimited. - public var maxResults: Int? - - /// Older `sourcekit-lsp` binaries reject request sent from client to - /// server when the parameter is missing. By adding the parameter with default - /// value true the client that uses **LanguageServerProtocol** as model can still - /// interact with shipped `sourcekit-lsp` instances. - @available(*, deprecated, message: "Not used") - public private(set) var serverSideFiltering: Bool = true - - public init(maxResults: Int? = 200) { - self.maxResults = maxResults - } -} diff --git a/Sources/SKCore/BuildConfiguration.swift b/Sources/SKCore/BuildConfiguration.swift index 60e277084..b58a94781 100644 --- a/Sources/SKCore/BuildConfiguration.swift +++ b/Sources/SKCore/BuildConfiguration.swift @@ -10,7 +10,7 @@ // //===----------------------------------------------------------------------===// -public enum BuildConfiguration: String, Sendable { +public enum BuildConfiguration: String, Codable, Sendable { case debug case release } diff --git a/Sources/SKCore/BuildServerBuildSystem.swift b/Sources/SKCore/BuildServerBuildSystem.swift index c00e88b9d..d46c95050 100644 --- a/Sources/SKCore/BuildServerBuildSystem.swift +++ b/Sources/SKCore/BuildServerBuildSystem.swift @@ -45,7 +45,6 @@ func executable(_ name: String) -> String { /// `buildServer.json` configuration file provided in the repo root. public actor BuildServerBuildSystem: MessageHandler { public let projectRoot: AbsolutePath - let buildFolder: AbsolutePath? let serverConfig: BuildServerConfig var buildServer: JSONRPCConnection? @@ -82,7 +81,6 @@ public actor BuildServerBuildSystem: MessageHandler { public init( projectRoot: AbsolutePath, - buildFolder: AbsolutePath?, fileSystem: FileSystem = localFileSystem ) async throws { let configPath = projectRoot.appending(component: "buildServer.json") @@ -100,7 +98,6 @@ public actor BuildServerBuildSystem: MessageHandler { currentWorkingDirectory: fileSystem.currentWorkingDirectory ) #endif - self.buildFolder = buildFolder self.projectRoot = projectRoot self.serverConfig = config try await self.initializeBuildServer() @@ -109,11 +106,11 @@ public actor BuildServerBuildSystem: MessageHandler { /// Creates a build system using the Build Server Protocol config. /// /// - Returns: nil if `projectRoot` has no config or there is an error parsing it. - public init?(projectRoot: AbsolutePath?, buildSetup: BuildSetup) async { - if projectRoot == nil { return nil } + public init?(projectRoot: AbsolutePath?) async { + guard let projectRoot else { return nil } do { - try await self.init(projectRoot: projectRoot!, buildFolder: buildSetup.path) + try await self.init(projectRoot: projectRoot) } catch is FileSystemError { // config file was missing, no build server for this workspace return nil diff --git a/Sources/SKCore/BuildSetup.swift b/Sources/SKCore/BuildSetup.swift deleted file mode 100644 index d03d5c3f0..000000000 --- a/Sources/SKCore/BuildSetup.swift +++ /dev/null @@ -1,67 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2014 - 2019 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 SKSupport - -@preconcurrency import struct PackageModel.BuildFlags -import struct TSCBasic.AbsolutePath - -/// Build configuration -public struct BuildSetup: Sendable { - - /// Default configuration - public static let `default` = BuildSetup( - configuration: nil, - defaultWorkspaceType: nil, - path: nil, - flags: BuildFlags() - ) - - /// Build configuration (debug|release). - public var configuration: BuildConfiguration? - - /// Default workspace type (buildserver|compdb|swiftpm). Overrides workspace type selection logic. - public var defaultWorkspaceType: WorkspaceType? - - /// Build artifacts directory path. If nil, the build system may choose a default value. - public var path: AbsolutePath? - - /// Additional build flags - public var flags: BuildFlags - - public init( - configuration: BuildConfiguration?, - defaultWorkspaceType: WorkspaceType?, - path: AbsolutePath?, - flags: BuildFlags - ) { - self.configuration = configuration - self.defaultWorkspaceType = defaultWorkspaceType - self.path = path - self.flags = flags - } - - /// Create a new `BuildSetup` merging this and `other`. - /// - /// For any option that only takes a single value (like `configuration`), `other` takes precedence. For all array - /// arguments, `other` is appended to the options provided by this setup. - public func merging(_ other: BuildSetup) -> BuildSetup { - var flags = self.flags - flags = flags.merging(other.flags) - return BuildSetup( - configuration: other.configuration ?? self.configuration, - defaultWorkspaceType: other.defaultWorkspaceType ?? self.defaultWorkspaceType, - path: other.path ?? self.path, - flags: flags - ) - } -} diff --git a/Sources/SKCore/CMakeLists.txt b/Sources/SKCore/CMakeLists.txt index db40baea8..e7ec61f28 100644 --- a/Sources/SKCore/CMakeLists.txt +++ b/Sources/SKCore/CMakeLists.txt @@ -2,7 +2,6 @@ add_library(SKCore STATIC BuildConfiguration.swift BuildServerBuildSystem.swift - BuildSetup.swift BuildSystem.swift BuildSystemDelegate.swift BuildSystemManager.swift @@ -13,6 +12,7 @@ add_library(SKCore STATIC IndexTaskID.swift MainFilesProvider.swift PathPrefixMapping.swift + SourceKitLSPOptions.swift SplitShellCommand.swift Toolchain.swift ToolchainRegistry.swift diff --git a/Sources/SKCore/FallbackBuildSystem.swift b/Sources/SKCore/FallbackBuildSystem.swift index 30c1e73b1..084b011bb 100644 --- a/Sources/SKCore/FallbackBuildSystem.swift +++ b/Sources/SKCore/FallbackBuildSystem.swift @@ -22,11 +22,10 @@ import class TSCBasic.Process /// A simple BuildSystem suitable as a fallback when accurate settings are unknown. public actor FallbackBuildSystem { + private let options: SourceKitLSPOptions.FallbackBuildSystemOptions - let buildSetup: BuildSetup - - public init(buildSetup: BuildSetup) { - self.buildSetup = buildSetup + public init(options: SourceKitLSPOptions.FallbackBuildSystemOptions) { + self.options = options } /// The path to the SDK. @@ -71,7 +70,7 @@ public actor FallbackBuildSystem { func settingsSwift(_ file: String) -> FileBuildSettings { var args: [String] = [] - args.append(contentsOf: self.buildSetup.flags.swiftCompilerFlags) + args.append(contentsOf: self.options.swiftCompilerFlags ?? []) if let sdkpath = sdkpath, !args.contains("-sdk") { args += [ "-sdk", @@ -86,9 +85,9 @@ public actor FallbackBuildSystem { var args: [String] = [] switch language { case .c: - args.append(contentsOf: self.buildSetup.flags.cCompilerFlags) + args.append(contentsOf: self.options.cCompilerFlags ?? []) case .cpp: - args.append(contentsOf: self.buildSetup.flags.cxxCompilerFlags) + args.append(contentsOf: self.options.cxxCompilerFlags ?? []) default: break } diff --git a/Sources/SKCore/SourceKitLSPOptions.swift b/Sources/SKCore/SourceKitLSPOptions.swift new file mode 100644 index 000000000..0a9ad011a --- /dev/null +++ b/Sources/SKCore/SourceKitLSPOptions.swift @@ -0,0 +1,253 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +import Foundation +import LSPLogging +import SKCore +import SKSupport + +import struct TSCBasic.AbsolutePath + +/// Options that can be used to modify SourceKit-LSP's behavior. +/// +/// See `ConfigurationFile.md` for a description of the configuration file's behavior. +public struct SourceKitLSPOptions: Sendable, Codable { + public struct SwiftPMOptions: Sendable, Codable { + /// Build configuration (debug|release). + public var configuration: BuildConfiguration? + + /// Build artifacts directory path. If nil, the build system may choose a default value. + public var scratchPath: String? + + public var cCompilerFlags: [String]? + public var cxxCompilerFlags: [String]? + public var swiftCompilerFlags: [String]? + public var linkerFlags: [String]? + + public init( + configuration: BuildConfiguration? = nil, + scratchPath: String? = nil, + cCompilerFlags: [String]? = nil, + cxxCompilerFlags: [String]? = nil, + swiftCompilerFlags: [String]? = nil, + linkerFlags: [String]? = nil + ) { + self.configuration = configuration + self.scratchPath = scratchPath + self.cCompilerFlags = cCompilerFlags + self.cxxCompilerFlags = cxxCompilerFlags + self.swiftCompilerFlags = swiftCompilerFlags + self.linkerFlags = linkerFlags + } + + static func merging(base: SwiftPMOptions, override: SwiftPMOptions?) -> SwiftPMOptions { + return SwiftPMOptions( + configuration: override?.configuration ?? base.configuration, + scratchPath: override?.scratchPath ?? base.scratchPath, + cCompilerFlags: override?.cCompilerFlags ?? base.cCompilerFlags, + cxxCompilerFlags: override?.cxxCompilerFlags ?? base.cxxCompilerFlags, + swiftCompilerFlags: override?.swiftCompilerFlags ?? base.swiftCompilerFlags, + linkerFlags: override?.linkerFlags ?? base.linkerFlags + ) + } + } + + public struct CompilationDatabaseOptions: Sendable, Codable { + /// Additional paths to search for a compilation database, relative to a workspace root. + public var searchPaths: [String]? + + public init(searchPaths: [String]? = nil) { + self.searchPaths = searchPaths + } + + static func merging( + base: CompilationDatabaseOptions, + override: CompilationDatabaseOptions? + ) -> CompilationDatabaseOptions { + return CompilationDatabaseOptions(searchPaths: override?.searchPaths ?? base.searchPaths) + } + } + + public struct FallbackBuildSystemOptions: Sendable, Codable { + public var cCompilerFlags: [String]? + public var cxxCompilerFlags: [String]? + public var swiftCompilerFlags: [String]? + + public init( + cCompilerFlags: [String]? = nil, + cxxCompilerFlags: [String]? = nil, + swiftCompilerFlags: [String]? = nil + ) { + self.cCompilerFlags = cCompilerFlags + self.cxxCompilerFlags = cxxCompilerFlags + self.swiftCompilerFlags = swiftCompilerFlags + } + + static func merging( + base: FallbackBuildSystemOptions, + override: FallbackBuildSystemOptions? + ) -> FallbackBuildSystemOptions { + return FallbackBuildSystemOptions( + cCompilerFlags: override?.cCompilerFlags ?? base.cCompilerFlags, + cxxCompilerFlags: override?.cxxCompilerFlags ?? base.cxxCompilerFlags, + swiftCompilerFlags: override?.swiftCompilerFlags ?? base.swiftCompilerFlags + ) + } + } + + public struct IndexOptions: Sendable, Codable { + public var indexStorePath: String? + public var indexDatabasePath: String? + public var indexPrefixMap: [String: String]? + public var maxCoresPercentageToUseForBackgroundIndexing: Double? + public var updateIndexStoreTimeout: Int? + + public init( + indexStorePath: String? = nil, + indexDatabasePath: String? = nil, + indexPrefixMap: [String: String]? = nil, + maxCoresPercentageToUseForBackgroundIndexing: Double? = nil, + updateIndexStoreTimeout: Int? = nil + ) { + self.indexStorePath = indexStorePath + self.indexDatabasePath = indexDatabasePath + self.indexPrefixMap = indexPrefixMap + self.maxCoresPercentageToUseForBackgroundIndexing = maxCoresPercentageToUseForBackgroundIndexing + self.updateIndexStoreTimeout = updateIndexStoreTimeout + } + + static func merging(base: IndexOptions, override: IndexOptions?) -> IndexOptions { + return IndexOptions( + indexStorePath: override?.indexStorePath ?? base.indexStorePath, + indexDatabasePath: override?.indexDatabasePath ?? base.indexDatabasePath, + indexPrefixMap: override?.indexPrefixMap ?? base.indexPrefixMap, + maxCoresPercentageToUseForBackgroundIndexing: override?.maxCoresPercentageToUseForBackgroundIndexing + ?? base.maxCoresPercentageToUseForBackgroundIndexing, + updateIndexStoreTimeout: override?.updateIndexStoreTimeout ?? base.updateIndexStoreTimeout + ) + } + } + + public var swiftPM: SwiftPMOptions? + public var compilationDatabase: CompilationDatabaseOptions? + public var fallbackBuildSystem: FallbackBuildSystemOptions? + public var clangdOptions: [String]? + public var index: IndexOptions? + + /// Default workspace type (buildserver|compdb|swiftpm). Overrides workspace type selection logic. + public var defaultWorkspaceType: WorkspaceType? + public var generatedFilesPath: String? + + /// Experimental features that are enabled. + public var experimentalFeatures: Set? = nil + + /// The time that `SwiftLanguageService` should wait after an edit before starting to compute diagnostics and + /// sending a `PublishDiagnosticsNotification`. + /// + /// This is mostly intended for testing purposes so we don't need to wait the debouncing time to get a diagnostics + /// notification when running unit tests. + public var swiftPublishDiagnosticsDebounce: Double? = nil + + public var swiftPublishDiagnosticsDebounceDuration: TimeInterval { + if let workDoneProgressDebounce { + return workDoneProgressDebounce + } + return 2 /* seconds */ + } + + /// When a task is started that should be displayed to the client as a work done progress, how many milliseconds to + /// wait before actually starting the work done progress. This prevents flickering of the work done progress in the + /// client for short-lived index tasks which end within this duration. + public var workDoneProgressDebounce: Double? = nil + + public var workDoneProgressDebounceDuration: Duration { + if let workDoneProgressDebounce { + return .seconds(workDoneProgressDebounce) + } + return .seconds(0) + } + + public init( + swiftPM: SwiftPMOptions = .init(), + fallbackBuildSystem: FallbackBuildSystemOptions = .init(), + compilationDatabase: CompilationDatabaseOptions = .init(), + clangdOptions: [String]? = nil, + index: IndexOptions = .init(), + defaultWorkspaceType: WorkspaceType? = nil, + generatedFilesPath: String? = nil, + experimentalFeatures: Set? = nil, + swiftPublishDiagnosticsDebounce: Double? = nil, + workDoneProgressDebounce: Double? = nil + ) { + self.swiftPM = swiftPM + self.fallbackBuildSystem = fallbackBuildSystem + self.compilationDatabase = compilationDatabase + self.clangdOptions = clangdOptions + self.index = index + self.generatedFilesPath = generatedFilesPath + self.defaultWorkspaceType = defaultWorkspaceType + self.experimentalFeatures = experimentalFeatures + self.swiftPublishDiagnosticsDebounce = swiftPublishDiagnosticsDebounce + self.workDoneProgressDebounce = workDoneProgressDebounce + } + + public init?(path: URL?) { + guard let path, let contents = try? String(contentsOf: path, encoding: .utf8) else { + return nil + } + guard + let decoded = orLog( + "Parsing config.json", + { try JSONDecoder().decode(SourceKitLSPOptions.self, from: contents) } + ) + else { + return nil + } + self = decoded + } + + public static func merging(base: SourceKitLSPOptions, override: SourceKitLSPOptions?) -> SourceKitLSPOptions { + return SourceKitLSPOptions( + swiftPM: SwiftPMOptions.merging(base: base.swiftPM ?? .init(), override: override?.swiftPM), + fallbackBuildSystem: FallbackBuildSystemOptions.merging( + base: base.fallbackBuildSystem ?? .init(), + override: override?.fallbackBuildSystem + ), + compilationDatabase: CompilationDatabaseOptions.merging( + base: base.compilationDatabase ?? .init(), + override: override?.compilationDatabase + ), + clangdOptions: override?.clangdOptions ?? base.clangdOptions, + index: IndexOptions.merging(base: base.index ?? .init(), override: override?.index), + defaultWorkspaceType: override?.defaultWorkspaceType ?? base.defaultWorkspaceType, + generatedFilesPath: override?.generatedFilesPath ?? base.generatedFilesPath, + experimentalFeatures: override?.experimentalFeatures ?? base.experimentalFeatures, + swiftPublishDiagnosticsDebounce: override?.swiftPublishDiagnosticsDebounce + ?? base.swiftPublishDiagnosticsDebounce, + workDoneProgressDebounce: override?.workDoneProgressDebounce ?? base.workDoneProgressDebounce + ) + } + + public var generatedFilesAbsolutePath: AbsolutePath { + if let absolutePath = AbsolutePath(validatingOrNil: generatedFilesPath) { + return absolutePath + } + return defaultDirectoryForGeneratedFiles + } + + public func hasExperimentalFeature(_ feature: ExperimentalFeature) -> Bool { + guard let experimentalFeatures else { + return false + } + return experimentalFeatures.contains(feature) + } +} diff --git a/Sources/SKCore/WorkspaceType.swift b/Sources/SKCore/WorkspaceType.swift index 9ed4f129b..4d502d26a 100644 --- a/Sources/SKCore/WorkspaceType.swift +++ b/Sources/SKCore/WorkspaceType.swift @@ -10,7 +10,7 @@ // //===----------------------------------------------------------------------===// -public enum WorkspaceType: String, Sendable { +public enum WorkspaceType: String, Codable, Sendable { case buildServer case compilationDatabase case swiftPM diff --git a/Sources/SKSupport/AbsolutePath+Init.swift b/Sources/SKSupport/AbsolutePath+Init.swift new file mode 100644 index 000000000..c4e232bb2 --- /dev/null +++ b/Sources/SKSupport/AbsolutePath+Init.swift @@ -0,0 +1,24 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2020 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 struct TSCBasic.AbsolutePath + +extension AbsolutePath { + /// Same as `init(validating:)` but returns `nil` on validation failure instead of throwing. + public init?(validatingOrNil string: String?) { + guard let string, let path = try? AbsolutePath(validating: string) else { + return nil + } + self = path + } + +} diff --git a/Sources/SKSupport/CMakeLists.txt b/Sources/SKSupport/CMakeLists.txt index a41ff98fa..8982432b6 100644 --- a/Sources/SKSupport/CMakeLists.txt +++ b/Sources/SKSupport/CMakeLists.txt @@ -1,5 +1,6 @@ add_library(SKSupport STATIC + AbsolutePath+Init.swift Atomics.swift ByteString.swift Connection+Send.swift diff --git a/Sources/SKSwiftPMWorkspace/SwiftPMBuildSystem.swift b/Sources/SKSwiftPMWorkspace/SwiftPMBuildSystem.swift index f2a5cca72..0f47fa071 100644 --- a/Sources/SKSwiftPMWorkspace/SwiftPMBuildSystem.swift +++ b/Sources/SKSwiftPMWorkspace/SwiftPMBuildSystem.swift @@ -130,8 +130,8 @@ public actor SwiftPMBuildSystem { private let workspacePath: TSCAbsolutePath - /// The build setup that allows the user to pass extra compiler flags. - private let buildSetup: BuildSetup + /// Options that allow the user to pass extra compiler flags. + private let options: SourceKitLSPOptions.SwiftPMOptions /// The directory containing `Package.swift`. @_spi(Testing) @@ -196,13 +196,13 @@ public actor SwiftPMBuildSystem { workspacePath: TSCAbsolutePath, toolchainRegistry: ToolchainRegistry, fileSystem: FileSystem = localFileSystem, - buildSetup: BuildSetup, + options: SourceKitLSPOptions.SwiftPMOptions, experimentalFeatures: Set, reloadPackageStatusCallback: @escaping (ReloadPackageStatus) async -> Void = { _ in }, testHooks: SwiftPMTestHooks ) async throws { self.workspacePath = workspacePath - self.buildSetup = buildSetup + self.options = options self.fileSystem = fileSystem guard let toolchain = await preferredToolchain(toolchainRegistry) else { throw Error.cannotDetermineHostToolchain @@ -231,8 +231,10 @@ public actor SwiftPMBuildSystem { ) if experimentalFeatures.contains(.backgroundIndexing) { location.scratchDirectory = AbsolutePath(packageRoot.appending(component: ".index-build")) - } else if let scratchDirectory = buildSetup.path { - location.scratchDirectory = AbsolutePath(scratchDirectory) + } else if let scratchDirectory = options.scratchPath, + let scratchDirectoryPath = try? AbsolutePath(validating: scratchDirectory) + { + location.scratchDirectory = scratchDirectoryPath } var configuration = WorkspaceConfiguration.default @@ -246,13 +248,20 @@ public actor SwiftPMBuildSystem { ) let buildConfiguration: PackageModel.BuildConfiguration - switch buildSetup.configuration { + switch options.configuration { case .debug, nil: buildConfiguration = .debug case .release: buildConfiguration = .release } + let buildFlags = BuildFlags( + cCompilerFlags: options.cCompilerFlags ?? [], + cxxCompilerFlags: options.cxxCompilerFlags ?? [], + swiftCompilerFlags: options.swiftCompilerFlags ?? [], + linkerFlags: options.linkerFlags ?? [] + ) + self.toolsBuildParameters = try BuildParameters( destination: .host, dataPath: location.scratchDirectory.appending( @@ -260,7 +269,7 @@ public actor SwiftPMBuildSystem { ), configuration: buildConfiguration, toolchain: swiftPMToolchain, - flags: buildSetup.flags + flags: buildFlags ) self.destinationBuildParameters = try BuildParameters( @@ -270,7 +279,7 @@ public actor SwiftPMBuildSystem { ), configuration: buildConfiguration, toolchain: swiftPMToolchain, - flags: buildSetup.flags + flags: buildFlags ) self.modulesGraph = try ModulesGraph( @@ -303,7 +312,7 @@ public actor SwiftPMBuildSystem { public init?( uri: DocumentURI, toolchainRegistry: ToolchainRegistry, - buildSetup: BuildSetup, + options: SourceKitLSPOptions.SwiftPMOptions, experimentalFeatures: Set, reloadPackageStatusCallback: @escaping (ReloadPackageStatus) async -> Void, testHooks: SwiftPMTestHooks @@ -316,7 +325,7 @@ public actor SwiftPMBuildSystem { workspacePath: try TSCAbsolutePath(validating: fileURL.path), toolchainRegistry: toolchainRegistry, fileSystem: localFileSystem, - buildSetup: buildSetup, + options: options, experimentalFeatures: experimentalFeatures, reloadPackageStatusCallback: reloadPackageStatusCallback, testHooks: testHooks @@ -604,14 +613,13 @@ extension SwiftPMBuildSystem: SKCore.BuildSystem { "--disable-index-store", "--target", target.targetID, ] - if let configuration = buildSetup.configuration { + if let configuration = options.configuration { arguments += ["-c", configuration.rawValue] } - arguments += buildSetup.flags.cCompilerFlags.flatMap { ["-Xcc", $0] } - arguments += buildSetup.flags.cxxCompilerFlags.flatMap { ["-Xcxx", $0] } - arguments += buildSetup.flags.swiftCompilerFlags.flatMap { ["-Xswiftc", $0] } - arguments += buildSetup.flags.linkerFlags.flatMap { ["-Xlinker", $0] } - arguments += buildSetup.flags.xcbuildFlags?.flatMap { ["-Xxcbuild", $0] } ?? [] + arguments += options.cCompilerFlags?.flatMap { ["-Xcc", $0] } ?? [] + arguments += options.cxxCompilerFlags?.flatMap { ["-Xcxx", $0] } ?? [] + arguments += options.swiftCompilerFlags?.flatMap { ["-Xswiftc", $0] } ?? [] + arguments += options.linkerFlags?.flatMap { ["-Xlinker", $0] } ?? [] if experimentalFeatures.contains(.swiftpmPrepareForIndexing) { arguments.append("--experimental-prepare-for-indexing") } diff --git a/Sources/SKTestSupport/IndexedSingleSwiftFileTestProject.swift b/Sources/SKTestSupport/IndexedSingleSwiftFileTestProject.swift index 509766369..774ca269e 100644 --- a/Sources/SKTestSupport/IndexedSingleSwiftFileTestProject.swift +++ b/Sources/SKTestSupport/IndexedSingleSwiftFileTestProject.swift @@ -118,13 +118,10 @@ public struct IndexedSingleSwiftFileTestProject { } // Create the test client - var options = SourceKitLSPServer.Options.testDefault - options.indexOptions = IndexOptions( - indexStorePath: try AbsolutePath(validating: indexURL.path), - indexDatabasePath: try AbsolutePath(validating: indexDBURL.path) - ) + var options = SourceKitLSPOptions.testDefault() + options.index = SourceKitLSPOptions.IndexOptions(indexStorePath: indexURL.path, indexDatabasePath: indexDBURL.path) self.testClient = try await TestSourceKitLSPClient( - serverOptions: options, + options: options, workspaceFolders: [ WorkspaceFolder(uri: DocumentURI(testWorkspaceDirectory)) ], diff --git a/Sources/SKTestSupport/MultiFileTestProject.swift b/Sources/SKTestSupport/MultiFileTestProject.swift index 61f1ac35c..b25fa27f6 100644 --- a/Sources/SKTestSupport/MultiFileTestProject.swift +++ b/Sources/SKTestSupport/MultiFileTestProject.swift @@ -81,7 +81,8 @@ public class MultiFileTestProject { files: [RelativeFileLocation: String], workspaces: (URL) async throws -> [WorkspaceFolder] = { [WorkspaceFolder(uri: DocumentURI($0))] }, capabilities: ClientCapabilities = ClientCapabilities(), - serverOptions: SourceKitLSPServer.Options = .testDefault, + options: SourceKitLSPOptions = .testDefault(), + testHooks: TestHooks = TestHooks(), enableBackgroundIndexing: Bool = false, usePullDiagnostics: Bool = true, preInitialization: ((TestSourceKitLSPClient) -> Void)? = nil, @@ -115,7 +116,8 @@ public class MultiFileTestProject { self.fileData = fileData self.testClient = try await TestSourceKitLSPClient( - serverOptions: serverOptions, + options: options, + testHooks: testHooks, capabilities: capabilities, usePullDiagnostics: usePullDiagnostics, enableBackgroundIndexing: enableBackgroundIndexing, diff --git a/Sources/SKTestSupport/SwiftPMTestProject.swift b/Sources/SKTestSupport/SwiftPMTestProject.swift index 71bda51da..024fa6fff 100644 --- a/Sources/SKTestSupport/SwiftPMTestProject.swift +++ b/Sources/SKTestSupport/SwiftPMTestProject.swift @@ -151,7 +151,8 @@ public class SwiftPMTestProject: MultiFileTestProject { manifest: String = SwiftPMTestProject.defaultPackageManifest, workspaces: (URL) async throws -> [WorkspaceFolder] = { [WorkspaceFolder(uri: DocumentURI($0))] }, capabilities: ClientCapabilities = ClientCapabilities(), - serverOptions: SourceKitLSPServer.Options = .testDefault, + options: SourceKitLSPOptions = .testDefault(), + testHooks: TestHooks = TestHooks(), enableBackgroundIndexing: Bool = false, usePullDiagnostics: Bool = true, pollIndex: Bool = true, @@ -163,7 +164,7 @@ public class SwiftPMTestProject: MultiFileTestProject { for (fileLocation, contents) in files { let directories = switch fileLocation.directories.first { - case "Sources", "Tests", "Plugins": + case "Sources", "Tests", "Plugins", "": fileLocation.directories case nil: ["Sources", "MyLibrary"] @@ -190,7 +191,8 @@ public class SwiftPMTestProject: MultiFileTestProject { files: filesByPath, workspaces: workspaces, capabilities: capabilities, - serverOptions: serverOptions, + options: options, + testHooks: testHooks, enableBackgroundIndexing: enableBackgroundIndexing, usePullDiagnostics: usePullDiagnostics, preInitialization: preInitialization, diff --git a/Sources/SKTestSupport/TestSourceKitLSPClient.swift b/Sources/SKTestSupport/TestSourceKitLSPClient.swift index bce22101e..92924acd0 100644 --- a/Sources/SKTestSupport/TestSourceKitLSPClient.swift +++ b/Sources/SKTestSupport/TestSourceKitLSPClient.swift @@ -22,9 +22,10 @@ import SwiftExtensions import SwiftSyntax import XCTest -extension SourceKitLSPServer.Options { - /// The default SourceKitLSPServer options for testing. - public static let testDefault = Self(swiftPublishDiagnosticsDebounceDuration: 0) +extension SourceKitLSPOptions { + public static func testDefault(experimentalFeatures: Set? = nil) -> SourceKitLSPOptions { + return SourceKitLSPOptions(experimentalFeatures: experimentalFeatures, swiftPublishDiagnosticsDebounce: 0) + } } fileprivate struct NotificationTimeoutError: Error, CustomStringConvertible { @@ -94,7 +95,8 @@ public final class TestSourceKitLSPClient: MessageHandler, Sendable { /// This allows e.g. a `IndexedSingleSwiftFileTestProject` to delete its temporary files when they are no longer /// needed. public init( - serverOptions: SourceKitLSPServer.Options = .testDefault, + options: SourceKitLSPOptions = .testDefault(), + testHooks: TestHooks = TestHooks(), initialize: Bool = true, initializationOptions: LSPAny? = nil, capabilities: ClientCapabilities = ClientCapabilities(), @@ -104,12 +106,14 @@ public final class TestSourceKitLSPClient: MessageHandler, Sendable { preInitialization: ((TestSourceKitLSPClient) -> Void)? = nil, cleanUp: @Sendable @escaping () -> Void = {} ) async throws { - var serverOptions = serverOptions + var options = options if let globalModuleCache { - serverOptions.buildSetup.flags.swiftCompilerFlags += ["-module-cache-path", globalModuleCache.path] + options.swiftPM = options.swiftPM ?? SourceKitLSPOptions.SwiftPMOptions() + options.swiftPM!.swiftCompilerFlags = + (options.swiftPM!.swiftCompilerFlags ?? []) + ["-module-cache-path", globalModuleCache.path] } if enableBackgroundIndexing { - serverOptions.experimentalFeatures.insert(.backgroundIndexing) + options.experimentalFeatures = (options.experimentalFeatures ?? []).union([.backgroundIndexing]) } var notificationYielder: AsyncStream.Continuation! @@ -123,7 +127,8 @@ public final class TestSourceKitLSPClient: MessageHandler, Sendable { server = SourceKitLSPServer( client: serverToClientConnection, toolchainRegistry: ToolchainRegistry.forTesting, - options: serverOptions, + options: options, + testHooks: testHooks, onExit: { serverToClientConnection.close() } diff --git a/Sources/SourceKitLSP/CMakeLists.txt b/Sources/SourceKitLSP/CMakeLists.txt index b949a19a4..821167f9f 100644 --- a/Sources/SourceKitLSP/CMakeLists.txt +++ b/Sources/SourceKitLSP/CMakeLists.txt @@ -15,9 +15,9 @@ add_library(SourceKitLSP STATIC SourceKitIndexDelegate.swift SourceKitLSPCommandMetadata.swift SourceKitLSPServer.swift - SourceKitLSPServer+Options.swift SymbolLocation+DocumentURI.swift TestDiscovery.swift + TestHooks.swift TextEdit+IsNoop.swift WorkDoneProgressManager.swift Workspace.swift diff --git a/Sources/SourceKitLSP/Clang/ClangLanguageService.swift b/Sources/SourceKitLSP/Clang/ClangLanguageService.swift index 0ec42ee66..0fcd6b614 100644 --- a/Sources/SourceKitLSP/Clang/ClangLanguageService.swift +++ b/Sources/SourceKitLSP/Clang/ClangLanguageService.swift @@ -110,7 +110,8 @@ actor ClangLanguageService: LanguageService, MessageHandler { public init?( sourceKitLSPServer: SourceKitLSPServer, toolchain: Toolchain, - options: SourceKitLSPServer.Options, + options: SourceKitLSPOptions, + testHooks: TestHooks, workspace: Workspace ) async throws { guard let clangdPath = toolchain.clangd else { @@ -118,7 +119,7 @@ actor ClangLanguageService: LanguageService, MessageHandler { } self.clangPath = toolchain.clang self.clangdPath = clangdPath - self.clangdOptions = options.clangdOptions + self.clangdOptions = options.clangdOptions ?? [] self.workspace = WeakWorkspace(workspace) self.state = .connected self.sourceKitLSPServer = sourceKitLSPServer diff --git a/Sources/SourceKitLSP/CreateBuildSystem.swift b/Sources/SourceKitLSP/CreateBuildSystem.swift index a9b6d8cb4..6798e51f7 100644 --- a/Sources/SourceKitLSP/CreateBuildSystem.swift +++ b/Sources/SourceKitLSP/CreateBuildSystem.swift @@ -21,7 +21,8 @@ import struct TSCBasic.RelativePath /// Tries to create a build system for a workspace at the given location, with the given parameters. func createBuildSystem( rootUri: DocumentURI, - options: SourceKitLSPServer.Options, + options: SourceKitLSPOptions, + testHooks: TestHooks, toolchainRegistry: ToolchainRegistry, reloadPackageStatusCallback: @Sendable @escaping (ReloadPackageStatus) async -> Void ) async -> BuildSystem? { @@ -37,26 +38,26 @@ func createBuildSystem( return await SwiftPMBuildSystem( uri: rootUri, toolchainRegistry: toolchainRegistry, - buildSetup: options.buildSetup, - experimentalFeatures: options.experimentalFeatures, + options: options.swiftPM ?? .init(), + experimentalFeatures: options.experimentalFeatures ?? [], reloadPackageStatusCallback: reloadPackageStatusCallback, - testHooks: options.swiftpmTestHooks + testHooks: testHooks.swiftpmTestHooks ) } func createCompilationDatabaseBuildSystem(rootPath: AbsolutePath) -> CompilationDatabaseBuildSystem? { return CompilationDatabaseBuildSystem( projectRoot: rootPath, - searchPaths: options.compilationDatabaseSearchPaths + searchPaths: (options.compilationDatabase?.searchPaths ?? []).compactMap { try? RelativePath(validating: $0) } ) } func createBuildServerBuildSystem(rootPath: AbsolutePath) async -> BuildServerBuildSystem? { - return await BuildServerBuildSystem(projectRoot: rootPath, buildSetup: options.buildSetup) + return await BuildServerBuildSystem(projectRoot: rootPath) } let defaultBuildSystem: BuildSystem? = - switch options.buildSetup.defaultWorkspaceType { + switch options.defaultWorkspaceType { case .buildServer: await createBuildServerBuildSystem(rootPath: rootPath) case .compilationDatabase: createCompilationDatabaseBuildSystem(rootPath: rootPath) case .swiftPM: await createSwiftPMBuildSystem(rootUri: rootUri) diff --git a/Sources/SourceKitLSP/LanguageService.swift b/Sources/SourceKitLSP/LanguageService.swift index bb50c4612..1b2429e37 100644 --- a/Sources/SourceKitLSP/LanguageService.swift +++ b/Sources/SourceKitLSP/LanguageService.swift @@ -87,7 +87,8 @@ public protocol LanguageService: AnyObject, Sendable { init?( sourceKitLSPServer: SourceKitLSPServer, toolchain: Toolchain, - options: SourceKitLSPServer.Options, + options: SourceKitLSPOptions, + testHooks: TestHooks, workspace: Workspace ) async throws diff --git a/Sources/SourceKitLSP/SourceKitLSPServer+Options.swift b/Sources/SourceKitLSP/SourceKitLSPServer+Options.swift deleted file mode 100644 index 6dfd1cebc..000000000 --- a/Sources/SourceKitLSP/SourceKitLSPServer+Options.swift +++ /dev/null @@ -1,102 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2014 - 2020 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 -import LanguageServerProtocol -import SKCore -import SKSupport -import SKSwiftPMWorkspace -import SemanticIndex - -import struct TSCBasic.AbsolutePath -import struct TSCBasic.RelativePath - -extension SourceKitLSPServer { - /// Configuration options for the SourceKitServer. - public struct Options: Sendable { - /// Additional compiler flags (e.g. `-Xswiftc` for SwiftPM projects) and other build-related - /// configuration. - public var buildSetup: BuildSetup - - /// Additional arguments to pass to `clangd` on the command-line. - public var clangdOptions: [String] - - /// Additional paths to search for a compilation database, relative to a workspace root. - public var compilationDatabaseSearchPaths: [RelativePath] - - /// Additional options for the index. - public var indexOptions: IndexOptions - - /// Options for code-completion. - public var completionOptions: SKCompletionOptions - - /// Override the default directory where generated files will be stored - public var generatedFilesPath: AbsolutePath - - /// Path to the generated interfaces - /// `/GeneratedInterfaces/` - public var generatedInterfacesPath: AbsolutePath { - generatedFilesPath.appending(component: "GeneratedInterfaces") - } - - /// Path to the generated macro expansions - /// ``/GeneratedMacroExpansions/ - public var generatedMacroExpansionsPath: AbsolutePath { - generatedFilesPath.appending(component: "GeneratedMacroExpansions") - } - - /// The time that `SwiftLanguageService` should wait after an edit before starting to compute diagnostics and - /// sending a `PublishDiagnosticsNotification`. - /// - /// This is mostly intended for testing purposes so we don't need to wait the debouncing time to get a diagnostics - /// notification when running unit tests. - public var swiftPublishDiagnosticsDebounceDuration: TimeInterval - - /// When a task is started that should be displayed to the client as a work done progress, how many milliseconds to - /// wait before actually starting the work done progress. This prevents flickering of the work done progress in the - /// client for short-lived index tasks which end within this duration. - public var workDoneProgressDebounceDuration: Duration - - /// Experimental features that are enabled. - public var experimentalFeatures: Set - - public var indexTestHooks: IndexTestHooks - - public var swiftpmTestHooks: SwiftPMTestHooks - - public init( - buildSetup: BuildSetup = .default, - clangdOptions: [String] = [], - compilationDatabaseSearchPaths: [RelativePath] = [], - indexOptions: IndexOptions = .init(), - completionOptions: SKCompletionOptions = .init(), - generatedFilesPath: AbsolutePath = defaultDirectoryForGeneratedFiles, - swiftPublishDiagnosticsDebounceDuration: TimeInterval = 2, /* 2s */ - workDoneProgressDebounceDuration: Duration = .seconds(0), - experimentalFeatures: Set = [], - indexTestHooks: IndexTestHooks = IndexTestHooks(), - swiftpmTestHooks: SwiftPMTestHooks = SwiftPMTestHooks() - ) { - self.buildSetup = buildSetup - self.clangdOptions = clangdOptions - self.compilationDatabaseSearchPaths = compilationDatabaseSearchPaths - self.indexOptions = indexOptions - self.completionOptions = completionOptions - self.generatedFilesPath = generatedFilesPath - self.swiftPublishDiagnosticsDebounceDuration = swiftPublishDiagnosticsDebounceDuration - self.experimentalFeatures = experimentalFeatures - self.workDoneProgressDebounceDuration = workDoneProgressDebounceDuration - self.indexTestHooks = indexTestHooks - self.swiftpmTestHooks = swiftpmTestHooks - } - } -} diff --git a/Sources/SourceKitLSP/SourceKitLSPServer.swift b/Sources/SourceKitLSP/SourceKitLSPServer.swift index 500f67e8c..fe41305f2 100644 --- a/Sources/SourceKitLSP/SourceKitLSPServer.swift +++ b/Sources/SourceKitLSP/SourceKitLSPServer.swift @@ -104,7 +104,9 @@ 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 - var options: Options + let options: SourceKitLSPOptions + + let testHooks: TestHooks let toolchainRegistry: ToolchainRegistry @@ -201,16 +203,18 @@ public actor SourceKitLSPServer { public init( client: Connection, toolchainRegistry: ToolchainRegistry, - options: Options, + options: SourceKitLSPOptions, + testHooks: TestHooks, onExit: @escaping () -> Void = {} ) { self.toolchainRegistry = toolchainRegistry self.options = options + self.testHooks = testHooks self.onExit = onExit self.client = client let processorCount = ProcessInfo.processInfo.processorCount - let lowPriorityCores = options.indexOptions.maxCoresPercentageToUseForBackgroundIndexing * Double(processorCount) + let lowPriorityCores = (options.index?.maxCoresPercentageToUseForBackgroundIndexing ?? 1) * Double(processorCount) self.indexTaskScheduler = TaskScheduler(maxConcurrentTasksByPriority: [ (TaskPriority.medium, processorCount), (TaskPriority.low, max(Int(lowPriorityCores), 1)), @@ -446,11 +450,12 @@ public actor SourceKitLSPServer { } // Start a new service. - return await orLog("failed to start language service", level: .error) { [options] in + return await orLog("failed to start language service", level: .error) { [options = workspace.options, testHooks] in let service = try await serverType.serverType.init( sourceKitLSPServer: self, toolchain: toolchain, options: options, + testHooks: testHooks, workspace: workspace ) @@ -883,10 +888,19 @@ extension SourceKitLSPServer { logger.log("Cannot open workspace before server is initialized") return nil } - let options = self.options + let testHooks = self.testHooks + let options = SourceKitLSPOptions.merging( + base: self.options, + override: SourceKitLSPOptions( + path: workspaceFolder.uri.fileURL? + .appendingPathComponent(".sourcekit-lsp") + .appendingPathComponent("config.json") + ) + ) let buildSystem = await createBuildSystem( rootUri: workspaceFolder.uri, options: options, + testHooks: testHooks, toolchainRegistry: toolchainRegistry, reloadPackageStatusCallback: { [weak self] status in await self?.reloadPackageStatusCallback(status) @@ -914,7 +928,7 @@ extension SourceKitLSPServer { buildSystem: buildSystem, toolchainRegistry: self.toolchainRegistry, options: options, - indexOptions: self.options.indexOptions, + testHooks: testHooks, indexTaskScheduler: indexTaskScheduler, logMessageToIndexLog: { [weak self] taskID, message in self?.logMessageToIndexLog(taskID: taskID, message: message) @@ -926,7 +940,7 @@ extension SourceKitLSPServer { self?.indexProgressManager.indexProgressStatusDidChange() } ) - if let workspace, options.experimentalFeatures.contains(.backgroundIndexing), workspace.semanticIndexManager == nil, + if let workspace, options.hasExperimentalFeature(.backgroundIndexing), workspace.semanticIndexManager == nil, !self.didSendBackgroundIndexingNotSupportedNotification { self.sendNotificationToClient( @@ -944,27 +958,9 @@ extension SourceKitLSPServer { } func initialize(_ req: InitializeRequest) async throws -> InitializeResult { - if case .dictionary(let options) = req.initializationOptions { - if case .bool(let listenToUnitEvents) = options["listenToUnitEvents"] { - self.options.indexOptions.listenToUnitEvents = listenToUnitEvents - } - if case .dictionary(let completionOptions) = options["completion"] { - switch completionOptions["maxResults"] { - case .none: - break - case .some(.null): - self.options.completionOptions.maxResults = nil - case .some(.int(let maxResults)): - self.options.completionOptions.maxResults = maxResults - case .some(let invalid): - logger.error("Expected null or int for 'maxResults'; got \(String(reflecting: invalid))") - } - } - } - capabilityRegistry = CapabilityRegistry(clientCapabilities: req.capabilities) - await workspaceQueue.async { [options] in + await workspaceQueue.async { [testHooks] in if let workspaceFolders = req.workspaceFolders { self.workspacesAndIsImplicit += await workspaceFolders.asyncCompactMap { guard let workspace = await self.createWorkspace($0) else { @@ -992,7 +988,8 @@ extension SourceKitLSPServer { rootUri: req.rootURI, capabilityRegistry: self.capabilityRegistry!, toolchainRegistry: self.toolchainRegistry, - options: options, + options: self.options, + testHooks: testHooks, underlyingBuildSystem: nil, index: nil, indexDelegate: nil, diff --git a/Sources/SourceKitLSP/Swift/CodeCompletion.swift b/Sources/SourceKitLSP/Swift/CodeCompletion.swift index fae12340c..b33d234a2 100644 --- a/Sources/SourceKitLSP/Swift/CodeCompletion.swift +++ b/Sources/SourceKitLSP/Swift/CodeCompletion.swift @@ -24,8 +24,6 @@ extension SwiftLanguageService { let offset = snapshot.utf8Offset(of: completionPos) let filterText = String(snapshot.text[snapshot.indexOf(utf8Offset: offset).. CompletionList { @@ -112,8 +111,7 @@ class CodeCompletionSession { return try await session.update( filterText: filterText, position: cursorPosition, - in: snapshot, - options: options + in: snapshot ) } @@ -131,7 +129,7 @@ class CodeCompletionSession { clientSupportsSnippets: clientSupportsSnippets ) completionSessions[ObjectIdentifier(sourcekitd)] = session - return try await session.open(filterText: filterText, position: cursorPosition, in: snapshot, options: options) + return try await session.open(filterText: filterText, position: cursorPosition, in: snapshot) } return try await task.valuePropagatingCancellation @@ -178,8 +176,7 @@ class CodeCompletionSession { private func open( filterText: String, position: Position, - in snapshot: DocumentSnapshot, - options: SKCompletionOptions + in snapshot: DocumentSnapshot ) async throws -> CompletionList { logger.info("Opening code completion session: \(self.description) filter=\(filterText)") guard snapshot.version == self.snapshot.version else { @@ -192,7 +189,7 @@ class CodeCompletionSession { keys.name: uri.pseudoPath, keys.sourceFile: uri.pseudoPath, keys.sourceText: snapshot.text, - keys.codeCompleteOptions: optionsDictionary(filterText: filterText, options: options), + keys.codeCompleteOptions: optionsDictionary(filterText: filterText), keys.compilerArgs: compileCommand?.compilerArgs as [SKDRequestValue]?, ]) @@ -217,8 +214,7 @@ class CodeCompletionSession { private func update( filterText: String, position: Position, - in snapshot: DocumentSnapshot, - options: SKCompletionOptions + in snapshot: DocumentSnapshot ) async throws -> CompletionList { // FIXME: Assertion for prefix of snapshot matching what we started with. @@ -227,7 +223,7 @@ class CodeCompletionSession { keys.request: sourcekitd.requests.codeCompleteUpdate, keys.offset: utf8StartOffset, keys.name: uri.pseudoPath, - keys.codeCompleteOptions: optionsDictionary(filterText: filterText, options: options), + keys.codeCompleteOptions: optionsDictionary(filterText: filterText), ]) let dict = try await sourcekitd.send(req, fileContents: snapshot.text) @@ -245,8 +241,7 @@ class CodeCompletionSession { } private func optionsDictionary( - filterText: String, - options: SKCompletionOptions + filterText: String ) -> SKDRequestDictionary { let dict = sourcekitd.dictionary([ // Sorting and priority options. @@ -257,7 +252,7 @@ class CodeCompletionSession { keys.topNonLiteral: 0, // Filtering options. keys.filterText: filterText, - keys.requestLimit: options.maxResults, + keys.requestLimit: 200, ]) return dict } diff --git a/Sources/SourceKitLSP/Swift/SwiftLanguageService.swift b/Sources/SourceKitLSP/Swift/SwiftLanguageService.swift index b63070cad..1f8414b1f 100644 --- a/Sources/SourceKitLSP/Swift/SwiftLanguageService.swift +++ b/Sources/SourceKitLSP/Swift/SwiftLanguageService.swift @@ -107,7 +107,7 @@ public actor SwiftLanguageService: LanguageService, Sendable { let capabilityRegistry: CapabilityRegistry - let serverOptions: SourceKitLSPServer.Options + let testHooks: TestHooks /// Directory where generated Files will be stored. let generatedFilesPath: URL @@ -173,7 +173,8 @@ public actor SwiftLanguageService: LanguageService, Sendable { public init?( sourceKitLSPServer: SourceKitLSPServer, toolchain: Toolchain, - options: SourceKitLSPServer.Options, + options: SourceKitLSPOptions, + testHooks: TestHooks, workspace: Workspace ) async throws { guard let sourcekitd = toolchain.sourcekitd else { return nil } @@ -182,7 +183,7 @@ public actor SwiftLanguageService: LanguageService, Sendable { self.sourcekitd = try await DynamicallyLoadedSourceKitD.getOrCreate(dylibPath: sourcekitd) self.capabilityRegistry = workspace.capabilityRegistry self.semanticIndexManager = workspace.semanticIndexManager - self.serverOptions = options + self.testHooks = testHooks self.documentManager = DocumentManager() self.state = .connected self.diagnosticReportManager = nil // Needed to work around rdar://116221716 @@ -198,8 +199,7 @@ public actor SwiftLanguageService: LanguageService, Sendable { } } - self.generatedFilesPath = options.generatedFilesPath.asURL - try FileManager.default.createDirectory(at: generatedFilesPath, withIntermediateDirectories: true) + self.generatedFilesPath = options.generatedFilesAbsolutePath.asURL self.diagnosticReportManager = DiagnosticReportManager( sourcekitd: self.sourcekitd, @@ -809,8 +809,7 @@ extension SwiftLanguageService { var canInlineMacro = false let showMacroExpansionsIsEnabled = - await self.sourceKitLSPServer?.options.experimentalFeatures - .contains(.showMacroExpansions) ?? false + self.sourceKitLSPServer?.options.hasExperimentalFeature(.showMacroExpansions) ?? false var refactorActions = cursorInfoResponse.refactorActions.compactMap { let lspCommand = $0.asCommand() diff --git a/Sources/SourceKitLSP/TestHooks.swift b/Sources/SourceKitLSP/TestHooks.swift new file mode 100644 index 000000000..3ff550223 --- /dev/null +++ b/Sources/SourceKitLSP/TestHooks.swift @@ -0,0 +1,36 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2020 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 +import LanguageServerProtocol +import SKCore +import SKSupport +import SKSwiftPMWorkspace +import SemanticIndex + +import struct TSCBasic.AbsolutePath +import struct TSCBasic.RelativePath + +/// Closures can be used to inspect or modify internal behavior in SourceKit-LSP. +public struct TestHooks: Sendable { + public var indexTestHooks: IndexTestHooks + + public var swiftpmTestHooks: SwiftPMTestHooks + + public init( + indexTestHooks: IndexTestHooks = IndexTestHooks(), + swiftpmTestHooks: SwiftPMTestHooks = SwiftPMTestHooks() + ) { + self.indexTestHooks = indexTestHooks + self.swiftpmTestHooks = swiftpmTestHooks + } +} diff --git a/Sources/SourceKitLSP/Workspace.swift b/Sources/SourceKitLSP/Workspace.swift index 91bfa53cf..e4fcbe76f 100644 --- a/Sources/SourceKitLSP/Workspace.swift +++ b/Sources/SourceKitLSP/Workspace.swift @@ -57,8 +57,7 @@ public final class Workspace: Sendable { /// The build system manager to use for documents in this workspace. public let buildSystemManager: BuildSystemManager - /// Build setup - public let buildSetup: BuildSetup + let options: SourceKitLSPOptions /// The source code index, if available. /// @@ -89,7 +88,8 @@ public final class Workspace: Sendable { rootUri: DocumentURI?, capabilityRegistry: CapabilityRegistry, toolchainRegistry: ToolchainRegistry, - options: SourceKitLSPServer.Options, + options: SourceKitLSPOptions, + testHooks: TestHooks, underlyingBuildSystem: BuildSystem?, index uncheckedIndex: UncheckedIndex?, indexDelegate: SourceKitIndexDelegate?, @@ -99,25 +99,31 @@ public final class Workspace: Sendable { indexProgressStatusDidChange: @escaping @Sendable () -> Void ) async { self.documentManager = documentManager - self.buildSetup = options.buildSetup self.rootUri = rootUri self.capabilityRegistry = capabilityRegistry + self.options = options self._uncheckedIndex = ThreadSafeBox(initialValue: uncheckedIndex) self.buildSystemManager = await BuildSystemManager( buildSystem: underlyingBuildSystem, - fallbackBuildSystem: FallbackBuildSystem(buildSetup: buildSetup), + fallbackBuildSystem: FallbackBuildSystem(options: options.fallbackBuildSystem ?? .init()), mainFilesProvider: uncheckedIndex, toolchainRegistry: toolchainRegistry ) - if options.experimentalFeatures.contains(.backgroundIndexing), + if options.hasExperimentalFeature(.backgroundIndexing), let uncheckedIndex, await buildSystemManager.supportsPreparation { + let updateIndexStoreTimeoutDuration: Duration = + if let timeout = options.index?.updateIndexStoreTimeout { + .seconds(timeout) + } else { + .seconds(120) + } self.semanticIndexManager = SemanticIndexManager( index: uncheckedIndex, buildSystemManager: buildSystemManager, - updateIndexStoreTimeout: options.indexOptions.updateIndexStoreTimeout, - testHooks: options.indexTestHooks, + updateIndexStoreTimeout: updateIndexStoreTimeoutDuration, + testHooks: testHooks.indexTestHooks, indexTaskScheduler: indexTaskScheduler, logMessageToIndexLog: logMessageToIndexLog, indexTasksWereScheduled: indexTasksWereScheduled, @@ -154,8 +160,8 @@ public final class Workspace: Sendable { capabilityRegistry: CapabilityRegistry, buildSystem: BuildSystem?, toolchainRegistry: ToolchainRegistry, - options: SourceKitLSPServer.Options, - indexOptions: IndexOptions = IndexOptions(), + options: SourceKitLSPOptions, + testHooks: TestHooks, indexTaskScheduler: TaskScheduler, logMessageToIndexLog: @escaping @Sendable (_ taskID: IndexTaskID, _ message: String) -> Void, indexTasksWereScheduled: @Sendable @escaping (Int) -> Void, @@ -164,22 +170,30 @@ public final class Workspace: Sendable { var index: IndexStoreDB? = nil var indexDelegate: SourceKitIndexDelegate? = nil - let indexOptions = options.indexOptions - if let storePath = await firstNonNil(indexOptions.indexStorePath, await buildSystem?.indexStorePath), - let dbPath = await firstNonNil(indexOptions.indexDatabasePath, await buildSystem?.indexDatabasePath), + let indexOptions = options.index + if let storePath = await firstNonNil( + AbsolutePath(validatingOrNil: indexOptions?.indexStorePath), + await buildSystem?.indexStorePath + ), + let dbPath = await firstNonNil( + AbsolutePath(validatingOrNil: indexOptions?.indexDatabasePath), + await buildSystem?.indexDatabasePath + ), let libPath = await toolchainRegistry.default?.libIndexStore { do { let lib = try IndexStoreLibrary(dylibPath: libPath.pathString) indexDelegate = SourceKitIndexDelegate() let prefixMappings = - await firstNonNil(indexOptions.indexPrefixMappings, await buildSystem?.indexPrefixMappings) ?? [] + await firstNonNil( + indexOptions?.indexPrefixMap?.map { PathPrefixMapping(original: $0.key, replacement: $0.value) }, + await buildSystem?.indexPrefixMappings + ) ?? [] index = try IndexStoreDB( storePath: storePath.pathString, databasePath: dbPath.pathString, library: lib, delegate: indexDelegate, - listenToUnitEvents: indexOptions.listenToUnitEvents, prefixMappings: prefixMappings.map { PathMapping(original: $0.original, replacement: $0.replacement) } ) logger.debug("Opened IndexStoreDB at \(dbPath) with store path \(storePath)") @@ -194,6 +208,7 @@ public final class Workspace: Sendable { capabilityRegistry: capabilityRegistry, toolchainRegistry: toolchainRegistry, options: options, + testHooks: testHooks, underlyingBuildSystem: buildSystem, index: UncheckedIndex(index), indexDelegate: indexDelegate, @@ -206,7 +221,8 @@ public final class Workspace: Sendable { @_spi(Testing) public static func forTesting( toolchainRegistry: ToolchainRegistry, - options: SourceKitLSPServer.Options, + options: SourceKitLSPOptions, + testHooks: TestHooks, underlyingBuildSystem: BuildSystem, indexTaskScheduler: TaskScheduler ) async -> Workspace { @@ -216,6 +232,7 @@ public final class Workspace: Sendable { capabilityRegistry: CapabilityRegistry(clientCapabilities: ClientCapabilities()), toolchainRegistry: toolchainRegistry, options: options, + testHooks: testHooks, underlyingBuildSystem: underlyingBuildSystem, index: nil, indexDelegate: nil, diff --git a/Sources/sourcekit-lsp/SourceKitLSP.swift b/Sources/sourcekit-lsp/SourceKitLSP.swift index cee244c3b..8b3c2f26a 100644 --- a/Sources/sourcekit-lsp/SourceKitLSP.swift +++ b/Sources/sourcekit-lsp/SourceKitLSP.swift @@ -120,7 +120,7 @@ struct SourceKitLSP: AsyncParsableCommand { name: [.long, .customLong("build-path")], help: "Specify build/cache directory (--build-path option is deprecated, --scratch-path should be used instead)" ) - var scratchPath: AbsolutePath? + var scratchPath: String? @Option( name: .customLong("Xcc", withSingleDash: true), @@ -161,13 +161,13 @@ struct SourceKitLSP: AsyncParsableCommand { name: .customLong("index-store-path", withSingleDash: true), help: "Override index-store-path from the build system" ) - var indexStorePath: AbsolutePath? + var indexStorePath: String? @Option( name: .customLong("index-db-path", withSingleDash: true), help: "Override index-database-path from the build system" ) - var indexDatabasePath: AbsolutePath? + var indexDatabasePath: String? @Option( name: .customLong("index-prefix-map", withSingleDash: true), @@ -187,12 +187,12 @@ struct SourceKitLSP: AsyncParsableCommand { help: "Specify a relative path where sourcekit-lsp should search for `compile_commands.json` or `compile_flags.txt` relative to the root of a workspace. Multiple search paths may be specified by repeating this option." ) - var compilationDatabaseSearchPaths = [RelativePath]() + var compilationDatabaseSearchPaths = [String]() @Option( help: "Specify the directory where generated files will be stored" ) - var generatedFilesPath = defaultDirectoryForGeneratedFiles + var generatedFilesPath: String = defaultDirectoryForGeneratedFiles.pathString @Option( name: .customLong("experimental-feature"), @@ -203,36 +203,72 @@ struct SourceKitLSP: AsyncParsableCommand { ) var experimentalFeatures: [ExperimentalFeature] = [] - // MARK: Configuration options intended for SourceKit-LSP developers. - - @Option(help: .hidden) - var completionMaxResults = 200 - - @Option(name: .customLong("progress-debounce-duration"), help: .hidden) - var workDoneProgressDebounceDuration: Int = 1_000 - - func mapOptions() -> SourceKitLSPServer.Options { - var serverOptions = SourceKitLSPServer.Options() - - serverOptions.buildSetup.configuration = buildConfiguration - serverOptions.buildSetup.defaultWorkspaceType = defaultWorkspaceType - serverOptions.buildSetup.path = scratchPath - serverOptions.buildSetup.flags.cCompilerFlags = buildFlagsCc - serverOptions.buildSetup.flags.cxxCompilerFlags = buildFlagsCxx - serverOptions.buildSetup.flags.linkerFlags = buildFlagsLinker - serverOptions.buildSetup.flags.swiftCompilerFlags = buildFlagsSwift - serverOptions.clangdOptions = clangdOptions - serverOptions.compilationDatabaseSearchPaths = compilationDatabaseSearchPaths - serverOptions.indexOptions.indexStorePath = indexStorePath - serverOptions.indexOptions.indexDatabasePath = indexDatabasePath - serverOptions.indexOptions.indexPrefixMappings = indexPrefixMappings - serverOptions.completionOptions.maxResults = completionMaxResults - serverOptions.generatedFilesPath = generatedFilesPath - serverOptions.experimentalFeatures = Set(experimentalFeatures) - serverOptions.completionOptions.maxResults = completionMaxResults - serverOptions.workDoneProgressDebounceDuration = .milliseconds(workDoneProgressDebounceDuration) - - return serverOptions + /// Maps The options passed on the command line to a `SourceKitLSPOptions` struct. + func commandLineOptions() -> SourceKitLSPOptions { + return SourceKitLSPOptions( + swiftPM: SourceKitLSPOptions.SwiftPMOptions( + configuration: buildConfiguration, + scratchPath: scratchPath, + cCompilerFlags: buildFlagsCc, + cxxCompilerFlags: buildFlagsCxx, + swiftCompilerFlags: buildFlagsSwift, + linkerFlags: buildFlagsLinker + ), + fallbackBuildSystem: SourceKitLSPOptions.FallbackBuildSystemOptions( + cCompilerFlags: buildFlagsCc, + cxxCompilerFlags: buildFlagsCxx, + swiftCompilerFlags: buildFlagsSwift + ), + compilationDatabase: SourceKitLSPOptions.CompilationDatabaseOptions(searchPaths: compilationDatabaseSearchPaths), + clangdOptions: clangdOptions, + index: SourceKitLSPOptions.IndexOptions( + indexStorePath: indexStorePath, + indexDatabasePath: indexDatabasePath, + indexPrefixMap: [String: String]( + indexPrefixMappings.map { ($0.original, $0.replacement) }, + uniquingKeysWith: { lhs, rhs in rhs } + ), + maxCoresPercentageToUseForBackgroundIndexing: nil, + updateIndexStoreTimeout: nil + ), + defaultWorkspaceType: defaultWorkspaceType, + generatedFilesPath: generatedFilesPath, + experimentalFeatures: Set(experimentalFeatures) + ) + } + + var globalConfigurationOptions: SourceKitLSPOptions { + var options = SourceKitLSPOptions.merging( + base: commandLineOptions(), + override: SourceKitLSPOptions( + path: URL(fileURLWithPath: ("~/.sourcekit-lsp/config.json" as NSString).expandingTildeInPath) + ) + ) + #if canImport(Darwin) + for applicationSupportDir in FileManager.default.urls(for: .applicationSupportDirectory, in: [.allDomainsMask]) { + options = SourceKitLSPOptions.merging( + base: options, + override: SourceKitLSPOptions( + path: + applicationSupportDir + .appendingPathComponent("org.swift.sourcekit-lsp") + .appendingPathComponent("config.json") + ) + ) + } + #endif + if let xdgConfigHome = ProcessInfo.processInfo.environment["XDG_CONFIG_HOME"] { + options = SourceKitLSPOptions.merging( + base: options, + override: SourceKitLSPOptions( + path: + URL(fileURLWithPath: xdgConfigHome) + .appendingPathComponent("org.swift.sourcekit-lsp") + .appendingPathComponent("config.json") + ) + ) + } + return options } func run() async throws { @@ -269,7 +305,8 @@ struct SourceKitLSP: AsyncParsableCommand { let server = SourceKitLSPServer( client: clientConnection, toolchainRegistry: ToolchainRegistry(installPath: installPath, localFileSystem), - options: mapOptions(), + options: globalConfigurationOptions, + testHooks: TestHooks(), onExit: { clientConnection.close() } diff --git a/Tests/SKCoreTests/BuildServerBuildSystemTests.swift b/Tests/SKCoreTests/BuildServerBuildSystemTests.swift index 934dcffaf..9c04c9e9f 100644 --- a/Tests/SKCoreTests/BuildServerBuildSystemTests.swift +++ b/Tests/SKCoreTests/BuildServerBuildSystemTests.swift @@ -56,7 +56,7 @@ final class BuildServerBuildSystemTests: XCTestCase { let buildFolder = try! AbsolutePath(validating: NSTemporaryDirectory()) func testServerInitialize() async throws { - let buildSystem = try await BuildServerBuildSystem(projectRoot: root, buildFolder: buildFolder) + let buildSystem = try await BuildServerBuildSystem(projectRoot: root) assertEqual( await buildSystem.indexDatabasePath, @@ -69,7 +69,7 @@ final class BuildServerBuildSystemTests: XCTestCase { } func testFileRegistration() async throws { - let buildSystem = try await BuildServerBuildSystem(projectRoot: root, buildFolder: buildFolder) + let buildSystem = try await BuildServerBuildSystem(projectRoot: root) let fileUrl = URL(fileURLWithPath: "/some/file/path") let expectation = XCTestExpectation(description: "\(fileUrl) settings updated") @@ -85,7 +85,7 @@ final class BuildServerBuildSystemTests: XCTestCase { } func testBuildTargetsChanged() async throws { - let buildSystem = try await BuildServerBuildSystem(projectRoot: root, buildFolder: buildFolder) + let buildSystem = try await BuildServerBuildSystem(projectRoot: root) let fileUrl = URL(fileURLWithPath: "/some/file/path") let expectation = XCTestExpectation(description: "target changed") diff --git a/Tests/SKCoreTests/BuildSystemManagerTests.swift b/Tests/SKCoreTests/BuildSystemManagerTests.swift index fee487add..abaeaabb2 100644 --- a/Tests/SKCoreTests/BuildSystemManagerTests.swift +++ b/Tests/SKCoreTests/BuildSystemManagerTests.swift @@ -36,7 +36,7 @@ final class BuildSystemManagerTests: XCTestCase { let bsm = await BuildSystemManager( buildSystem: nil, - fallbackBuildSystem: FallbackBuildSystem(buildSetup: .default), + fallbackBuildSystem: FallbackBuildSystem(options: SourceKitLSPOptions.FallbackBuildSystemOptions()), mainFilesProvider: mainFiles, toolchainRegistry: ToolchainRegistry.forTesting ) @@ -141,7 +141,7 @@ final class BuildSystemManagerTests: XCTestCase { let a = try DocumentURI(string: "bsm:a.swift") let mainFiles = ManualMainFilesProvider([a: [a]]) let bs = ManualBuildSystem() - let fallback = FallbackBuildSystem(buildSetup: .default) + let fallback = FallbackBuildSystem(options: SourceKitLSPOptions.FallbackBuildSystemOptions()) let bsm = await BuildSystemManager( buildSystem: bs, fallbackBuildSystem: fallback, diff --git a/Tests/SKCoreTests/FallbackBuildSystemTests.swift b/Tests/SKCoreTests/FallbackBuildSystemTests.swift index 748171e02..0c5084eb8 100644 --- a/Tests/SKCoreTests/FallbackBuildSystemTests.swift +++ b/Tests/SKCoreTests/FallbackBuildSystemTests.swift @@ -24,7 +24,7 @@ final class FallbackBuildSystemTests: XCTestCase { let sdk = try AbsolutePath(validating: "/my/sdk") let source = try AbsolutePath(validating: "/my/source.swift") - let bs = FallbackBuildSystem(buildSetup: .default) + let bs = FallbackBuildSystem(options: SourceKitLSPOptions.FallbackBuildSystemOptions()) await bs.setSdkPath(sdk) assertNil(await bs.indexStorePath) @@ -57,16 +57,13 @@ final class FallbackBuildSystemTests: XCTestCase { let sdk = try AbsolutePath(validating: "/my/sdk") let source = try AbsolutePath(validating: "/my/source.swift") - let buildSetup = BuildSetup( - configuration: .debug, - defaultWorkspaceType: nil, - path: nil, - flags: BuildFlags(swiftCompilerFlags: [ + let options = SourceKitLSPOptions.FallbackBuildSystemOptions( + swiftCompilerFlags: [ "-Xfrontend", "-debug-constraints", - ]) + ] ) - let bs = FallbackBuildSystem(buildSetup: buildSetup) + let bs = FallbackBuildSystem(options: options) await bs.setSdkPath(sdk) let args = await bs.buildSettings(for: source.asURI, language: .swift)?.compilerArguments @@ -97,18 +94,15 @@ final class FallbackBuildSystemTests: XCTestCase { let sdk = try AbsolutePath(validating: "/my/sdk") let source = try AbsolutePath(validating: "/my/source.swift") - let buildSetup = BuildSetup( - configuration: .debug, - defaultWorkspaceType: nil, - path: nil, - flags: BuildFlags(swiftCompilerFlags: [ + let options = SourceKitLSPOptions.FallbackBuildSystemOptions( + swiftCompilerFlags: [ "-sdk", "/some/custom/sdk", "-Xfrontend", "-debug-constraints", - ]) + ] ) - let bs = FallbackBuildSystem(buildSetup: buildSetup) + let bs = FallbackBuildSystem(options: options) await bs.setSdkPath(sdk) assertEqual( @@ -140,7 +134,7 @@ final class FallbackBuildSystemTests: XCTestCase { let sdk = try AbsolutePath(validating: "/my/sdk") let source = try AbsolutePath(validating: "/my/source.cpp") - let bs = FallbackBuildSystem(buildSetup: .default) + let bs = FallbackBuildSystem(options: SourceKitLSPOptions.FallbackBuildSystemOptions()) await bs.setSdkPath(sdk) let settings = await bs.buildSettings(for: source.asURI, language: .cpp)! @@ -170,15 +164,10 @@ final class FallbackBuildSystemTests: XCTestCase { let sdk = try AbsolutePath(validating: "/my/sdk") let source = try AbsolutePath(validating: "/my/source.cpp") - let buildSetup = BuildSetup( - configuration: .debug, - defaultWorkspaceType: nil, - path: nil, - flags: BuildFlags(cxxCompilerFlags: [ - "-v" - ]) + let options = SourceKitLSPOptions.FallbackBuildSystemOptions( + cxxCompilerFlags: ["-v"] ) - let bs = FallbackBuildSystem(buildSetup: buildSetup) + let bs = FallbackBuildSystem(options: options) await bs.setSdkPath(sdk) assertEqual( @@ -206,17 +195,14 @@ final class FallbackBuildSystemTests: XCTestCase { let sdk = try AbsolutePath(validating: "/my/sdk") let source = try AbsolutePath(validating: "/my/source.cpp") - let buildSetup = BuildSetup( - configuration: .debug, - defaultWorkspaceType: nil, - path: nil, - flags: BuildFlags(cxxCompilerFlags: [ + let options = SourceKitLSPOptions.FallbackBuildSystemOptions( + cxxCompilerFlags: [ "-isysroot", "/my/custom/sdk", "-v", - ]) + ] ) - let bs = FallbackBuildSystem(buildSetup: buildSetup) + let bs = FallbackBuildSystem(options: options) await bs.setSdkPath(sdk) assertEqual( @@ -244,7 +230,7 @@ final class FallbackBuildSystemTests: XCTestCase { func testC() async throws { let source = try AbsolutePath(validating: "/my/source.c") - let bs = FallbackBuildSystem(buildSetup: .default) + let bs = FallbackBuildSystem(options: SourceKitLSPOptions.FallbackBuildSystemOptions()) await bs.setSdkPath(nil) assertEqual( await bs.buildSettings(for: source.asURI, language: .c)?.compilerArguments, @@ -257,15 +243,10 @@ final class FallbackBuildSystemTests: XCTestCase { func testCWithCustomFlags() async throws { let source = try AbsolutePath(validating: "/my/source.c") - let buildSetup = BuildSetup( - configuration: .debug, - defaultWorkspaceType: nil, - path: nil, - flags: BuildFlags(cCompilerFlags: [ - "-v" - ]) + let options = SourceKitLSPOptions.FallbackBuildSystemOptions( + cCompilerFlags: ["-v"] ) - let bs = FallbackBuildSystem(buildSetup: buildSetup) + let bs = FallbackBuildSystem(options: options) await bs.setSdkPath(nil) assertEqual( await bs.buildSettings(for: source.asURI, language: .c)?.compilerArguments, @@ -278,7 +259,7 @@ final class FallbackBuildSystemTests: XCTestCase { func testObjC() async throws { let source = try AbsolutePath(validating: "/my/source.m") - let bs = FallbackBuildSystem(buildSetup: .default) + let bs = FallbackBuildSystem(options: SourceKitLSPOptions.FallbackBuildSystemOptions()) await bs.setSdkPath(nil) assertEqual( await bs.buildSettings(for: source.asURI, language: .objective_c)?.compilerArguments, @@ -290,7 +271,7 @@ final class FallbackBuildSystemTests: XCTestCase { func testObjCXX() async throws { let source = try AbsolutePath(validating: "/my/source.mm") - let bs = FallbackBuildSystem(buildSetup: .default) + let bs = FallbackBuildSystem(options: SourceKitLSPOptions.FallbackBuildSystemOptions()) await bs.setSdkPath(nil) assertEqual( await bs.buildSettings(for: source.asURI, language: .objective_cpp)?.compilerArguments, @@ -302,7 +283,7 @@ final class FallbackBuildSystemTests: XCTestCase { func testUnknown() async throws { let source = try AbsolutePath(validating: "/my/source.mm") - let bs = FallbackBuildSystem(buildSetup: .default) + let bs = FallbackBuildSystem(options: SourceKitLSPOptions.FallbackBuildSystemOptions()) assertNil(await bs.buildSettings(for: source.asURI, language: Language(rawValue: "unknown"))) } } diff --git a/Tests/SKSwiftPMWorkspaceTests/SwiftPMBuildSystemTests.swift b/Tests/SKSwiftPMWorkspaceTests/SwiftPMBuildSystemTests.swift index 1e481c418..92a1a38ac 100644 --- a/Tests/SKSwiftPMWorkspaceTests/SwiftPMBuildSystemTests.swift +++ b/Tests/SKSwiftPMWorkspaceTests/SwiftPMBuildSystemTests.swift @@ -53,7 +53,7 @@ final class SwiftPMBuildSystemTests: XCTestCase { workspacePath: packageRoot, toolchainRegistry: tr, fileSystem: fs, - buildSetup: SourceKitLSPServer.Options.testDefault.buildSetup, + options: SourceKitLSPOptions.SwiftPMOptions(), experimentalFeatures: [], testHooks: SwiftPMTestHooks() ) @@ -81,7 +81,7 @@ final class SwiftPMBuildSystemTests: XCTestCase { workspacePath: packageRoot, toolchainRegistry: tr, fileSystem: fs, - buildSetup: SourceKitLSPServer.Options.testDefault.buildSetup, + options: SourceKitLSPOptions.SwiftPMOptions(), experimentalFeatures: [], testHooks: SwiftPMTestHooks() ) @@ -112,7 +112,7 @@ final class SwiftPMBuildSystemTests: XCTestCase { workspacePath: packageRoot, toolchainRegistry: ToolchainRegistry(toolchains: []), fileSystem: fs, - buildSetup: SourceKitLSPServer.Options.testDefault.buildSetup, + options: SourceKitLSPOptions.SwiftPMOptions(), experimentalFeatures: [], testHooks: SwiftPMTestHooks() ) @@ -144,7 +144,7 @@ final class SwiftPMBuildSystemTests: XCTestCase { workspacePath: packageRoot, toolchainRegistry: tr, fileSystem: fs, - buildSetup: SourceKitLSPServer.Options.testDefault.buildSetup, + options: SourceKitLSPOptions.SwiftPMOptions(), experimentalFeatures: [], testHooks: SwiftPMTestHooks() ) @@ -210,7 +210,7 @@ final class SwiftPMBuildSystemTests: XCTestCase { workspacePath: packageRoot, toolchainRegistry: tr, fileSystem: localFileSystem, - buildSetup: SourceKitLSPServer.Options.testDefault.buildSetup, + options: SourceKitLSPOptions.SwiftPMOptions(), experimentalFeatures: [], testHooks: SwiftPMTestHooks() ) @@ -263,18 +263,18 @@ final class SwiftPMBuildSystemTests: XCTestCase { let packageRoot = tempDir.appending(component: "pkg") let tr = ToolchainRegistry.forTesting - let config = BuildSetup( + let options = SourceKitLSPOptions.SwiftPMOptions( configuration: .release, - defaultWorkspaceType: nil, - path: packageRoot.appending(component: "non_default_build_path"), - flags: BuildFlags(cCompilerFlags: ["-m32"], swiftCompilerFlags: ["-typecheck"]) + scratchPath: packageRoot.appending(component: "non_default_build_path").pathString, + cCompilerFlags: ["-m32"], + swiftCompilerFlags: ["-typecheck"] ) let swiftpmBuildSystem = try await SwiftPMBuildSystem( workspacePath: packageRoot, toolchainRegistry: tr, fileSystem: fs, - buildSetup: config, + options: options, experimentalFeatures: [], testHooks: SwiftPMTestHooks() ) @@ -282,7 +282,7 @@ final class SwiftPMBuildSystemTests: XCTestCase { let aswift = packageRoot.appending(components: "Sources", "lib", "a.swift") let hostTriple = await swiftpmBuildSystem.destinationBuildParameters.triple - let build = buildPath(root: packageRoot, config: config, platform: hostTriple.platformBuildPathComponent) + let build = buildPath(root: packageRoot, options: options, platform: hostTriple.platformBuildPathComponent) assertEqual(await swiftpmBuildSystem.buildPath, build) let arguments = try await unwrap(swiftpmBuildSystem.buildSettings(for: aswift.asURI, language: .swift)) @@ -317,7 +317,7 @@ final class SwiftPMBuildSystemTests: XCTestCase { workspacePath: packageRoot, toolchainRegistry: tr, fileSystem: fs, - buildSetup: SourceKitLSPServer.Options.testDefault.buildSetup, + options: SourceKitLSPOptions.SwiftPMOptions(), experimentalFeatures: [], testHooks: SwiftPMTestHooks() ) @@ -355,7 +355,7 @@ final class SwiftPMBuildSystemTests: XCTestCase { workspacePath: packageRoot, toolchainRegistry: tr, fileSystem: fs, - buildSetup: SourceKitLSPServer.Options.testDefault.buildSetup, + options: SourceKitLSPOptions.SwiftPMOptions(), experimentalFeatures: [], testHooks: SwiftPMTestHooks() ) @@ -405,7 +405,7 @@ final class SwiftPMBuildSystemTests: XCTestCase { workspacePath: packageRoot, toolchainRegistry: tr, fileSystem: fs, - buildSetup: SourceKitLSPServer.Options.testDefault.buildSetup, + options: SourceKitLSPOptions.SwiftPMOptions(), experimentalFeatures: [], testHooks: SwiftPMTestHooks() ) @@ -471,7 +471,7 @@ final class SwiftPMBuildSystemTests: XCTestCase { workspacePath: packageRoot, toolchainRegistry: tr, fileSystem: fs, - buildSetup: SourceKitLSPServer.Options.testDefault.buildSetup, + options: SourceKitLSPOptions.SwiftPMOptions(), experimentalFeatures: [], testHooks: SwiftPMTestHooks() ) @@ -516,7 +516,7 @@ final class SwiftPMBuildSystemTests: XCTestCase { workspacePath: packageRoot, toolchainRegistry: tr, fileSystem: fs, - buildSetup: SourceKitLSPServer.Options.testDefault.buildSetup, + options: SourceKitLSPOptions.SwiftPMOptions(), experimentalFeatures: [], testHooks: SwiftPMTestHooks() ) @@ -598,7 +598,7 @@ final class SwiftPMBuildSystemTests: XCTestCase { workspacePath: packageRoot, toolchainRegistry: ToolchainRegistry.forTesting, fileSystem: fs, - buildSetup: SourceKitLSPServer.Options.testDefault.buildSetup, + options: SourceKitLSPOptions.SwiftPMOptions(), experimentalFeatures: [], testHooks: SwiftPMTestHooks() ) @@ -651,7 +651,7 @@ final class SwiftPMBuildSystemTests: XCTestCase { workspacePath: packageRoot, toolchainRegistry: tr, fileSystem: fs, - buildSetup: SourceKitLSPServer.Options.testDefault.buildSetup, + options: SourceKitLSPOptions.SwiftPMOptions(), experimentalFeatures: [], testHooks: SwiftPMTestHooks() ) @@ -720,7 +720,7 @@ final class SwiftPMBuildSystemTests: XCTestCase { workspacePath: symlinkRoot, toolchainRegistry: ToolchainRegistry.forTesting, fileSystem: fs, - buildSetup: SourceKitLSPServer.Options.testDefault.buildSetup, + options: SourceKitLSPOptions.SwiftPMOptions(), experimentalFeatures: [], testHooks: SwiftPMTestHooks() ) @@ -761,7 +761,7 @@ final class SwiftPMBuildSystemTests: XCTestCase { workspacePath: packageRoot, toolchainRegistry: tr, fileSystem: fs, - buildSetup: SourceKitLSPServer.Options.testDefault.buildSetup, + options: SourceKitLSPOptions.SwiftPMOptions(), experimentalFeatures: [], testHooks: SwiftPMTestHooks() ) @@ -803,7 +803,7 @@ final class SwiftPMBuildSystemTests: XCTestCase { workspacePath: packageRoot, toolchainRegistry: tr, fileSystem: fs, - buildSetup: SourceKitLSPServer.Options.testDefault.buildSetup, + options: SourceKitLSPOptions.SwiftPMOptions(), experimentalFeatures: [], testHooks: SwiftPMTestHooks() ) @@ -839,7 +839,7 @@ final class SwiftPMBuildSystemTests: XCTestCase { workspacePath: packageRoot, toolchainRegistry: tr, fileSystem: fs, - buildSetup: SourceKitLSPServer.Options.testDefault.buildSetup, + options: SourceKitLSPOptions.SwiftPMOptions(), experimentalFeatures: [], testHooks: SwiftPMTestHooks() ) @@ -900,9 +900,9 @@ private func assertArgumentsContain( private func buildPath( root: AbsolutePath, - config: BuildSetup = SourceKitLSPServer.Options.testDefault.buildSetup, + options: SourceKitLSPOptions.SwiftPMOptions = SourceKitLSPOptions.SwiftPMOptions(), platform: String ) -> AbsolutePath { - let buildPath = config.path ?? root.appending(component: ".build") - return buildPath.appending(components: platform, "\(config.configuration ?? .debug)") + let buildPath = AbsolutePath(validatingOrNil: options.scratchPath) ?? root.appending(component: ".build") + return buildPath.appending(components: platform, "\(options.configuration ?? .debug)") } diff --git a/Tests/SourceKitLSPTests/BackgroundIndexingTests.swift b/Tests/SourceKitLSPTests/BackgroundIndexingTests.swift index 5b452ed25..5cbc7edfb 100644 --- a/Tests/SourceKitLSPTests/BackgroundIndexingTests.swift +++ b/Tests/SourceKitLSPTests/BackgroundIndexingTests.swift @@ -160,11 +160,11 @@ final class BackgroundIndexingTests: XCTestCase { } func testBackgroundIndexingHappensWithLowPriority() async throws { - var serverOptions = SourceKitLSPServer.Options.testDefault - serverOptions.indexTestHooks.preparationTaskDidFinish = { taskDescription in + var testHooks = TestHooks() + testHooks.indexTestHooks.preparationTaskDidFinish = { taskDescription in XCTAssert(Task.currentPriority == .low, "\(taskDescription) ran with priority \(Task.currentPriority)") } - serverOptions.indexTestHooks.updateIndexStoreTaskDidFinish = { taskDescription in + testHooks.indexTestHooks.updateIndexStoreTaskDidFinish = { taskDescription in XCTAssert(Task.currentPriority == .low, "\(taskDescription) ran with priority \(Task.currentPriority)") } let project = try await SwiftPMTestProject( @@ -188,7 +188,7 @@ final class BackgroundIndexingTests: XCTestCase { ] ) """, - serverOptions: serverOptions, + testHooks: testHooks, enableBackgroundIndexing: true, pollIndex: false ) @@ -327,8 +327,8 @@ final class BackgroundIndexingTests: XCTestCase { let receivedReportProgressNotification = WrappedSemaphore( name: "Received work done progress saying indexing" ) - var serverOptions = SourceKitLSPServer.Options.testDefault - serverOptions.indexTestHooks = IndexTestHooks( + var testHooks = TestHooks() + testHooks.indexTestHooks = IndexTestHooks( buildGraphGenerationDidFinish: { receivedBeginProgressNotification.waitOrXCTFail() }, @@ -346,7 +346,7 @@ final class BackgroundIndexingTests: XCTestCase { """ ], capabilities: ClientCapabilities(window: WindowClientCapabilities(workDoneProgress: true)), - serverOptions: serverOptions, + testHooks: testHooks, enableBackgroundIndexing: true, pollIndex: false, preInitialization: { testClient in @@ -532,7 +532,7 @@ final class BackgroundIndexingTests: XCTestCase { } func testPrepareTargetAfterEditToDependency() async throws { - var serverOptions = SourceKitLSPServer.Options.testDefault + var testHooks = TestHooks() let expectedPreparationTracker = ExpectedIndexTaskTracker(expectedPreparations: [ [ ExpectedPreparation(targetID: "LibA", runDestinationID: "destination"), @@ -542,7 +542,7 @@ final class BackgroundIndexingTests: XCTestCase { ExpectedPreparation(targetID: "LibB", runDestinationID: "destination") ], ]) - serverOptions.indexTestHooks = expectedPreparationTracker.testHooks + testHooks.indexTestHooks = expectedPreparationTracker.testHooks let project = try await SwiftPMTestProject( files: [ @@ -564,7 +564,7 @@ final class BackgroundIndexingTests: XCTestCase { ) """, capabilities: ClientCapabilities(window: WindowClientCapabilities(workDoneProgress: true)), - serverOptions: serverOptions, + testHooks: testHooks, enableBackgroundIndexing: true, cleanUp: { expectedPreparationTracker.keepAlive() } ) @@ -639,7 +639,7 @@ final class BackgroundIndexingTests: XCTestCase { let libBStartedPreparation = WrappedSemaphore(name: "LibB started preparing") let libDPreparedForEditing = WrappedSemaphore(name: "LibD prepared for editing") - var serverOptions = SourceKitLSPServer.Options.testDefault + var testHooks = TestHooks() let expectedPreparationTracker = ExpectedIndexTaskTracker(expectedPreparations: [ // Preparation of targets during the initial of the target [ @@ -666,7 +666,7 @@ final class BackgroundIndexingTests: XCTestCase { ) ], ]) - serverOptions.indexTestHooks = expectedPreparationTracker.testHooks + testHooks.indexTestHooks = expectedPreparationTracker.testHooks let project = try await SwiftPMTestProject( files: [ @@ -686,7 +686,7 @@ final class BackgroundIndexingTests: XCTestCase { ] ) """, - serverOptions: serverOptions, + testHooks: testHooks, enableBackgroundIndexing: true, cleanUp: { expectedPreparationTracker.keepAlive() } ) @@ -731,11 +731,11 @@ final class BackgroundIndexingTests: XCTestCase { // Block the index tasks until we have received a log notification to make sure we stream out results as they come // in and not only when the indexing task has finished - var serverOptions = SourceKitLSPServer.Options.testDefault - serverOptions.indexTestHooks.preparationTaskDidFinish = { _ in + var testHooks = TestHooks() + testHooks.indexTestHooks.preparationTaskDidFinish = { _ in didReceivePreparationIndexLogMessage.waitOrXCTFail() } - serverOptions.indexTestHooks.updateIndexStoreTaskDidFinish = { _ in + testHooks.indexTestHooks.updateIndexStoreTaskDidFinish = { _ in didReceiveIndexingLogMessage.waitOrXCTFail() updateIndexStoreTaskDidFinish.signal() } @@ -744,7 +744,7 @@ final class BackgroundIndexingTests: XCTestCase { files: [ "MyFile.swift": "" ], - serverOptions: serverOptions, + testHooks: testHooks, enableBackgroundIndexing: true, pollIndex: false ) @@ -769,7 +769,7 @@ final class BackgroundIndexingTests: XCTestCase { let fileAIndexingStarted = WrappedSemaphore(name: "FileA indexing started") let fileBIndexingStarted = WrappedSemaphore(name: "FileB indexing started") - var serverOptions = SourceKitLSPServer.Options.testDefault + var testHooks = TestHooks() let expectedIndexTaskTracker = ExpectedIndexTaskTracker( expectedIndexStoreUpdates: [ [ @@ -794,14 +794,14 @@ final class BackgroundIndexingTests: XCTestCase { ] ] ) - serverOptions.indexTestHooks = expectedIndexTaskTracker.testHooks + testHooks.indexTestHooks = expectedIndexTaskTracker.testHooks _ = try await SwiftPMTestProject( files: [ "FileA.swift": "", "FileB.swift": "", ], - serverOptions: serverOptions, + testHooks: testHooks, enableBackgroundIndexing: true, cleanUp: { expectedIndexTaskTracker.keepAlive() } ) @@ -832,7 +832,7 @@ final class BackgroundIndexingTests: XCTestCase { enableBackgroundIndexing: true ) - var otherClientOptions = SourceKitLSPServer.Options.testDefault + var otherClientOptions = TestHooks() otherClientOptions.indexTestHooks = IndexTestHooks( preparationTaskDidStart: { taskDescription in XCTFail("Did not expect any target preparation, got \(taskDescription.targetsToPrepare)") @@ -842,7 +842,7 @@ final class BackgroundIndexingTests: XCTestCase { } ) let otherClient = try await TestSourceKitLSPClient( - serverOptions: otherClientOptions, + testHooks: otherClientOptions, enableBackgroundIndexing: true, workspaceFolders: [ WorkspaceFolder(uri: DocumentURI(project.scratchDirectory)) @@ -968,8 +968,9 @@ final class BackgroundIndexingTests: XCTestCase { } func testUseBuildFlagsDuringPreparation() async throws { - var serverOptions = SourceKitLSPServer.Options.testDefault - serverOptions.buildSetup.flags.swiftCompilerFlags += ["-D", "MY_FLAG"] + var options = SourceKitLSPOptions.testDefault() + options.swiftPM = options.swiftPM ?? SourceKitLSPOptions.SwiftPMOptions() + options.swiftPM!.swiftCompilerFlags = ["-D", "MY_FLAG"] let project = try await SwiftPMTestProject( files: [ "Lib/Lib.swift": """ @@ -994,7 +995,7 @@ final class BackgroundIndexingTests: XCTestCase { ] ) """, - serverOptions: serverOptions, + options: options, enableBackgroundIndexing: true ) @@ -1066,8 +1067,7 @@ final class BackgroundIndexingTests: XCTestCase { func testCrossModuleFunctionalityEvenIfLowLevelModuleHasErrors() async throws { try await SkipUnless.swiftPMSupportsExperimentalPrepareForIndexing() - var serverOptions = SourceKitLSPServer.Options.testDefault - serverOptions.experimentalFeatures.insert(.swiftpmPrepareForIndexing) + let options = SourceKitLSPOptions.testDefault(experimentalFeatures: [.swiftpmPrepareForIndexing]) let project = try await SwiftPMTestProject( files: [ "LibA/LibA.swift": """ @@ -1100,7 +1100,7 @@ final class BackgroundIndexingTests: XCTestCase { ] ) """, - serverOptions: serverOptions, + options: options, enableBackgroundIndexing: true ) @@ -1227,15 +1227,15 @@ final class BackgroundIndexingTests: XCTestCase { func testAddingRandomSwiftFileDoesNotTriggerPackageReload() async throws { let packageInitialized = AtomicBool(initialValue: false) - var serverOptions = SourceKitLSPServer.Options.testDefault - serverOptions.swiftpmTestHooks.reloadPackageDidStart = { + var testHooks = TestHooks() + testHooks.swiftpmTestHooks.reloadPackageDidStart = { if packageInitialized.value { XCTFail("Build graph should not get reloaded when random file gets added") } } let project = try await SwiftPMTestProject( files: ["Test.swift": ""], - serverOptions: serverOptions, + testHooks: testHooks, enableBackgroundIndexing: true ) packageInitialized.value = true @@ -1349,9 +1349,9 @@ final class BackgroundIndexingTests: XCTestCase { try await SkipUnless.swiftPMSupportsExperimentalPrepareForIndexing() try SkipUnless.longTestsEnabled() - var serverOptions = SourceKitLSPServer.Options.testDefault - serverOptions.experimentalFeatures.insert(.swiftpmPrepareForIndexing) - serverOptions.indexOptions.updateIndexStoreTimeout = .seconds(1) + var options = SourceKitLSPOptions.testDefault(experimentalFeatures: [.swiftpmPrepareForIndexing]) + options.index = options.index ?? .init() + options.index!.updateIndexStoreTimeout = 1 /* second */ let dateStarted = Date() _ = try await SwiftPMTestProject( @@ -1362,7 +1362,7 @@ final class BackgroundIndexingTests: XCTestCase { } """ ], - serverOptions: serverOptions, + options: options, enableBackgroundIndexing: true ) // Creating the `SwiftPMTestProject` implicitly waits for background indexing to finish. diff --git a/Tests/SourceKitLSPTests/BuildSystemTests.swift b/Tests/SourceKitLSPTests/BuildSystemTests.swift index c860996e0..6611d1c7f 100644 --- a/Tests/SourceKitLSPTests/BuildSystemTests.swift +++ b/Tests/SourceKitLSPTests/BuildSystemTests.swift @@ -138,7 +138,8 @@ final class BuildSystemTests: XCTestCase { self.workspace = await Workspace.forTesting( toolchainRegistry: ToolchainRegistry.forTesting, - options: SourceKitLSPServer.Options.testDefault, + options: SourceKitLSPOptions.testDefault(), + testHooks: TestHooks(), underlyingBuildSystem: buildSystem, indexTaskScheduler: .forTesting ) @@ -196,7 +197,7 @@ final class BuildSystemTests: XCTestCase { func testSwiftDocumentUpdatedBuildSettings() async throws { let doc = DocumentURI(for: .swift) - let args = await FallbackBuildSystem(buildSetup: .default) + let args = await FallbackBuildSystem(options: SourceKitLSPOptions.FallbackBuildSystemOptions()) .buildSettings(for: doc, language: .swift)! .compilerArguments @@ -267,7 +268,8 @@ final class BuildSystemTests: XCTestCase { let doc = DocumentURI(for: .swift) // Primary settings must be different than the fallback settings. - var primarySettings = await FallbackBuildSystem(buildSetup: .default).buildSettings(for: doc, language: .swift)! + var primarySettings = await FallbackBuildSystem(options: SourceKitLSPOptions.FallbackBuildSystemOptions()) + .buildSettings(for: doc, language: .swift)! primarySettings.isFallback = false primarySettings.compilerArguments.append("-DPRIMARY") diff --git a/Tests/SourceKitLSPTests/ExecuteCommandTests.swift b/Tests/SourceKitLSPTests/ExecuteCommandTests.swift index f2678397f..490e6c4c4 100644 --- a/Tests/SourceKitLSPTests/ExecuteCommandTests.swift +++ b/Tests/SourceKitLSPTests/ExecuteCommandTests.swift @@ -12,6 +12,7 @@ import LSPTestSupport import LanguageServerProtocol +import SKCore import SKTestSupport @_spi(Testing) import SourceKitLSP import SwiftExtensions @@ -140,8 +141,7 @@ final class ExecuteCommandTests: XCTestCase { func testFreestandingMacroExpansion() async throws { try await SkipUnless.canBuildMacroUsingSwiftSyntaxFromSourceKitLSPBuild() - var serverOptions = SourceKitLSPServer.Options.testDefault - serverOptions.experimentalFeatures.insert(.showMacroExpansions) + let options = SourceKitLSPOptions.testDefault(experimentalFeatures: [.showMacroExpansions]) let project = try await SwiftPMTestProject( files: [ @@ -181,7 +181,7 @@ final class ExecuteCommandTests: XCTestCase { """, ], manifest: SwiftPMTestProject.macroPackageManifest, - serverOptions: serverOptions + options: options ) try await SwiftPMTestProject.build(at: project.scratchDirectory) diff --git a/Tests/SourceKitLSPTests/LocalSwiftTests.swift b/Tests/SourceKitLSPTests/LocalSwiftTests.swift index 063987db0..1f0be02cb 100644 --- a/Tests/SourceKitLSPTests/LocalSwiftTests.swift +++ b/Tests/SourceKitLSPTests/LocalSwiftTests.swift @@ -1400,11 +1400,10 @@ final class LocalSwiftTests: XCTestCase { func testDebouncePublishDiagnosticsNotification() async throws { try SkipUnless.longTestsEnabled() - var serverOptions = SourceKitLSPServer.Options.testDefault - serverOptions.swiftPublishDiagnosticsDebounceDuration = 1 /* 1s */ + let options = SourceKitLSPOptions(swiftPublishDiagnosticsDebounce: 1 /* second */) // Construct our own `TestSourceKitLSPClient` instead of the one from set up because we want a higher debounce interval. - let testClient = try await TestSourceKitLSPClient(serverOptions: serverOptions, usePullDiagnostics: false) + let testClient = try await TestSourceKitLSPClient(options: options, usePullDiagnostics: false) let uri = DocumentURI(URL(fileURLWithPath: "/\(UUID())/a.swift")) testClient.openDocument("foo", uri: uri) diff --git a/Tests/SourceKitLSPTests/PullDiagnosticsTests.swift b/Tests/SourceKitLSPTests/PullDiagnosticsTests.swift index 85b0fa970..8e2f7dfa2 100644 --- a/Tests/SourceKitLSPTests/PullDiagnosticsTests.swift +++ b/Tests/SourceKitLSPTests/PullDiagnosticsTests.swift @@ -260,8 +260,8 @@ final class PullDiagnosticsTests: XCTestCase { func testDiagnosticsWaitForDocumentToBePrepared() async throws { let diagnosticRequestSent = AtomicBool(initialValue: false) - var serverOptions = SourceKitLSPServer.Options.testDefault - serverOptions.indexTestHooks.preparationTaskDidStart = { @Sendable taskDescription in + var testHooks = TestHooks() + testHooks.indexTestHooks.preparationTaskDidStart = { @Sendable taskDescription in // Only start preparation after we sent the diagnostic request. In almost all cases, this should not give // preparation enough time to finish before the diagnostic request is handled unless we wait for preparation in // the diagnostic request. @@ -297,7 +297,7 @@ final class PullDiagnosticsTests: XCTestCase { ] ) """, - serverOptions: serverOptions, + testHooks: testHooks, enableBackgroundIndexing: true, pollIndex: false ) @@ -317,8 +317,8 @@ final class PullDiagnosticsTests: XCTestCase { func testDontReturnEmptyDiagnosticsIfDiagnosticRequestIsCancelled() async throws { let diagnosticRequestCancelled = self.expectation(description: "diagnostic request cancelled") - var serverOptions = SourceKitLSPServer.Options.testDefault - serverOptions.indexTestHooks.preparationTaskDidStart = { _ in + var testHooks = TestHooks() + testHooks.indexTestHooks.preparationTaskDidStart = { _ in await self.fulfillment(of: [diagnosticRequestCancelled], timeout: defaultTimeout) // Poll until the `CancelRequestNotification` has been propagated to the request handling. for _ in 0.. LSPAny? { - return LSPAny.dictionary([ - "completion": .dictionary([ - "maxResults": options.maxResults == nil ? .null : .int(options.maxResults!) - ]) - ]) - } - private var snippetCapabilities = ClientCapabilities( textDocument: TextDocumentClientCapabilities( completion: TextDocumentClientCapabilities.Completion( @@ -54,15 +46,15 @@ final class SwiftCompletionTests: XCTestCase { // MARK: - Tests func testCompletionServerFilter() async throws { - try await testCompletionBasic(options: SKCompletionOptions(maxResults: nil)) + try await testCompletionBasic() } func testCompletionDefaultFilter() async throws { - try await testCompletionBasic(options: SKCompletionOptions()) + try await testCompletionBasic() } - func testCompletionBasic(options: SKCompletionOptions) async throws { - let testClient = try await TestSourceKitLSPClient(initializationOptions: initializationOptions(for: options)) + func testCompletionBasic() async throws { + let testClient = try await TestSourceKitLSPClient() let uri = DocumentURI(for: .swift) testClient.openDocument(text, uri: uri) @@ -376,141 +368,8 @@ final class SwiftCompletionTests: XCTestCase { ) } - func testMaxResults() async throws { - let testClient = try await TestSourceKitLSPClient() - let uri = DocumentURI(for: .swift) - testClient.openDocument( - """ - struct S { - func f1() {} - func f2() {} - func f3() {} - func f4() {} - func f5() {} - func test() { - self.f - } - } - """, - uri: uri - ) - - // Server-wide option - assertEqual( - 5, - countFs( - try await testClient.send( - CompletionRequest( - textDocument: TextDocumentIdentifier(uri), - position: Position(line: 7, utf16index: 9) - ) - ) - ) - ) - - // Explicit option - assertEqual( - 5, - countFs( - try await testClient.send( - CompletionRequest( - textDocument: TextDocumentIdentifier(uri), - position: Position(line: 7, utf16index: 9), - sourcekitlspOptions: SKCompletionOptions(maxResults: nil) - ) - ) - ) - ) - - // MARK: Limited - - assertEqual( - 5, - countFs( - try await testClient.send( - CompletionRequest( - textDocument: TextDocumentIdentifier(uri), - position: Position(line: 7, utf16index: 9), - sourcekitlspOptions: SKCompletionOptions(maxResults: 1000) - ) - ) - ) - ) - - assertEqual( - 3, - countFs( - try await testClient.send( - CompletionRequest( - textDocument: TextDocumentIdentifier(uri), - position: Position(line: 7, utf16index: 9), - sourcekitlspOptions: SKCompletionOptions(maxResults: 3) - ) - ) - ) - ) - assertEqual( - 1, - countFs( - try await testClient.send( - CompletionRequest( - textDocument: TextDocumentIdentifier(uri), - position: Position(line: 7, utf16index: 9), - sourcekitlspOptions: SKCompletionOptions(maxResults: 1) - ) - ) - ) - ) - - // 0 also means unlimited - assertEqual( - 5, - countFs( - try await testClient.send( - CompletionRequest( - textDocument: TextDocumentIdentifier(uri), - position: Position(line: 7, utf16index: 9), - sourcekitlspOptions: SKCompletionOptions(maxResults: 0) - ) - ) - ) - ) - - // MARK: With filter='f' - - assertEqual( - 5, - countFs( - try await testClient.send( - CompletionRequest( - textDocument: TextDocumentIdentifier(uri), - position: Position(line: 7, utf16index: 10), - sourcekitlspOptions: SKCompletionOptions(maxResults: nil) - ) - ) - ) - ) - assertEqual( - 3, - countFs( - try await testClient.send( - CompletionRequest( - textDocument: TextDocumentIdentifier(uri), - position: Position(line: 7, utf16index: 10), - sourcekitlspOptions: SKCompletionOptions(maxResults: 3) - ) - ) - ) - ) - - } - func testRefilterAfterIncompleteResults() async throws { - let testClient = try await TestSourceKitLSPClient( - initializationOptions: initializationOptions( - for: SKCompletionOptions(maxResults: 20) - ) - ) + let testClient = try await TestSourceKitLSPClient() let uri = DocumentURI(for: .swift) testClient.openDocument( """ @@ -615,9 +474,9 @@ final class SwiftCompletionTests: XCTestCase { ) ) - // Trigger kind changed => OK (20 is maxResults since we're outside the member completion) + // Trigger kind changed => OK (200 is maxResults since we're outside the member completion) assertEqual( - 20, + 200, try await testClient.send( CompletionRequest( textDocument: TextDocumentIdentifier(uri), diff --git a/Tests/SourceKitLSPTests/WorkspaceTests.swift b/Tests/SourceKitLSPTests/WorkspaceTests.swift index 0eb2176df..5cccfbbfa 100644 --- a/Tests/SourceKitLSPTests/WorkspaceTests.swift +++ b/Tests/SourceKitLSPTests/WorkspaceTests.swift @@ -830,4 +830,35 @@ final class WorkspaceTests: XCTestCase { // rdar://73762053: This should also suggest clib_other XCTAssert(cCompletionResponse.items.contains(where: { $0.insertText == "clib_func" })) } + + func testWorkspaceOptions() 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 + } + """, + ] + ) + + 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'"]) + } }