Files
sourcekit-lsp/Tests/SourceKitLSPTests/RenameAssertions.swift

190 lines
7.2 KiB
Swift

//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2023 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
//
//===----------------------------------------------------------------------===//
@_spi(SourceKitLSP) import LanguageServerProtocol
import SKTestSupport
import SKUtilities
import XCTest
internal func apply(edits: [TextEdit], to source: String) -> String {
var lineTable = LineTable(source)
let edits = edits.sorted(by: { $0.range.lowerBound < $1.range.lowerBound })
for edit in edits.reversed() {
lineTable.replace(
fromLine: edit.range.lowerBound.line,
utf16Offset: edit.range.lowerBound.utf16index,
toLine: edit.range.upperBound.line,
utf16Offset: edit.range.upperBound.utf16index,
with: edit.newText
)
}
return lineTable.content
}
/// Perform a rename request at every location marker in `markedSource`, renaming it to `newName`.
/// Test that applying the edits returned from the requests always result in `expected`.
func assertSingleFileRename(
_ markedSource: String,
language: Language = .swift,
newName: String,
expectedPrepareRenamePlaceholder: String,
expected: String,
testName: String = #function,
file: StaticString = #filePath,
line: UInt = #line
) async throws {
let testClient = try await TestSourceKitLSPClient()
let uri = DocumentURI(for: language, testName: testName)
let positions = testClient.openDocument(markedSource, uri: uri, language: language)
guard !positions.allMarkers.isEmpty else {
XCTFail("Test case did not contain any markers at which to invoke the rename", file: file, line: line)
return
}
for marker in positions.allMarkers {
let position = positions[marker]
let prepareRenameResponse = try await testClient.send(
PrepareRenameRequest(textDocument: TextDocumentIdentifier(uri), position: position)
)
if let prepareRenameResponse {
XCTAssertEqual(
prepareRenameResponse.placeholder,
expectedPrepareRenamePlaceholder,
"Prepare rename placeholder does not match while performing rename at \(marker)",
file: file,
line: line
)
// VS Code considers the upper bound of a range as part of the identifier so both `contains` and equality in
// `upperBound` are fine.
XCTAssert(
prepareRenameResponse.range.contains(position) || prepareRenameResponse.range.upperBound == position,
"Prepare rename range \(prepareRenameResponse.range) does not contain rename position \(position)",
file: file,
line: line
)
} else {
XCTFail("Expected non-nil prepareRename response", file: file, line: line)
}
let response = try await testClient.send(
RenameRequest(
textDocument: TextDocumentIdentifier(uri),
position: positions[marker],
newName: newName
)
)
let edits = try XCTUnwrap(response?.changes?[uri], "while performing rename at \(marker)", file: file, line: line)
let source = extractMarkers(markedSource).textWithoutMarkers
let renamed = apply(edits: edits, to: source)
XCTAssertEqual(renamed, expected, "while performing rename at \(marker)", file: file, line: line)
}
}
/// Assert that applying changes to `originalFiles` results in `expected`.
///
/// Upon failure, `message` is added to the XCTest failure messages to provide context which rename failed.
func assertRenamedSourceMatches(
originalFiles: [RelativeFileLocation: String],
changes: [DocumentURI: [TextEdit]],
expected: [RelativeFileLocation: String],
in ws: MultiFileTestProject,
message: String,
testName: String = #function,
file: StaticString = #filePath,
line: UInt = #line
) throws {
for (expectedFileLocation, expectedRenamed) in expected {
let originalMarkedSource = try XCTUnwrap(
originalFiles[expectedFileLocation],
"No original source for \(expectedFileLocation.fileName) specified; \(message)",
file: file,
line: line
)
let originalSource = extractMarkers(originalMarkedSource).textWithoutMarkers
let edits = changes[try ws.uri(for: expectedFileLocation.fileName)] ?? []
let renamed = apply(edits: edits, to: originalSource)
XCTAssertEqual(
renamed,
expectedRenamed,
"applying edits did not match expected renamed source for \(expectedFileLocation.fileName); \(message)",
file: file,
line: line
)
}
}
/// Perform a rename request at every location marker except 0️⃣ in `files`, renaming it to `newName`. The location
/// marker 0️⃣ is intended to be used as an anchor for `preRenameActions`.
///
/// Test that applying the edits returned from the requests always result in `expected`.
///
/// `preRenameActions` is executed after opening the workspace but before performing the rename. This allows a workspace
/// to be placed in a state where there are in-memory changes that haven't been written to disk yet.
func assertMultiFileRename(
files: [RelativeFileLocation: String],
headerFileLanguage: Language? = nil,
newName: String,
expectedPrepareRenamePlaceholder: String,
expected: [RelativeFileLocation: String],
manifest: String = SwiftPMTestProject.defaultPackageManifest,
preRenameActions: (SwiftPMTestProject) throws -> Void = { _ in },
testName: String = #function,
file: StaticString = #filePath,
line: UInt = #line
) async throws {
let project = try await SwiftPMTestProject(
files: files,
manifest: manifest,
enableBackgroundIndexing: true,
testName: testName
)
try preRenameActions(project)
for (fileLocation, markedSource) in files.sorted(by: { $0.key.fileName < $1.key.fileName }) {
let markers = extractMarkers(markedSource).markers.keys.sorted().filter { $0 != "0️⃣" }
if markers.isEmpty {
continue
}
let (uri, positions) = try project.openDocument(
fileLocation.fileName,
language: fileLocation.fileName.hasSuffix(".h") ? headerFileLanguage : nil
)
defer {
project.testClient.send(DidCloseTextDocumentNotification(textDocument: TextDocumentIdentifier(uri)))
}
for marker in markers {
let prepareRenameResponse = try await project.testClient.send(
PrepareRenameRequest(textDocument: TextDocumentIdentifier(uri), position: positions[marker])
)
XCTAssertEqual(
prepareRenameResponse?.placeholder,
expectedPrepareRenamePlaceholder,
"Prepare rename placeholder does not match while performing rename at \(marker)",
file: file,
line: line
)
let response = try await project.testClient.send(
RenameRequest(textDocument: TextDocumentIdentifier(uri), position: positions[marker], newName: newName)
)
let changes = try XCTUnwrap(response?.changes, "Did not receive any edits", file: file, line: line)
try assertRenamedSourceMatches(
originalFiles: files,
changes: changes,
expected: expected,
in: project,
message: "while performing rename at \(marker)",
file: file,
line: line
)
}
}
}