Merge pull request #2262 from xedin/adopt-refactoring-actions-to-use-edit-provider

[SwiftLanguageService] Adopt changes to package manifest refactoring …
This commit is contained in:
Ben Barham
2025-08-27 13:30:03 -07:00
committed by GitHub
3 changed files with 60 additions and 121 deletions

View File

@@ -50,16 +50,20 @@ struct PackageManifestEdits: SyntaxCodeActionProvider {
type: type
)
let edits = try AddPackageTarget.manifestRefactor(
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.manifestRefactor(
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.manifestRefactor(
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,85 +187,6 @@ 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.
/// `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
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..<zeroPosition,
newText: contents.description
)
documentChanges.append(.createFile(createFile))
documentChanges.append(
.textDocumentEdit(
TextDocumentEdit(
textDocument: .init(documentURI, version: snapshot.version),
edits: [.textEdit(edit)]
)
)
)
}
return WorkspaceEdit(
changes: [snapshot.uri: manifestTextEdits],
documentChanges: documentChanges
)
}
}
fileprivate extension SyntaxProtocol {
// Find an enclosing call syntax expression.
func findEnclosingCall() -> FunctionCallExprSyntax? {

View File

@@ -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]
)
}
}

View File

@@ -623,29 +623,19 @@ final class CodeActionTests: XCTestCase {
}
)
guard let addTestChanges = addTestAction?.edit?.documentChanges else {
guard let addTestChanges = addTestAction?.edit?.changes else {
XCTFail("Didn't have changes in the 'Add test target (Swift Testing)' action")
return
}
guard
let addTestEdit = addTestChanges.lazy.compactMap({ change in
switch change {
case .textDocumentEdit(let edit): edit
default: nil
}
}).first
else {
guard let manifestEdits = addTestChanges[uri] else {
XCTFail("Didn't have edits")
return
}
XCTAssertTrue(
addTestEdit.edits.contains { edit in
switch edit {
case .textEdit(let edit): edit.newText.contains("testTarget")
case .annotatedTextEdit(let edit): edit.newText.contains("testTarget")
}
manifestEdits.contains { edit in
edit.newText.contains("testTarget")
}
)