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

How to Use SwiftSyntax for Better Productivity

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/

Kuniwak

March 22, 2019
Tweet

More Decks by Kuniwak

Other Decks in Programming

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. Readable/writable Data Convert Readable/Writable data into code   Source

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

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

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

    This is an "if" statement Convert code into Readable/Writable data
  7.  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
  8.  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
  9.  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
  10.  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
  11.  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
  12.  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"
  13. 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
  14. 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
  15. 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
  16. 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
  17. 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
  18. 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
  19. 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 

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

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

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

    4XJGU4ZOUBY  if let self = self {} 30
  23. 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
  24. 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
  25. 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
  26. 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
  27. 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
  28. 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
  29. 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
  30. 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
  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)) 39
  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)) "if let self= self {}" 40 We got the fixed code!
  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)) "if let self= self {}" 41 But a space after "self" is missing...
  34. 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
  35. if let this = self {}  if let self

    = self {} 43 DONE Generating fixed code using 4XJGU4ZOUBY
  36. 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