Upgrade to Pro — share decks privately, control downloads, hide ads and more …

SwiftSyntaxでUIKitとSwiftUIの使用率を完璧に計測できちゃう件について

 SwiftSyntaxでUIKitとSwiftUIの使用率を完璧に計測できちゃう件について

「potatotips #89 iOS/Android開発Tips共有会」の登壇資料です。
https://potatotips.connpass.com/event/331173/

LINE Digital Frontier - TECH

October 23, 2024
Tweet

More Decks by LINE Digital Frontier - TECH

Other Decks in Technology

Transcript

  1. SwiftSyntax UIKit SwiftUI 用 202 4 . 1 0 .

    23 @dsxsxsxs © LINE Digital Frontier Corporation
  2. import 文 🤔 水 ❌ ❌ import SwiftUI import SwiftUI

    import SwiftUI import SwiftUI import SwiftUI import SwiftUI import SwiftUI import SwiftUI import SwiftUI import SwiftUI import SwiftUI import SwiftUI import SwiftUI import SwiftUI import SwiftUI import SwiftUI import SwiftUI import SwiftUI
  3. No

  4. func getSwiftFiles(in directory: URL) -> [URL] { var fileURLs: [URL]

    = [] let fileManager = FileManager.default if let enumerator = fileManager.enumerator(at: directory, includingPropertiesForKeys: nil) { for case let fileURL as URL in enumerator { if fileURL.pathExtension == "swift" { fileURLs.append(fileURL) } } } return fileURLs } .swift AI 生 👍
  5. func declSyntaxs(of fileURL: URL) async throws -> [DeclSyntaxProtocol] { var

    decls: [DeclSyntaxProtocol] = [] let data = try Data(contentsOf: fileURL) let sourceFile = Parser.parse(source: String(data: data, encoding: .utf8)!) for statement in sourceFile.statements { if let classDecl = statement.item.as(ClassDeclSyntax.self) { decls.append(classDecl) } else if let structDecl = statement.item.as(StructDeclSyntax.self) { decls.append(structDecl) } } return decls } SwiftSyntaxParser 文
  6. let initialUIKitTypes: Set<String> = [ "UIView", "UIButton", "UILabel", "UITableView", "UICollectionView",

    "UIScrollView", "UIImageView", "UIStackView", "UITextField", "UITextView", "UIBarButtonItem", "UINavigationBar", "UIViewController", "UINavigationBar", "UINavigationController", "UITabBarItem", "UITabBar", "UITabBarController", "UIHostingController", "UICollectionViewController", "UITableViewController", "UIControl", "UIPickerView", "UIDatePicker", "UISwitch", "UIProgressView", "UIActivityIndicatorView", "UIStepper", “UISegmentedControl" ] let initialSwiftUITypes: Set<String> = [ "View", "Text", "Button", "Image", "List", "VStack", "HStack", "ZStack", "LazyVStack", "LazyHStack", "LazyZStack", "ScrollView", "NavigationView", "Form", "Group", "Section", "Spacer", "Divider", "Picker", "Slider", "Stepper", "Toggle", "TextField", "TextEditor", "Shape", "Path", "Rectangle", "RoundedRectangle", "Circle", "Ellipse", "Capsule", "GeometryReader", "Color", "ClipShape", "Mask" ] AI 生 👍
  7. if let classDecl = decl.as(ClassDeclSyntax.self), let inheritanceClause = classDecl.inheritanceClause {

    let className = classDecl.name.text let typeThatHit = inheritanceClause .inheritedTypes.map { $0.type.trimmed.description } .first { uiKit.contains($0) } if typeThatHit != nil { uiKitTypes.insert(className) print("🔴 run \(run), found \(className) in \(fileURL.lastPathComponent)") } } UIKit
  8. if let structDecl = decl.as(StructDeclSyntax.self), let inheritanceClause = structDecl.inheritanceClause {

    let structName = structDecl.name.text let typeThatHit = inheritanceClause .inheritedTypes.map { $0.type.trimmed.description } .first { swiftUI.contains($0) } if typeThatHit != nil { swiftUITypes.insert(structName) print("🟢 run \(run), found \(structName) in \(fileURL.lastPathComponent)") } } SwiftUI ⾒
  9. func analyzeFiles(swiftFiles: [URL], uiKit: Set<String>, swiftUI: Set<String>, run: Int) ->

    (uiKitTypes: Set<String>, swiftUITypes: Set<String>) { var uiKitTypes: Set<String> = [] var swiftUITypes: Set<String> = [] for fileURL in swiftFiles { do { let decls = try await declSyntaxs(of: fileURL) for decl in decls { if /* লུ */ { uiKitTypes.insert(className) print("🔴 run \(run), found \(className) in \(fileURL.lastPathComponent)") } else if /* লུ */ { swiftUITypes.insert(structName) print("🟢 run \(run), found \(structName) in \(fileURL.lastPathComponent)") } } } catch { print("Failed to parse \(fileURL): \(error)") } } return (uiKitTypes, swiftUITypes) }
  10. func analyzeProject(at directory: URL) { let start = Date().timeIntervalSince1970 let

    swiftFiles = getSwiftFiles(in: directory) var uiKitTypes: Set<String> = [] var swiftUITypes: Set<String> = [] print(“😡 \(swiftFiles.count) files") var run = 0 var uikit: Set<String> = initialUIKitTypes var swiftUI: Set<String> = initialSwiftUITypes while !uikit.isEmpty || !swiftUI.isEmpty { run += 1 let (foundUIKit, foundSwiftUI) = analyzeFiles(swiftFiles: swiftFiles, uiKit: uikit, swiftUI: swiftUI, run: run) uiKitTypes.formUnion(foundUIKit) swiftUITypes.formUnion(foundSwiftUI) uikit = foundUIKit swiftUI = foundSwiftUI } print("✅ no more childs found. finish.") 見
  11. let totalCount = uiKitTypes.count + swiftUITypes.count let uiKitPercentage = totalCount

    > 0 ? (Double(uiKitTypes.count) / Double(totalCount)) * 100 : 0 let swiftUIPercentage = totalCount > 0 ? (Double(swiftUITypes.count) / Double(totalCount)) * 100 : 0 print("UIKit: \(uiKitTypes.sorted())") print("SwiftUI: \(swiftUITypes.sorted())") print("UIKit \(uiKitTypes.count)/\(totalCount) percentage: \(uiKitPercentage)%") print("SwiftUI \(swiftUITypes.count)/\(totalCount) percentage: \(swiftUIPercentage)%") let end = Date().timeIntervalSince1970 print("🕒 Time taken: \(end - start) seconds") 力