Pro Yearly is on sale from $80 to $50! »

How to Use SwiftSyntax for Better Productivity

151a0b14f5914e786e2e104cfb3a9b2f?s=47 Kuniwak
March 22, 2019

How to Use SwiftSyntax for Better Productivity

SwiftSyntax can realize swift code generation, rewriting, static inspection and so on.
However, how to use SwiftSyntax is almost known. I'd like to talk about this method in digest.

try! Swift Tokyo 2019
https://www.tryswift.co/events/2019/tokyo/en/

151a0b14f5914e786e2e104cfb3a9b2f?s=128

Kuniwak

March 22, 2019
Tweet

Transcript

  1. How to Use 4XJGU4ZOUBY
 for Better Productivity Kuniwak - DeNA

    Co., Ltd. March 22, 2019 - try! Swift Tokyo 2019
  2. 2 Some code will be presented in the slides,
 you

    can see the slides and the code here: ൃදதʹίʔυ͕ग़͖ͯ·͕͢ɺಡΈͮΒ͍৔߹͸
 ҎԼ͔ΒεϥΠυΛ͝ཡ͍ͩ͘͞ɿ speakerdeck.com/orgachem
  3. About me

  4. ,VOJXBL • Software Engineer in Test • github.com/Kuniwak • qiita.com/Kuniwak

    • Favorites: testing/parsing • IDE: AppCode 4
  5. What I want to share
 in these 5 minutes

  6. 6TJOH4XJGU4ZOUBY we can easily do Linting and Code Generation 6

  7. What's SwiftSyntax?

  8. 4XJGU4ZOUBYis a library developed by Apple github.com/apple/swift-syntax 8

  9. 2 things Swift- Syntax can do

  10. Readable/writable Data Convert Readable/Writable data into code   Source

    Code Readable/writable Data 10 Source Code Convert code into Readable/Writable data
  11. Readable/writable Data  Convert Readable/Writable data into code  Source

    Code Readable/writable Data 11 Source Code Convert code into Readable/Writable data
  12. Convert code into Readable/Writable data  if let self =

    self {} 12 This is an example code to explain
  13.  if let self = self {} If statement 13

    This is an "if" statement Convert code into Readable/Writable data
  14.  if let self = self {} if let self

    = self {} If statement Body Condition 14 This if statements has
 2 components Convert code into Readable/Writable data
  15.  if let self = self {} if let self

    = self {} If statement Body Option binding condition 15 In this case, the Condition is
 the "Option binding condition" Convert code into Readable/Writable data
  16.  if let self = self {} if let self

    = self {} If statement Body Option binding condition Identifier
 expression Identifier
 pattern if let self = self {} 16 This option binding condition has
 2 components: "Identifier pattern" and
 "Identifier expression" Convert code into Readable/Writable data
  17.  if let self = self {} if let self

    = self {} if let self = self {} 17 If we look at the components,
 we can see we're traversing
 a tree through our code Convert code into Readable/Writable data
  18.  if let self = self {} if let self

    = self {} if let self = self {} 18 ෳจ If statement Body Identifier pattern Identifier expression Option binding condition Convert code into Readable/Writable data
  19.  19 if let self = self {} if let

    self = self {} If statement Body Option binding condition Identifier
 expression Identifier
 pattern if let self = self {} If statement Body Identifier pattern Identifier expression Option binding condition Convert code into Readable/Writable data This is cleed a "Syntax Tree"
  20. Reading a Syntax Tree
 using SwiftSyntax

  21. Reading a Syntax Tree using 4XJGU4ZOUBY  if let self

    = self {} if let self = self {} If statement Body Option binding condition Identifier
 expression Identifier
 pattern if let self = self {} Let's read its name! 21
  22. import SwiftSyntax let url = URL(fileURLWithPath: "example.swift") let source =

    try String(contentsOf: url) let syntax = try SyntaxParser.parse(source: source) Importing SwiftSyntax, creating the syntax tree that we need for reading the code and 22
  23. if let self = self {} if let self =

    self {} If statement Body Option binding condition Identifier
 expression Identifier
 pattern if let self = self {} ifStmt  Getting the if statement is
 bitty complicated. Thus it'll
 be explained later 23 Reading a Syntax Tree using 4XJGU4ZOUBY
  24. if let self = self {} if let self =

    self {} If statement Body Option binding condition Identifier
 expression Identifier
 pattern if let self = self {} ifStmt .conditions[0] .condition 
  24 Reading a Syntax Tree using 4XJGU4ZOUBY
  25. if let self = self {} if let self =

    self {} If statement Body Option binding condition Identifier
 expression Identifier
 pattern if let self = self {} "self" ifStmt .conditions[0] .condition .pattern
 .identifier .text  25 Reading a Syntax Tree using 4XJGU4ZOUBY
  26. let name = ifStmt .conditions[0] .condition .pattern
 .identifier .text if

    name == "this" { print("Oops!") } For example, this is a Lint to
 check whether variable names
 follow your guideline Now, we can implement our Lint easily  26
  27. import SwiftSyntax let url = URL(fileURLWithPath: "example.swift") let source =

    try String(contentsOf: url) let syntax = try SyntaxParser.parse(source: source)
 let stmts = Array(syntax.statements)
 guard let ifStmt = stmts.first?.item as? IfStmtSyntax else { print("If statement not found at the position.") return } You can see how to get the if statement using dump() the syntax tree How to get the If statement 27 Skipped in 
 presentation 

  28. Readable/writable Data Convert Readable/Writable data into code Convert code into

    Readable/Writable data   Source Code Readable/writable Data 28 Source Code DONE
  29. Readable/writable Data Convert Readable/Writable data into code Convert code into

    Readable/Writable data   Source Code Readable/writable Data 29 Source Code DONE
  30. if let this = self {} Generating fixed code using

    4XJGU4ZOUBY  if let self = self {} 30
  31. import SwiftSyntax class RenameRewriter: SyntaxRewriter { override func visit(_ node:

    IdentifierPatternSyntax) -> PatternSyntax { if node.identifier.text == "this" { var modifiedNode = node modifiedNode.identifier = SyntaxFactory .makeIdentifier("self") return modifiedNode } return node } } let syntax = try SyntaxParser.parse(source: String(contentsOf: url)) print(RenameRewriter().visit(syntax)) 31
  32. import SwiftSyntax class RenameRewriter: SyntaxRewriter { override func visit(_ node:

    IdentifierPatternSyntax) -> PatternSyntax { if node.identifier.text == "this" { var modifiedNode = node modifiedNode.identifier = SyntaxFactory .makeIdentifier("self") return modifiedNode } return node } } let syntax = try SyntaxParser.parse(source: String(contentsOf: url)) print(RenameRewriter().visit(syntax)) Use SwiftSyntax 32
  33. import SwiftSyntax class RenameRewriter: SyntaxRewriter { override func visit(_ node:

    IdentifierPatternSyntax) -> PatternSyntax { if node.identifier.text == "this" { var modifiedNode = node modifiedNode.identifier = SyntaxFactory .makeIdentifier("self") return modifiedNode } return node } } let syntax = try SyntaxParser.parse(source: String(contentsOf: url)) print(RenameRewriter().visit(syntax)) Get a syntax tree using SwiftSyntax 33
  34. import SwiftSyntax class RenameRewriter: SyntaxRewriter { override func visit(_ node:

    IdentifierPatternSyntax) -> PatternSyntax { if node.identifier.text == "this" { var modifiedNode = node modifiedNode.identifier = SyntaxFactory .makeIdentifier("self") return modifiedNode } return node } } let syntax = try SyntaxParser.parse(source: String(contentsOf: url)) print(RenameRewriter().visit(syntax)) Rewrite the syntax tree and print it 34
  35. import SwiftSyntax class RenameRewriter: SyntaxRewriter { override func visit(_ node:

    IdentifierPatternSyntax) -> PatternSyntax { if node.identifier.text == "this" { var modifiedNode = node modifiedNode.identifier = SyntaxFactory .makeIdentifier("self") return modifiedNode } return node } } let syntax = try SyntaxParser.parse(source: String(contentsOf: url)) print(RenameRewriter().visit(syntax)) A class for rewriting syntax trees 35
  36. import SwiftSyntax class RenameRewriter: SyntaxRewriter { override func visit(_ node:

    IdentifierPatternSyntax) -> PatternSyntax { if node.identifier.text == "this" { var modifiedNode = node modifiedNode.identifier = SyntaxFactory .makeIdentifier("self") return modifiedNode } return node } } let syntax = try SyntaxParser.parse(source: String(contentsOf: url)) print(RenameRewriter().visit(syntax)) Use the override method to rewrite the node In this case, we want to rewrite the 9in "if let 9 = ..."
 (we can see the type of 9 by using dump) 36
  37. import SwiftSyntax class RenameRewriter: SyntaxRewriter { override func visit(_ node:

    IdentifierPatternSyntax) -> PatternSyntax { if node.identifier.text == "this" { var modifiedNode = node modifiedNode.identifier = SyntaxFactory .makeIdentifier("self") return modifiedNode } return node } } let syntax = try SyntaxParser.parse(source: String(contentsOf: url)) print(RenameRewriter().visit(syntax)) Rewrite the syntax tree if it says "if let this= ..." 37
  38. import SwiftSyntax class RenameRewriter: SyntaxRewriter { override func visit(_ node:

    IdentifierPatternSyntax) -> PatternSyntax { if node.identifier.text == "this" { var modifiedNode = node modifiedNode.identifier = SyntaxFactory .makeIdentifier("self") return modifiedNode } return node } } let syntax = try SyntaxParser.parse(source: String(contentsOf: url)) print(RenameRewriter().visit(syntax)) Overwrite with "self" 38
  39. import SwiftSyntax class RenameRewriter: SyntaxRewriter { override func visit(_ node:

    IdentifierPatternSyntax) -> PatternSyntax { if node.identifier.text == "this" { var modifiedNode = node modifiedNode.identifier = SyntaxFactory .makeIdentifier("self") return modifiedNode } return node } } let syntax = try SyntaxParser.parse(source: String(contentsOf: url)) print(RenameRewriter().visit(syntax)) 39
  40. import SwiftSyntax class RenameRewriter: SyntaxRewriter { override func visit(_ node:

    IdentifierPatternSyntax) -> PatternSyntax { if node.identifier.text == "this" { var modifiedNode = node modifiedNode.identifier = SyntaxFactory .makeIdentifier("self") return modifiedNode } return node } } let syntax = try SyntaxParser.parse(source: String(contentsOf: url)) print(RenameRewriter().visit(syntax)) "if let self= self {}" 40 We got the fixed code!
  41. import SwiftSyntax class RenameRewriter: SyntaxRewriter { override func visit(_ node:

    IdentifierPatternSyntax) -> PatternSyntax { if node.identifier.text == "this" { var modifiedNode = node modifiedNode.identifier = SyntaxFactory .makeIdentifier("self") return modifiedNode } return node } } let syntax = try SyntaxParser.parse(source: String(contentsOf: url)) print(RenameRewriter().visit(syntax)) "if let self= self {}" 41 But a space after "self" is missing...
  42. import SwiftSyntax class RenameRewriter: SyntaxRewriter { override func visit(_ node:

    IdentifierPatternSyntax) -> PatternSyntax { if node.identifier.text == "this" { var modifiedNode = node let originalIdentifier = node.identifier modifiedNode.identifier = SyntaxFactory .makeIdentifier("self") .withTrailingTrivia(originalIdentifier.trailingTrivia) return modifiedNode } return node } } let syntax = try SyntaxParser.parse(source: String(contentsOf: url)) print(RenameRewriter().visit(syntax)) We can restore the space by using Trivias Skipped in 
 presentation 
 42
  43. if let this = self {}  if let self

    = self {} 43 DONE Generating fixed code using 4XJGU4ZOUBY
  44. Readable/writable Data Convert Readable/Writable data into code Convert code into

    Readable/Writable data   Source Code Readable/writable Data 44 Source Code DONE DONE
  45. We implemented a Lint and Code Generator by using 4XJGU4ZOUBY!

    Conclusion 45