Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 27 additions & 94 deletions Sources/SwiftLanguageService/CodeActions/PackageManifestEdits.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
)
}
Expand Down Expand Up @@ -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
)
)
}
Expand Down Expand Up @@ -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 {
Expand All @@ -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? {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,26 +37,15 @@ 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 []
}

return [
CodeAction(
title: Self.title,
kind: .refactorInline,
edit: WorkspaceEdit(changes: [scope.snapshot.uri: textEdits])
edit: workspaceEdit
)
]
}
Expand Down Expand Up @@ -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]
)
}
}
18 changes: 4 additions & 14 deletions Tests/SourceKitLSPTests/CodeActionTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
)

Expand Down