Skip to content

Commit e2e4929

Browse files
committed
Address CR comments
1 parent 20826dd commit e2e4929

File tree

5 files changed

+61
-37
lines changed

5 files changed

+61
-37
lines changed

DatadogInternal/Sources/Utils/CustomDump.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
* SOFTWARE.
2929
*/
3030

31+
#if DEBUG
3132
// swiftlint:disable function_default_parameter_at_end
3233

3334
import Foundation
@@ -550,7 +551,6 @@ public struct FileHandlerOutputStream: TextOutputStream {
550551
}
551552
// swiftlint:enable function_default_parameter_at_end
552553

553-
#if DEBUG
554554
/// Dumps the given value's contents in a file.
555555
public func dump<T>(_ value: T, filename: String) throws {
556556
let manager = FileManager.default

DatadogRUM/Sources/Instrumentation/Views/SwiftUI/SwiftUIViewNameExtractor.swift

+31-18
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,16 @@ internal struct SwiftUIReflectionBasedViewNameExtractor: SwiftUIViewNameExtracto
3030
self.createReflector = reflectorFactory
3131
}
3232

33-
/// Attempts to extract a meaningful SwiftUI view name from a UIViewController
33+
/// Convenience initializer
34+
init(telemetry: Telemetry) {
35+
self.init(reflectorFactory: { subject in
36+
Reflector(subject: subject, telemetry: telemetry)
37+
})
38+
}
39+
40+
/// Attempts to extract a meaningful SwiftUI view name from a `UIViewController`
3441
/// - Parameter viewController: The `UIViewController` potentially hosting a SwiftUI view
35-
/// - Returns: The extracted view name or nil if extraction failed
42+
/// - Returns: The extracted view name or `nil` if extraction failed
3643
func extractName(from viewController: UIViewController) -> String? {
3744
// Skip known container controllers that shouldn't be tracked
3845
if shouldSkipViewController(viewController: viewController) {
@@ -64,6 +71,10 @@ internal struct SwiftUIReflectionBasedViewNameExtractor: SwiftUIViewNameExtracto
6471
return extractViewName(from: typeDescription(of: output))
6572
}
6673

74+
if let output = SwiftUIViewPath.hostingControllerRoot.traverse(with: reflector) {
75+
return extractViewName(from: typeDescription(of: output))
76+
}
77+
6778
case .navigationController:
6879
// Try detail view first
6980
if let output = SwiftUIViewPath.navigationStackDetail.traverse(with: reflector) {
@@ -93,30 +104,32 @@ internal struct SwiftUIReflectionBasedViewNameExtractor: SwiftUIViewNameExtracto
93104
}
94105

95106
// MARK: - Helpers
96-
/// Extracts a view name from a type description using regex patterns
97-
internal func extractViewName(from input: String) -> String? {
98-
// Pattern 1: Extract the view name from generic types like LazyView<ContentView>
99-
let simpleGenericPattern = #"<([^<>,]+)>"#
100-
if let match = input.range(of: simpleGenericPattern, options: .regularExpression) {
101-
let startIndex = input.index(match.lowerBound, offsetBy: 1)
102-
let endIndex = input.index(match.upperBound, offsetBy: -1)
103-
return String(input[startIndex..<endIndex])
107+
private static let genericTypePattern: NSRegularExpression? = {
108+
do {
109+
return try NSRegularExpression(pattern: #"<(?:[^,>]*,\s+)?([^<>,]+?)>"#)
110+
} catch {
111+
return nil
104112
}
113+
}()
105114

106-
// Pattern 2: Extract the view name from complex generic types like ParameterizedLazyView<String, DetailView>
107-
let complexGenericPattern = #"<.*,\s*([^<>,]+)>"#
108-
if let match = input.range(of: complexGenericPattern, options: .regularExpression),
109-
let captureRange = input.range(of: #"([^<>,]+)(?=>)"#, options: .regularExpression, range: match) {
110-
return String(input[captureRange]).trimmingCharacters(in: .whitespaces)
115+
/// Extracts a view name from a type description
116+
internal func extractViewName(from input: String) -> String? {
117+
// Extract the view name from generic types like ParameterizedLazyView<String, DetailView>
118+
if let match = Self.genericTypePattern?.firstMatch(
119+
in: input,
120+
options: [],
121+
range: NSRange(input.startIndex..<input.endIndex, in: input)
122+
),
123+
let range = Range(match.range(at: 1), in: input) {
124+
return String(input[range])
111125
}
112126

113-
// Pattern 3: Extract the view name from metatypes like DetailView.Type
127+
// Extract the view name from metatypes like DetailView.Type
114128
if input.hasSuffix(".Type") {
115129
return String(input.dropLast(5))
116130
}
117131

118-
// Return the input as a fallback
119-
return input
132+
return nil
120133
}
121134

122135
private func extractTabViewName(from viewController: UIViewController) -> String? {

DatadogRUM/Sources/Instrumentation/Views/SwiftUI/SwiftUIViewPath.swift

+13-10
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import DatadogInternal
88

99
/// Common path components for SwiftUI view traversal
10-
internal enum ViewNode: String {
10+
internal enum SwiftUIViewNode: String {
1111
case host
1212
case rootView = "_rootView"
1313
case root
@@ -18,9 +18,9 @@ internal enum ViewNode: String {
1818
case item
1919
case type
2020

21-
static let hostingBase: [ViewNode] = [.host, .rootView]
22-
static let navigationBase: [ViewNode] = [.host, .rootView, .storage, .view, .content, .content, .content]
23-
static let sheetBase: [ViewNode] = [.host, .rootView, .storage, .view, .content]
21+
static let hostingBase: [SwiftUIViewNode] = [.host, .rootView]
22+
static let navigationBase: [SwiftUIViewNode] = [.host, .rootView, .storage, .view, .content, .content, .content]
23+
static let sheetBase: [SwiftUIViewNode] = [.host, .rootView, .storage, .view, .content]
2424
}
2525

2626
/// Defines the various traversal paths for different SwiftUI view structures
@@ -29,24 +29,27 @@ internal enum ViewNode: String {
2929
/// through reflection.
3030
internal enum SwiftUIViewPath {
3131
case hostingController
32+
case hostingControllerRoot
3233
case navigationStack
3334
case navigationStackDetail
3435
case navigationStackContainer
3536
case sheetContent
3637

3738
/// The sequence of property names to traverse for this view type
38-
var pathComponents: [ViewNode] {
39+
var pathComponents: [SwiftUIViewNode] {
3940
switch self {
4041
case .hostingController:
41-
return ViewNode.hostingBase + [.content, .storage, .view]
42+
return SwiftUIViewNode.hostingBase + [.content, .storage, .view]
43+
case .hostingControllerRoot:
44+
return SwiftUIViewNode.hostingBase
4245
case .navigationStack:
43-
return ViewNode.navigationBase
46+
return SwiftUIViewNode.navigationBase
4447
case .navigationStackDetail:
45-
return ViewNode.navigationBase + [.content, .list, .item, .type]
48+
return SwiftUIViewNode.navigationBase + [.content, .list, .item, .type]
4649
case .navigationStackContainer:
47-
return ViewNode.navigationBase + [.root]
50+
return SwiftUIViewNode.navigationBase + [.root]
4851
case .sheetContent:
49-
return ViewNode.sheetBase
52+
return SwiftUIViewNode.sheetBase
5053
}
5154
}
5255

DatadogRUM/Sources/RUM.swift

+4-2
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ public enum RUM {
1717
/// - configuration: Configuration of the feature.
1818
/// - core: The instance of Datadog SDK to enable RUM in (global instance by default).
1919
public static func enable(
20-
with configuration: RUM.Configuration, in core: DatadogCoreProtocol = CoreRegistry.default
20+
with configuration: RUM.Configuration,
21+
in core: DatadogCoreProtocol = CoreRegistry.default
2122
) {
2223
do {
2324
try enableOrThrow(with: configuration, in: core)
@@ -27,7 +28,8 @@ public enum RUM {
2728
}
2829

2930
internal static func enableOrThrow(
30-
with configuration: RUM.Configuration, in core: DatadogCoreProtocol
31+
with configuration: RUM.Configuration,
32+
in core: DatadogCoreProtocol
3133
) throws {
3234
guard !(core is NOPDatadogCore) else {
3335
throw ProgrammerError(

DatadogRUM/Tests/Instrumentation/Views/SwiftUIViewNameExtractorTests.swift

+12-6
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,16 @@ class SwiftUIViewNameExtractorTests: XCTestCase {
2525
// MARK: - View Name Extraction Tests
2626
func testViewNameExtraction() {
2727
let testCases: [(String, String)] = [
28-
("LazyView<ContentView>", "ContentView"),
28+
("LazyView<ViewType>", "ViewType"),
2929
("SheetContent<Text>", "Text"),
3030
("Optional<Text>", "Text"),
31-
("Optional<ProfileView>", "ProfileView"),
32-
("LazyView<HomeView>", "HomeView"),
33-
("ParameterizedLazyView<String, DetailViewForNavigationDestination>", "DetailViewForNavigationDestination"),
34-
("DetailView.Type", "DetailView"),
35-
("SheetContent<ModalSheet>", "ModalSheet")
31+
("Optional<ViewType>", "ViewType"),
32+
("ParameterizedLazyView<String, ViewType>", "ViewType"),
33+
("ParameterizedLazyView<String, ViewType>(value: \"xxx\", content: (Function))", "ViewType"),
34+
("ViewType.Type", "ViewType"),
35+
("SheetContent<ViewType>", "ViewType"),
36+
("ModifiedView<ModifierType, ModifiedView<ModifierType, ModifiedView<ModifierType, ModifiedView<ModifierType, ViewType>>>>", "ViewType"),
37+
("ModifiedView<ModifierType, ModifiedView<ModifierType, ModifiedView<ModifierType, ModifiedView<ModifierType, ContainerType<ViewType>>>>>", "ViewType")
3638
]
3739

3840
for (input, expected) in testCases {
@@ -47,6 +49,10 @@ class SwiftUIViewNameExtractorTests: XCTestCase {
4749
SwiftUIViewPath.hostingController.pathComponents,
4850
[.host, .rootView, .content, .storage, .view]
4951
)
52+
XCTAssertEqual(
53+
SwiftUIViewPath.hostingControllerRoot.pathComponents,
54+
[.host, .rootView]
55+
)
5056
XCTAssertEqual(
5157
SwiftUIViewPath.navigationStack.pathComponents,
5258
[.host, .rootView, .storage, .view, .content, .content, .content]

0 commit comments

Comments
 (0)