Skip to content

Commit 34a36b4

Browse files
committed
Refactor rename to support index-based discovery of rename locations
1 parent d931806 commit 34a36b4

File tree

3 files changed

+92
-59
lines changed

3 files changed

+92
-59
lines changed

Sources/SourceKitD/SKDRequestArray.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,12 @@ public final class SKDRequestArray {
4040
public func append(_ value: SKDRequestDictionary) {
4141
sourcekitd.api.request_array_set_value(array, -1, value.dict)
4242
}
43+
44+
public static func += (array: SKDRequestArray, other: some Sequence<SKDRequestDictionary>) {
45+
for item in other {
46+
array.append(item)
47+
}
48+
}
4349
}
4450

4551
extension SKDRequestArray: CustomStringConvertible {

Sources/SourceKitLSP/Swift/Rename.swift

Lines changed: 59 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -268,33 +268,47 @@ fileprivate struct SyntacticRenameName {
268268
}
269269
}
270270

271+
struct RenameLocation {
272+
/// The line of the identifier to be renamed (1-based).
273+
let line: Int
274+
/// The column of the identifier to be renamed in UTF-8 bytes (1-based).
275+
let utf8Column: Int
276+
let usage: RelatedIdentifier.Usage
277+
}
278+
279+
private extension DocumentSnapshot {
280+
init(_ url: URL, language: Language) throws {
281+
let contents = try String(contentsOf: url)
282+
self.init(uri: DocumentURI(url), language: language, version: 0, lineTable: LineTable(contents))
283+
}
284+
}
285+
271286
extension SwiftLanguageServer {
272-
/// From a list of rename locations, provided by a related identifiers request, compute the list of
273-
/// `SyntacticRenameName`s that define which ranges need to be edited to rename a compound decl name.
287+
/// From a list of rename locations compute the list of `SyntacticRenameName`s that define which ranges need to be
288+
/// edited to rename a compound decl name.
289+
///
290+
/// - Parameters:
291+
/// - renameLocations: The locations to rename
292+
/// - oldName: The compound decl name that the declaration had before the rename. Used to verify that the rename
293+
/// locations match that name. Eg. `myFunc(argLabel:otherLabel:)` or `myVar`
294+
/// - snapshot: If the document has been modified from the on-disk version, the current snapshot. `nil` to read the
295+
/// file contents from disk.
274296
private func getSyntacticRenameRanges(
275-
relatedIdentifiers: RelatedIdentifiersResponse,
297+
renameLocations: [RenameLocation],
298+
oldName: String,
276299
in snapshot: DocumentSnapshot
277300
) async throws -> [SyntacticRenameName] {
278301
let locations = SKDRequestArray(sourcekitd: sourcekitd)
279-
for relatedIdentifier in relatedIdentifiers.relatedIdentifiers {
280-
let position = relatedIdentifier.range.lowerBound
281-
guard let utf8Column = snapshot.lineTable.utf8ColumnAt(line: position.line, utf16Column: position.utf16index)
282-
else {
283-
logger.fault("Unable to find UTF-8 column for \(position.line):\(position.utf16index)")
284-
continue
285-
}
286-
let renameLocation = SKDRequestDictionary(sourcekitd: sourcekitd)
287-
renameLocation[keys.line] = position.line + 1
288-
renameLocation[keys.column] = utf8Column + 1
289-
renameLocation[keys.nameType] = relatedIdentifier.usage.uid(keys: keys)
290-
locations.append(renameLocation)
291-
}
292-
guard let name = relatedIdentifiers.name else {
293-
throw ResponseError.unknown("Running sourcekit-lsp with a version of sourcekitd that does not support rename")
302+
locations += renameLocations.map { renameLocation in
303+
let skRenameLocation = SKDRequestDictionary(sourcekitd: sourcekitd)
304+
skRenameLocation[keys.line] = renameLocation.line
305+
skRenameLocation[keys.column] = renameLocation.utf8Column
306+
skRenameLocation[keys.nameType] = renameLocation.usage.uid(keys: keys)
307+
return skRenameLocation
294308
}
295309
let renameLocation = SKDRequestDictionary(sourcekitd: sourcekitd)
296310
renameLocation[keys.locations] = locations
297-
renameLocation[keys.name] = name
311+
renameLocation[keys.name] = oldName
298312

299313
let renameLocations = SKDRequestArray(sourcekitd: sourcekitd)
300314
renameLocations.append(renameLocation)
@@ -323,22 +337,38 @@ extension SwiftLanguageServer {
323337
in: snapshot,
324338
includeNonEditableBaseNames: true
325339
)
340+
guard let oldName = relatedIdentifiers.name else {
341+
throw ResponseError.unknown("Running sourcekit-lsp with a version of sourcekitd that does not support rename")
342+
}
343+
344+
try Task.checkCancellation()
326345

346+
let renameLocations = relatedIdentifiers.relatedIdentifiers.compactMap { (relatedIdentifier) -> RenameLocation? in
347+
let position = relatedIdentifier.range.lowerBound
348+
guard let utf8Column = snapshot.lineTable.utf8ColumnAt(line: position.line, utf16Column: position.utf16index)
349+
else {
350+
logger.fault("Unable to find UTF-8 column for \(position.line):\(position.utf16index)")
351+
return nil
352+
}
353+
return RenameLocation(line: position.line + 1, utf8Column: utf8Column + 1, usage: relatedIdentifier.usage)
354+
}
355+
327356
try Task.checkCancellation()
328357

329-
let compoundRenameRanges = try await getSyntacticRenameRanges(relatedIdentifiers: relatedIdentifiers, in: snapshot)
358+
let edits = try await renameRanges(from: renameLocations, in: snapshot, oldName: oldName, newName: try CompoundDeclName(request.newName))
330359

331-
try Task.checkCancellation()
360+
return WorkspaceEdit(changes: [
361+
snapshot.uri: edits
362+
])
363+
}
332364

333-
let oldName =
334-
if let name = relatedIdentifiers.name {
335-
try CompoundDeclName(name)
336-
} else {
337-
throw ResponseError.unknown("Running sourcekit-lsp with a version of sourcekitd that does not support rename")
338-
}
339-
let newName = try CompoundDeclName(request.newName)
365+
private func renameRanges(from renameLocations: [RenameLocation], in snapshot: DocumentSnapshot, oldName oldNameString: String, newName: CompoundDeclName) async throws -> [TextEdit] {
366+
let compoundRenameRanges = try await getSyntacticRenameRanges(renameLocations: renameLocations, oldName: oldNameString, in: snapshot)
367+
let oldName = try CompoundDeclName(oldNameString)
368+
369+
try Task.checkCancellation()
340370

341-
let edits = compoundRenameRanges.flatMap { (compoundRenameRange) -> [TextEdit] in
371+
return compoundRenameRanges.flatMap { (compoundRenameRange) -> [TextEdit] in
342372
switch compoundRenameRange.category {
343373
case .unmatched, .mismatch:
344374
// The location didn't match. Don't rename it
@@ -428,9 +458,6 @@ extension SwiftLanguageServer {
428458
}
429459
}
430460
}
431-
return WorkspaceEdit(changes: [
432-
snapshot.uri: edits
433-
])
434461
}
435462
}
436463

Tests/SourceKitLSPTests/RenameTests.swift

Lines changed: 27 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ private func apply(edits: [TextEdit], to source: String) -> String {
3232
return lineTable.content
3333
}
3434

35-
private func assertRename(
35+
private func assertSingleFileRename(
3636
_ markedSource: String,
3737
newName: String,
3838
expected: String,
@@ -57,7 +57,7 @@ private func assertRename(
5757

5858
final class RenameTests: XCTestCase {
5959
func testRenameVariableBaseName() async throws {
60-
try await assertRename(
60+
try await assertSingleFileRename(
6161
"""
6262
let 1️⃣foo = 1
6363
print(foo)
@@ -71,7 +71,7 @@ final class RenameTests: XCTestCase {
7171
}
7272

7373
func testRenameFunctionBaseName() async throws {
74-
try await assertRename(
74+
try await assertSingleFileRename(
7575
"""
7676
func 1️⃣foo() {}
7777
foo()
@@ -85,7 +85,7 @@ final class RenameTests: XCTestCase {
8585
}
8686

8787
func testRenameFunctionParameter() async throws {
88-
try await assertRename(
88+
try await assertSingleFileRename(
8989
"""
9090
func 1️⃣foo(x: Int) {}
9191
foo(x: 1)
@@ -99,7 +99,7 @@ final class RenameTests: XCTestCase {
9999
}
100100

101101
func testSecondParameterNameIfMatches() async throws {
102-
try await assertRename(
102+
try await assertSingleFileRename(
103103
"""
104104
func 1️⃣foo(x y: Int) {}
105105
foo(x: 1)
@@ -113,7 +113,7 @@ final class RenameTests: XCTestCase {
113113
}
114114

115115
func testIntroduceLabel() async throws {
116-
try await assertRename(
116+
try await assertSingleFileRename(
117117
"""
118118
func 1️⃣foo(_ y: Int) {}
119119
foo(1)
@@ -127,7 +127,7 @@ final class RenameTests: XCTestCase {
127127
}
128128

129129
func testRemoveLabel() async throws {
130-
try await assertRename(
130+
try await assertSingleFileRename(
131131
"""
132132
func 1️⃣foo(x: Int) {}
133133
foo(x: 1)
@@ -141,7 +141,7 @@ final class RenameTests: XCTestCase {
141141
}
142142

143143
func testRemoveLabelWithExistingInternalName() async throws {
144-
try await assertRename(
144+
try await assertSingleFileRename(
145145
"""
146146
func 1️⃣foo(x a: Int) {}
147147
foo(x: 1)
@@ -155,7 +155,7 @@ final class RenameTests: XCTestCase {
155155
}
156156

157157
func testRenameSubscript() async throws {
158-
try await assertRename(
158+
try await assertSingleFileRename(
159159
"""
160160
struct Foo {
161161
1️⃣subscript(x x: Int) -> Int { x }
@@ -173,7 +173,7 @@ final class RenameTests: XCTestCase {
173173
}
174174

175175
func testRemoveExternalLabelFromSubscript() async throws {
176-
try await assertRename(
176+
try await assertSingleFileRename(
177177
"""
178178
struct Foo {
179179
1️⃣subscript(x x: Int) -> Int { x }
@@ -191,7 +191,7 @@ final class RenameTests: XCTestCase {
191191
}
192192

193193
func testIntroduceExternalLabelFromSubscript() async throws {
194-
try await assertRename(
194+
try await assertSingleFileRename(
195195
"""
196196
struct Foo {
197197
1️⃣subscript(x: Int) -> Int { x }
@@ -209,7 +209,7 @@ final class RenameTests: XCTestCase {
209209
}
210210

211211
func testIgnoreRenameSubscriptBaseName() async throws {
212-
try await assertRename(
212+
try await assertSingleFileRename(
213213
"""
214214
struct Foo {
215215
1️⃣subscript(x: Int) -> Int { x }
@@ -227,7 +227,7 @@ final class RenameTests: XCTestCase {
227227
}
228228

229229
func testRenameInitializerLabels() async throws {
230-
try await assertRename(
230+
try await assertSingleFileRename(
231231
"""
232232
struct Foo {
233233
1️⃣init(x: Int) {}
@@ -245,7 +245,7 @@ final class RenameTests: XCTestCase {
245245
}
246246

247247
func testIgnoreRenameOfInitBaseName() async throws {
248-
try await assertRename(
248+
try await assertSingleFileRename(
249249
"""
250250
struct Foo {
251251
1️⃣init(x: Int) {}
@@ -263,7 +263,7 @@ final class RenameTests: XCTestCase {
263263
}
264264

265265
func testRenameCompoundFunctionName() async throws {
266-
try await assertRename(
266+
try await assertSingleFileRename(
267267
"""
268268
func 1️⃣foo(a: Int) {}
269269
_ = foo(a:)
@@ -277,7 +277,7 @@ final class RenameTests: XCTestCase {
277277
}
278278

279279
func testRemoveLabelFromCompoundFunctionName() async throws {
280-
try await assertRename(
280+
try await assertSingleFileRename(
281281
"""
282282
func 1️⃣foo(a: Int) {}
283283
_ = foo(a:)
@@ -291,7 +291,7 @@ final class RenameTests: XCTestCase {
291291
}
292292

293293
func testIntroduceLabelToCompoundFunctionName() async throws {
294-
try await assertRename(
294+
try await assertSingleFileRename(
295295
"""
296296
func 1️⃣foo(_ a: Int) {}
297297
_ = foo(_:)
@@ -305,7 +305,7 @@ final class RenameTests: XCTestCase {
305305
}
306306

307307
func testRenameFromReference() async throws {
308-
try await assertRename(
308+
try await assertSingleFileRename(
309309
"""
310310
func foo(_ a: Int) {}
311311
_ = 1️⃣foo(_:)
@@ -319,7 +319,7 @@ final class RenameTests: XCTestCase {
319319
}
320320

321321
func testRenameMultipleParameters() async throws {
322-
try await assertRename(
322+
try await assertSingleFileRename(
323323
"""
324324
func 1️⃣foo(a: Int, b: Int) {}
325325
foo(a: 1, b: 1)
@@ -333,7 +333,7 @@ final class RenameTests: XCTestCase {
333333
}
334334

335335
func testDontRenameParametersOmittedFromNewName() async throws {
336-
try await assertRename(
336+
try await assertSingleFileRename(
337337
"""
338338
func 1️⃣foo(a: Int, b: Int) {}
339339
foo(a: 1, b: 1)
@@ -347,7 +347,7 @@ final class RenameTests: XCTestCase {
347347
}
348348

349349
func testIgnoreAdditionalParametersInNewName() async throws {
350-
try await assertRename(
350+
try await assertSingleFileRename(
351351
"""
352352
func 1️⃣foo(a: Int) {}
353353
foo(a: 1)
@@ -361,7 +361,7 @@ final class RenameTests: XCTestCase {
361361
}
362362

363363
func testOnlySpecifyBaseNameWhenRenamingFunction() async throws {
364-
try await assertRename(
364+
try await assertSingleFileRename(
365365
"""
366366
func 1️⃣foo(a: Int) {}
367367
foo(a: 1)
@@ -375,7 +375,7 @@ final class RenameTests: XCTestCase {
375375
}
376376

377377
func testIgnoreParametersInNewNameWhenRenamingVariable() async throws {
378-
try await assertRename(
378+
try await assertSingleFileRename(
379379
"""
380380
let 1️⃣foo = 1
381381
_ = foo
@@ -421,7 +421,7 @@ final class RenameTests: XCTestCase {
421421
}
422422

423423
func testSpacesInNewParameterNames() async throws {
424-
try await assertRename(
424+
try await assertSingleFileRename(
425425
"""
426426
func 1️⃣foo(a: Int) {}
427427
foo(a: 1)
@@ -435,7 +435,7 @@ final class RenameTests: XCTestCase {
435435
}
436436

437437
func testRenameOperator() async throws {
438-
try await assertRename(
438+
try await assertSingleFileRename(
439439
"""
440440
struct Foo {}
441441
func 1️⃣+(x: Foo, y: Foo) {}
@@ -451,7 +451,7 @@ final class RenameTests: XCTestCase {
451451
}
452452

453453
func testRenameParameterToEmptyName() async throws {
454-
try await assertRename(
454+
try await assertSingleFileRename(
455455
"""
456456
func 1️⃣foo(x: Int) {}
457457
foo(x: 1)
@@ -468,7 +468,7 @@ final class RenameTests: XCTestCase {
468468
#if !canImport(Darwin)
469469
throw XCTSkip("#selector in test case doesn't compile without Objective-C runtime.")
470470
#endif
471-
try await assertRename(
471+
try await assertSingleFileRename(
472472
"""
473473
import Foundation
474474
class Foo: NSObject {

0 commit comments

Comments
 (0)