@@ -134,7 +134,9 @@ class FixItApplierApplyEditsTests: XCTestCase {
134134 . init( range: 3 ..< 7 , replacement: " cd " ) ,
135135 ] ,
136136 // The second edit is skipped.
137- possibleOutputs: [ " aboo = 1 " , " varcd = 1 " ]
137+ outputs: [
138+ . either( [ " aboo = 1 " , " varcd = 1 " ] )
139+ ]
138140 )
139141 }
140142
@@ -148,19 +150,36 @@ class FixItApplierApplyEditsTests: XCTestCase {
148150 . init( range: 0 ..< 5 , replacement: " _ " ) ,
149151 . init( range: 0 ..< 3 , replacement: " let " ) ,
150152 ] ,
151- possibleOutputs: [ " _ = 11 " , " let x = 11 " ]
153+ outputs: [
154+ . either( [ " _ = 11 " , " let x = 11 " ] )
155+ ]
152156 )
153157 }
154158
155159 func testMultipleOverlappingInsertions( ) {
160+ assertAppliedEdits (
161+ to: " x = 1 " ,
162+ edits: [
163+ . init( range: 1 ..< 1 , replacement: " y " ) ,
164+ . init( range: 1 ..< 1 , replacement: " z " ) ,
165+ ] ,
166+ outputs: [
167+ . either( [ " xyz = 1 " , " xzy = 1 " ] )
168+ ]
169+ )
170+
156171 assertAppliedEdits (
157172 to: " x = 1 " ,
158173 edits: [
159174 . init( range: 0 ..< 0 , replacement: " var " ) ,
160175 . init( range: 0 ..< 0 , replacement: " var " ) ,
176+ . init( range: 4 ..< 5 , replacement: " 2 " ) ,
161177 . init( range: 0 ..< 0 , replacement: " var " ) ,
162178 ] ,
163- output: " var var var x = 1 "
179+ outputs: [
180+ . output( " var var var x = 2 " , allowDuplicateInsertions: true ) ,
181+ . output( " var x = 2 " , allowDuplicateInsertions: false ) ,
182+ ]
164183 )
165184 }
166185
@@ -182,43 +201,110 @@ class FixItApplierApplyEditsTests: XCTestCase {
182201 . init( range: 2 ..< 2 , replacement: " a " ) , // Insertion
183202 ] ,
184203 // FIXME: This behavior where these edits are not considered overlapping doesn't feel desirable
185- possibleOutputs: [ " _x = 1 " , " _ a= 1 " ]
204+ outputs: [
205+ . either( [ " _x = 1 " , " _ a= 1 " ] )
206+ ]
186207 )
187208 }
188209}
189210
190- /// Asserts that at least one element in `possibleOutputs` matches the result
191- /// of applying an array of edits to `input`, for all permutations of `edits`.
211+ private enum Output {
212+ case output( String , allowDuplicateInsertions: Bool ? = nil )
213+ case either( [ String ] , allowDuplicateInsertions: Bool ? = nil )
214+
215+ var possibleOutputs : [ String ] {
216+ switch self {
217+ case . output( let output, _) :
218+ return [ output]
219+ case . either( let outputs, _) :
220+ return outputs
221+ }
222+ }
223+
224+ var allowDuplicateInsertions : Bool ? {
225+ switch self {
226+ case . output( _, let allowDuplicateInsertions) ,
227+ . either( _, let allowDuplicateInsertions) :
228+ return allowDuplicateInsertions
229+ }
230+ }
231+ }
232+
233+ /// Asserts that the given outputs match the result of applying an array of
234+ /// edits to `input`, for all permutations of `edits`.
192235private func assertAppliedEdits(
193236 to tree: SourceFileSyntax ,
194237 edits: [ SourceEdit ] ,
195- possibleOutputs : [ String ]
238+ outputs : [ Output ]
196239) {
197- precondition ( !possibleOutputs. isEmpty)
198-
199- var indices = Array ( edits. indices)
200- while true {
201- let result = FixItApplier . apply ( edits: indices. map { edits [ $0] } , to: tree)
202- guard possibleOutputs. contains ( result) else {
203- XCTFail ( " \" \( result) \" is not equal to either of \( possibleOutputs) " )
204- return
205- }
240+ precondition ( !outputs. isEmpty)
241+
242+ NEXT_OUTPUT: for output in outputs {
243+ let allowDuplicateInsertionsValues =
244+ switch output. allowDuplicateInsertions {
245+ case . none:
246+ [ true , false ]
247+ case . some( let value) :
248+ [ value]
249+ }
250+
251+ let possibleOutputs = output. possibleOutputs
252+
253+ // Check this output against all permutations of edits.
254+ var indices = Array ( edits. indices)
255+ while true {
256+ let editsPermutation = indices. map { edits [ $0] }
257+
258+ for allowDuplicateInsertionsValue in allowDuplicateInsertionsValues {
259+ let actualOutput = FixItApplier . apply (
260+ edits: editsPermutation,
261+ to: tree,
262+ allowDuplicateInsertions: allowDuplicateInsertionsValue
263+ )
206264
207- let keepGoing = indices. nextPermutation ( )
208- guard keepGoing else {
209- break
265+ guard possibleOutputs. contains ( actualOutput) else {
266+ XCTFail (
267+ """
268+
269+ Actual output:
270+ \" \( actualOutput) \"
271+ Expected output:
272+ either of \( possibleOutputs)
273+ Edits:
274+ \( editsPermutation)
275+ allowDuplicateInsertions:
276+ \( allowDuplicateInsertionsValue)
277+ """
278+ )
279+
280+ // Fail once for all permutations to avoid excessive logging.
281+ continue NEXT_OUTPUT
282+ }
283+ }
284+
285+ let keepGoing = indices. nextPermutation ( )
286+ guard keepGoing else {
287+ break
288+ }
210289 }
211290 }
212291}
213292
214293/// Asserts that `output` matches the result of applying an array of edits to
215- /// `input`, for all permutations of `edits`.
294+ /// `input`, for all permutations of `edits` and for `allowDuplicateInsertions`
295+ /// both `true` and `false`.
216296private func assertAppliedEdits(
217297 to tree: SourceFileSyntax ,
218298 edits: [ SourceEdit ] ,
219299 output: String
220300) {
221- assertAppliedEdits ( to: tree, edits: edits, possibleOutputs: [ output] )
301+ assertAppliedEdits (
302+ to: tree,
303+ edits: edits,
304+ outputs: [
305+ . output( output, allowDuplicateInsertions: nil )
306+ ]
307+ )
222308}
223309
224310// Grabbed from https://github.com/apple/swift-algorithms/blob/main/Sources/Algorithms/Permutations.swift
0 commit comments