Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

• Yutaro Muta @yutailang0119 • Hatena Co., Ltd. @Kyoto • Conference Staff • builderscon 2017, 2018 • try! Swift Tokyo 2019, 2020? • and more Who am I ?

Slide 3

Slide 3 text

Goal • SwiftSyntaxΛ࢖͏͖͔͚ͬʹͳΔ

Slide 4

Slide 4 text

apple/swift-syntax

Slide 5

Slide 5 text

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.

Slide 6

Slide 6 text

libSyntax vs SwiftSyntax • libSyntax • SwiftίϯύΠϥͰ࠾༻ • API͸Swift ͱ C++Λࠞࡏͯ͠ఏڙ • SwiftSyntax • libSyntaxͷSwiftϥούʔ • SwiftPackageManagerͰ࢖༻Ͱ͖Δ • ։ൃஈ֊

Slide 7

Slide 7 text

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.

Slide 8

Slide 8 text

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Λѻ͏্Ͱ͸ඞਢ!!!

Slide 9

Slide 9 text

͓୊

Slide 10

Slide 10 text

͓୊: UIColor • UIColor.init(red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat) • ௚ײతʹͲΜͳ৭ͳͷ͔͕Θ͔ΓͮΒ͍ • HexColor (16ਐτϦϓϨοτ) ͰऔΓѻ͍͍ͨ => HexColorͰॳظԽ͢Δϝιουʹॻ͖׵͑Δ

Slide 11

Slide 11 text

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) } } }

Slide 12

Slide 12 text

yutailang0119/swift-color-detector

Slide 13

Slide 13 text

Demo

Slide 14

Slide 14 text

0. SwiftSyntax΁ͷґଘ

Slide 15

Slide 15 text

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"]), ] )

Slide 16

Slide 16 text

1. UIColor.init(red: _, green: _, blue: _, alpha: _) ͷಛఆ

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

1.1 UIColor.initͷಛఆ func isUIColorInitializer(of node: FunctionCallExprSyntax) -> Bool { return (node.calledExpression.description == "UIColor"&& node.leftParen != nil) || node.calledExpression.description == "UIColor.init" } ਫ਼౓Λ্͛Δ΂͘ɺݕূத

Slide 19

Slide 19 text

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 } }

Slide 20

Slide 20 text

2. UIColor.init(hex string: String, alpha: CGFloat) ΁ͷॻ͖׵͑

Slide 21

Slide 21 text

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 }

Slide 22

Slide 22 text

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 } }

Slide 23

Slide 23 text

3. ࣮ߦ

Slide 24

Slide 24 text

࣮ߦ 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) }

Slide 25

Slide 25 text

ޙ͸ҰͭҰͭͷϑΝΠϧʹ ࣮ߦ͍͚ͯͩ͘͠

Slide 26

Slide 26 text

One more thing...

Slide 27

Slide 27 text

yutailang0119/FileScanKit

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

&OKPZ4XJGU4ZOUBYMJGF 5IBOLT w NVUBZVUBSP!HNBJMDPN w IUUQTUXJUUFSDPNZVUBJMBOH w IUUQTHJUIVCDPNZVUBJMBOH