Files
sourcekit-lsp/Tests/SKUtilitiesTests/LineTableTests.swift
2025-10-31 14:11:11 -07:00

225 lines
7.9 KiB
Swift

//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
import SKLogging
import SKTestSupport
import SKUtilities
import SwiftExtensions
import TSCBasic
import XCTest
fileprivate extension LineTable {
var lines: [Substring] {
(0..<lineCount).map { line(at: $0)! }
}
}
final class LineTableTests: SourceKitLSPTestCase {
func checkLines(_ string: String, _ expected: [String], file: StaticString = #filePath, line: UInt = #line) {
let table = LineTable(string)
XCTAssertEqual(table.lines.map(String.init), expected, file: file, line: line)
}
func checkOffsets(_ string: String, _ expected: [Int], file: StaticString = #filePath, line: UInt = #line) {
let table = LineTable(string)
XCTAssertEqual(
table.lines.map { string.utf8.distance(from: string.startIndex, to: $0.startIndex) },
expected,
file: file,
line: line
)
}
func testLineTable() {
checkLines("", [""])
checkLines("a", ["a"])
checkLines("abc", ["abc"])
checkLines("abc\n", ["abc\n", ""])
checkLines("\n", ["\n", ""])
checkLines("\n\n", ["\n", "\n", ""])
checkLines("\r\n", ["\r\n", ""])
checkLines("\r\r", ["\r", "\r", ""])
checkLines("a\nb", ["a\n", "b"])
checkLines("a\nb\n", ["a\n", "b\n", ""])
checkLines("a\nb\nccccc", ["a\n", "b\n", "ccccc"])
checkLines("\u{1}\nb", ["\u{1}\n", "b"])
checkLines("\u{10000}\nb", ["\u{10000}\n", "b"])
checkLines("\n\u{10000}", ["\n", "\u{10000}"])
checkLines("\n\u{10000}b", ["\n", "\u{10000}b"])
checkLines("\n\u{10000}\nc", ["\n", "\u{10000}\n", "c"])
checkLines("\n\u{10000}b\nc", ["\n", "\u{10000}b\n", "c"])
checkOffsets("a", [0])
checkOffsets("abc", [0])
checkOffsets("\n", [0, 1])
checkOffsets("\n\n", [0, 1, 2])
checkOffsets("\n\r\n\n", [0, 1, 3, 4])
checkOffsets("a\nb", [0, 2])
checkOffsets("a\nb\n", [0, 2, 4])
checkOffsets("a\nbbb\nccccc", [0, 2, 6])
checkOffsets("a\nbbb\nccccc", [0, 2, 6])
checkOffsets("\u{1}\nb", [0, 2])
checkOffsets("\u{100}\nb", [0, 3])
checkOffsets("\u{1000}\nb", [0, 4])
checkOffsets("\u{10000}\nb", [0, 5])
checkOffsets("\n\u{10000}", [0, 1])
checkOffsets("\n\u{10000}b", [0, 1])
checkOffsets("\n\u{10000}b\nc", [0, 1, 7])
XCTAssertEqual(LineTable("").line(at: 0), "")
XCTAssertEqual(LineTable("\n").line(at: 1), "")
}
func checkLineAndColumns(
_ table: LineTable,
_ utf8Offset: Int,
_ expected: (line: Int, utf16Column: Int),
file: StaticString = #filePath,
line: UInt = #line
) {
let actual = table.lineAndUTF16ColumnOf(utf8Offset: utf8Offset)
XCTAssert(actual == expected, "\(actual) != \(expected)", file: file, line: line)
}
func checkUTF8OffsetOf(
_ table: LineTable,
_ query: (line: Int, utf16Column: Int),
_ expected: Int,
file: StaticString = #filePath,
line: UInt = #line
) {
XCTAssertEqual(
table.utf8OffsetOf(line: query.line, utf16Column: query.utf16Column),
expected,
file: file,
line: line
)
}
func checkUTF16ColumnAt(
_ table: LineTable,
_ query: (line: Int, utf8Column: Int),
_ expected: Int,
file: StaticString = #filePath,
line: UInt = #line
) {
XCTAssertEqual(
table.utf16ColumnAt(line: query.line, utf8Column: query.utf8Column),
expected,
file: file,
line: line
)
}
func testLineTableLinePositionTranslation() {
let t1 = LineTable(
"""
0123
5678
abcd
"""
)
checkLineAndColumns(t1, 0, (line: 0, utf16Column: 0))
checkLineAndColumns(t1, 2, (line: 0, utf16Column: 2))
checkLineAndColumns(t1, 4, (line: 0, utf16Column: 4))
checkLineAndColumns(t1, 5, (line: 1, utf16Column: 0))
checkLineAndColumns(t1, 9, (line: 1, utf16Column: 4))
checkLineAndColumns(t1, 10, (line: 2, utf16Column: 0))
checkLineAndColumns(t1, 14, (line: 2, utf16Column: 4))
checkLineAndColumns(t1, 15, (line: 2, utf16Column: 4))
checkUTF8OffsetOf(t1, (line: 0, utf16Column: 0), 0)
checkUTF8OffsetOf(t1, (line: 0, utf16Column: 2), 2)
checkUTF8OffsetOf(t1, (line: 0, utf16Column: 4), 4)
checkUTF8OffsetOf(t1, (line: 0, utf16Column: 5), 5)
checkUTF8OffsetOf(t1, (line: 0, utf16Column: 6), 5) // Recovers to end of line 0
checkUTF8OffsetOf(t1, (line: 1, utf16Column: 0), 5)
checkUTF8OffsetOf(t1, (line: 1, utf16Column: 4), 9)
checkUTF8OffsetOf(t1, (line: 2, utf16Column: 0), 10)
checkUTF8OffsetOf(t1, (line: 2, utf16Column: 4), 14)
checkUTF8OffsetOf(t1, (line: 2, utf16Column: 5), 14) // Recovers to end of line 2
checkUTF8OffsetOf(t1, (line: 3, utf16Column: 0), 14) // Recovers to end of source file
checkUTF16ColumnAt(t1, (line: 0, utf8Column: 4), 4)
checkUTF16ColumnAt(t1, (line: 0, utf8Column: 5), 5)
checkUTF16ColumnAt(t1, (line: 0, utf8Column: 6), 5) // Recovers to end of line 0
checkUTF16ColumnAt(t1, (line: 2, utf8Column: 0), 0)
checkUTF16ColumnAt(t1, (line: 3, utf8Column: 0), 0) // Line out of bounds, so keeps column
let t2 = LineTable(
"""
こんにちは
안녕하세요
\u{1F600}\u{1F648}
"""
)
checkLineAndColumns(t2, 0, (line: 0, utf16Column: 0))
checkLineAndColumns(t2, 15, (line: 0, utf16Column: 5))
checkLineAndColumns(t2, 19, (line: 1, utf16Column: 1))
checkLineAndColumns(t2, 32, (line: 2, utf16Column: 0))
checkLineAndColumns(t2, 36, (line: 2, utf16Column: 2))
checkLineAndColumns(t2, 40, (line: 2, utf16Column: 4))
checkUTF8OffsetOf(t2, (line: 0, utf16Column: 0), 0)
checkUTF8OffsetOf(t2, (line: 0, utf16Column: 5), 15)
checkUTF8OffsetOf(t2, (line: 0, utf16Column: 6), 16)
checkUTF8OffsetOf(t2, (line: 1, utf16Column: 1), 19)
checkUTF8OffsetOf(t2, (line: 1, utf16Column: 6), 32)
checkUTF8OffsetOf(t2, (line: 1, utf16Column: 7), 32) // Recovers to end of line 1
checkUTF8OffsetOf(t2, (line: 2, utf16Column: 0), 32)
checkUTF8OffsetOf(t2, (line: 2, utf16Column: 2), 36)
checkUTF8OffsetOf(t2, (line: 2, utf16Column: 4), 40)
checkUTF8OffsetOf(t2, (line: 2, utf16Column: 5), 40) // Recovers to end of line 2
checkUTF16ColumnAt(t2, (line: 0, utf8Column: 3), 1)
checkUTF16ColumnAt(t2, (line: 0, utf8Column: 15), 5)
checkUTF16ColumnAt(t2, (line: 2, utf8Column: 4), 2)
}
func testLineTableEditing() {
var t = LineTable("")
t.replace(fromLine: 0, utf16Offset: 0, utf16Length: 0, with: "a")
XCTAssertEqual(t, LineTable("a"))
t.replace(fromLine: 0, utf16Offset: 0, utf16Length: 0, with: "cb")
XCTAssertEqual(t, LineTable("cba"))
t.replace(fromLine: 0, utf16Offset: 1, utf16Length: 1, with: "d")
XCTAssertEqual(t, LineTable("cda"))
t.replace(fromLine: 0, utf16Offset: 3, utf16Length: 0, with: "e")
XCTAssertEqual(t, LineTable("cdae"))
t.replace(fromLine: 0, utf16Offset: 3, utf16Length: 1, with: "")
XCTAssertEqual(t, LineTable("cda"))
t = LineTable("a")
t.replace(fromLine: 0, utf16Offset: 0, utf16Length: 0, with: "\n")
XCTAssertEqual(t, LineTable("\na"))
t.replace(fromLine: 1, utf16Offset: 0, utf16Length: 0, with: "\n")
XCTAssertEqual(t, LineTable("\n\na"))
t.replace(fromLine: 2, utf16Offset: 1, utf16Length: 0, with: "\n")
XCTAssertEqual(t, LineTable("\n\na\n"))
t = LineTable(
"""
abcd
efgh
"""
)
t.replace(fromLine: 0, utf16Offset: 2, toLine: 1, utf16Offset: 1, with: "x")
XCTAssertEqual(t, LineTable("abxfgh"))
t.replace(fromLine: 0, utf16Offset: 2, toLine: 0, utf16Offset: 4, with: "p\nq\n")
XCTAssertEqual(t, LineTable("abp\nq\ngh"))
}
}