diff --git a/Release Notes/600.md b/Release Notes/600.md index 4d05a0f15c7..879485815ff 100644 --- a/Release Notes/600.md +++ b/Release Notes/600.md @@ -1,13 +1,13 @@ # Swift Syntax 600 Release Notes ## New APIs -- FixIt now has a new computed propery named edits - - Description: the edits represent the non-overlapping textual edits that need to be performed when the Fix-It is applied. +- FixIt now has a new computed property named `edits` + - Description: the `edits` represent the non-overlapping textual `edits` that need to be performed when the Fix-It is applied. - Issue: https://github.com/apple/sourcekit-lsp/issues/909 - Pull Request: https://github.com/apple/swift-syntax/pull/2314 -- SourceEdit - - Description: SourceEdit has been moved from SwiftRefactor to SwiftSyntax +- `SourceEdit` + - Description: `SourceEdit` has been moved from SwiftRefactor to SwiftSyntax - Issue: https://github.com/apple/sourcekit-lsp/issues/909 - Pull Request: https://github.com/apple/swift-syntax/pull/2314 @@ -58,6 +58,10 @@ - Description: Uses heuristics to infer the indentation width used in a syntax tree. - Pull Request: https://github.com/apple/swift-syntax/pull/2514 +- `IncrementalEdit` stores replacement text + - Description: `IncrementalEdit` used to store the range that was replaced and the length of the replacement but not the replacement bytes by itself. `IncrementalEdit` now has a `replacement` property that contains the replacement bytes. + - Pull Request: https://github.com/apple/swift-syntax/pull/2527 + ## API Behavior Changes ## Deprecations diff --git a/Sources/SwiftParser/IncrementalParseTransition.swift b/Sources/SwiftParser/IncrementalParseTransition.swift index d7fba721036..3134fe2a7c3 100644 --- a/Sources/SwiftParser/IncrementalParseTransition.swift +++ b/Sources/SwiftParser/IncrementalParseTransition.swift @@ -347,17 +347,22 @@ public struct ConcurrentEdits: Sendable { if existingEdit.replacementRange.intersectsOrTouches(editToAdd.range) { let intersectionLength = existingEdit.replacementRange.intersected(editToAdd.range).length + let replacement: [UInt8] + replacement = + existingEdit.replacement.prefix(max(0, editToAdd.offset - existingEdit.replacementRange.offset)) + + editToAdd.replacement + + existingEdit.replacement.suffix(max(0, existingEdit.replacementRange.endOffset - editToAdd.endOffset)) editToAdd = IncrementalEdit( offset: Swift.min(existingEdit.offset, editToAdd.offset), length: existingEdit.length + editToAdd.length - intersectionLength, - replacementLength: existingEdit.replacementLength + editToAdd.replacementLength - intersectionLength + replacement: replacement ) editIndicesMergedWithNewEdit.append(index) } else if existingEdit.offset < editToAdd.endOffset { editToAdd = IncrementalEdit( offset: editToAdd.offset - existingEdit.replacementLength + existingEdit.length, length: editToAdd.length, - replacementLength: editToAdd.replacementLength + replacement: editToAdd.replacement ) } } diff --git a/Sources/SwiftSyntax/Utils.swift b/Sources/SwiftSyntax/Utils.swift index eebf805b5ab..b9ac1e85a7c 100644 --- a/Sources/SwiftSyntax/Utils.swift +++ b/Sources/SwiftSyntax/Utils.swift @@ -50,8 +50,12 @@ public struct ByteSourceRange: Equatable, Sendable { public struct IncrementalEdit: Equatable, Sendable { /// The byte range of the original source buffer that the edit applies to. public let range: ByteSourceRange + + /// The UTF-8 bytes that should be inserted as part of the edit + public let replacement: [UInt8] + /// The length of the edit replacement in UTF8 bytes. - public let replacementLength: Int + public var replacementLength: Int { replacement.count } public var offset: Int { return range.offset } @@ -64,14 +68,25 @@ public struct IncrementalEdit: Equatable, Sendable { return ByteSourceRange(offset: offset, length: replacementLength) } + @available(*, deprecated, message: "Use IncrementalEdit(range:replacement:) instead") public init(range: ByteSourceRange, replacementLength: Int) { self.range = range - self.replacementLength = replacementLength + self.replacement = Array(repeating: UInt8(ascii: " "), count: replacementLength) } + @available(*, deprecated, message: "Use IncrementalEdit(offset:length:replacement:) instead") public init(offset: Int, length: Int, replacementLength: Int) { self.range = ByteSourceRange(offset: offset, length: length) - self.replacementLength = replacementLength + self.replacement = Array(repeating: UInt8(ascii: " "), count: replacementLength) + } + + public init(offset: Int, length: Int, replacement: [UInt8]) { + self.range = ByteSourceRange(offset: offset, length: length) + self.replacement = replacement + } + + public init(offset: Int, length: Int, replacement: String) { + self.init(offset: offset, length: length, replacement: Array(replacement.utf8)) } public func intersectsOrTouchesRange(_ other: ByteSourceRange) -> Bool { diff --git a/Sources/_SwiftSyntaxTestSupport/IncrementalParseTestUtils.swift b/Sources/_SwiftSyntaxTestSupport/IncrementalParseTestUtils.swift index 5742f7463ab..5591aa15b92 100644 --- a/Sources/_SwiftSyntaxTestSupport/IncrementalParseTestUtils.swift +++ b/Sources/_SwiftSyntaxTestSupport/IncrementalParseTestUtils.swift @@ -182,10 +182,7 @@ public func extractEditsAndSources(from source: String) -> (edits: ConcurrentEdi from: source.index(after: startIndex), to: separateIndex ), - replacementLength: source.utf8.distance( - from: source.index(after: separateIndex), - to: endIndex - ) + replacement: Array(source.utf8[source.index(after: separateIndex).. (edits: ConcurrentEdi public func applyEdits( _ edits: [IncrementalEdit], concurrent: Bool, - to testString: String, - replacementChar: Character = "?" + to testString: String ) -> String { - guard let replacementAscii = replacementChar.asciiValue else { - fatalError("replacementChar must be an ASCII character") - } var edits = edits if concurrent { XCTAssert(ConcurrentEdits._isValidConcurrentEditArray(edits)) @@ -232,7 +225,7 @@ public func applyEdits( for edit in edits { assert(edit.endOffset <= bytes.count) bytes.removeSubrange(edit.offset..