Fix off-by-one errors in format range

This commit is contained in:
Nick Lockwood
2025-07-29 15:51:25 +01:00
committed by Cal Stephens
parent 04743b591e
commit 5d8d83ef2f
3 changed files with 74 additions and 36 deletions
+7 -4
View File
@@ -431,8 +431,9 @@ public func tokenRange(forLineRange lineRange: ClosedRange<Int>, in tokens: [Tok
let startOffset = SourceOffset(line: lineRange.lowerBound, column: 0)
let endOffset = SourceOffset(line: lineRange.upperBound + 1, column: 0)
// NOTE: tab width is not relevant for line-based offsets
return tokenIndex(for: startOffset, in: tokens, tabWidth: 1)
..< tokenIndex(for: endOffset, in: tokens, tabWidth: 1)
let tokenStart = max(0, tokenIndex(for: startOffset, in: tokens, tabWidth: 1) - 1)
let tokenEnd = max(tokenStart, tokenIndex(for: endOffset, in: tokens, tabWidth: 1) - 1)
return tokenStart ..< tokenEnd
}
/// Get new offset for an original offset (before formatting)
@@ -499,13 +500,14 @@ public func applyRules(
to originalTokens: [Token],
with options: FormatOptions,
trackChanges: Bool,
range: Range<Int>?,
range originalRange: Range<Int>?,
maxIterations: Int = 10
) throws -> (tokens: [Token], changes: [Formatter.Change]) {
precondition(maxIterations > 1)
let originalRules = originalRules.sorted()
var tokens = originalTokens
var range = originalRange
// Ensure rule names have been set
if originalRules.first?.name == FormatRule.unnamedRule {
@@ -637,8 +639,9 @@ public func applyRules(
return (tokens, changes)
}
// Update tokens
// Update tokens and range
tokens = newTokens
range = formatter.range
// Remove rules that should only be run once
if iteration == 0 {
+66 -4
View File
@@ -519,7 +519,7 @@ class FormatterTests: XCTestCase {
])
}
// MARK: range
// MARK: Format range
func testCodeOutsideRangeNotFormatted() throws {
let input = tokenize("""
@@ -529,9 +529,14 @@ class FormatterTests: XCTestCase {
}
""")
for range in [0 ..< 2, 5 ..< 7, 14 ..< 16, 17 ..< 19] {
XCTAssertEqual(try format(input,
rules: FormatRules.all,
range: range).tokens, input)
XCTAssertEqual(try sourceCode(
for: format(
input,
rules: FormatRules.all,
range: range
).tokens
),
sourceCode(for: input), "range \(range)")
}
let output1 = tokenize("""
func foo () {
@@ -556,6 +561,63 @@ class FormatterTests: XCTestCase {
).tokens), output2)
}
// MARK: format line range
func testFormattingRange() {
let input = """
let badlySpaced1:Int = 5
let badlySpaced2:Int=5
let badlySpaced3 : Int = 5
"""
let output = """
let badlySpaced1:Int = 5
let badlySpaced2: Int = 5
let badlySpaced3 : Int = 5
"""
XCTAssertEqual(try format(input, lineRange: 2 ... 2).output, output)
}
func testFormattingRange2() {
let input = """
enum ImagesToShow {
case none
case mentioned
case all
}
"""
let output = """
enum ImagesToShow
{
case none
case mentioned
case all
}
"""
let options = FormatOptions(allmanBraces: true)
XCTAssertEqual(try format(input, options: options, lineRange: 1 ... 2).output, output)
}
func testFormattingRangeNoCrash() {
let input = """
func foo() {
if bar {
print( "foo")
}
}
"""
let output = """
func foo() {
if bar {
print("foo")
}
}
"""
let inputTokens = tokenize(input), outputTokens = tokenize(output)
XCTAssertEqual(tokenRange(forLineRange: 3 ... 4, in: inputTokens), 14 ..< 26)
XCTAssertEqual(tokenRange(forLineRange: 3 ... 4, in: outputTokens), 14 ..< 25)
XCTAssertEqual(try format(input, lineRange: 3 ... 4).output, output)
}
// MARK: endOfScope
func testEndOfScopeInSwitch() throws {
+1 -28
View File
@@ -155,33 +155,6 @@ class SwiftFormatTests: XCTestCase {
XCTAssertEqual(try format(input, rules: [], options: options).output, input)
}
// MARK: format line range
func testFormattingRange() {
let input = """
let badlySpaced1:Int = 5
let badlySpaced2:Int=5
let badlySpaced3 : Int = 5
"""
let output = """
let badlySpaced1:Int = 5
let badlySpaced2: Int = 5
let badlySpaced3 : Int = 5
"""
XCTAssertEqual(try format(input, lineRange: 2 ... 2).output, output)
}
func testFormattingRangeNoCrash() {
let input = """
func foo() {
if bar {
print( "foo")
}
}
"""
XCTAssertNoThrow(try format(input, lineRange: 3 ... 4))
}
// MARK: conflict markers
func testFormattingFailsForConflict() {
@@ -264,7 +237,7 @@ class SwiftFormatTests: XCTestCase {
func testTokenRange() {
let tokens = tokenize("// a comment\n let foo = 5\n")
XCTAssertEqual(tokenRange(forLineRange: 1 ... 1, in: tokens), 0 ..< 4)
XCTAssertEqual(tokenRange(forLineRange: 1 ... 1, in: tokens), 0 ..< 3)
}
// MARK: newOffset