From 0594744a93fcb8138a6361a743913e58ada07b15 Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Tue, 26 Aug 2025 11:51:07 -0700 Subject: [PATCH 1/2] [SwiftLanguageService] Adopt changes to package manifest refactoring actions Previously package manifest refactoring actions conformed to a custom refactoring provider and produced a `PackageEdit` that, in addition to manifest file changes, included a list of auxiliary files. This is no longer the case and all package manifest refactoring actions are now responsible only for manifest file transformations, the rest is handled separately. --- .../CodeActions/PackageManifestEdits.swift | 75 ++----------------- Tests/SourceKitLSPTests/CodeActionTests.swift | 18 +---- 2 files changed, 11 insertions(+), 82 deletions(-) diff --git a/Sources/SwiftLanguageService/CodeActions/PackageManifestEdits.swift b/Sources/SwiftLanguageService/CodeActions/PackageManifestEdits.swift index 34a7d1077..5d6b892e9 100644 --- a/Sources/SwiftLanguageService/CodeActions/PackageManifestEdits.swift +++ b/Sources/SwiftLanguageService/CodeActions/PackageManifestEdits.swift @@ -50,7 +50,7 @@ struct PackageManifestEdits: SyntaxCodeActionProvider { type: type ) - let edits = try AddPackageTarget.manifestRefactor( + let edits = try AddPackageTarget.textRefactor( syntax: scope.file, in: .init(target: target) ) @@ -98,7 +98,7 @@ struct PackageManifestEdits: SyntaxCodeActionProvider { dependencies: [.byName(name: targetName)], ) - let edits = try AddPackageTarget.manifestRefactor( + let edits = try AddPackageTarget.textRefactor( syntax: scope.file, in: .init(target: target, testHarness: testingLibrary) ) @@ -151,7 +151,7 @@ struct PackageManifestEdits: SyntaxCodeActionProvider { targets: [targetName] ) - let edits = try AddProduct.manifestRefactor( + let edits = try AddProduct.textRefactor( syntax: scope.file, in: .init(product: product) ) @@ -175,81 +175,20 @@ struct PackageManifestEdits: SyntaxCodeActionProvider { ] } -fileprivate extension PackageEdit { - /// Translate package manifest edits into a workspace edit. This can - /// involve both modifications to the manifest file as well as the creation - /// of new files. +fileprivate extension [SourceEdit] { + /// Translate package manifest edits into a workspace edit. /// `snapshot` is the latest snapshot of the `Package.swift` file. func asWorkspaceEdit(snapshot: DocumentSnapshot) -> WorkspaceEdit { // The edits to perform on the manifest itself. - let manifestTextEdits = manifestEdits.map { edit in + let manifestTextEdits = map { edit in TextEdit( range: snapshot.absolutePositionRange(of: edit.range), newText: edit.replacement ) } - // If we couldn't figure out the manifest directory, or there are no - // files to add, the only changes are the manifest edits. We're done - // here. - let manifestDirectoryURL = snapshot.uri.fileURL? - .deletingLastPathComponent() - guard let manifestDirectoryURL, !auxiliaryFiles.isEmpty else { - return WorkspaceEdit( - changes: [snapshot.uri: manifestTextEdits] - ) - } - - // Use the more full-featured documentChanges, which takes precedence - // over the individual changes to documents. - var documentChanges: [WorkspaceEditDocumentChange] = [] - - // Put the manifest changes into the array. - documentChanges.append( - .textDocumentEdit( - TextDocumentEdit( - textDocument: .init(snapshot.uri, version: snapshot.version), - edits: manifestTextEdits.map { .textEdit($0) } - ) - ) - ) - - // Create an populate all of the auxiliary files. - for (relativePath, contents) in auxiliaryFiles { - guard - let url = URL( - string: relativePath, - relativeTo: manifestDirectoryURL - ) - else { - continue - } - - let documentURI = DocumentURI(url) - let createFile = CreateFile( - uri: documentURI - ) - - let zeroPosition = Position(line: 0, utf16index: 0) - let edit = TextEdit( - range: zeroPosition.. Date: Tue, 26 Aug 2025 13:43:36 -0700 Subject: [PATCH 2/2] [SwiftLanguageService] CodeActions: Use `asWorkspaceEdit` in more places --- .../CodeActions/PackageManifestEdits.swift | 60 +++++++++---------- .../SyntaxRefactoringCodeActionProvider.swift | 42 +++++++++---- 2 files changed, 56 insertions(+), 46 deletions(-) diff --git a/Sources/SwiftLanguageService/CodeActions/PackageManifestEdits.swift b/Sources/SwiftLanguageService/CodeActions/PackageManifestEdits.swift index 5d6b892e9..866ec15ec 100644 --- a/Sources/SwiftLanguageService/CodeActions/PackageManifestEdits.swift +++ b/Sources/SwiftLanguageService/CodeActions/PackageManifestEdits.swift @@ -50,16 +50,20 @@ struct PackageManifestEdits: SyntaxCodeActionProvider { type: type ) - let edits = try AddPackageTarget.textRefactor( - syntax: scope.file, - in: .init(target: target) - ) + guard + let edit = try AddPackageTarget.textRefactor( + syntax: scope.file, + in: .init(target: target) + ).asWorkspaceEdit(snapshot: scope.snapshot) + else { + continue + } actions.append( CodeAction( title: "Add \(name) target", kind: .refactor, - edit: edits.asWorkspaceEdit(snapshot: scope.snapshot) + edit: edit ) ) } @@ -98,16 +102,20 @@ struct PackageManifestEdits: SyntaxCodeActionProvider { dependencies: [.byName(name: targetName)], ) - let edits = try AddPackageTarget.textRefactor( - syntax: scope.file, - in: .init(target: target, testHarness: testingLibrary) - ) + guard + let edit = try AddPackageTarget.textRefactor( + syntax: scope.file, + in: .init(target: target, testHarness: testingLibrary) + ).asWorkspaceEdit(snapshot: scope.snapshot) + else { + continue + } actions.append( CodeAction( title: "Add test target (\(libraryName))", kind: .refactor, - edit: edits.asWorkspaceEdit(snapshot: scope.snapshot) + edit: edit ) ) } @@ -151,16 +159,20 @@ struct PackageManifestEdits: SyntaxCodeActionProvider { targets: [targetName] ) - let edits = try AddProduct.textRefactor( - syntax: scope.file, - in: .init(product: product) - ) + guard + let edit = try AddProduct.textRefactor( + syntax: scope.file, + in: .init(product: product) + ).asWorkspaceEdit(snapshot: scope.snapshot) + else { + return [] + } return [ CodeAction( title: "Add product to export this target", kind: .refactor, - edit: edits.asWorkspaceEdit(snapshot: scope.snapshot) + edit: edit ) ] } catch { @@ -175,24 +187,6 @@ struct PackageManifestEdits: SyntaxCodeActionProvider { ] } -fileprivate extension [SourceEdit] { - /// Translate package manifest edits into a workspace edit. - /// `snapshot` is the latest snapshot of the `Package.swift` file. - func asWorkspaceEdit(snapshot: DocumentSnapshot) -> WorkspaceEdit { - // The edits to perform on the manifest itself. - let manifestTextEdits = map { edit in - TextEdit( - range: snapshot.absolutePositionRange(of: edit.range), - newText: edit.replacement - ) - } - - return WorkspaceEdit( - changes: [snapshot.uri: manifestTextEdits] - ) - } -} - fileprivate extension SyntaxProtocol { // Find an enclosing call syntax expression. func findEnclosingCall() -> FunctionCallExprSyntax? { diff --git a/Sources/SwiftLanguageService/CodeActions/SyntaxRefactoringCodeActionProvider.swift b/Sources/SwiftLanguageService/CodeActions/SyntaxRefactoringCodeActionProvider.swift index e52934f54..08ce89437 100644 --- a/Sources/SwiftLanguageService/CodeActions/SyntaxRefactoringCodeActionProvider.swift +++ b/Sources/SwiftLanguageService/CodeActions/SyntaxRefactoringCodeActionProvider.swift @@ -37,18 +37,7 @@ extension SyntaxRefactoringCodeActionProvider where Self.Context == Void { return [] } - let textEdits = sourceEdits.compactMap { (edit) -> TextEdit? in - let edit = TextEdit( - range: scope.snapshot.absolutePositionRange(of: edit.range), - newText: edit.replacement - ) - if edit.isNoOp(in: scope.snapshot) { - return nil - } - return edit - } - - if textEdits.isEmpty { + guard let workspaceEdit = sourceEdits.asWorkspaceEdit(snapshot: scope.snapshot) else { return [] } @@ -56,7 +45,7 @@ extension SyntaxRefactoringCodeActionProvider where Self.Context == Void { CodeAction( title: Self.title, kind: .refactorInline, - edit: WorkspaceEdit(changes: [scope.snapshot.uri: textEdits]) + edit: workspaceEdit ) ] } @@ -140,3 +129,30 @@ extension SyntaxProtocol { return nil } } + +extension [SourceEdit] { + /// Translate source edits into a workspace edit. + /// `snapshot` is the latest snapshot of the document to which these edits belong. + func asWorkspaceEdit(snapshot: DocumentSnapshot) -> WorkspaceEdit? { + let textEdits = compactMap { edit -> TextEdit? in + let edit = TextEdit( + range: snapshot.absolutePositionRange(of: edit.range), + newText: edit.replacement + ) + + if edit.isNoOp(in: snapshot) { + return nil + } + + return edit + } + + if textEdits.isEmpty { + return nil + } + + return WorkspaceEdit( + changes: [snapshot.uri: textEdits] + ) + } +}