@@ -265,7 +265,7 @@ public struct CoverageOptions: ParsableArguments {
265
265
. customLong( " show-code-coverage-path " ) ,
266
266
. customLong( " show-coverage-path " ) ,
267
267
] ,
268
- help: " Print the path of the exported code coverage JSON file . " ,
268
+ help: " Print the path of the exported code coverage files . " ,
269
269
)
270
270
var shouldPrintPath : Bool = false
271
271
@@ -399,11 +399,40 @@ package struct CoverageFormatOutput: Encodable {
399
399
package init ( ) {
400
400
self . _underlying = [ CoverageFormat : AbsolutePath] ( )
401
401
}
402
-
402
+
403
403
package init ( data: [ CoverageFormat : AbsolutePath ] ) {
404
404
self . _underlying = data
405
405
}
406
406
407
+ // Custom encoding to ensure the dictionary is encoded as a JSON object, not an array
408
+ public func encode( to encoder: Encoder ) throws {
409
+ // Use keyed container to encode each format and its path
410
+ // This will create proper JSON objects and proper plain text "key: value" format
411
+ var container = encoder. container ( keyedBy: DynamicCodingKey . self)
412
+
413
+ // Sort entries for consistent output
414
+ let sortedEntries = _underlying. sorted { $0. key. rawValue < $1. key. rawValue }
415
+
416
+ for (format, path) in sortedEntries {
417
+ let key = DynamicCodingKey ( stringValue: format. rawValue) !
418
+ try container. encode ( path. pathString, forKey: key)
419
+ }
420
+ }
421
+
422
+ // Dynamic coding keys for the formats
423
+ private struct DynamicCodingKey : CodingKey {
424
+ var stringValue : String
425
+ var intValue : Int ? { nil }
426
+
427
+ init ? ( stringValue: String ) {
428
+ self . stringValue = stringValue
429
+ }
430
+
431
+ init ? ( intValue: Int ) {
432
+ return nil
433
+ }
434
+ }
435
+
407
436
/// Adds a key/value pair to the underlying dictionary.
408
437
/// - Parameters:
409
438
/// - format: The coverage format key
@@ -420,7 +449,7 @@ package struct CoverageFormatOutput: Encodable {
420
449
package subscript( format: CoverageFormat ) -> AbsolutePath ? {
421
450
return _underlying [ format]
422
451
}
423
-
452
+
424
453
/// Gets the path for a format, throwing an error if it doesn't exist.
425
454
/// - Parameter format: The coverage format
426
455
/// - Returns: The absolute path for the format
@@ -431,48 +460,17 @@ package struct CoverageFormatOutput: Encodable {
431
460
}
432
461
return path
433
462
}
434
-
463
+
435
464
/// Returns all formats currently stored
436
465
package var formats : [ CoverageFormat ] {
437
466
return Array ( _underlying. keys) . sorted ( )
438
467
}
439
-
468
+
440
469
/// Iterate over format/path pairs
441
470
package func forEach( _ body: ( CoverageFormat , AbsolutePath ) throws -> Void ) rethrows {
442
471
try _underlying. forEach ( body)
443
472
}
444
-
445
- /// Encodes the coverage format output as JSON string
446
- /// - Returns: JSON string representation of the format/path mapping
447
- /// - Throws: `StringError` if JSON encoding fails
448
- package func encodeAsJSON( ) throws -> String {
449
- let sortedData = _underlying. sorted { $0. key. rawValue < $1. key. rawValue }
450
- let jsonObject : [ String : String ] = Dictionary ( uniqueKeysWithValues: sortedData. map { ( $0. key. rawValue, $0. value. pathString) } )
451
-
452
- do {
453
- let jsonData = try JSONSerialization . data ( withJSONObject: jsonObject, options: [ . prettyPrinted, . sortedKeys] )
454
- guard let jsonString = String ( data: jsonData, encoding: . utf8) else {
455
- throw StringError ( " Failed to convert JSON data to string " )
456
- }
457
- return jsonString
458
- } catch {
459
- throw StringError ( " Failed to encode coverage format output as JSON: \( error) " )
460
- }
461
- }
462
-
463
- /// Encodes the coverage format output as plain text
464
- /// - Returns: Text string with format/path pairs, one per line
465
- package func encodeAsText( ) -> String {
466
- let sortedFormats = _underlying. keys. sorted ( )
467
- return sortedFormats. map { format in
468
- let value = _underlying [ format] !. pathString
469
- if _underlying. count == 1 {
470
- return value
471
- } else {
472
- return " \( format. rawValue. uppercased ( ) ) : \( value) "
473
- }
474
- } . joined ( separator: " \n " )
475
- }
473
+
476
474
}
477
475
478
476
struct CodeCoverageConfiguration {
@@ -1122,29 +1120,29 @@ extension SwiftTestCommand {
1122
1120
let config = try await self . getCodeCoverageConfiguration ( swiftCommandState, format: format)
1123
1121
coverageData [ format] = config. outputDir
1124
1122
}
1125
-
1126
- let coverageOutput = CoverageFormatOutput ( data: coverageData)
1127
-
1128
- switch printMode {
1129
- case . json:
1130
- let jsonOutput = try coverageOutput. encodeAsJSON ( )
1131
- print ( jsonOutput)
1132
- case . text:
1133
- let textOutput = coverageOutput. encodeAsText ( )
1134
- print ( textOutput)
1135
- }
1136
1123
1137
- print ( " ----------------------- " )
1138
1124
let data : Data
1139
1125
switch printMode {
1140
1126
case . json:
1127
+ let coverageOutput = CoverageFormatOutput ( data: coverageData)
1141
1128
let encoder = JSONEncoder . makeWithDefaults ( )
1142
1129
encoder. keyEncodingStrategy = . convertToSnakeCase
1143
1130
data = try encoder. encode ( coverageOutput)
1144
1131
case . text:
1145
- var encoder = PlainTextEncoder ( )
1146
- encoder. formattingOptions = [ . prettyPrinted]
1147
- data = try encoder. encode ( coverageOutput)
1132
+ // When there's only one format, don't show the key prefix
1133
+ if formats. count == 1 , let singlePath = coverageData. values. first {
1134
+ swiftCommandState. observabilityScope. emit (
1135
+ warning: """
1136
+ The contents of this output are subject to change in the future. Use `--print-coverage-path-mode json` if the output is required in a script.
1137
+ """ ,
1138
+ )
1139
+ data = Data ( " \( singlePath. pathString) " . utf8)
1140
+ } else {
1141
+ let coverageOutput = CoverageFormatOutput ( data: coverageData)
1142
+ var encoder = PlainTextEncoder ( )
1143
+ encoder. formattingOptions = [ . prettyPrinted]
1144
+ data = try encoder. encode ( coverageOutput)
1145
+ }
1148
1146
}
1149
1147
print ( String ( decoding: data, as: UTF8 . self) )
1150
1148
}
0 commit comments