diff --git a/Sources/ViewInspector/BaseTypes.swift b/Sources/ViewInspector/BaseTypes.swift index b4d514d6..2e54f237 100644 --- a/Sources/ViewInspector/BaseTypes.swift +++ b/Sources/ViewInspector/BaseTypes.swift @@ -54,7 +54,7 @@ public protocol KnownViewType { public extension KnownViewType { static var namespacedPrefixes: [String] { guard !typePrefix.isEmpty else { return [] } - return ["SwiftUI." + typePrefix] + return [.swiftUINamespaceRegex + typePrefix] } static var isTransitive: Bool { false } static func inspectionCall(typeName: String) -> String { @@ -68,15 +68,33 @@ internal extension String { prefix(1).lowercased() + dropFirst() } - func hasPrefix(regex: String) -> Bool { - guard let ex = try? NSRegularExpression(pattern: regex) else { return false } - let range = NSRange(location: 0, length: utf16.count) - return ex.firstMatch(in: self, range: range)?.range.lowerBound == 0 - } + static var swiftUINamespaceRegex: String { "SwiftUI(|[a-zA-Z0-9]{0,2}\\))\\." } func removingSwiftUINamespace() -> String { - guard hasPrefix("SwiftUI.") else { return self } - return String(suffix(count - 8)) + return removing(prefix: .swiftUINamespaceRegex) + .removing(prefix: "SwiftUI.") + } + + func removing(prefix: String) -> String { + guard hasPrefix(prefix) else { return self } + return String(suffix(count - prefix.count)) + } + + fileprivate func hasPrefix(regex: String, wholeMatch: Bool) -> Bool { + let range = NSRange(location: 0, length: utf16.count) + guard let ex = try? NSRegularExpression(pattern: regex), + let match = ex.firstMatch(in: self, range: range) + else { return false } + if wholeMatch { + return match.range == range + } + return match.range.lowerBound == 0 + } +} + +internal extension Array where Element == String { + func containsPrefixRegex(matching name: String, wholeMatch: Bool = true) -> Bool { + return contains(where: { name.hasPrefix(regex: $0, wholeMatch: wholeMatch) }) } } diff --git a/Sources/ViewInspector/Inspector.swift b/Sources/ViewInspector/Inspector.swift index 6d7f1dc5..ead3d09a 100644 --- a/Sources/ViewInspector/Inspector.swift +++ b/Sources/ViewInspector/Inspector.swift @@ -66,10 +66,10 @@ internal extension Inspector { private static func isSystemType(name: String) -> Bool { return [ - "SwiftUI[a-zA-Z0-9]{0,2}\\)?", - "_CoreLocationUI_SwiftUI", "_MapKit_SwiftUI", - "_AuthenticationServices_SwiftUI", "_AVKit_SwiftUI", - ].contains(where: { name.hasPrefix(regex: $0 + "\\.") }) + String.swiftUINamespaceRegex, + "_CoreLocationUI_SwiftUI\\.", "_MapKit_SwiftUI\\.", + "_AuthenticationServices_SwiftUI\\.", "_AVKit_SwiftUI\\.", + ].containsPrefixRegex(matching: name, wholeMatch: false) } static func typeName(type: Any.Type, @@ -284,10 +284,11 @@ internal extension Inspector { """) } } - if namespacedPrefixes.contains(typePrefix) { + if namespacedPrefixes.containsPrefixRegex(matching: typePrefix) { return } - if let prefix = namespacedPrefixes.first { + if var prefix = namespacedPrefixes.first { + prefix = prefix.replacingOccurrences(of: String.swiftUINamespaceRegex, with: "SwiftUI.") let typePrefix = typeName(type: type(of: value), namespaced: true) throw InspectionError.typeMismatch(factual: typePrefix, expected: prefix) } diff --git a/Sources/ViewInspector/SwiftUI/Alert.swift b/Sources/ViewInspector/SwiftUI/Alert.swift index 61141225..aca39044 100644 --- a/Sources/ViewInspector/SwiftUI/Alert.swift +++ b/Sources/ViewInspector/SwiftUI/Alert.swift @@ -9,7 +9,7 @@ public extension ViewType { public static var typePrefix: String = ViewType.PopupContainer.typePrefix static var typePrefixIOS15: String = "AlertModifier" public static var namespacedPrefixes: [String] { - [typePrefix, "SwiftUI." + typePrefixIOS15] + [typePrefix, .swiftUINamespaceRegex + typePrefixIOS15] } public static func inspectionCall(typeName: String) -> String { return "alert(\(ViewType.indexPlaceholder))" diff --git a/Sources/ViewInspector/SwiftUI/Gesture.swift b/Sources/ViewInspector/SwiftUI/Gesture.swift index 70623218..e4105c29 100644 --- a/Sources/ViewInspector/SwiftUI/Gesture.swift +++ b/Sources/ViewInspector/SwiftUI/Gesture.swift @@ -16,15 +16,15 @@ public extension ViewType { public static var namespacedPrefixes: [String] { var prefixes = [ - "SwiftUI.AddGestureModifier", - "SwiftUI.HighPriorityGestureModifier", - "SwiftUI.SimultaneousGestureModifier", - "SwiftUI._ChangedGesture", - "SwiftUI._EndedGesture", - "SwiftUI._MapGesture", - "SwiftUI._ModifiersGesture", - "SwiftUI.GestureStateGesture" - ] + "AddGestureModifier", + "HighPriorityGestureModifier", + "SimultaneousGestureModifier", + "_ChangedGesture", + "_EndedGesture", + "_MapGesture", + "_ModifiersGesture", + "GestureStateGesture" + ].map { String.swiftUINamespaceRegex + $0 } prefixes.append(Inspector.typeName(type: T.self, namespaced: true, generics: .remove)) return prefixes } diff --git a/Sources/ViewInspector/SwiftUI/Toolbar.swift b/Sources/ViewInspector/SwiftUI/Toolbar.swift index 0cc065d2..3ed43c34 100644 --- a/Sources/ViewInspector/SwiftUI/Toolbar.swift +++ b/Sources/ViewInspector/SwiftUI/Toolbar.swift @@ -11,13 +11,6 @@ public extension ViewType { return "_ToolbarItemGroupModifier" } }() - public static var namespacedPrefixes: [String] { - #if os(tvOS) - return ["SwiftUI." + typePrefix, "SwiftUI1c)." + typePrefix] - #else - return ["SwiftUI." + typePrefix] - #endif - } public static func inspectionCall(typeName: String) -> String { return "toolbar(\(ViewType.indexPlaceholder))" } diff --git a/Sources/ViewInspector/ViewSearchIndex.swift b/Sources/ViewInspector/ViewSearchIndex.swift index 6c7d20ab..d4410d06 100644 --- a/Sources/ViewInspector/ViewSearchIndex.swift +++ b/Sources/ViewInspector/ViewSearchIndex.swift @@ -88,7 +88,9 @@ internal extension ViewSearch { let fullName = Inspector.typeName(value: content.view, namespaced: true, generics: .remove) if shortName.count > 0, let identity = index[String(shortName.prefix(1))]? - .first(where: { $0.viewType.namespacedPrefixes.contains(fullName) }) { + .first(where: { + $0.viewType.namespacedPrefixes.containsPrefixRegex(matching: fullName) + }) { return identity } if (try? content.extractCustomView()) != nil {