diff --git a/Sources/LanguageServerProtocol/Requests/ShowDocumentRequest.swift b/Sources/LanguageServerProtocol/Requests/ShowDocumentRequest.swift index 8ec0b6c16..c40139ed9 100644 --- a/Sources/LanguageServerProtocol/Requests/ShowDocumentRequest.swift +++ b/Sources/LanguageServerProtocol/Requests/ShowDocumentRequest.swift @@ -10,13 +10,8 @@ // //===----------------------------------------------------------------------===// -/// Request from the server to the client to show a document on the client side. -/// -/// - Parameters: -/// - uri: The uri to show. -/// - external: An optional boolean indicates to show the resource in an external program. -/// - takeFocus: An optional boolean to indicate whether the editor showing the document should take focus or not. -/// - selection: An optional selection range if the document is a text document. +/// Request from the server to the client to show a document on the client +/// side. public struct ShowDocumentRequest: RequestType { public static let method: String = "window/showDocument" public typealias Response = ShowDocumentResponse diff --git a/Sources/SKSupport/FileSystem.swift b/Sources/SKSupport/FileSystem.swift index 6467de1e5..aa8b7e0e3 100644 --- a/Sources/SKSupport/FileSystem.swift +++ b/Sources/SKSupport/FileSystem.swift @@ -31,12 +31,20 @@ extension AbsolutePath { } } -/// The directory to write generated module interfaces +/// The default directory to write generated files +/// `/sourcekit-lsp/` +public var defaultDirectoryForGeneratedFiles: AbsolutePath { + try! AbsolutePath(validating: NSTemporaryDirectory()).appending(component: "sourcekit-lsp") +} + +/// The default directory to write generated module interfaces +/// `/sourcekit-lsp/GeneratedInterfaces/` public var defaultDirectoryForGeneratedInterfaces: AbsolutePath { - try! AbsolutePath(validating: NSTemporaryDirectory()).appending(component: "GeneratedInterfaces") + defaultDirectoryForGeneratedFiles.appending(component: "GeneratedInterfaces") } -/// The directory to write generated macro expansions +/// The default directory to write generated macro expansions +/// `/sourcekit-lsp/GeneratedMacroExpansions/` public var defaultDirectoryForGeneratedMacroExpansions: AbsolutePath { - try! AbsolutePath(validating: NSTemporaryDirectory()).appending(component: "GeneratedMacroExpansions") + defaultDirectoryForGeneratedFiles.appending(component: "GeneratedMacroExpansions") } diff --git a/Sources/SourceKitLSP/SourceKitLSPServer+Options.swift b/Sources/SourceKitLSP/SourceKitLSPServer+Options.swift index 9d0c4753c..bba1739d1 100644 --- a/Sources/SourceKitLSP/SourceKitLSPServer+Options.swift +++ b/Sources/SourceKitLSP/SourceKitLSPServer+Options.swift @@ -38,11 +38,20 @@ extension SourceKitLSPServer { /// Options for code-completion. public var completionOptions: SKCompletionOptions - /// Override the default directory where generated interfaces will be stored - public var generatedInterfacesPath: AbsolutePath + /// Override the default directory where generated files will be stored + public var generatedFilesPath: AbsolutePath - /// Override the default directory where generated macro expansions will be stored - public var generatedMacroExpansionsPath: 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`. @@ -62,8 +71,7 @@ extension SourceKitLSPServer { compilationDatabaseSearchPaths: [RelativePath] = [], indexOptions: IndexOptions = .init(), completionOptions: SKCompletionOptions = .init(), - generatedInterfacesPath: AbsolutePath = defaultDirectoryForGeneratedInterfaces, - generatedMacroExpansionsPath: AbsolutePath = defaultDirectoryForGeneratedMacroExpansions, + generatedFilesPath: AbsolutePath = defaultDirectoryForGeneratedFiles, swiftPublishDiagnosticsDebounceDuration: TimeInterval = 2, /* 2s */ experimentalFeatures: Set = [], indexTestHooks: IndexTestHooks = IndexTestHooks() @@ -73,8 +81,7 @@ extension SourceKitLSPServer { self.compilationDatabaseSearchPaths = compilationDatabaseSearchPaths self.indexOptions = indexOptions self.completionOptions = completionOptions - self.generatedInterfacesPath = generatedInterfacesPath - self.generatedMacroExpansionsPath = generatedMacroExpansionsPath + self.generatedFilesPath = generatedFilesPath self.swiftPublishDiagnosticsDebounceDuration = swiftPublishDiagnosticsDebounceDuration self.experimentalFeatures = experimentalFeatures self.indexTestHooks = indexTestHooks diff --git a/Sources/SourceKitLSP/Swift/ExpandMacroCommand.swift b/Sources/SourceKitLSP/Swift/ExpandMacroCommand.swift index abedb63db..4bc2abd91 100644 --- a/Sources/SourceKitLSP/Swift/ExpandMacroCommand.swift +++ b/Sources/SourceKitLSP/Swift/ExpandMacroCommand.swift @@ -14,6 +14,8 @@ import LanguageServerProtocol import SourceKitD public struct ExpandMacroCommand: RefactorCommand { + typealias Response = MacroExpansion + public static let identifier: String = "expand.macro.command" /// The name of this refactoring action. @@ -55,8 +57,12 @@ public struct ExpandMacroCommand: RefactorCommand { ) } - public init(title: String, actionString: String, positionRange: Range, textDocument: TextDocumentIdentifier) - { + public init( + title: String, + actionString: String, + positionRange: Range, + textDocument: TextDocumentIdentifier + ) { self.title = title self.actionString = actionString self.positionRange = positionRange diff --git a/Sources/SourceKitLSP/Swift/MacroExpansion.swift b/Sources/SourceKitLSP/Swift/MacroExpansion.swift index f23f4013d..1742c8075 100644 --- a/Sources/SourceKitLSP/Swift/MacroExpansion.swift +++ b/Sources/SourceKitLSP/Swift/MacroExpansion.swift @@ -16,9 +16,9 @@ import SourceKitD /// Detailed information about the result of a macro expansion operation. /// -/// Wraps the information returned by sourcekitd's `semantic_refactoring` request, such as the necessary macro expansion edits. -struct MacroExpansion: Refactoring { - +/// Wraps the information returned by sourcekitd's `semantic_refactoring` +/// request, such as the necessary macro expansion edits. +struct MacroExpansion: RefactoringResponse { /// The title of the refactoring action. var title: String @@ -31,50 +31,52 @@ struct MacroExpansion: Refactoring { init(title: String, uri: DocumentURI, refactoringEdits: [RefactoringEdit]) { self.title = title self.uri = uri - self.edits = refactoringEdits.map { refactoringEdit in - MacroExpansionEdit( + self.edits = refactoringEdits.compactMap { refactoringEdit in + + guard let bufferName = refactoringEdit.bufferName else { + logger.error("Unable to retrieve some parts of the expansion") + return nil + } + + return MacroExpansionEdit( range: refactoringEdit.startPosition.. LSPAny { guard let sourceKitLSPServer else { - // `SourceKitLSPServer` has been destructed. We are tearing down the language - // server. Nothing left to do. + // `SourceKitLSPServer` has been destructed. We are tearing down the + // language server. Nothing left to do. throw ResponseError.unknown("Connection to the editor closed") } - guard let refactorCommand = refactorCommand as? ExpandMacroCommand else { - throw ResponseError.unknown("refactorCommand is not a ExpandMacroCommand") - } - - let expansion = try await self.refactoring(refactorCommand, MacroExpansion.self) + let expansion = try await self.refactoring(expandMacroCommand) for macroEdit in expansion.edits { let macroExpansionFilePath = self.generatedMacroExpansionsPath.appendingPathComponent( macroEdit.bufferName ) let macroExpansionDocURI = DocumentURI(macroExpansionFilePath) - if let _ = try? self.documentManager.latestSnapshot(macroExpansionDocURI) { - continue - } do { try macroEdit.newText.write(to: macroExpansionFilePath, atomically: true, encoding: String.Encoding.utf8) @@ -82,10 +84,12 @@ extension SwiftLanguageService { throw ResponseError.unknown("Unable to write macro expansion to file path: \"\(macroExpansionFilePath.path)\"") } - let req = ShowDocumentRequest(uri: macroExpansionDocURI, selection: macroEdit.range) - let response = try await sourceKitLSPServer.sendRequestToClient(req) - if !response.success { - logger.error("client refused to show document for \(expansion.title, privacy: .public)!") + Task { + let req = ShowDocumentRequest(uri: macroExpansionDocURI, selection: macroEdit.range) + let response = try await sourceKitLSPServer.sendRequestToClient(req) + if !response.success { + logger.error("client refused to show document for \(expansion.title, privacy: .public)!") + } } } diff --git a/Sources/SourceKitLSP/Swift/MacroExpansionEdit.swift b/Sources/SourceKitLSP/Swift/MacroExpansionEdit.swift index 8a4ac2e6b..e8472b0f1 100644 --- a/Sources/SourceKitLSP/Swift/MacroExpansionEdit.swift +++ b/Sources/SourceKitLSP/Swift/MacroExpansionEdit.swift @@ -13,8 +13,9 @@ import LanguageServerProtocol import SourceKitD -/// Represents a macro expansion as an edit. Notionally, a subclass of `TextEdit` -public struct MacroExpansionEdit: ResponseType, Hashable, Sendable { +/// Represents a macro expansion as an edit. Notionally, a subclass of +/// `TextEdit` +public struct MacroExpansionEdit: Hashable, Sendable, Codable { /// The range of text to be replaced. @CustomCodable public var range: Range diff --git a/Sources/SourceKitLSP/Swift/RefactorCommand.swift b/Sources/SourceKitLSP/Swift/RefactorCommand.swift index 5f51b7990..fabe124a7 100644 --- a/Sources/SourceKitLSP/Swift/RefactorCommand.swift +++ b/Sources/SourceKitLSP/Swift/RefactorCommand.swift @@ -13,9 +13,13 @@ import LanguageServerProtocol import SourceKitD -/// A protocol to be utilised by all commands where underlies sourcekitd calls to perform semantic refactoring. +/// A protocol to be utilised by all commands where underlies sourcekitd calls +/// to perform semantic refactoring. protocol RefactorCommand: SwiftCommand { + /// The response type of the refactor command + associatedtype Response: RefactoringResponse + /// The sourcekitd identifier of the refactoring action. var actionString: String { get set } diff --git a/Sources/SourceKitLSP/Swift/Refactoring.swift b/Sources/SourceKitLSP/Swift/Refactoring.swift index 987875a03..4cdd6018a 100644 --- a/Sources/SourceKitLSP/Swift/Refactoring.swift +++ b/Sources/SourceKitLSP/Swift/Refactoring.swift @@ -13,22 +13,31 @@ import LanguageServerProtocol import SourceKitD -protocol Refactoring { +protocol RefactoringResponse { + init(title: String, uri: DocumentURI, refactoringEdits: [RefactoringEdit]) - /// Create an instance of `Refactoring` from a sourcekitd semantic refactoring response dictionary, if possible. Passes the response to `init(title: String, uri: DocumentURI, refactoringEdits: [RefactoringEdit])` in a neat manner + /// Create an instance of `RefactoringResponse` from a sourcekitd semantic + /// refactoring response dictionary, if possible. Passes the response to + /// `init(title: String, uri: DocumentURI, refactoringEdits: + /// [RefactoringEdit])` in a neat manner init?(_ title: String, _ dict: SKDResponseDictionary, _ snapshot: DocumentSnapshot, _ keys: sourcekitd_api_keys) } typealias RefactoringEdit = (startPosition: Position, endPosition: Position, newText: String, bufferName: String?) -extension Refactoring { - /// Create an instance of `Refactoring` from a sourcekitd semantic refactoring response dictionary, if possible. Passes the response to `init(title: String, uri: DocumentURI, refactoringEdits: [RefactoringEdit])` in a neat manner +extension RefactoringResponse { + + /// Create an instance of `RefactoringResponse` from a sourcekitd semantic + /// refactoring response dictionary, if possible. Passes the response to + /// `init(title: String, uri: DocumentURI, refactoringEdits: + /// [RefactoringEdit])` in a neat manner /// /// - Parameters: /// - title: The title of the refactoring action. /// - dict: Response dictionary to extract information from. - /// - url: The client URL that triggered the `semantic_refactoring` request. + /// - snapshot: The snapshot that triggered the `semantic_refactoring` + /// request. /// - keys: The sourcekitd key set to use for looking up into `dict`. init?(_ title: String, _ dict: SKDResponseDictionary, _ snapshot: DocumentSnapshot, _ keys: sourcekitd_api_keys) { guard let categorizedEdits: SKDResponseArray = dict[keys.categorizedEdits] else { @@ -61,7 +70,8 @@ extension Refactoring { utf8Column: endColumn - 1 ) // Snippets are only supported in code completion. - // Remove SourceKit placeholders in refactoring actions because they can't be represented in the editor properly. + // Remove SourceKit placeholders in refactoring actions because they + // can't be represented in the editor properly. let textWithSnippets = rewriteSourceKitPlaceholders(in: text, clientSupportsSnippets: false) refactoringEdits.append( ( @@ -83,16 +93,20 @@ extension Refactoring { } extension SwiftLanguageService { - /// Provides detailed information about the result of a specific refactoring operation. + /// Provides detailed information about the result of a specific refactoring + /// operation. /// - /// Wraps the information returned by sourcekitd's `semantic_refactoring` request, such as the necessary edits and placeholder locations. + /// Wraps the information returned by sourcekitd's `semantic_refactoring` + /// request, such as the necessary edits and placeholder locations. /// /// - Parameters: /// - command: The semantic `RefactorCommand` that triggered this request. /// - responseType: The response type `T.Type` of the particular command /// - Returns: - /// - The `Refactoring` as `T` - func refactoring(_ refactorCommand: any RefactorCommand, _ responseType: T.Type) async throws -> T { + /// - The `RefactoringResponse` as `T` + func refactoring(_ refactorCommand: T) async throws + -> T.Response + { let keys = self.keys let uri = refactorCommand.textDocument.uri @@ -116,7 +130,7 @@ extension SwiftLanguageService { ]) let dict = try await self.sourcekitd.send(skreq, fileContents: snapshot.text) - guard let refactor = T(refactorCommand.title, dict, snapshot, self.keys) else { + guard let refactor = T.Response(refactorCommand.title, dict, snapshot, self.keys) else { throw SemanticRefactoringError.noEditsNeeded(uri) } return refactor diff --git a/Sources/SourceKitLSP/Swift/SemanticRefactorCommand.swift b/Sources/SourceKitLSP/Swift/SemanticRefactorCommand.swift index 8557b4460..521dc51d5 100644 --- a/Sources/SourceKitLSP/Swift/SemanticRefactorCommand.swift +++ b/Sources/SourceKitLSP/Swift/SemanticRefactorCommand.swift @@ -9,10 +9,12 @@ // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// + import LanguageServerProtocol import SourceKitD public struct SemanticRefactorCommand: RefactorCommand { + typealias Response = SemanticRefactoring public static let identifier: String = "semantic.refactor.command" diff --git a/Sources/SourceKitLSP/Swift/SemanticRefactoring.swift b/Sources/SourceKitLSP/Swift/SemanticRefactoring.swift index 7693a6b58..04101d901 100644 --- a/Sources/SourceKitLSP/Swift/SemanticRefactoring.swift +++ b/Sources/SourceKitLSP/Swift/SemanticRefactoring.swift @@ -1,4 +1,3 @@ -import LSPLogging //===----------------------------------------------------------------------===// // // This source file is part of the Swift.org open source project @@ -10,13 +9,16 @@ import LSPLogging // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// + +import LSPLogging import LanguageServerProtocol import SourceKitD /// Detailed information about the result of a specific refactoring operation. /// -/// Wraps the information returned by sourcekitd's `semantic_refactoring` request, such as the necessary edits and placeholder locations. -struct SemanticRefactoring: Refactoring { +/// Wraps the information returned by sourcekitd's `semantic_refactoring` +/// request, such as the necessary edits and placeholder locations. +struct SemanticRefactoring: RefactoringResponse { /// The title of the refactoring action. var title: String @@ -61,27 +63,26 @@ extension SwiftLanguageService { /// Handles the `SemanticRefactorCommand`. /// - /// Makes request to sourcekitd and wraps the result into a `SemanticRefactoring` and then makes an `ApplyEditRequest` to the client side for the actual refactoring. + /// Makes request to sourcekitd and wraps the result into a + /// `SemanticRefactoring` and then makes an `ApplyEditRequest` to the client + /// side for the actual refactoring. /// /// - Parameters: - /// - refactorCommand: The semantic refactor `SwiftCommand` that triggered this request. + /// - semanticRefactorCommand: The `SemanticRefactorCommand` that triggered + /// this request. /// /// - Returns: /// - a `WorkspaceEdit` with the necessary refactors as a `LSPAny` func semanticRefactoring( - _ refactorCommand: any SwiftCommand + _ semanticRefactorCommand: SemanticRefactorCommand ) async throws -> LSPAny { guard let sourceKitLSPServer else { - // `SourceKitLSPServer` has been destructed. We are tearing down the language - // server. Nothing left to do. + // `SourceKitLSPServer` has been destructed. We are tearing down the + // language server. Nothing left to do. throw ResponseError.unknown("Connection to the editor closed") } - guard let refactorCommand = refactorCommand as? SemanticRefactorCommand else { - throw ResponseError.unknown("refactorCommand is not a SemanticRefactorCommand") - } - - let semanticRefactor = try await self.refactoring(refactorCommand, SemanticRefactoring.self) + let semanticRefactor = try await self.refactoring(semanticRefactorCommand) let edit = semanticRefactor.edit let req = ApplyEditRequest(label: semanticRefactor.title, edit: edit) diff --git a/Sources/SourceKitLSP/Swift/SwiftCommand.swift b/Sources/SourceKitLSP/Swift/SwiftCommand.swift index 41e9fae80..2f3ff178d 100644 --- a/Sources/SourceKitLSP/Swift/SwiftCommand.swift +++ b/Sources/SourceKitLSP/Swift/SwiftCommand.swift @@ -27,11 +27,9 @@ public protocol SwiftCommand: Codable, Hashable, LSPAnyCodable { var title: String { get set } } -public typealias SwiftCommandHandler = (any SwiftCommand) async throws -> LSPAny - extension SwiftCommand { /// Converts this `SwiftCommand` to a generic LSP `Command` object. - public func asCommand() throws -> Command { + public func asCommand() -> Command { let argument = encodeToLSPAny() return Command(title: title, command: Self.identifier, arguments: [argument]) } diff --git a/Sources/SourceKitLSP/Swift/SwiftLanguageService.swift b/Sources/SourceKitLSP/Swift/SwiftLanguageService.swift index 8f114b241..65e42997e 100644 --- a/Sources/SourceKitLSP/Swift/SwiftLanguageService.swift +++ b/Sources/SourceKitLSP/Swift/SwiftLanguageService.swift @@ -109,11 +109,18 @@ public actor SwiftLanguageService: LanguageService, Sendable { let serverOptions: SourceKitLSPServer.Options + /// Directory where generated Files will be stored. + let generatedFilesPath: URL + /// Directory where generated Swift interfaces will be stored. - let generatedInterfacesPath: URL + var generatedInterfacesPath: URL { + generatedFilesPath.appendingPathComponent("GeneratedInterfaces") + } /// Directory where generated Macro expansions will be stored. - let generatedMacroExpansionsPath: URL + var generatedMacroExpansionsPath: URL { + generatedFilesPath.appendingPathComponent("GeneratedMacroExpansions") + } // FIXME: ideally we wouldn't need separate management from a parent server in the same process. var documentManager: DocumentManager @@ -204,10 +211,6 @@ public actor SwiftLanguageService: LanguageService, Sendable { self.serverOptions = options self.documentManager = DocumentManager() self.state = .connected - self.generatedInterfacesPath = options.generatedInterfacesPath.asURL - try FileManager.default.createDirectory(at: generatedInterfacesPath, withIntermediateDirectories: true) - self.generatedMacroExpansionsPath = options.generatedMacroExpansionsPath.asURL - try FileManager.default.createDirectory(at: generatedMacroExpansionsPath, withIntermediateDirectories: true) self.diagnosticReportManager = nil // Needed to work around rdar://116221716 // The debounce duration of 500ms was chosen arbitrarily without scientific research. @@ -220,12 +223,20 @@ public actor SwiftLanguageService: LanguageService, Sendable { try await sourceKitLSPServer.sendRequestToClient(DiagnosticsRefreshRequest()) } } + + self.generatedFilesPath = options.generatedFilesPath.asURL + try FileManager.default.createDirectory(at: generatedFilesPath, withIntermediateDirectories: true) + self.diagnosticReportManager = DiagnosticReportManager( sourcekitd: self.sourcekitd, syntaxTreeManager: syntaxTreeManager, documentManager: documentManager, clientHasDiagnosticsCodeDescriptionSupport: await self.clientHasDiagnosticsCodeDescriptionSupport ) + + // Create sub-directories for each type of generated file + try FileManager.default.createDirectory(at: generatedInterfacesPath, withIntermediateDirectories: true) + try FileManager.default.createDirectory(at: generatedMacroExpansionsPath, withIntermediateDirectories: true) } /// - Important: For testing only @@ -505,7 +516,7 @@ extension SwiftLanguageService { throw CancellationError() } - await sourceKitLSPServer.sendNotificationToClient( + sourceKitLSPServer.sendNotificationToClient( PublishDiagnosticsNotification( uri: document, diagnostics: diagnosticReport.items @@ -802,18 +813,13 @@ extension SwiftLanguageService { var canInlineMacro = false var refactorActions = cursorInfoResponse.refactorActions.compactMap { - do { - let lspCommand = try $0.asCommand() - canInlineMacro = $0.title == "Inline Macro" - return CodeAction(title: $0.title, kind: .refactor, command: lspCommand) - } catch { - logger.log("Failed to convert SwiftCommand to Command type: \(error.forLogging)") - return nil - } + let lspCommand = $0.asCommand() + canInlineMacro = $0.actionString == "source.refactoring.kind.inline.macro" + return CodeAction(title: $0.title, kind: .refactor, command: lspCommand) } - if (canInlineMacro) { - let expandMacroCommand = try! ExpandMacroCommand(positionRange: params.range, textDocument: params.textDocument) + if canInlineMacro { + let expandMacroCommand = ExpandMacroCommand(positionRange: params.range, textDocument: params.textDocument) .asCommand() refactorActions.append(CodeAction(title: expandMacroCommand.title, kind: .refactor, command: expandMacroCommand)) @@ -936,19 +942,13 @@ extension SwiftLanguageService { } public func executeCommand(_ req: ExecuteCommandRequest) async throws -> LSPAny? { - - let commandsAndHandlers: [(command: any SwiftCommand.Type, handler: SwiftCommandHandler)] = [ - (command: SemanticRefactorCommand.self, handler: semanticRefactoring), - (command: ExpandMacroCommand.self, handler: expandMacro), - ] - - for (command, handler) in commandsAndHandlers { - if let swiftCommand = req.swiftCommand(ofType: command) { - return try await handler(swiftCommand) - } + if let command = req.swiftCommand(ofType: SemanticRefactorCommand.self) { + return try await semanticRefactoring(command) + } else if let command = req.swiftCommand(ofType: ExpandMacroCommand.self) { + return try await expandMacro(command) + } else { + throw ResponseError.unknown("unknown command \(req.command)") } - - throw ResponseError.unknown("semantic refactoring: unknown command \(req.command)") } } diff --git a/Sources/sourcekit-lsp/SourceKitLSP.swift b/Sources/sourcekit-lsp/SourceKitLSP.swift index 6739c3e23..771edaabc 100644 --- a/Sources/sourcekit-lsp/SourceKitLSP.swift +++ b/Sources/sourcekit-lsp/SourceKitLSP.swift @@ -189,14 +189,9 @@ struct SourceKitLSP: AsyncParsableCommand { var compilationDatabaseSearchPaths = [RelativePath]() @Option( - help: "Specify the directory where generated interfaces will be stored" + help: "Specify the directory where generated files will be stored" ) - var generatedInterfacesPath = defaultDirectoryForGeneratedInterfaces - - @Option( - help: "Specify the directory where generated macro expansions will be stored" - ) - var generatedMacroExpansionsPath = defaultDirectoryForGeneratedMacroExpansions + var generatedFilesPath = defaultDirectoryForGeneratedFiles @Option( help: "When server-side filtering is enabled, the maximum number of results to return" @@ -228,8 +223,7 @@ struct SourceKitLSP: AsyncParsableCommand { serverOptions.indexOptions.indexDatabasePath = indexDatabasePath serverOptions.indexOptions.indexPrefixMappings = indexPrefixMappings serverOptions.completionOptions.maxResults = completionMaxResults - serverOptions.generatedInterfacesPath = generatedInterfacesPath - serverOptions.generatedMacroExpansionsPath = generatedMacroExpansionsPath + serverOptions.generatedFilesPath = generatedFilesPath serverOptions.experimentalFeatures = Set(experimentalFeatures) return serverOptions diff --git a/Tests/SKSwiftPMWorkspaceTests/SwiftPMBuildSystemTests.swift b/Tests/SKSwiftPMWorkspaceTests/SwiftPMBuildSystemTests.swift index f0b8b0cb7..9f666fcbc 100644 --- a/Tests/SKSwiftPMWorkspaceTests/SwiftPMBuildSystemTests.swift +++ b/Tests/SKSwiftPMWorkspaceTests/SwiftPMBuildSystemTests.swift @@ -842,53 +842,6 @@ final class SwiftPMBuildSystemTests: XCTestCase { assertArgumentsContain(aswift.pathString, arguments: arguments) } } - - func testBuildMacro() async throws { - try await SkipUnless.canBuildMacroUsingSwiftSyntaxFromSourceKitLSPBuild() - // This test is just a dummy to show how to create a `SwiftPMTestProject` that builds a macro using the SwiftSyntax - // modules that were already built during the build of SourceKit-LSP. - // It should be removed once we have a real test that tests macros (like macro expansion). - let project = try await SwiftPMTestProject( - files: [ - "MyMacros/MyMacros.swift": #""" - import SwiftCompilerPlugin - import SwiftSyntax - import SwiftSyntaxBuilder - import SwiftSyntaxMacros - - public struct StringifyMacro: ExpressionMacro { - public static func expansion( - of node: some FreestandingMacroExpansionSyntax, - in context: some MacroExpansionContext - ) -> ExprSyntax { - guard let argument = node.argumentList.first?.expression else { - fatalError("compiler bug: the macro does not have any arguments") - } - - return "(\(argument), \(literal: argument.description))" - } - } - - @main - struct MyMacroPlugin: CompilerPlugin { - let providingMacros: [Macro.Type] = [ - StringifyMacro.self, - ] - } - """#, - "MyMacroClient/MyMacroClient.swift": """ - @freestanding(expression) - public macro stringify(_ value: T) -> (T, String) = #externalMacro(module: "MyMacros", type: "StringifyMacro") - - func test() { - #stringify(1 + 2) - } - """, - ], - manifest: SwiftPMTestProject.macroPackageManifest - ) - try await SwiftPMTestProject.build(at: project.scratchDirectory) - } } private func assertArgumentsDoNotContain( diff --git a/Tests/SourceKitLSPTests/ExecuteCommandTests.swift b/Tests/SourceKitLSPTests/ExecuteCommandTests.swift index ab5dfbbe2..56ae3dbcd 100644 --- a/Tests/SourceKitLSPTests/ExecuteCommandTests.swift +++ b/Tests/SourceKitLSPTests/ExecuteCommandTests.swift @@ -39,7 +39,7 @@ final class ExecuteCommandTests: XCTestCase { let metadata = SourceKitLSPCommandMetadata(textDocument: TextDocumentIdentifier(uri)) - var command = try args.asCommand() + var command = args.asCommand() command.arguments?.append(metadata.encodeToLSPAny()) let request = ExecuteCommandRequest(command: command.command, arguments: command.arguments) @@ -95,7 +95,7 @@ final class ExecuteCommandTests: XCTestCase { let metadata = SourceKitLSPCommandMetadata(textDocument: TextDocumentIdentifier(uri)) - var command = try args.asCommand() + var command = args.asCommand() command.arguments?.append(metadata.encodeToLSPAny()) let request = ExecuteCommandRequest(command: command.command, arguments: command.arguments) @@ -162,9 +162,9 @@ final class ExecuteCommandTests: XCTestCase { @main struct MyMacroPlugin: CompilerPlugin { - let providingMacros: [Macro.Type] = [ - StringifyMacro.self, - ] + let providingMacros: [Macro.Type] = [ + StringifyMacro.self, + ] } """#, "MyMacroClient/MyMacroClient.swift": """ @@ -172,7 +172,7 @@ final class ExecuteCommandTests: XCTestCase { public macro stringify(_ value: T) -> (T, String) = #externalMacro(module: "MyMacros", type: "StringifyMacro") func test() { - 1️⃣#stringify2️⃣(1 + 2) + 1️⃣#2️⃣stringify3️⃣(1 + 2) } """, ], @@ -182,47 +182,56 @@ final class ExecuteCommandTests: XCTestCase { let (uri, positions) = try project.openDocument("MyMacroClient.swift") - let args = ExpandMacroCommand( - positionRange: positions["1️⃣"].. ShowDocumentResponse in - return ShowDocumentResponse(success: true) - } + let expectation = XCTestExpectation(description: "Handle Show Document Request") + var showDocumentRequestURI: DocumentURI? = nil + + project.testClient.handleSingleRequest { (req: ShowDocumentRequest) in + showDocumentRequestURI = req.uri + expectation.fulfill() + return ShowDocumentResponse(success: true) + } - let result = try await project.testClient.send(request) + let result = try await project.testClient.send(request) - guard let resultArray: [MacroExpansionEdit] = Array(fromLSPArray: result ?? .null) else { - XCTFail("Result is not an array.") - return - } + guard let resultArray: [MacroExpansionEdit] = Array(fromLSPArray: result ?? .null) else { + XCTFail("Result is not an array.") + return + } - XCTAssertEqual(resultArray.count, 1) - XCTAssertEqual(resultArray[0].newText, "(1 + 2, \"1 + 2\")") + XCTAssertEqual(resultArray.count, 1) + XCTAssertEqual(resultArray.only?.newText, "(1 + 2, \"1 + 2\")") - let generatedMacroExpansionsPath = SourceKitLSPServer.Options.testDefault.generatedMacroExpansionsPath + await fulfillment(of: [expectation], timeout: 10.0) - XCTAssert( - (try? generatedMacroExpansionsPath.appending(component: resultArray[0].bufferName).asURL - .checkResourceIsReachable()) ?? false - ) + let url = try XCTUnwrap(showDocumentRequestURI?.fileURL) - let fileContents = try XCTUnwrap( - try? String( - contentsOf: generatedMacroExpansionsPath.appending(component: resultArray[0].bufferName).asURL, - encoding: .utf8 + XCTAssert( + (try? url.checkResourceIsReachable()) ?? false ) - ) - XCTAssertTrue(fileContents.contains("(1 + 2, \"1 + 2\")")) + let fileContents = try XCTUnwrap(try? String(contentsOf: url, encoding: .utf8)) + + XCTAssert(fileContents.contains("(1 + 2, \"1 + 2\")")) + } } func testLSPCommandMetadataRetrieval() {