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

First getting started of SwiftSyntax

First getting started of SwiftSyntax

D36d6bab8f1e0ff4bb3377571e5f7dcd?s=128

Yutaro Muta

April 19, 2019
Tweet

Transcript

  1. SwiftSyntax௒ೖ໳ 2019/04/19 @Mobile Act KYOTO #1 Yutaro Muta @yutailang0119

  2. • Yutaro Muta @yutailang0119 • Hatena Co., Ltd. @Kyoto •

    Conference Staff • builderscon 2017, 2018 • try! Swift Tokyo 2019, 2020? • and more Who am I ?
  3. Goal • SwiftSyntaxΛ࢖͏͖͔͚ͬʹͳΔ

  4. apple/swift-syntax

  5. apple/swift-syntax • https://github.com/apple/swift-syntax > SwiftSyntax is a set of Swift

    bindings for the libSyntax library. It allows for Swift tools to parse, inspect, generate, and transform Swift source code.
  6. libSyntax vs SwiftSyntax • libSyntax • SwiftίϯύΠϥͰ࠾༻ • API͸Swift ͱ

    C++Λࠞࡏͯ͠ఏڙ • SwiftSyntax • libSyntaxͷSwiftϥούʔ • SwiftPackageManagerͰ࢖༻Ͱ͖Δ • ։ൃஈ֊
  7. Some Example Users • Swift AST Explorer: a Swift AST

    visualizer. • Swift Stress Tester: a test driver for sourcekitd and Swift evolution. • SwiftRewriter: a Swift code formatter. • SwiftPack: a tool for automatically embedding Swift library source. • Periphery: a tool to detect unused code. • BartyCrouch: a tool to incrementally update strings files to help App localization. • Muter: Automated mutation testing for Swift • Swift Variable Injector: a tool to replace string literals with environment variables values.
  8. Some Example Users • Swift AST Explorer: a Swift AST

    visualizer. • Swift Stress Tester: a test driver for sourcekitd and Swift evolution. • SwiftRewriter: a Swift code formatter. • SwiftPack: a tool for automatically embedding Swift library source. • Periphery: a tool to detect unused code. • BartyCrouch: a tool to incrementally update strings files to help App localization. • Muter: Automated mutation testing for Swift • Swift Variable Injector: a tool to replace string literals with environment variables values. SwiftͷASTΛѻ͏্Ͱ͸ඞਢ!!!
  9. ͓୊

  10. ͓୊: UIColor • UIColor.init(red: CGFloat, green: CGFloat, blue: CGFloat, alpha:

    CGFloat) • ௚ײతʹͲΜͳ৭ͳͷ͔͕Θ͔ΓͮΒ͍ • HexColor (16ਐτϦϓϨοτ) ͰऔΓѻ͍͍ͨ => HexColorͰॳظԽ͢Δϝιουʹॻ͖׵͑Δ
  11. UIColor.init(hex string: String, alpha: CGFloat) https://gist.github.com/yutailang0119/bef7cbe591ba6f76318219ba8bb69946 extension UIColor { convenience

    init(hex string: String, alpha: CGFloat) { let hex = string.replacingOccurrences(of: "#", with: "") let scanner = Scanner(string: hex) var color: UInt32 = 0 if scanner.scanHexInt32(&color) { let r = CGFloat((color & 0xFF0000) >> 16) / 255.0 let g = CGFloat((color & 0x00FF00) >> 8) / 255.0 let b = CGFloat(color & 0x0000FF) / 255.0 self.init(red: r, green: g, blue: b, alpha: alpha) } else { self.init(white: 1.0, alpha: 1) } } }
  12. yutailang0119/swift-color-detector

  13. Demo

  14. 0. SwiftSyntax΁ͷґଘ

  15. Package.swift // swift-tools-version:5.0 // The swift-tools-version declares the minimum version

    of Swift required to build this package. import PackageDescription let package = Package( name: "swift-color-detector", products: [ .executable(name:"swift-color-detector", targets: ["swift-color-detector"]), .library(name: "SwiftColorDetectKit", targets: ["SwiftColorDetectKit"]), ], dependencies: [ .package(url: "https://github.com/apple/swift-syntax.git", from: Version(0, 50000, 0)), ... ], targets: [ .target(name: "swift-color-detector", dependencies: ["SwiftColorDetectKit", ...]), .target(name: "SwiftColorDetectKit", dependencies: ["SwiftSyntax", ...]), .testTarget(name: "SwiftColorDetectKitTests", dependencies: ["SwiftColorDetectKit"]), ] )
  16. 1. UIColor.init(red: _, green: _, blue: _, alpha: _) ͷಛఆ

  17. 1.1 UIColor.initͷಛఆ func isUIColorInitializer(of node: FunctionCallExprSyntax) -> Bool { return

    (node.calledExpression.description == "UIColor"&& node.leftParen != nil) || node.calledExpression.description == "UIColor.init" }
  18. 1.1 UIColor.initͷಛఆ func isUIColorInitializer(of node: FunctionCallExprSyntax) -> Bool { return

    (node.calledExpression.description == "UIColor"&& node.leftParen != nil) || node.calledExpression.description == "UIColor.init" } ਫ਼౓Λ্͛Δ΂͘ɺݕূத
  19. 1.2 Label red, green, blue, alphaͷಛఆ func detectRGBColor(from node: FunctionCallExprSyntax)

    -> RGBColor? { guard isUIColorInitializer(of: node) else { return nil } var red, green, blue, alpha: CGFloat? node.argumentList.forEach { argumentSyntax in guard let label = argumentSyntax.label else { return } switch label.text { case "red": red = CGFloat(expression: argumentSyntax.expression) case "green": green = CGFloat(expression: argumentSyntax.expression) case "blue": blue = CGFloat(expression: argumentSyntax.expression) case "alpha": alpha = CGFloat(expression: argumentSyntax.expression) default: break } } guard let rgbColor = RGBColor(red: red, green: green, blue: blue, alpha: alpha) else { return nil } return rgbColor } }
  20. 2. UIColor.init(hex string: String, alpha: CGFloat) ΁ͷॻ͖׵͑

  21. 2.1Ҿ਺ϦετΛ૊ΈཱͯΔ import SwiftSyntax var hexInitializerArgumentListSyntax: FunctionCallArgumentListSyntax { let space =

    Trivia.init(arrayLiteral: TriviaPiece.spaces(1)) let colon = SyntaxFactory.makeIdentifier(":").withTrailingTrivia(space) let hexArgument = FunctionCallArgumentSyntax { builder in let label = SyntaxFactory.makeIdentifier("hex") let value = SyntaxFactory.makeStringLiteral("\"\(hex)\"") let expression = SyntaxFactory.makeStringLiteralExpr(stringLiteral: value) let trailingComma = SyntaxFactory.makeIdentifier(",").withTrailingTrivia(space) builder.useLabel(label) builder.useColon(colon) builder.useExpression(expression) builder.useTrailingComma(trailingComma) } let alphaArgument = FunctionCallArgumentSyntax { builder in let label = SyntaxFactory.makeIdentifier("alpha") let value = SyntaxFactory.makeFloatingLiteral("\(alpha)") let expression = SyntaxFactory.makeFloatLiteralExpr(floatingDigits: value) builder.useLabel(label) builder.useColon(colon) builder.useExpression(expression) } let argumentList = SyntaxFactory.makeFunctionCallArgumentList([hexArgument, alphaArgument]) return argumentList // hex: \"#8C0000\", alpha: 1.0 }
  22. 2.2 UIColor.initΛॻ͖׵͑Δ import SwiftSyntax class RGBColorSyntaxRewriter: SyntaxRewriter { override func

    visit(_ node: FunctionCallExprSyntax) -> ExprSyntax { guard let rgbColor = detectRGBColor(from: node) else { return node } let colorInitializerSyntax = node.withArgumentList(rgbColor.hexInitializerArgumentListSyntax) // UIColor(hex: "#8C0000", alpha: 1.0) return colorInitializerSyntax } }
  23. 3. ࣮ߦ

  24. ࣮ߦ import SwiftSyntax public func rewrite() throws { let sourceFile

    = try SyntaxTreeParser.parse(path) let rewriter = RGBColorSyntaxRewriter(filePath: path) let syntax = rewriter.visit(sourceFile) // ϑΝΠϧͷॻ͖׵͑ try syntax.description.write(to: path, atomically: true, encoding: .utf8) }
  25. ޙ͸ҰͭҰͭͷϑΝΠϧʹ ࣮ߦ͍͚ͯͩ͘͠

  26. One more thing...

  27. yutailang0119/FileScanKit

  28. yutailang0119/FileScanKit • ࢦఆύεҎԼͷϑΝΠϧύεΛ໢ཏతʹऔಘ͢ΔͨΊͷSwift޲͚Library • Support • Recursion • all •

    depth limit • File extension • Ignore paths
  29. &OKPZ4XJGU4ZOUBYMJGF 5IBOLT w NVUBZVUBSP!HNBJMDPN w IUUQTUXJUUFSDPNZVUBJMBOH w IUUQTHJUIVCDPNZVUBJMBOH