Skip to content

Commit 3cf162e

Browse files
authored
Expose ABI.EncodedSourceLocation as tools SPI. (#1589)
Follow-up to #1587. Exposes `ABI.EncodedSourceLocation` as SPI along with some conversion helpers to go to/from a `SourceLocation` instance. A value of this type is associated with many events in the event stream. ### Checklist: - [x] Code and documentation should follow the style of the [Style Guide](https://github.com/apple/swift-testing/blob/main/Documentation/StyleGuide.md). - [x] If public symbols are renamed or modified, DocC references should be updated.
1 parent 3f4f1df commit 3cf162e

File tree

6 files changed

+106
-39
lines changed

6 files changed

+106
-39
lines changed

Sources/Testing/ABI/Encoded/ABI.EncodedEvent.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,8 @@ extension ABI {
9696
///
9797
/// - Warning: Source locations at this level of the JSON schema are not yet
9898
/// part of said JSON schema.
99-
var _sourceLocation: EncodedSourceLocation<V>?
99+
@_spi(Experimental)
100+
public var _sourceLocation: EncodedSourceLocation<V>?
100101

101102
/// The iteration of the `testID` being executed.
102103
///

Sources/Testing/ABI/Encoded/ABI.EncodedIssue.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ extension ABI {
4848
var isKnown: Bool
4949

5050
/// The location in source where this issue occurred, if available.
51-
var sourceLocation: EncodedSourceLocation<V>?
51+
public var sourceLocation: EncodedSourceLocation<V>?
5252

5353
/// The backtrace where this issue occurred, if available.
5454
///

Sources/Testing/ABI/Encoded/ABI.EncodedSourceLocation.swift

Lines changed: 85 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -12,56 +12,107 @@ extension ABI {
1212
/// A type implementing the JSON encoding of ``SourceLocation`` for the ABI
1313
/// entry point and event stream output.
1414
///
15-
/// This type is not part of the public interface of the testing library. It
16-
/// assists in converting values to JSON; clients that consume this JSON are
17-
/// expected to write their own decoders.
18-
struct EncodedSourceLocation<V>: Sendable where V: ABI.Version {
19-
/// See ``SourceLocation`` for a discussion of these properties.
20-
var fileID: String?
21-
var filePath: String?
22-
var _filePath: String?
23-
var line: Int
24-
var column: Int
15+
/// You can use this type and its conformance to [`Codable`](https://developer.apple.com/documentation/swift/codable),
16+
/// when integrating the testing library with development tools. It is not
17+
/// part of the testing library's public interface.
18+
public struct EncodedSourceLocation<V>: Sendable where V: ABI.Version {
19+
/// The file ID of the source file.
20+
public var fileID: String?
2521

26-
init(encoding sourceLocation: borrowing SourceLocation) {
27-
fileID = sourceLocation.fileID
28-
29-
// When using the 6.3 schema, don't encode synthesized file IDs.
30-
if V.versionNumber >= ABI.v6_3.versionNumber,
31-
sourceLocation.moduleName == SourceLocation.synthesizedModuleName {
32-
fileID = nil
33-
}
34-
35-
// When using the 6.3 schema, we encode both "filePath" and "_filePath" to
36-
// ease migration for existing tools.
37-
if V.versionNumber >= ABI.v6_3.versionNumber {
38-
filePath = sourceLocation.filePath
22+
/// The file path of the source file.
23+
public var filePath: String? {
24+
get {
25+
_filePath_v6_3 ?? _filePath_v0
3926
}
40-
if V.versionNumber <= ABI.v6_3.versionNumber {
41-
_filePath = sourceLocation.filePath
27+
set {
28+
// When using the 6.3 schema, we encode both "filePath" and "_filePath"
29+
// to ease migration for existing tools.
30+
if V.versionNumber >= ABI.v6_3.versionNumber {
31+
_filePath_v6_3 = newValue
32+
}
33+
if V.versionNumber <= ABI.v6_3.versionNumber {
34+
_filePath_v0 = newValue
35+
}
4236
}
43-
44-
line = sourceLocation.line
45-
column = sourceLocation.column
4637
}
38+
39+
/// The line in the source file.
40+
public var line: Int = 1
41+
42+
/// The column in the source file.
43+
public var column: Int = 1
44+
45+
/// Storage for ``filePath`` under the `"_filePath"` JSON key, as used prior
46+
/// to Swift 6.3.
47+
private var _filePath_v0: String?
48+
49+
/// Storage for ``filePath`` under the `"filePath"` JSON key, as used in
50+
/// Swift 6.3 and newer.
51+
private var _filePath_v6_3: String?
4752
}
4853
}
4954

5055
// MARK: - Codable
5156

52-
extension ABI.EncodedSourceLocation: Codable {}
57+
extension ABI.EncodedSourceLocation: Codable {
58+
private enum CodingKeys: String, CodingKey {
59+
case fileID
60+
case _filePath_v0 = "_filePath"
61+
case _filePath_v6_3 = "filePath"
62+
case line
63+
case column
64+
}
65+
}
66+
67+
// MARK: - Conversion to/from library types
68+
69+
@_spi(ForToolsIntegrationOnly)
70+
extension ABI.EncodedSourceLocation {
71+
/// Initialize an instance of this type from the given value.
72+
///
73+
/// - Parameters:
74+
/// - sourceLocation: The source location to initialize this instance from.
75+
public init(encoding sourceLocation: borrowing SourceLocation) {
76+
fileID = sourceLocation.fileID
77+
filePath = sourceLocation.filePath
78+
line = sourceLocation.line
79+
column = sourceLocation.column
5380

54-
// MARK: -
81+
// When using the 6.3 schema, don't encode synthesized file IDs.
82+
if V.versionNumber >= ABI.v6_3.versionNumber,
83+
sourceLocation.moduleName == SourceLocation.synthesizedModuleName {
84+
fileID = nil
85+
}
86+
}
87+
}
5588

89+
@_spi(ForToolsIntegrationOnly)
5690
extension SourceLocation {
57-
init?<V>(_ sourceLocation: ABI.EncodedSourceLocation<V>) {
91+
/// Initialize an instance of this type from the given value.
92+
///
93+
/// - Parameters:
94+
/// - sourceLocation: The encoded source location to initialize this
95+
/// instance from.
96+
///
97+
/// If `sourceLocation` does not specify a value for its `fileID` field, the
98+
/// testing library synthesizes a value from its `filePath` property and
99+
/// hard-codes a module name of `"__C"`.
100+
///
101+
/// If `sourceLocation` does not specify a value for its `filePath` field,
102+
/// this initializer returns `nil`.
103+
public init?<V>(decoding sourceLocation: ABI.EncodedSourceLocation<V>) {
58104
let fileID = sourceLocation.fileID
59-
guard let filePath = sourceLocation.filePath ?? sourceLocation._filePath else {
60-
return nil
61-
}
105+
let filePath = sourceLocation.filePath
62106
let line = max(1, sourceLocation.line)
63107
let column = max(1, sourceLocation.column)
64108

109+
if let fileID, !fileID.utf8.contains(UInt8(ascii: "/")) {
110+
return nil
111+
}
112+
guard let filePath else {
113+
return nil
114+
}
115+
65116
self.init(fileIDSynthesizingIfNeeded: fileID, filePath: filePath, line: line, column: column)
66117
}
67118
}

Sources/Testing/ABI/Encoded/ABI.EncodedTest.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ extension ABI {
3838
var displayName: String?
3939

4040
/// The source location of this test.
41-
var sourceLocation: EncodedSourceLocation<V>
41+
public var sourceLocation: EncodedSourceLocation<V>
4242

4343
/// A type implementing the JSON encoding of ``Test/ID`` for the ABI entry
4444
/// point and event stream output.

Sources/Testing/SourceAttribution/SourceLocation.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,8 @@ extension SourceLocation: Codable {
232232
}
233233

234234
/// The name of the ersatz Swift module used for synthesized file IDs.
235-
static var synthesizedModuleName: String {
235+
@_spi(ForToolsIntegrationOnly)
236+
public static var synthesizedModuleName: String {
236237
"__C"
237238
}
238239

Tests/TestingTests/SourceLocationTests.swift

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,12 +80,26 @@ struct SourceLocationTests {
8080
let esl = try json.withUTF8 { json in
8181
try JSON.decode(ABI.EncodedSourceLocation<ABI.v6_3>.self, from: UnsafeRawBufferPointer(json))
8282
}
83-
let sourceLocation = try #require(SourceLocation(esl))
83+
let sourceLocation = try #require(SourceLocation(decoding: esl))
8484
#expect(SourceLocation.synthesizedModuleName == "__C")
8585
#expect(sourceLocation.fileID == "\(SourceLocation.synthesizedModuleName)/FileName.swift")
8686
#expect(sourceLocation.moduleName == SourceLocation.synthesizedModuleName)
8787
#expect(sourceLocation.fileName == "FileName.swift")
8888
}
89+
90+
@Test("SourceLocation does not accept a bad file ID from an EncodedSourceLocation")
91+
func badFileIDInEncodedSourceLocation() throws {
92+
#if os(Windows)
93+
var json = #"{"filePath": "C:\fake/dir/FileName.swift/", "fileID": "bad", "line": 1, "column": 1}"#
94+
#else
95+
var json = #"{"filePath": "/fake/dir/FileName.swift/", "fileID": "bad", "line": 1, "column": 1}"#
96+
#endif
97+
let esl = try json.withUTF8 { json in
98+
try JSON.decode(ABI.EncodedSourceLocation<ABI.v6_3>.self, from: UnsafeRawBufferPointer(json))
99+
}
100+
let sourceLocation = SourceLocation(decoding: esl)
101+
#expect(sourceLocation == nil)
102+
}
89103
#endif
90104

91105
@Test("SourceLocation.line and .column properties")

0 commit comments

Comments
 (0)