@@ -38,11 +38,12 @@ private func assertSingleFileRename(
38
38
_ markedSource: String ,
39
39
newName: String ,
40
40
expected: String ,
41
+ testName: String = #function,
41
42
file: StaticString = #file,
42
43
line: UInt = #line
43
44
) async throws {
44
45
let testClient = try await TestSourceKitLSPClient ( )
45
- let uri = DocumentURI . for ( . swift)
46
+ let uri = DocumentURI . for ( . swift, testName : testName )
46
47
let positions = testClient. openDocument ( markedSource, uri: uri)
47
48
for marker in positions. allMarkers {
48
49
let response = try await testClient. send (
@@ -59,6 +60,90 @@ private func assertSingleFileRename(
59
60
}
60
61
}
61
62
63
+ /// Assert that applying changes to `originalFiles` results in `expected`.
64
+ ///
65
+ /// Upon failure, `message` is added to the XCTest failure messages to provide context which rename failed.
66
+ private func assertRenamedSourceMatches(
67
+ originalFiles: [ RelativeFileLocation : String ] ,
68
+ changes: [ DocumentURI : [ TextEdit ] ] ,
69
+ expected: [ RelativeFileLocation : String ] ,
70
+ in ws: MultiFileTestWorkspace ,
71
+ message: String ,
72
+ testName: String = #function,
73
+ file: StaticString = #file,
74
+ line: UInt = #line
75
+ ) throws {
76
+ for (expectedFileLocation, expectedRenamed) in expected {
77
+ let originalMarkedSource = try XCTUnwrap (
78
+ originalFiles [ expectedFileLocation] ,
79
+ " No original source for \( expectedFileLocation. fileName) specified; \( message) " ,
80
+ file: file,
81
+ line: line
82
+ )
83
+ let originalSource = extractMarkers ( originalMarkedSource) . textWithoutMarkers
84
+ let edits = changes [ try ws. uri ( for: expectedFileLocation. fileName) ] ?? [ ]
85
+ let renamed = apply ( edits: edits, to: originalSource)
86
+ XCTAssertEqual (
87
+ renamed,
88
+ expectedRenamed,
89
+ " applying edits did not match expected renamed source for \( expectedFileLocation. fileName) ; \( message) " ,
90
+ file: file,
91
+ line: line
92
+ )
93
+ }
94
+ }
95
+
96
+ /// Perform a rename request at every location marker except 0️⃣ in `files`, renaming it to `newName`. The location
97
+ /// marker 0️⃣ is intended to be used as an anchor for `preRenameActions`.
98
+ ///
99
+ /// Test that applying the edits returned from the requests always result in `expected`.
100
+ ///
101
+ /// `preRenameActions` is executed after opening the workspace but before performing the rename. This allows a workspace
102
+ /// to be placed in a state where there are in-memory changes that haven't been written to disk yet.
103
+ private func assertMultiFileRename(
104
+ files: [ RelativeFileLocation : String ] ,
105
+ newName: String ,
106
+ expected: [ RelativeFileLocation : String ] ,
107
+ manifest: String = SwiftPMTestWorkspace . defaultPackageManifest,
108
+ preRenameActions: ( SwiftPMTestWorkspace ) throws -> Void = { _ in } ,
109
+ testName: String = #function,
110
+ file: StaticString = #file,
111
+ line: UInt = #line
112
+ ) async throws {
113
+ let ws = try await SwiftPMTestWorkspace (
114
+ files: files,
115
+ manifest: manifest,
116
+ build: true ,
117
+ testName: testName
118
+ )
119
+ try preRenameActions ( ws)
120
+ for (fileLocation, markedSource) in files. sorted ( by: { $0. key. fileName < $1. key. fileName } ) {
121
+ let markers = extractMarkers ( markedSource) . markers. keys. sorted ( ) . filter { $0 != " 0️⃣ " }
122
+ if markers. isEmpty {
123
+ continue
124
+ }
125
+ let ( uri, positions) = try ws. openDocument ( fileLocation. fileName)
126
+ defer {
127
+ ws. testClient. send ( DidCloseTextDocumentNotification ( textDocument: TextDocumentIdentifier ( uri) ) )
128
+ }
129
+ for marker in markers {
130
+ let response = try await ws. testClient. send (
131
+ RenameRequest ( textDocument: TextDocumentIdentifier ( uri) , position: positions [ marker] , newName: newName)
132
+ )
133
+ let changes = try XCTUnwrap ( response? . changes)
134
+ try assertRenamedSourceMatches (
135
+ originalFiles: files,
136
+ changes: changes,
137
+ expected: expected,
138
+ in: ws,
139
+ message: " while performing rename at \( marker) " ,
140
+ file: file,
141
+ line: line
142
+ )
143
+ }
144
+ }
145
+ }
146
+
62
147
final class RenameTests : XCTestCase {
63
148
func testRenameVariableBaseName( ) async throws {
64
149
try await assertSingleFileRename (
@@ -464,46 +549,53 @@ final class RenameTests: XCTestCase {
464
549
}
465
550
466
551
func testCrossFileSwiftRename( ) async throws {
467
- let ws = try await SwiftPMTestWorkspace (
552
+ try await assertMultiFileRename (
468
553
files: [
469
554
" a.swift " : """
470
- func 1️⃣foo2️⃣ () {}
555
+ func 1️⃣foo () {}
471
556
""" ,
472
557
" b.swift " : """
473
558
func test() {
474
- 3️⃣foo4️⃣ ()
559
+ 2️⃣foo ()
475
560
}
476
561
""" ,
477
562
] ,
478
- build: true
479
- )
480
-
481
- let ( aUri, aPositions) = try ws. openDocument ( " a.swift " )
482
- let response = try await ws. testClient. send (
483
- RenameRequest ( textDocument: TextDocumentIdentifier ( aUri) , position: aPositions [ " 1️⃣ " ] , newName: " bar " )
484
- )
485
- let changes = try XCTUnwrap ( response? . changes)
486
- XCTAssertEqual (
487
- changes,
488
- [
489
- aUri: [ TextEdit ( range: aPositions [ " 1️⃣ " ] ..< aPositions [ " 2️⃣ " ] , newText: " bar " ) ] ,
490
- try ws. uri ( for: " b.swift " ) : [
491
- TextEdit ( range: try ws. position ( of: " 3️⃣ " , in: " b.swift " ) ..< ws. position ( of: " 4️⃣ " , in: " b.swift " ) , newText: " bar " )
492
- ] ,
563
+ newName: " bar " ,
564
+ expected: [
565
+ " a.swift " : """
566
+ func bar() {}
567
+ """ ,
568
+ " b.swift " : """
569
+ func test() {
570
+ bar()
571
+ }
572
+ """ ,
493
573
]
494
574
)
495
575
}
496
576
497
577
func testSwiftCrossModuleRename( ) async throws {
498
- let ws = try await SwiftPMTestWorkspace (
578
+ try await assertMultiFileRename (
499
579
files: [
500
580
" LibA/LibA.swift " : """
501
- public func 1️⃣foo2️⃣(3️⃣argLabel4️⃣ : Int) {}
581
+ public func 1️⃣foo(argLabel : Int) {}
502
582
""" ,
503
583
" LibB/LibB.swift " : """
504
584
import LibA
505
585
public func test() {
506
- 5️⃣foo6️⃣(7️⃣argLabel8️⃣: 1)
586
+ 5️⃣foo(argLabel: 1)
587
+ }
588
+ """ ,
589
+ ] ,
590
+ newName: " bar(new:) " ,
591
+ expected: [
592
+ " LibA/LibA.swift " : """
593
+ public func bar(new: Int) {}
594
+ """ ,
595
+ " LibB/LibB.swift " : """
596
+ import LibA
597
+ public func test() {
598
+ bar(new: 1)
507
599
}
508
600
""" ,
509
601
] ,
@@ -519,125 +611,79 @@ final class RenameTests: XCTestCase {
519
611
.target(name: " LibB " , dependencies: [ " LibA " ]),
520
612
]
521
613
)
522
- """ ,
523
- build: true
524
- )
525
-
526
- let expectedChanges = [
527
- try ws. uri ( for: " LibA.swift " ) : [
528
- TextEdit (
529
- range: try ws. position ( of: " 1️⃣ " , in: " LibA.swift " ) ..< ws. position ( of: " 2️⃣ " , in: " LibA.swift " ) ,
530
- newText: " bar "
531
- ) ,
532
- TextEdit (
533
- range: try ws. position ( of: " 3️⃣ " , in: " LibA.swift " ) ..< ws. position ( of: " 4️⃣ " , in: " LibA.swift " ) ,
534
- newText: " new "
535
- ) ,
536
- ] ,
537
- try ws. uri ( for: " LibB.swift " ) : [
538
- TextEdit (
539
- range: try ws. position ( of: " 5️⃣ " , in: " LibB.swift " ) ..< ws. position ( of: " 6️⃣ " , in: " LibB.swift " ) ,
540
- newText: " bar "
541
- ) ,
542
- TextEdit (
543
- range: try ws. position ( of: " 7️⃣ " , in: " LibB.swift " ) ..< ws. position ( of: " 8️⃣ " , in: " LibB.swift " ) ,
544
- newText: " new "
545
- ) ,
546
- ] ,
547
- ]
548
-
549
- let ( aUri, aPositions) = try ws. openDocument ( " LibA.swift " )
550
-
551
- let definitionResponse = try await ws. testClient. send (
552
- RenameRequest ( textDocument: TextDocumentIdentifier ( aUri) , position: aPositions [ " 1️⃣ " ] , newName: " bar(new:) " )
553
- )
554
- XCTAssertEqual ( try XCTUnwrap ( definitionResponse? . changes) , expectedChanges)
555
-
556
- let ( bUri, bPositions) = try ws. openDocument ( " LibB.swift " )
557
-
558
- let callResponse = try await ws. testClient. send (
559
- RenameRequest ( textDocument: TextDocumentIdentifier ( bUri) , position: bPositions [ " 5️⃣ " ] , newName: " bar(new:) " )
614
+ """
560
615
)
561
- XCTAssertEqual ( try XCTUnwrap ( callResponse? . changes) , expectedChanges)
562
616
}
563
617
564
618
func testTryIndexLocationsDontMatchInMemoryLocations( ) async throws {
565
- let ws = try await SwiftPMTestWorkspace (
619
+ try await assertMultiFileRename (
566
620
files: [
567
621
" a.swift " : """
568
- func 1️⃣foo2️⃣ () {}
622
+ func 1️⃣foo () {}
569
623
""" ,
570
624
" b.swift " : """
571
625
0️⃣func test() {
572
626
foo()
573
627
}
574
628
""" ,
575
629
] ,
576
- build: true
577
- )
578
-
579
- // Modify b.swift so that the locations from the index no longer match the in-memory document.
580
- let ( bUri, bPositions) = try ws. openDocument ( " b.swift " )
581
- ws. testClient. send (
582
- DidChangeTextDocumentNotification (
583
- textDocument: VersionedTextDocumentIdentifier ( bUri, version: 1 ) ,
584
- contentChanges: [ TextDocumentContentChangeEvent ( range: Range ( bPositions [ " 0️⃣ " ] ) , text: " \n " ) ]
585
- )
586
- )
587
-
588
- // We should notice that the locations from the index don't match the current state of b.swift and not include any
589
- // edits in b.swift
590
- let ( aUri, aPositions) = try ws. openDocument ( " a.swift " )
591
- let response = try await ws. testClient. send (
592
- RenameRequest ( textDocument: TextDocumentIdentifier ( aUri) , position: aPositions [ " 1️⃣ " ] , newName: " bar " )
593
- )
594
- let changes = try XCTUnwrap ( response? . changes)
595
- XCTAssertEqual (
596
- changes,
597
- [ aUri: [ TextEdit ( range: aPositions [ " 1️⃣ " ] ..< aPositions [ " 2️⃣ " ] , newText: " bar " ) ] ]
630
+ newName: " bar " ,
631
+ expected: [
632
+ " a.swift " : """
633
+ func bar() {}
634
+ """ ,
635
+ " b.swift " : """
636
+ func test() {
637
+ foo()
638
+ }
639
+ """ ,
640
+ ] ,
641
+ preRenameActions: { ws in
642
+ let ( bUri, bPositions) = try ws. openDocument ( " b.swift " )
643
+ ws. testClient. send (
644
+ DidChangeTextDocumentNotification (
645
+ textDocument: VersionedTextDocumentIdentifier ( bUri, version: 1 ) ,
646
+ contentChanges: [ TextDocumentContentChangeEvent ( range: Range ( bPositions [ " 0️⃣ " ] ) , text: " \n " ) ]
647
+ )
648
+ )
649
+ }
598
650
)
599
651
}
600
652
601
653
func testTryIndexLocationsDontMatchInMemoryLocationsByLineColumnButNotOffset( ) async throws {
602
- let ws = try await SwiftPMTestWorkspace (
654
+ try await assertMultiFileRename (
603
655
files: [
604
656
" a.swift " : """
605
- func 1️⃣foo2️⃣ () {}
657
+ func 1️⃣foo () {}
606
658
""" ,
607
659
" b.swift " : """
608
660
0️⃣func test() {
609
- 3️⃣foo4️⃣ ()
661
+ foo ()
610
662
}
611
663
""" ,
612
664
] ,
613
- build: true
614
- )
615
-
616
- // Modify b.swift so that the locations from the index no longer match the in-memory document based on offsets but
617
- // without introducing new lines so that line/column references are still correct
618
- let ( bUri, bPositions) = try ws. openDocument ( " b.swift " )
619
- ws. testClient. send (
620
- DidChangeTextDocumentNotification (
621
- textDocument: VersionedTextDocumentIdentifier ( bUri, version: 1 ) ,
622
- contentChanges: [
623
- TextDocumentContentChangeEvent ( range: Range ( bPositions [ " 0️⃣ " ] ) , text: " /* this is just a comment */" )
624
- ]
625
- )
626
- )
627
-
628
- // Index and find-syntactic-rename ranges work based on line/column so we should still be able to match the location
629
- // of `foo` after the edit.
630
- let ( aUri, aPositions) = try ws. openDocument ( " a.swift " )
631
- let response = try await ws. testClient. send (
632
- RenameRequest ( textDocument: TextDocumentIdentifier ( aUri) , position: aPositions [ " 1️⃣ " ] , newName: " bar " )
633
- )
634
- let changes = try XCTUnwrap ( response? . changes)
635
- XCTAssertEqual (
636
- changes,
637
- [
638
- aUri: [ TextEdit ( range: aPositions [ " 1️⃣ " ] ..< aPositions [ " 2️⃣ " ] , newText: " bar " ) ] ,
639
- bUri: [ TextEdit ( range: bPositions [ " 3️⃣ " ] ..< bPositions [ " 4️⃣ " ] , newText: " bar " ) ] ,
640
- ]
665
+ newName: " bar " ,
666
+ expected: [
667
+ " a.swift " : """
668
+ func bar() {}
669
+ """ ,
670
+ " b.swift " : """
671
+ func test() {
672
+ bar()
673
+ }
674
+ """ ,
675
+ ] ,
676
+ preRenameActions: { ws in
677
+ let ( bUri, bPositions) = try ws. openDocument ( " b.swift " )
678
+ ws. testClient. send (
679
+ DidChangeTextDocumentNotification (
680
+ textDocument: VersionedTextDocumentIdentifier ( bUri, version: 1 ) ,
681
+ contentChanges: [
682
+ TextDocumentContentChangeEvent ( range: Range ( bPositions [ " 0️⃣ " ] ) , text: " /* this is just a comment */" )
683
+ ]
684
+ )
685
+ )
686
+ }
641
687
)
642
688
}
643
689
}
0 commit comments