├── .gitignore ├── LICENSE ├── Package.swift ├── README.md ├── Sources └── StringIndex │ ├── OffsetIndex.swift │ ├── StringIndex.swift │ └── Subscripts.swift ├── StringIndex.podspec └── Tests ├── LinuxMain.swift └── StringIndexTests ├── StringIndexTests.swift └── XCTestManifests.swift /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## User settings 6 | xcuserdata/ 7 | 8 | ## Obj-C/Swift specific 9 | *.hmap 10 | 11 | ## App packaging 12 | *.ipa 13 | *.dSYM.zip 14 | *.dSYM 15 | 16 | ## Playgrounds 17 | timeline.xctimeline 18 | playground.xcworkspace 19 | 20 | # Swift Package Manager 21 | # 22 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 23 | # Packages/ 24 | # Package.pins 25 | Package.resolved 26 | *.xcodeproj 27 | # 28 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 29 | # hence it is not needed unless you have added a package configuration file to your project 30 | .swiftpm 31 | 32 | .build/ 33 | 34 | # CocoaPods 35 | # 36 | # We recommend against adding the Pods directory to your .gitignore. However 37 | # you should judge for yourself, the pros and cons are mentioned at: 38 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 39 | # 40 | # Pods/ 41 | # 42 | # Add this line if you want to avoid checking in source code from the Xcode workspace 43 | # *.xcworkspace 44 | 45 | # Carthage 46 | # 47 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 48 | # Carthage/Checkouts 49 | 50 | Carthage/Build/ 51 | 52 | # fastlane 53 | # 54 | # It is recommended to not store the screenshots in the git repo. 55 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 56 | # For more information about the recommended setup visit: 57 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 58 | 59 | fastlane/report.xml 60 | fastlane/Preview.html 61 | fastlane/screenshots/**/*.png 62 | fastlane/test_output 63 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 John Holdsworth 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.1 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "StringIndex", 8 | products: [ 9 | // Products define the executables and libraries produced by a package, and make them visible to other packages. 10 | .library( 11 | name: "StringIndex", 12 | targets: ["StringIndex"]), 13 | ], 14 | dependencies: [ 15 | // Dependencies declare other packages that this package depends on. 16 | // .package(url: /* package url */, from: "1.0.0"), 17 | ], 18 | targets: [ 19 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 20 | // Targets can depend on other targets in this package, and on products in packages which this package depends on. 21 | .target( 22 | name: "StringIndex", 23 | dependencies: []), 24 | .testTarget( 25 | name: "StringIndexTests", 26 | dependencies: ["StringIndex"]), 27 | ] 28 | ) 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## StringIndex - Reasonable indexing into Swift Strings 2 | 3 | An experimental package to explore what can be done about Swift's 4 | dystopian string indexing. At the moment, you have to perform this 5 | memorable dance to get the 5th character of a `String`: 6 | 7 | ``` 8 | let fifthChar: Character = str[str.index(str.startIndex, offsetBy: 4)] 9 | ``` 10 | This package defines addition, and subtraction operators for the 11 | `String.Index` type returning a temporary enum which conveys 12 | the offset and index to subscript operators on `StringProtocol` which 13 | advances by the offset lazilly (when it knows the String being indexed). 14 | The result of this is you can now get the same result by typing: 15 | 16 | ``` 17 | let fifthChar: Character = str[.start+4] 18 | ``` 19 | There are also range operators and subscripts defined so you can 20 | use the following to remove the leading and trailing characters of 21 | a string for example: 22 | 23 | ``` 24 | let trimmed: Substring = str[.start+1 ..< .end-1] 25 | ``` 26 | Or you can search in a `String` for another `String` and 27 | use the index of the start or the end of the match: 28 | 29 | ``` 30 | let firstWord: Substring = str[..<(.first(of:" "))] 31 | let lastWord: Substring = str[(.last(of: " ", end: true))...] 32 | ``` 33 | You can search for regular expression patterns: 34 | 35 | ``` 36 | let firstWord: Substring = str[..<(.first(of:#"\w+"#, regex: true, end: true))] 37 | let lastWord: Substring = str[(.last(of: #"\w+"#, regex: true))...] 38 | ``` 39 | Movements around the string can be chained together using the `+` opertator: 40 | 41 | ``` 42 | let firstTwoWords = str[..<(.first(of:#"\w+"#, regex: true, end: true) + 43 | .first(of:#"\w+"#, regex: true, end: true))] 44 | ``` 45 | To realise a String.Index from these expressions use the 46 | index(of:) method on String from the package. 47 | 48 | ``` 49 | XCTAssertEqual(str.index(of: .start), str.startIndex) 50 | XCTAssertEqual(str.index(of: .first(of: " ")), str.firstIndex(of: " ")) 51 | ``` 52 | All subscript operators have setters defined so you can modify 53 | string contents. There are also subscripts prefixed by the label 54 | `safe:` that can return nil if offseting results in an invalid index. 55 | 56 | ``` 57 | XCTAssertNil(str[safe: .start-1]) 58 | XCTAssertNil(str[safe: .end+1]) 59 | ``` 60 | Attempting to assign to an invalid index is still a fatal error. 61 | Have a look through the tests to see what else you can do. 62 | 63 | $Date: 2025/01/07 $ 64 | -------------------------------------------------------------------------------- /Sources/StringIndex/OffsetIndex.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OffsetIndex.swift 3 | // StringIndex 4 | // 5 | // Created by John Holdsworth on 17/12/2024. 6 | // 7 | // A few operators simplifying offsettting a String index 8 | // 9 | // Repo: https://github.com/johnno1962/StringIndex.git 10 | // 11 | // $Id: //depot/StringIndex/Sources/StringIndex/OffsetIndex.swift#4 $ 12 | // 13 | 14 | import Foundation 15 | 16 | extension String { 17 | /// Represents an index to be offset 18 | public enum OffsetImpl: Comparable { 19 | 20 | case offsetIndex(index: IndexType?, offset: Int), start, end, 21 | first(of: String, regex: Bool = false, end: Bool = false), 22 | last(of: String, regex: Bool = false, end: Bool = false) 23 | indirect case either(_ index: OffsetImpl, or: OffsetImpl), 24 | // can chain either an OffsetIndex or an integer offset 25 | chained(previous: OffsetImpl, next: OffsetImpl?, offset: Int) 26 | /// Required by Comparable to check when creating ranges 27 | public static func < (lhs: OffsetImpl, rhs: OffsetImpl) -> Bool { 28 | return false // slight cheat here as we don't know the string 29 | } 30 | 31 | // Chaining offsets in expressions 32 | public static func + (index: OffsetImpl, offset: Int) -> OffsetImpl { 33 | return .chained(previous: index, next: nil, offset: offset) 34 | } 35 | public static func - (index: OffsetImpl, offset: Int) -> OffsetImpl { 36 | return index + -offset 37 | } 38 | public static func + (index: OffsetImpl, 39 | offset: OffsetImpl) -> OffsetImpl { 40 | return .chained(previous: index, next: offset, offset: 0) 41 | } 42 | public static func || (either: OffsetImpl, 43 | or: OffsetImpl) -> OffsetImpl { 44 | return .either(either, or: or) 45 | } 46 | 47 | /// realise index from OffsetIndex 48 | public func index(in string: IndexType.StringType, from: IndexType? = nil) -> IndexType? { 49 | switch self { 50 | case .offsetIndex(let index, let offset): 51 | guard let index = index else { return nil } 52 | return index.safeIndex(offsetBy: offset, in: string) 53 | 54 | // Public interface 55 | case .start: 56 | return IndexType.start(in: string) 57 | case .end: 58 | return IndexType.end(in: string) 59 | case .first(let target, let regex, let end): 60 | return locate(in: string, target: target, from: from, 61 | last: false, regex: regex, end: end) 62 | case .last(let target, let regex, let end): 63 | return locate(in: string, target: target, from: from, 64 | last: true, regex: regex, end: end) 65 | case .either(let first, let second): 66 | return first.index(in: string) ?? second.index(in: string) 67 | 68 | case .chained(let previous, let next, let offset): 69 | guard let from = previous.index(in: string, from: from) else { return nil } 70 | return next != nil ? next!.index(in: string, from: from) : 71 | from.safeIndex(offsetBy: offset, in: string) 72 | } 73 | } 74 | public func locate(in string: IndexType.StringType, target: String, from: IndexType?, 75 | last: Bool, regex: Bool, end: Bool) -> IndexType? { 76 | let bounds = last ? 77 | IndexType.start(in: string)..<(from ?? IndexType.end(in: string)) : 78 | (from ?? IndexType.start(in: string)).. 19 | // Basic operators to offset String.Index when used in a subscript 20 | public static func + (index: Self, offset: Int) -> OffsetIndex { 21 | return OffsetIndex.offsetIndex(index: index, offset: offset) 22 | } 23 | public static func - (index: Self, offset: Int) -> OffsetIndex { 24 | return index + -offset 25 | } 26 | public static func + (index: Self, offset: OffsetIndex) 27 | -> OffsetIndex { 28 | return OffsetIndex.offsetIndex(index: index, offset: 0) + offset 29 | } 30 | 31 | // Mixed String.Index and OffsetIndex in range 32 | public static func ..< (lhs: OffsetIndex, rhs: Self) 33 | -> Range { 34 | return lhs ..< rhs+0 35 | } 36 | public static func ..< (lhs: Self, rhs: OffsetIndex) 37 | -> Range { 38 | return lhs+0 ..< rhs 39 | } 40 | } 41 | 42 | public protocol OffsetIndexable: Comparable { 43 | associatedtype OffsetType: Comparable 44 | associatedtype StringType 45 | static func nsRange(_ range: Range, 46 | in: String) -> NSRange 47 | static func myRange(from: Range, 48 | in: String) -> Range 49 | static func toRange(from: Range, 50 | in: String) -> Range? 51 | static func string(in: StringType) -> String 52 | static func start(in: StringType) -> Self 53 | static func end(in: StringType) -> Self 54 | func safeIndex(offsetBy: Int, in string: Self.StringType) -> Self? 55 | } 56 | 57 | extension String.Index: OffsetIndexable { 58 | public typealias OffsetType = String.OffsetImpl 59 | public typealias StringType = String 60 | public static func nsRange(_ range: Range, 61 | in string: String) -> NSRange { 62 | return NSRange(range, in: string) 63 | } 64 | public static func myRange(from: Range, 65 | in: String) -> Range { 66 | return from 67 | } 68 | public static func toRange(from: Range, 69 | in: String) -> Range? { 70 | return from 71 | } 72 | public static func string(in string: StringType) -> String { 73 | return string 74 | } 75 | public static func start(in string: StringType) -> Self { 76 | return string.startIndex 77 | } 78 | public static func end(in string: StringType) -> Self { 79 | return string.endIndex 80 | } 81 | /// nilable version of index(_ i: Self.Index, offsetBy: Int) 82 | public func safeIndex(offsetBy: Int, in string: Self.StringType) -> Self? { 83 | return string.index(self, offsetBy: offsetBy, limitedBy: 84 | offsetBy<0 ? string.startIndex : string.endIndex) 85 | } 86 | } 87 | 88 | extension Range where Bound == String.Index { 89 | public init?(_ range: Range, in string: S) { 90 | guard let lower = range.lowerBound.index(in: String(string)), 91 | let upper = range.upperBound.index(in: String(string)), 92 | lower <= upper else { 93 | return nil 94 | } 95 | self = lower ..< upper 96 | } 97 | } 98 | 99 | extension NSRange { 100 | public init?(_ range: Range, in string: S) { 101 | guard let lower = range.lowerBound.index(in: String(string)), 102 | let upper = range.upperBound.index(in: String(string)), 103 | lower <= upper else { 104 | return nil 105 | } 106 | self.init(lower ..< upper, in: string) 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /Sources/StringIndex/Subscripts.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Subscripts.swift 3 | // StringIndex 4 | // 5 | // Created by John Holdsworth on 17/12/2024. 6 | // 7 | // A few operators simplifying offsettting a String index 8 | // 9 | // Repo: https://github.com/johnno1962/StringIndex.git 10 | // 11 | // $Id: //depot/StringIndex/Sources/StringIndex/Subscripts.swift#5 $ 12 | // 13 | 14 | import Foundation 15 | 16 | extension StringProtocol { 17 | public typealias IndexType = String.Index 18 | public typealias OffsetIndex = IndexType.OffsetType 19 | public typealias OISubstring = String // Can/should? be Substring 20 | public typealias OOISubstring = OISubstring? // "safe:" prefixed subscripts 21 | 22 | public func index(of: OffsetIndex) -> IndexType? { 23 | return of.index(in: String(self)) 24 | } 25 | /// Subscripts on StringProtocol for OffsetIndex type 26 | public subscript (offset: OffsetIndex) -> Character { 27 | get { 28 | guard let result = self[safe: offset] else { 29 | fatalError("Invalid offset index \(offset), \(#function)") 30 | } 31 | return result 32 | } 33 | set (newValue) { 34 | guard let start = offset.index(in: String(self)) else { 35 | fatalError("Invalid offset index \(offset), \(#function)") 36 | } 37 | // Assigning Chacater to endIndex is an append. 38 | let end = start + (start < IndexType.end(in: String(self)) ? 1 : 0) 39 | self[start ..< end] = OISubstring(String(newValue)) 40 | } 41 | } 42 | 43 | // lhs ..< rhs operator 44 | public subscript (range: Range) -> OISubstring { 45 | get { 46 | guard let result = self[safe: range] else { 47 | fatalError("Invalid range of offset index \(range), \(#function)") 48 | } 49 | return result 50 | } 51 | set (newValue) { 52 | guard let from = range.lowerBound.index(in: String(self)), 53 | let to = range.upperBound.index(in: String(self)) else { 54 | fatalError("Invalid range of offset index \(range), \(#function)") 55 | } 56 | let before = self[..) -> OISubstring { 62 | get { return self[.start ..< range.upperBound] } 63 | set (newValue) { self[.start ..< range.upperBound] = newValue } 64 | } 65 | // lhs... operator 66 | public subscript (range: PartialRangeFrom) -> OISubstring { 67 | get { return self[range.lowerBound ..< .end] } 68 | set (newValue) { self[range.lowerBound ..< .end] = newValue } 69 | } 70 | 71 | // ================================================================= 72 | // "safe" nil returning subscripts on StringProtocol for OffsetIndex 73 | // from: https://forums.swift.org/t/optional-safe-subscripting-for-arrays 74 | public subscript (safe offset: OffsetIndex) -> Character? { 75 | get { return offset.index(in: String(self)).flatMap { self[$0] } } 76 | set (newValue) { self[offset] = newValue! } 77 | } 78 | // lhs ..< rhs operator 79 | public subscript (safe range: Range) -> OOISubstring { 80 | get { 81 | guard let from = range.lowerBound.index(in: String(self)), 82 | let to = range.upperBound.index(in: String(self)), 83 | from <= to else { return nil } 84 | return OISubstring(self[from ..< to]) 85 | } 86 | set (newValue) { self[range] = newValue! } 87 | } 88 | // ..) -> OOISubstring { 90 | get { return self[safe: .start ..< range.upperBound] } 91 | set (newValue) { self[range] = newValue! } 92 | } 93 | // lhs... operator 94 | public subscript (safe range: PartialRangeFrom) -> OOISubstring { 95 | get { return self[safe: range.lowerBound ..< .end] } 96 | set (newValue) { self[range] = newValue! } 97 | } 98 | 99 | // ================================================================= 100 | // Misc. 101 | public mutating func replaceSubrange(_ bounds: Range, 102 | with newElements: C) where C : Collection, C.Element == Character { 103 | self[bounds] = OISubstring(newElements) 104 | } 105 | public mutating func insert(contentsOf newElements: S, at i: OffsetIndex) 106 | where S : Collection, S.Element == Character { 107 | replaceSubrange(i ..< i, with: newElements) 108 | } 109 | public mutating func insert(_ newElement: Character, at i: OffsetIndex) { 110 | insert(contentsOf: String(newElement), at: i) 111 | } 112 | } 113 | 114 | #if false // V. slow to compile 115 | public protocol OffsetIndexable { 116 | associatedtype IndexType: Offsetable 117 | func index(of: IndexType.OffsetType) -> IndexType? 118 | } 119 | 120 | extension String: OffsetIndexable { 121 | public func index(of: IndexType.OffsetType) -> IndexType? { 122 | return of.index(in: String(self)) 123 | } 124 | 125 | } 126 | extension Substring: OffsetIndexable { 127 | public typealias IndexType = String.IndexType 128 | public func index(of: IndexType.OffsetType) -> IndexType? { 129 | return of.index(in: String(self)) 130 | } 131 | } 132 | #endif 133 | -------------------------------------------------------------------------------- /StringIndex.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "StringIndex" 3 | s.version = "1.3.1" 4 | s.summary = "Simplified String indexing" 5 | s.homepage = "https://github.com/johnno1962/StringIndex" 6 | s.social_media_url = "https://twitter.com/Injection4Xcode" 7 | s.documentation_url = "https://github.com/johnno1962/StringIndex/blob/master/README.md" 8 | s.license = { :type => "MIT" } 9 | s.authors = { "johnno1962" => "stringindex@johnholdsworth.com" } 10 | 11 | s.osx.deployment_target = "10.10" 12 | s.ios.deployment_target = "10.0" 13 | s.source = { :git => "https://github.com/johnno1962/StringIndex.git", :tag => s.version } 14 | s.source_files = "Sources/StringIndex/*.swift" 15 | end 16 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | import StringIndexTests 4 | 5 | var tests = [XCTestCaseEntry]() 6 | tests += StringIndexTests.allTests() 7 | XCTMain(tests) 8 | -------------------------------------------------------------------------------- /Tests/StringIndexTests/StringIndexTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import Foundation 3 | @testable import StringIndex 4 | 5 | final class StringIndexTests: XCTestCase { 6 | func testExample() { 7 | // This is an example of a functional test case. 8 | // Use XCTAssert and related functions to verify 9 | // your tests produce the correct results. 10 | var str = "Hello, World!" 11 | str.insert("?", at: str.endIndex-1) 12 | XCTAssertEqual(str, "Hello, World?!") 13 | str[.first(of: "o")+1 + .first(of: "o")] = "a" 14 | XCTAssertEqual(str, "Hello, Warld?!") 15 | str[.start+1] = "o" 16 | XCTAssertEqual(str[..<(.first(of: " "))], "Hollo,") 17 | XCTAssertEqual(str[(str.endIndex-2)...], "?!") 18 | 19 | for i in 1...str.count { 20 | XCTAssertEqual(str[str.index(str.endIndex, offsetBy: -i)], 21 | str[str.endIndex-i]) 22 | } 23 | 24 | str.insert(".", at: .end+0+0) 25 | XCTAssertEqual(str, "Hollo, Warld?!.") 26 | 27 | XCTAssertEqual(str[.start+2 ..< .end-2], "llo, Warld?") 28 | XCTAssertEqual(str[..<(.first(of:" "))], "Hollo,") 29 | XCTAssertEqual(str[(.last(of: " ")+1)...], "Warld?!.") 30 | 31 | let fifthChar: Character = str[.start+4] 32 | let firstWord = str[..<(.first(of:" "))] 33 | let stripped = str[.start+1 ..< .end-1] 34 | let lastWord = str[(.last(of: " ")+1)...] 35 | 36 | XCTAssertEqual(fifthChar, "o") 37 | XCTAssertEqual(firstWord, "Hollo,") 38 | XCTAssertEqual(stripped, "ollo, Warld?!") 39 | XCTAssertEqual(lastWord, "Warld?!.") 40 | 41 | XCTAssertEqual(str.range(of: "l", 42 | range: Range(.first(of: "W") ..< .end, 43 | in: str)!)?.lowerBound, 44 | str.index(of:.last(of: "l"))) 45 | 46 | XCTAssertEqual(str.index(of: .either(.first(of: "z"), 47 | or: .first(of: "W"))), 48 | str.index(of: .first(of: "W"))) 49 | 50 | str[..<(.first(of:" "))] = "Hi," 51 | str[.last(of:"a")] = "o" 52 | XCTAssertEqual(str, "Hi, World?!.") 53 | XCTAssertEqual(str[(.last(of: " ")+1)...], "World?!.") 54 | 55 | XCTAssertEqual(str[.first(of: #"\w+"#, regex: true, end: false)], "H") 56 | XCTAssertEqual(str[.first(of: #"\w+"#, regex: true, end: true)], ",") 57 | XCTAssertEqual(str[.last(of: #"\w+"#, regex: true, end: false)], "W") 58 | XCTAssertEqual(str[.last(of: #"\w+"#, regex: true, end: true)], "?") 59 | 60 | XCTAssertEqual(str[.first(of: #"\w+"#, regex: true, end: true) ..< 61 | .last(of: #"\w+"#, regex: true)], ", ") 62 | 63 | XCTAssertEqual(str[..<(.first(of:#"\w+"#, regex: true, end: true) + 64 | .first(of:#"\w+"#, regex: true, end: true))], 65 | "Hi, World") 66 | 67 | XCTAssertNil(str[safe: .start-1]) 68 | XCTAssertNil(str[safe: .end+1]) 69 | XCTAssertNil(str[safe: .last(of: "🤠")]) 70 | XCTAssertNil(str[safe: ..<(.first(of: "z"))]) 71 | 72 | XCTAssertEqual(str.index(of: .start), str.startIndex) 73 | XCTAssertEqual(str.index(of: .first(of: " ")), str.firstIndex(of: " ")) 74 | 75 | str[.end] = "🤡" // append 76 | XCTAssertEqual(str, "Hi, World?!.🤡") 77 | } 78 | 79 | static var allTests = [ 80 | ("testExample", testExample), 81 | ] 82 | } 83 | -------------------------------------------------------------------------------- /Tests/StringIndexTests/XCTestManifests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | #if !canImport(ObjectiveC) 4 | public func allTests() -> [XCTestCaseEntry] { 5 | return [ 6 | testCase(StringIndexTests.allTests), 7 | ] 8 | } 9 | #endif 10 | --------------------------------------------------------------------------------