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

First getting started of SwiftSyntax

First getting started of SwiftSyntax

Avatar for Yutaro Muta

Yutaro Muta

April 19, 2019
Tweet

More Decks by Yutaro Muta

Other Decks in Programming

Transcript

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

    Conference Staff • builderscon 2017, 2018 • try! Swift Tokyo 2019, 2020? • and more Who am I ?
  2. 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.
  3. libSyntax vs SwiftSyntax • libSyntax • SwiftίϯύΠϥͰ࠾༻ • API͸Swift ͱ

    C++Λࠞࡏͯ͠ఏڙ • SwiftSyntax • libSyntaxͷSwiftϥούʔ • SwiftPackageManagerͰ࢖༻Ͱ͖Δ • ։ൃஈ֊
  4. 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.
  5. 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Λѻ͏্Ͱ͸ඞਢ!!!
  6. ͓୊: UIColor • UIColor.init(red: CGFloat, green: CGFloat, blue: CGFloat, alpha:

    CGFloat) • ௚ײతʹͲΜͳ৭ͳͷ͔͕Θ͔ΓͮΒ͍ • HexColor (16ਐτϦϓϨοτ) ͰऔΓѻ͍͍ͨ => HexColorͰॳظԽ͢Δϝιουʹॻ͖׵͑Δ
  7. 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) } } }
  8. 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"]), ] )
  9. 1.1 UIColor.initͷಛఆ func isUIColorInitializer(of node: FunctionCallExprSyntax) -> Bool { return

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

    (node.calledExpression.description == "UIColor"&& node.leftParen != nil) || node.calledExpression.description == "UIColor.init" } ਫ਼౓Λ্͛Δ΂͘ɺݕূத
  11. 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 } }
  12. 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 }
  13. 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 } }
  14. ࣮ߦ 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) }