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

Mastering SwiftSyntax

Mastering SwiftSyntax

- Introduction
- SwiftSyntax Overview
- Anatomy of Syntax Tree
- SwiftSyntax API
- Exercises
- Swift Macros
- SwiftLint
- Source Editor Extensions

Kishikawa Katsumi

September 01, 2023
Tweet

More Decks by Kishikawa Katsumi

Other Decks in Programming

Transcript

  1. .BTUFSJOH4XJGU4ZOUBY
    LJTIJLBXBLBUTVNJ
    !LJTIJLBXBLBUTVNJ!IBDIZEFSNJP
    LJTIJLBXBLBUTVNJ

    View Slide

  2. r 4XJGUιʔείʔυͷߏจղੳɺݕࠪɺॻ͖׵͑ɺιʔείʔυͷੜ੒Λ͓͜ͳ
    ͏4XJGUϥΠϒϥϦ
    r 4XJGUϓϩδΣΫτͷҰ෦ͱͯ͠"QQMF͔Βఏڙ͞Ε͍ͯΔެࣜπʔϧ
    IUUQTHJUIVCDPNBQQMFTXJGUTZOUBY
    r Φʔϓϯιʔειϑτ΢ΣΞ
    8IBUJT4XJGU4ZOUBY
    4XJGU4ZOUBYͱ͸Կ͔

    View Slide

  3. r SFBMN4XJGU-JOU
    IUUQTHJUIVCDPNSFBMN4XJGU-JOU
    r BQQMFTXJGUGPSNBU
    IUUQTHJUIVCDPNBQQMFTXJGUGPSNBU
    r LS[ZT[UPG[BCMPDLJ4PVSDFSZ
    IUUQTHJUIVCDPNLS[ZT[UPG[BCMPDLJ4PVSDFSZ
    r VCFSNPDLPMP
    IUUQTHJUIVCDPNVCFSNPDLPMP
    8IPVTF4XJGU4ZOUBY
    4XJGU4ZOUBYͷར༻ྫ

    View Slide

  4. r 4XJGU͔Βಋೖ༧ఆ
    r ίϯύΠϧ࣌ʹίʔυͷੜ੒Λͨ͠Γ
    ಠࣗͷݕࠪΛ௥ՃͰ͖Δ
    r ϚΫϩͷ࣮૷ʹ͸4XJGU4ZOUBYΛ࢖༻
    ͢Δ
    r ϚΫϩͷྫ#Previewɺ
    @Observableɺ@ModelͳͲ
    4XJGU.BDSPT
    4XJGUϚΫϩ

    View Slide

  5. r *OUSPEVDUJPO
    r 4XJGU4ZOUBY0WFSWJFX
    r "OBUPNZPG4ZOUBY5SFF
    r 4XJGU4ZOUBY"1*
    r &YFSDJTFT
    ‣ 4XJGU.BDSPT
    ‣ 4XJGU-JOU
    ‣ 4PVSDF&EJUPS&YUFOTJPOT
    0VUMJOF
    ໨࣍

    View Slide

  6. 4XJGU4ZOUBY0WFSWJFX

    View Slide

  7. let number = 100
    SourceFileSyntax
    CodeBlockItemListSyntax
    CodeBlockItemSyntax
    VariableDeclSyntax
    PatternBindingListSyntax
    PatternBindingSyntax
    IdentifierPatternSyntax
    InitializerClauseSyntax
    IntegerLiteralExprSyntax
    endOfFile
    number
    =
    100
    let
    4ZOUBY5SFF

    View Slide

  8. let sourceFile = Parser.parse(source: "let number = 100")
    print(sourceFile.debugDescription)
    import SwiftParser
    4XJGU1BSTFS
    4PVSDF$PEFUP4ZOUBY5SFF

    View Slide

  9. SourceFileSyntax
    ├─statements: CodeBlockItemListSyntax
    │ ╰─[0]: CodeBlockItemSyntax
    │ ╰─item: VariableDeclSyntax
    │ ├─attributes: AttributeListSyntax
    │ ├─modifiers: DeclModifierListSyntax
    │ ├─bindingSpecifier: keyword(SwiftSyntax.Keyword.let)
    │ ╰─bindings: PatternBindingListSyntax
    │ ╰─[0]: PatternBindingSyntax
    │ ├─pattern: IdentifierPatternSyntax
    │ │ ╰─identifier: identifier("number")
    │ ╰─initializer: InitializerClauseSyntax
    │ ├─equal: equal
    │ ╰─value: IntegerLiteralExprSyntax
    │ ╰─literal: integerLiteral("100")
    ╰─endOfFileToken: endOfFile
    let sourceFile = Parser.parse(source: "let number = 100")
    print(sourceFile.debugDescription) // 509.0.0 or later

    View Slide

  10. let number = 100 let amount = 100
    3FXSJUF4PVSDF$PEF
    3FQMBDF4ZOUBY5SFF/PEFXJUI/FX/PEF

    View Slide

  11. SourceFileSyntax
    CodeBlockItemListSyntax
    CodeBlockItemSyntax
    VariableDeclSyntax
    PatternBindingListSyntax
    PatternBindingSyntax
    IdentifierPatternSyntax
    InitializerClauseSyntax
    IntegerLiteralExprSyntax
    endOfFile
    number
    =
    100
    let
    SourceFileSyntax
    CodeBlockItemListSyntax
    CodeBlockItemSyntax
    VariableDeclSyntax
    PatternBindingListSyntax
    PatternBindingSyntax
    IdentifierPatternSyntax
    InitializerClauseSyntax
    IntegerLiteralExprSyntax
    endOfFile
    number
    =
    100
    let

    View Slide

  12. SourceFileSyntax
    CodeBlockItemListSyntax
    CodeBlockItemSyntax
    VariableDeclSyntax
    PatternBindingListSyntax
    PatternBindingSyntax
    IdentifierPatternSyntax
    InitializerClauseSyntax
    IntegerLiteralExprSyntax
    endOfFile
    number
    =
    100
    let
    SourceFileSyntax
    CodeBlockItemListSyntax
    CodeBlockItemSyntax
    VariableDeclSyntax
    PatternBindingListSyntax
    PatternBindingSyntax
    IdentifierPatternSyntax
    InitializerClauseSyntax
    IntegerLiteralExprSyntax
    endOfFile
    number
    =
    100
    let

    View Slide

  13. SourceFileSyntax
    CodeBlockItemListSyntax
    CodeBlockItemSyntax
    VariableDeclSyntax
    PatternBindingListSyntax
    PatternBindingSyntax
    IdentifierPatternSyntax
    InitializerClauseSyntax
    IntegerLiteralExprSyntax
    endOfFile
    amount
    =
    100
    let
    SourceFileSyntax
    CodeBlockItemListSyntax
    CodeBlockItemSyntax
    VariableDeclSyntax
    PatternBindingListSyntax
    PatternBindingSyntax
    IdentifierPatternSyntax
    InitializerClauseSyntax
    IntegerLiteralExprSyntax
    endOfFile
    number
    =
    100
    let

    View Slide

  14. 4ZOUBY5SFFΛஔ͖׵͍͑ͨϊʔυ·ͰͨͲΔ
    ॻ͖׵͑ͨ݁ՌΛද͢৽͍͠ϊʔυΛ༻ҙ͢Δ
    ৽͍͠ϊʔυͰஔ͖׵͍͑ͨϊʔυΛஔ׵͢Δ
    4UFQTUP3FXSJUF4PVSDF$PEF
    ιʔείʔυͷॻ͖׵͑ʹඞཁͳखॱ

    View Slide

  15. let number = 100
    SourceFileSyntax
    CodeBlockItemListSyntax
    CodeBlockItemSyntax
    VariableDeclSyntax
    PatternBindingListSyntax
    PatternBindingSyntax
    IdentifierPatternSyntax
    InitializerClauseSyntax
    IntegerLiteralExprSyntax
    endOfFile
    number
    =
    100
    let

    View Slide

  16. let number = 100
    SourceFileSyntax
    CodeBlockItemListSyntax
    CodeBlockItemSyntax
    VariableDeclSyntax
    PatternBindingListSyntax
    PatternBindingSyntax
    IdentifierPatternSyntax
    InitializerClauseSyntax
    IntegerLiteralExprSyntax
    endOfFile
    number
    =
    100
    let

    View Slide

  17. let number = 100
    SourceFileSyntax
    CodeBlockItemListSyntax
    CodeBlockItemSyntax
    VariableDeclSyntax
    PatternBindingListSyntax
    PatternBindingSyntax
    IdentifierPatternSyntax
    InitializerClauseSyntax
    IntegerLiteralExprSyntax
    endOfFile
    number
    =
    100
    let

    View Slide

  18. πϦʔߏ଄ΛԼํ޲ʹͨͲΔ
    SourceFileSyntax
    ├─statements
    │ ╰─[0]
    │ ╰─item
    │ ├─attributes
    │ ├─modifiers
    │ ├─bindingSpecifier
    │ ╰─bindings
    │ ╰─[0]
    │ ├─pattern
    │ │ ╰─identifier: "number"
    │ ╰─initializer
    │ ├─equal: equal
    │ ╰─value
    │ ╰─literal: "100"
    ╰─endOfFileToken
    let sourceFile =
    Parser.parse(source: "let number = 100")

    View Slide

  19. SourceFileSyntax
    ├─statements
    │ ╰─[0]
    │ ╰─item
    │ ├─attributes
    │ ├─modifiers
    │ ├─bindingSpecifier
    │ ╰─bindings
    │ ╰─[0]
    │ ├─pattern
    │ │ ╰─identifier: "number"
    │ ╰─initializer
    │ ├─equal: equal
    │ ╰─value
    │ ╰─literal: "100"
    ╰─endOfFileToken
    let statements = sourceFile.statements
    SourceFileSyntax

    View Slide

  20. let statements =
    Array(sourceFile.statements) SourceFileSyntax
    ├─statements
    │ ╰─[0]
    │ ╰─item
    │ ├─attributes
    │ ├─modifiers
    │ ├─bindingSpecifier
    │ ╰─bindings
    │ ╰─[0]
    │ ├─pattern
    │ │ ╰─identifier: "number"
    │ ╰─initializer
    │ ├─equal: equal
    │ ╰─value
    │ ╰─literal: "100"
    ╰─endOfFileToken

    View Slide

  21. let statements =
    Array(sourceFile.statements)
    let codeBlock = statements[0]
    CodeBlockItemSyntax
    SourceFileSyntax
    ├─statements
    │ ╰─[0]
    │ ╰─item
    │ ├─attributes
    │ ├─modifiers
    │ ├─bindingSpecifier
    │ ╰─bindings
    │ ╰─[0]
    │ ├─pattern
    │ │ ╰─identifier: "number"
    │ ╰─initializer
    │ ├─equal: equal
    │ ╰─value
    │ ╰─literal: "100"
    ╰─endOfFileToken

    View Slide

  22. let statements =
    Array(sourceFile.statements)
    let codeBlock = statements[0]
    let item = codeBlock.item
    SourceFileSyntax
    ├─statements
    │ ╰─[0]
    │ ╰─item
    │ ├─attributes
    │ ├─modifiers
    │ ├─bindingSpecifier
    │ ╰─bindings
    │ ╰─[0]
    │ ├─pattern
    │ │ ╰─identifier: "number"
    │ ╰─initializer
    │ ├─equal: equal
    │ ╰─value
    │ ╰─literal: "100"
    ╰─endOfFileToken

    View Slide

  23. SourceFileSyntax
    ├─statements
    │ ╰─[0]
    │ ╰─item
    │ ├─attributes
    │ ├─modifiers
    │ ├─bindingSpecifier
    │ ╰─bindings
    │ ╰─[0]
    │ ├─pattern
    │ │ ╰─identifier: "number"
    │ ╰─initializer
    │ ├─equal: equal
    │ ╰─value
    │ ╰─literal: "100"
    ╰─endOfFileToken
    let statements =
    Array(sourceFile.statements)
    let codeBlock = statements[0]
    let item = codeBlock.item
    print(item.syntaxNodeType)
    print(item.syntaxNodeType)

    View Slide

  24. let statements =
    Array(sourceFile.statements)
    let codeBlock = statements[0]
    let item = codeBlock.item
    print(item.syntaxNodeType)
    SourceFileSyntax
    ├─statements
    │ ╰─[0]
    │ ╰─item
    │ ├─attributes
    │ ├─modifiers
    │ ├─bindingSpecifier
    │ ╰─bindings
    │ ╰─[0]
    │ ├─pattern
    │ │ ╰─identifier: "number"
    │ ╰─initializer
    │ ├─equal: equal
    │ ╰─value
    │ ╰─literal: "100"
    ╰─endOfFileToken
    VariableDeclSyntax

    View Slide

  25. SourceFileSyntax
    ├─statements
    │ ╰─[0]
    │ ╰─item
    │ ├─attributes
    │ ├─modifiers
    │ ├─bindingSpecifier
    │ ╰─bindings
    │ ╰─[0]
    │ ├─pattern
    │ │ ╰─identifier: "number"
    │ ╰─initializer
    │ ├─equal: equal
    │ ╰─value
    │ ╰─literal: "100"
    ╰─endOfFileToken
    let statements =
    Array(sourceFile.statements)
    let codeBlock = statements[0]
    let item = codeBlock.item
    if let decl =
    item.as(VariableDeclSyntax.self) {
    }
    item.as(VariableDeclSyntax.self)

    View Slide

  26. SourceFileSyntax
    ├─statements
    │ ╰─[0]
    │ ╰─item
    │ ├─attributes
    │ ├─modifiers
    │ ├─bindingSpecifier
    │ ╰─bindings
    │ ╰─[0]
    │ ├─pattern
    │ │ ╰─identifier: "number"
    │ ╰─initializer
    │ ├─equal: equal
    │ ╰─value
    │ ╰─literal: "100"
    ╰─endOfFileToken
    let statements =
    Array(sourceFile.statements)
    let codeBlock = statements[0]
    let item = codeBlock.item
    if let decl =
    item.as(VariableDeclSyntax.self) {
    let bindings = Array(decl.bindings)
    }

    View Slide

  27. let statements =
    Array(sourceFile.statements)
    let codeBlock = statements[0]
    let item = codeBlock.item
    if let decl =
    item.as(VariableDeclSyntax.self) {
    let bindings = Array(decl.bindings)
    let binding = bindings[0]
    }
    SourceFileSyntax
    ├─statements
    │ ╰─[0]
    │ ╰─item
    │ ├─attributes
    │ ├─modifiers
    │ ├─bindingSpecifier
    │ ╰─bindings
    │ ╰─[0]
    │ ├─pattern
    │ │ ╰─identifier: "number"
    │ ╰─initializer
    │ ├─equal: equal
    │ ╰─value
    │ ╰─literal: "100"
    ╰─endOfFileToken

    View Slide

  28. let statements =
    Array(sourceFile.statements)
    let codeBlock = statements[0]
    let item = codeBlock.item
    if let decl =
    item.as(VariableDeclSyntax.self) {
    let bindings = Array(decl.bindings)
    let binding = bindings[0]
    if let pattern = binding.pattern.as(
    IdentifierPatternSyntax.self
    ) {
    }
    }
    SourceFileSyntax
    ├─statements
    │ ╰─[0]
    │ ╰─item
    │ ├─attributes
    │ ├─modifiers
    │ ├─bindingSpecifier
    │ ╰─bindings
    │ ╰─[0]
    │ ├─pattern
    │ │ ╰─identifier: "number"
    │ ╰─initializer
    │ ├─equal: equal
    │ ╰─value
    │ ╰─literal: "100"
    ╰─endOfFileToken

    View Slide

  29. let statements =
    Array(sourceFile.statements)
    let codeBlock = statements[0]
    let item = codeBlock.item
    if let decl =
    item.as(VariableDeclSyntax.self) {
    let bindings = Array(decl.bindings)
    let binding = bindings[0]
    if let pattern = binding.pattern.as(
    IdentifierPatternSyntax.self
    ) {
    let identifier = pattern.identifier
    }
    }
    SourceFileSyntax
    ├─statements
    │ ╰─[0]
    │ ╰─item
    │ ├─attributes
    │ ├─modifiers
    │ ├─bindingSpecifier
    │ ╰─bindings
    │ ╰─[0]
    │ ├─pattern
    │ │ ╰─identifier: "number"
    │ ╰─initializer
    │ ├─equal: equal
    │ ╰─value
    │ ╰─literal: "100"
    ╰─endOfFileToken
    if let pattern = binding.pattern.as(
    IdentifierPatternSyntax.self
    ) {
    let identifier = pattern.identifier
    }

    View Slide

  30. let number = 100
    SourceFileSyntax
    CodeBlockItemListSyntax
    CodeBlockItemSyntax
    VariableDeclSyntax
    PatternBindingListSyntax
    PatternBindingSyntax
    IdentifierPatternSyntax
    InitializerClauseSyntax
    IntegerLiteralExprSyntax
    endOfFile
    number
    =
    100
    let

    View Slide

  31. let number = 100
    SourceFileSyntax
    CodeBlockItemListSyntax
    CodeBlockItemSyntax
    VariableDeclSyntax
    PatternBindingListSyntax
    PatternBindingSyntax
    IdentifierPatternSyntax
    InitializerClauseSyntax
    IntegerLiteralExprSyntax
    endOfFile
    number
    =
    100
    let

    View Slide

  32. if let number = number {
    }
    ؔ਺ͷҾ਺
    ϩʔΧϧม਺ 0QUJPOBM#JOEJOH
    func () {
    let number = 0
    }
    CodeBlockItem
    PatternBindingListSyntax
    PatternBindingSyntax
    IdentifierPatternSyntax
    number
    VariableDeclSyntax OptionalBindingConditionSyntax
    let
    IdentifierPatternSyntax
    number
    ConditionElementSyntax
    func round(number: Int) {
    ...
    }
    FunctionParameterSyntax
    number
    FunctionParameterListSyntax
    πϦʔͷܗΛߏ଄తʹϚονϯά͢Δ

    View Slide

  33. 4XJGU"45&YQMPSFS
    IUUQTTXJGUBTUFYQMPSFSDPN

    View Slide

  34. 4XJGU"45&YQMPSFS
    IUUQTTXJGUBTUFYQMPSFSDPN

    View Slide

  35. View Slide

  36. πϦʔߏ଄Λ্ํ޲ʹͨͲΔ
    if let pattern = identifier.parent?.as(
    IdentifierPatternSyntax.self
    ) {
    if let binding = pattern.parent?.as(
    PatternBindingSyntax.self
    ) {
    }
    }
    SourceFileSyntax
    ├─statements
    │ ╰─[0]
    │ ╰─item
    │ ├─attributes
    │ ├─modifiers
    │ ├─bindingSpecifier
    │ ╰─bindings
    │ ╰─[0]
    │ ├─pattern
    │ │ ╰─identifier: "number"
    │ ╰─initializer
    │ ├─equal: equal
    │ ╰─value
    │ ╰─literal: "100"
    ╰─endOfFileToken

    View Slide

  37. ιʔείʔυͷॻ͖׵͑
    identifier.with(\.tokenKind, .identifier("amount"))

    View Slide

  38. ιʔείʔυͷॻ͖׵͑
    identifier.with(\.tokenKind, .identifier("amount"))
    amount
    number

    View Slide

  39. identifier.with(\.tokenKind, .identifier("amount"))
    IdentifierPatternSyntax
    number
    IdentifierPatternSyntax
    amount
    ผͷ৽͍͠5SFFΛฦ͢

    View Slide

  40. let result = decl.with(
    \.bindings, [
    binding.with(
    \.pattern, PatternSyntax(
    pattern.with(
    \.identifier, identifier.with(
    \.tokenKind, .identifier("amount")
    )
    )
    )
    )
    ]
    )
    let number = 100 let amount = 100

    View Slide

  41. let result = decl.with(
    \.bindings, [
    binding.with(
    \.pattern, PatternSyntax(
    pattern.with(
    \.identifier, identifier.with(
    \.tokenKind, .identifier("amount")
    )
    )
    )
    )
    ]
    )
    let number = 100 let amount = 100
    ݩͷཁૉ
    ݩͷཁૉ
    ݩͷཁૉ
    ͜ͷཁૉ͚ͩม͑Δ

    View Slide

  42. print(result.description)
    let amount = 100
    4ZOUBY5SFF͔ΒιʔείʔυΛੜ੒

    View Slide

  43. let number = 100
    WT3FHVMBS&YQSFTTJPO

    View Slide

  44. let number = 100
    WT3FHVMBS&YQSFTTJPO
    /\s*let\s+(number)\s*/

    View Slide

  45. let /* let number defines a constant. */ number = 100
    WT3FHVMBS&YQSFTTJPO
    /\s*let\s+(number)\s*/

    View Slide

  46. let /* let number defines a constant. */ number = 100
    WT3FHVMBS&YQSFTTJPO
    /\s*let\s+(number)\s*/

    View Slide

  47. VariableDeclSyntax
    ├─attributes: AttributeListSyntax
    ├─modifiers: DeclModifierListSyntax
    ├─bindingSpecifier: keyword(SwiftSyntax.Keyword.let)
    ╰─bindings: PatternBindingListSyntax
    ╰─[0]: PatternBindingSyntax
    ├─pattern: IdentifierPatternSyntax
    │ ╰─identifier: identifier("number")
    ╰─initializer: InitializerClauseSyntax
    ├─equal: equal
    ╰─value: IntegerLiteralExprSyntax
    ╰─literal: integerLiteral("100")
    let /* let number defines a constant. */ number = 100
    print(sourceFile.debugDescription)

    View Slide

  48. VariableDeclSyntax
    ├─attributes: AttributeListSyntax
    ├─modifiers: DeclModifierListSyntax
    ├─bindingSpecifier: keyword(SwiftSyntax.Keyword.let)
    trailingTrivia=[spaces(1), blockComment("..."), spaces(1)]
    ╰─bindings: PatternBindingListSyntax
    ╰─[0]: PatternBindingSyntax
    ├─pattern: IdentifierPatternSyntax
    │ ╰─identifier: identifier("number") trailingTrivia=spaces(1)
    ╰─initializer: InitializerClauseSyntax
    ├─equal: equal trailingTrivia=spaces(1)
    ╰─value: IntegerLiteralExprSyntax
    ╰─literal: integerLiteral("100")
    print(sourceFile.debugDescription(includeTrivia: true))
    let /* let number defines a constant. */ number = 100

    View Slide

  49. VariableDeclSyntax
    ├─attributes: AttributeListSyntax
    ├─modifiers: DeclModifierListSyntax
    ├─bindingSpecifier: keyword(SwiftSyntax.Keyword.let)
    trailingTrivia=[spaces(1), blockComment("..."), spaces(1)]
    ╰─bindings: PatternBindingListSyntax
    ╰─[0]: PatternBindingSyntax
    ├─pattern: IdentifierPatternSyntax
    │ ╰─identifier: identifier("number") trailingTrivia=spaces(1)
    ╰─initializer: InitializerClauseSyntax
    ├─equal: equal trailingTrivia=spaces(1)
    ╰─value: IntegerLiteralExprSyntax
    ╰─literal: integerLiteral("100")
    print(sourceFile.debugDescription(includeTrivia: true))
    let /* let number defines a constant. */ number = 100

    View Slide

  50. VariableDeclSyntax
    ├─attributes: AttributeListSyntax
    ├─modifiers: DeclModifierListSyntax
    ├─bindingSpecifier: keyword(SwiftSyntax.Keyword.let)
    trailingTrivia=[spaces(1), blockComment("..."), spaces(1)]
    ╰─bindings: PatternBindingListSyntax
    ╰─[0]: PatternBindingSyntax
    ├─pattern: IdentifierPatternSyntax
    │ ╰─identifier: identifier("number") trailingTrivia=spaces(1)
    ╰─initializer: InitializerClauseSyntax
    ├─equal: equal trailingTrivia=spaces(1)
    ╰─value: IntegerLiteralExprSyntax
    ╰─literal: integerLiteral("100")
    print(sourceFile.debugDescription(includeTrivia: true))
    let /* let number defines a constant. */ number = 100

    View Slide

  51. CodeBlockItem
    PatternBindingListSyntax
    PatternBindingSyntax
    IdentifierPatternSyntax
    number
    VariableDeclSyntax
    let number = 100
    3PVOE5SJQ4ZOUBY5SFF

    View Slide

  52. SourceFileSyntax
    CodeBlockItemListSyntax
    CodeBlockItemSyntax
    VariableDeclSyntax
    PatternBindingListSyntax
    PatternBindingSyntax
    IdentifierPatternSyntax
    InitializerClauseSyntax
    IntegerLiteralExprSyntax
    endOfFile
    number
    =
    100
    let
    let number = 100

    View Slide

  53. @nonobjc
    public let number: Int = 100
    SourceFileSyntax
    CodeBlockItemListSyntax
    CodeBlockItemSyntax
    VariableDeclSyntax
    PatternBindingListSyntax
    PatternBindingSyntax
    IdentifierPatternSyntax
    InitializerClauseSyntax
    IntegerLiteralExprSyntax
    endOfFile
    number
    =
    100
    let
    AttributeListSyntax
    @
    IdentifierTypeSyntax
    nonobjc
    DeclModifierListSyntax
    DeclModifierSyntax
    public

    View Slide

  54. @nonobjc
    public let number: Int = 100
    SourceFileSyntax
    CodeBlockItemListSyntax
    CodeBlockItemSyntax
    VariableDeclSyntax
    PatternBindingListSyntax
    PatternBindingSyntax
    IdentifierPatternSyntax
    InitializerClauseSyntax
    IntegerLiteralExprSyntax
    endOfFile
    number
    =
    100
    let
    AttributeListSyntax
    @
    IdentifierTypeSyntax
    nonobjc
    DeclModifierListSyntax
    DeclModifierSyntax
    public

    View Slide

  55. 8BZTUP1SPEVDF4ZOUBY5SFF
    r 4XJGU1BSTFS
    r ϊʔυͷΠχγϟϥΠβ
    r 4ZOUBY#VJMEFS
    ˞ཁ4XJGU4ZOUBY#VJMEFS
    ‣ 3FTVMU#VJMEFS
    ‣ 4USJOH*OUFSQPMBUJPO
    Parser.parse(source: "let number = 100")
    let tree = VariableDeclSyntax(
    .let,
    name: PatternSyntax(stringLiteral: "number"),
    initializer: InitializerClauseSyntax(
    value: IntegerLiteralExprSyntax(
    integerLiteral: 100
    )
    )
    )
    tree.formatted()
    let tree: DeclSyntax = "let number = 100"

    View Slide

  56. 4XJGU4ZOUBY0WFSWJFX
    r 1BSTFS͸ιʔείʔυ͔Β4ZOUBY5SFFΛੜ੒͢Δ
    r 4ZOUBY5SFF͸ιʔείʔυΛҰఆͷߏ଄ͱͯ͠ѻ͑Δ
    r ۭന΍վߦɺίϝϯτ͸5SJWJBͱͯ͠4ZOUBY5SFFΛߏ੒͢Δ
    r ಛఆͷมΘΒͳ͍ܗΛݟ͚ͭͯߏ଄తʹϚονϯά͢Δ
    r 4XJGU4ZOUBYΛ࢖ͬͯ4ZOUBY5SFFΛ෦෼తʹॻ͖׵͑ΒΕΔ

    View Slide

  57. "OBUPNZPG4ZOUBY5SFF

    View Slide

  58. SyntaxProtocol
    SourceFileSyntax
    Pr
    S DeclSyntax
    S CodeBlockItem
    S
    VariableDeclSyntax
    S

    View Slide

  59. SyntaxProtocol
    SourceFileSyntax
    Pr
    S DeclSyntax
    S CodeBlockItem
    S
    VariableDeclSyntax
    S

    View Slide

  60. SyntaxProtocol
    TokenSyntax
    Pr
    S DeclSynta
    S
    VariableDeclS
    S
    SourceFileSyntax
    S
    5PLFO τʔΫϯ
    4ZOUBY ߏจ

    View Slide

  61. SourceFileSyntax
    CodeBlockItemListSyntax
    CodeBlockItemSyntax
    VariableDeclSyntax
    PatternBindingListSyntax
    PatternBindingSyntax
    IdentifierPatternSyntax
    InitializerClauseSyntax
    IntegerLiteralExprSyntax
    endOfFile
    number
    =
    100
    let
    let number = 100
    5PLFO

    View Slide

  62. SourceFileSyntax
    CodeBlockItemListSyntax
    CodeBlockItemSyntax
    VariableDeclSyntax
    PatternBindingListSyntax
    PatternBindingSyntax
    IdentifierPatternSyntax
    InitializerClauseSyntax
    IntegerLiteralExprSyntax
    endOfFile
    number
    =
    100
    let
    let number = 100
    5PLFO

    View Slide

  63. SourceFileSyntax
    CodeBlockItemListSyntax
    CodeBlockItemSyntax
    VariableDeclSyntax
    PatternBindingListSyntax
    PatternBindingSyntax
    IdentifierPatternSyntax
    InitializerClauseSyntax
    IntegerLiteralExprSyntax
    endOfFile
    number
    =
    100
    let
    let number = 100
    5PLFO

    View Slide

  64. let number = 100
    5PLFO
    4ZOUBY5SFFͷ࠷খ୯Ґ

    View Slide

  65. 4ZOUBY/PEF
    ߏจͷछྨΛද͢
    let number = 100
    4PVSDF'JMF4ZOUBY
    7BSJBCMF%FDM4ZOUBY
    *EFOUJ
    fi
    FS1BUUFSO4ZOUBY

    View Slide

  66. if let decl = item.as(VariableDeclSyntax.self) {
    ...
    }
    if let decl = item as? VariableDeclSyntax {
    }
    ϊʔυͷܕΛม׵͢Δ
    ܕม׵ͷؔ਺Λ࢖͏

    View Slide

  67. ϊʔυͷܕΛม׵͢Δ
    if let decl = item.as(VariableDeclSyntax.self) { // == `as?` operator
    }
    if item.is(VariableDeclSyntax.self) { == `is` operator
    }
    let decl = item.cast(VariableDeclSyntax.self) // Force cast (== as!)
    let decl = VariableDeclSyntax(item) // == `as?` operator
    ܕม׵ͷؔ਺Λ࢖͏

    View Slide

  68. ϊʔυͷܕΛม׵͢Δ
    if let decl = item.as(VariableDeclSyntax.self) { // == `as?` operator
    }
    if item.is(VariableDeclSyntax.self) { == `is` operator
    }
    let decl = item.cast(VariableDeclSyntax.self) // Force cast (== as!)
    let decl = VariableDeclSyntax(item) // == `as?` operator
    ܕม׵ͷؔ਺Λ࢖͏

    View Slide

  69. ϊʔυͷܕΛม׵͢Δ
    if let decl = item.as(VariableDeclSyntax.self) { // == `as?` operator
    }
    if item.is(VariableDeclSyntax.self) { == `is` operator
    }
    let decl = item.cast(VariableDeclSyntax.self) // Force cast (== as!)
    let decl = VariableDeclSyntax(item) // == `as?` operator
    ܕม׵ͷؔ਺Λ࢖͏

    View Slide

  70. ϊʔυͷܕΛม׵͢Δ
    if let decl = item.as(VariableDeclSyntax.self) { // == `as?` operator
    }
    if item.is(VariableDeclSyntax.self) { == `is` operator
    }
    let decl = item.cast(VariableDeclSyntax.self) // Force cast (== as!)
    let decl = VariableDeclSyntax(item) // == `as?` operator
    ܕม׵ͷؔ਺Λ࢖͏

    View Slide

  71. r 4ZOUBY5SFFͷ֤ϊʔυ͸ͳΜΒ͔ͷ4ZOUBY 4ZOUBY1SPUPDPM

    r ۩ମతͳܕΛௐ΂Δ͜ͱͰߏจͷछྨ͕Θ͔Δ
    FH7BSJBCMF%FDM4ZOUBYɺ'VODUJPO$BMM&YQS4ZOUBYɺ'PS4UNU4ZOUBY
    r 4ZOUBYͷ࠷খ୯Ґ͸τʔΫϯ 5PLFO4ZOUBY

    r πϦʔߏ଄ΛԼํ޲ʹͨͲΔͱ͖͸ܕͷϓϩύςΟ͕࢖͑Δ
    FHsourceFile.statementsɺpattern.identifier
    ˞ෳ਺ͷܕͷީิ͕͋Δ৔߹͸μ΢ϯΩϟετ͕ඞཁ
    r ্ํ޲ʹཁૉΛͨͲΔͱ͖͸దٓμ΢ϯΩϟετ͢Δ
    "OBUPNZPG4ZOUBY5SFF

    View Slide

  72. r એݴ
    ‣ ม਺ɺؔ਺ɺΫϥεͷએݴͳͲ
    ‣ $MBTT%FDM4ZOUBYɺ'VODUJPO%FDM4ZOUBYɺ7BSJBCMF%FDM4ZOUBYɺ
    r ࣜ ஋Λฦ͢

    ‣ ஋Λฦ͢ߏจɻؔ਺ݺͼग़͠ɺϓϩύςΟΞΫηεɺΫϩʔδϟͳͲ
    ‣ $MPTVSF&YQS4ZOUBYɺ'VODUJPO$BMM&YQS4ZOUBYɺ
    4USJOH-JUFSBM&YQS4ZOUBYɺ
    r ͦͷଞ
    ‣ ܕΞϊςʔγϣϯͳͲ
    ‣ *EFOUJ
    fi
    FS5ZQF4ZOUBYɺ.FNCFS5ZQF4ZOUBYɺ0QUJPOBM5ZQF4ZOUBYɺ
    ͍Ζ͍Ζͳߏจϊʔυͷܕ

    View Slide

  73. ␣␣␣␣let␣ number␣ =␣ 100↲
    5PLFO4ZOUBY
    5PLFO4ZOUBY
    5PLFO4ZOUBY
    5PLFO4ZOUBY
    4ZOUBY5SFFͷ຤୺ɾ༿ϊʔυ

    View Slide

  74. ␣␣␣␣let␣ number␣ =␣ 100↲
    5PLFO4ZOUBY
    var leadingTrivia: Trivia
    trailingTrivia trailingTrivia
    trailingTrivia
    trailingTrivia
    -FBEJOH5SJWJB5SBJMJOH5SJWJB

    View Slide

  75. ␣␣␣␣let␣ number␣ =␣ 100↲
    5PLFO4ZOUBY
    keyword(Keyword.let)
    endOfFile
    integerLiteral("100")
    identifier("number")
    let firstToken = sourceFile.firstToken(viewMode: .sourceAccurate)
    let lastToken = sourceFile.lastToken(viewMode: .sourceAccurate)
    let previousToken = lastToken?.previousToken(viewMode: .sourceAccurate)
    let nextToken = firstToken?.nextToken(viewMode: .sourceAccurate)
    /BWJHBUF5PLFO4ZOUBY

    View Slide

  76. 4XJGU4ZOUBY"1*

    View Slide

  77. r 4ZOUBY7JTJUPS
    ‣ 4ZOUBY5SFFΛਂ͞༏ઌ୳ࡧ %'4
    ͰͨͲΔ
    αϒΫϥεͰॲཧΛ͢Δର৅ͷϊʔυͷWJTJUؔ਺ΛΦʔόʔϥΠυ͢Δ
    r 4ZOUBY3FXSJUFS
    ‣ 4ZOUBY7JTJUPSͷػೳʹՃ͑ͯɺWJTJUؔ਺ͰͨͲͬͨϊʔυΛผͷϊʔυʹॻ
    ͖׵͑Δ͜ͱ͕Ͱ͖Δ
    ‣ ϚΫϩ΍ϦϑΝΫλϦϯάɺϑΥʔϚολʔͳͲ4ZOUBY5SFFΛ༻͍ͯιʔε
    ίʔυΛม׵͢Δ༻్ʹ෯޿͘࢖ΘΕΔ
    4XJGU4ZOUBY"1*
    4ZOUBY7JTJUPS4ZOUBY3FXSJUFS

    View Slide

  78. 4ZOUBY7JTJUPS4ZOUBY3FXSJUFS
    visit()
    visit()
    visit()
    visit()
    visit()
    visit()
    visit()
    visit()
    visit()
    visit()
    visit()
    visit()
    visit()

    View Slide

  79. 4ZOUBY7JTJUPS
    open class SyntaxVisitor {
    public init(viewMode: SyntaxTreeViewMode)
    public func walk(_ node: some SyntaxProtocol)
    open func visit(_ node: AccessorBlockSyntax) -> SyntaxVisitorContinueKind
    open func visitPost(_ node: AccessorBlockSyntax)
    open func visit(_ node: AccessorDeclSyntax) -> SyntaxVisitorContinueKind
    open func visitPost(_ node: AccessorDeclSyntax)
    ...
    }

    View Slide

  80. 4ZOUBY7JTJUPS
    open class SyntaxVisitor {
    public init(viewMode: SyntaxTreeViewMode)
    public func walk(_ node: some SyntaxProtocol)
    open func visit(_ node: AccessorBlockSyntax) -> SyntaxVisitorContinueKind
    open func visitPost(_ node: AccessorBlockSyntax)
    open func visit(_ node: AccessorDeclSyntax) -> SyntaxVisitorContinueKind
    open func visitPost(_ node: AccessorDeclSyntax)
    ...
    }
    ୳ࡧ͢Δର৅ͷܕͷϊʔυͷ
    WJTJUؔ਺ΛΦʔόʔϥΠυ͢Δ

    View Slide

  81. &YBNQMF'JOE"MMGBUBM&SSPS'VODUJPO$BMMT
    4ZOUBY7JTJUPS
    class FatalErrorChecker: SyntaxVisitor {
    override func visit(_ node: FunctionCallExprSyntax) -> SyntaxVisitorContinueKind {
    if let calledExpression = node.calledExpression.as(DeclReferenceExprSyntax.self) {
    if calledExpression.baseName.tokenKind == .identifier("fatalError") {
    ...
    }
    }
    return .visitChildren
    }
    }

    View Slide

  82. &YBNQMF'JOE"MMGBUBM&SSPS'VODUJPO$BMMT
    4ZOUBY7JTJUPS
    class FatalErrorChecker: SyntaxVisitor {
    override func visit(_ node: FunctionCallExprSyntax) -> SyntaxVisitorContinueKind {
    if let calledExpression = node.calledExpression.as(DeclReferenceExprSyntax.self) {
    if calledExpression.baseName.tokenKind == .identifier("fatalError") {
    ...
    }
    }
    return .visitChildren
    }
    }

    View Slide

  83. &YBNQMF'JOE"MMGBUBM&SSPS'VODUJPO$BMMT
    4ZOUBY7JTJUPS
    class FatalErrorChecker: SyntaxVisitor {
    override func visit(_ node: FunctionCallExprSyntax) -> SyntaxVisitorContinueKind {
    if let calledExpression = node.calledExpression.as(DeclReferenceExprSyntax.self) {
    if calledExpression.baseName.tokenKind == .identifier("fatalError") {
    ...
    }
    }
    return .visitChildren
    }
    }

    View Slide

  84. ιʔείʔυͷςΩετΛൺֱ
    if calledExpression.baseName.tokenKind == .identifier("fatalError") {
    ...
    }
    *EFOUJGZUIF/PEFPG*OUFSFTU

    View Slide

  85. ιʔείʔυͷςΩετΛൺֱ
    if calledExpression.baseName.tokenKind == .identifier("fatalError") {
    ...
    }
    if calledExpression.baseName.trimmed.description == "fatalError" {
    ...
    }
    if calledExpression.baseName.text == "fatalError" {
    ...
    }
    if calledExpression.baseName.description == "fatalError" {
    ...
    }

    View Slide

  86. ιʔείʔυͷςΩετΛൺֱ
    if calledExpression.baseName.tokenKind == .identifier("fatalError") {
    ...
    }
    if calledExpression.baseName.trimmed.description == "fatalError" {
    ...
    }
    if calledExpression.baseName.text == "fatalError" {
    ...
    }
    if calledExpression.baseName.description == "fatalError" {
    ...
    }
    public extension SyntaxProtocol {
    ...
    var trimmed: Self {
    return self
    .with(\.leadingTrivia, [])
    .with(\.trailingTrivia, [])
    }
    ...
    }

    View Slide

  87. ιʔείʔυͷςΩετΛൺֱ
    4XJGU4ZOUBY#VJMEFS
    if calledExpression.baseName == "fatalError" {
    ...
    }
    import SwiftSyntaxBuilder
    ...
    4USJOHϦςϥϧ͔Β4ZOUBY͕࡞ΒΕΔͷͰ
    ίϯύΠϧΤϥʔʹͳΒͳ͍

    View Slide

  88. 4ZOUBY"OZ7JTJUPS
    &YBNQMF$POWFSU4ZOUBY5SFFUP)5.-
    class HTMLConverter: SyntaxAnyVisitor {
    var html = ""
    override func visitAny(_ node: Syntax) -> SyntaxVisitorContinueKind {
    html += #""#
    return .visitChildren
    }
    override func visitAnyPost(_ node: Syntax) {
    html += ""
    }
    }

    View Slide

  89. 4ZOUBY"OZ7JTJUPS
    &YBNQMF$POWFSU4ZOUBY5SFFUP)5.-
    class HTMLConverter: SyntaxAnyVisitor {
    var html = ""
    override func visitAny(_ node: Syntax) -> SyntaxVisitorContinueKind {
    html += #""#
    return .visitChildren
    }
    override func visitAnyPost(_ node: Syntax) {
    html += ""
    }
    }

    View Slide

  90. class HTMLConverter: SyntaxAnyVisitor {
    var html = ""
    override func visitAny(_ node: Syntax) -> SyntaxVisitorContinueKind {
    html += #""#
    return .visitChildren
    }
    override func visitAnyPost(_ node: Syntax) {
    html += ""
    }
    override func visit(_ node: TokenSyntax) -> SyntaxVisitorContinueKind {
    html += node.description
    return .visitChildren
    }
    override func visitPost(_ node: TokenSyntax) {}
    }

    View Slide

  91. class HTMLConverter: SyntaxAnyVisitor {
    var html = ""
    override func visitAny(_ node: Syntax) -> SyntaxVisitorContinueKind {
    html += #""#
    return .visitChildren
    }
    override func visitAnyPost(_ node: Syntax) {
    html += ""
    }
    override func visit(_ node: TokenSyntax) -> SyntaxVisitorContinueKind {
    html += node.description
    return .visitChildren
    }
    override func visitPost(_ node: TokenSyntax) {}
    }
    visitAnyPost()͕ݺ͹Εͳ͍Α͏ʹ

    View Slide

  92. class HTMLConverter: SyntaxAnyVisitor {
    var html = ""
    override func visitAny(_ node: Syntax) -> SyntaxVisitorContinueKind {
    html += #""#
    return .visitChildren
    }
    override func visitAnyPost(_ node: Syntax) {
    html += ""
    }
    override func visit(_ node: TokenSyntax) -> SyntaxVisitorContinueKind {
    html += node.description
    return .visitChildren
    }
    override func visitPost(_ node: TokenSyntax) {}
    }
    var kind = "\(node.tokenKind)"
    if let index = kind.firstIndex(of: "(") {
    kind = String(kind.prefix(upTo: index))
    }
    if kind.hasSuffix("Keyword") {
    kind = "keyword"
    }
    html += node.leadingTrivia.pieces.map { $0.htmlEscaped() }.joined()
    html += """
    \(node.text.htmlEscaped())
    """
    html += node.trailingTrivia.pieces.map { $0.htmlEscaped() }.joined()
    return .visitChildren

    View Slide

  93. ...






    let 



    number 



    100








    ...

    View Slide

  94. View Slide

  95. View Slide

  96. 4ZOUBY3FXSJUFS
    open class SyntaxRewriter {
    public func rewrite(_ node: some SyntaxProtocol) -> Syntax
    open func visitPre(_ node: Syntax)
    open func visitAny(_ node: Syntax) -> Syntax?
    open func visitPost(_ node: Syntax)
    open func visit(_ node: AccessorBlockSyntax) -> AccessorBlockSyntax
    open func visit(_ node: AccessorDeclListSyntax) -> AccessorDeclListSyntax
    ...

    View Slide

  97. 4ZOUBY3FXSJUFS
    open class SyntaxRewriter {
    public func rewrite(_ node: some SyntaxProtocol) -> Syntax
    open func visitPre(_ node: Syntax)
    open func visitAny(_ node: Syntax) -> Syntax?
    open func visitPost(_ node: Syntax)
    open func visit(_ node: AccessorBlockSyntax) -> AccessorBlockSyntax
    open func visit(_ node: AccessorDeclListSyntax) -> AccessorDeclListSyntax
    ...
    ฦͨ͠ϊʔυͷ಺༰Ͱ
    4ZOUBY5SFF͕ॻ͖׵ΘΔ

    View Slide

  98. 4ZOUBY3FXSJUFS
    open class SyntaxRewriter {
    public func rewrite(_ node: some SyntaxProtocol) -> Syntax
    open func visitPre(_ node: Syntax)
    open func visitAny(_ node: Syntax) -> Syntax?
    open func visitPost(_ node: Syntax)
    open func visit(_ node: AccessorBlockSyntax) -> AccessorBlockSyntax
    open func visit(_ node: AccessorDeclListSyntax) -> AccessorDeclListSyntax
    ...
    WJTJU"OZ @

    View Slide

  99. 4ZOUBY3FXSJUFS
    open class SyntaxRewriter {
    public func rewrite(_ node: some SyntaxProtocol) -> Syntax
    open func visitPre(_ node: Syntax)
    open func visitAny(_ node: Syntax) -> Syntax?
    open func visitPost(_ node: Syntax)
    open func visit(_ node: AccessorBlockSyntax) -> AccessorBlockSyntax
    open func visit(_ node: AccessorDeclListSyntax) -> AccessorDeclListSyntax
    ...
    WJTJU1SF @

    View Slide

  100. class FatalToPreconditionRewriter: SyntaxRewriter {
    override func visit(_ node: FunctionCallExprSyntax) -> ExprSyntax {
    if let calledExpression = node.calledExpression.as(DeclReferenceExprSyntax.self) {
    if calledExpression.baseName.tokenKind == .identifier("fatalError") {
    return super.visit(
    ExprSyntax(
    node.with(
    \.calledExpression,
    ExprSyntax(
    calledExpression
    .with(\.baseName, .identifier("preconditionFailure"))
    .with(\.leadingTrivia, calledExpression.leadingTrivia)
    )
    )
    )
    )
    }
    }
    return super.visit(node)
    }
    }

    View Slide

  101. class FatalToPreconditionRewriter: SyntaxRewriter {
    override func visit(_ node: FunctionCallExprSyntax) -> ExprSyntax {
    if let calledExpression = node.calledExpression.as(DeclReferenceExprSyntax.self) {
    if calledExpression.baseName.tokenKind == .identifier("fatalError") {
    return super.visit(
    ExprSyntax(
    node.with(
    \.calledExpression,
    ExprSyntax(
    calledExpression
    .with(\.baseName, .identifier("preconditionFailure"))
    .with(\.leadingTrivia, calledExpression.leadingTrivia)
    )
    )
    )
    )
    }
    }
    return super.visit(node)
    }
    }

    View Slide

  102. class FatalToPreconditionRewriter: SyntaxRewriter {
    override func visit(_ node: FunctionCallExprSyntax) -> ExprSyntax {
    if let calledExpression = node.calledExpression.as(DeclReferenceExprSyntax.self) {
    if calledExpression.baseName.tokenKind == .identifier("fatalError") {
    return super.visit(
    ExprSyntax(
    node.with(
    \.calledExpression,
    ExprSyntax(
    calledExpression
    .with(\.baseName, .identifier("preconditionFailure"))
    .with(\.leadingTrivia, calledExpression.leadingTrivia)
    )
    )
    )
    )
    }
    }
    return super.visit(node)
    }
    }

    View Slide

  103. class FatalToPreconditionRewriter: SyntaxRewriter {
    override func visit(_ node: FunctionCallExprSyntax) -> ExprSyntax {
    if let calledExpression = node.calledExpression.as(DeclReferenceExprSyntax.self) {
    if calledExpression.baseName.tokenKind == .identifier("fatalError") {
    return super.visit(
    ExprSyntax(
    node.with(
    \.calledExpression,
    ExprSyntax(
    calledExpression
    .with(\.baseName, .identifier("preconditionFailure"))
    .with(\.leadingTrivia, calledExpression.leadingTrivia)
    )
    )
    )
    )
    }
    }
    return super.visit(node)
    }
    }
    ݩͷιʔείʔυͷ5SJWJBΛ
    ͦͷ··෮ݩ͢Δ

    View Slide

  104. class FatalToPreconditionRewriter: SyntaxRewriter {
    override func visit(_ node: FunctionCallExprSyntax) -> ExprSyntax {
    if let calledExpression = node.calledExpression.as(DeclReferenceExprSyntax.self) {
    if calledExpression.baseName.tokenKind == .identifier("fatalError") {
    return super.visit(
    ExprSyntax(
    node.with(
    \.calledExpression,
    ExprSyntax(
    calledExpression
    .with(\.baseName, .identifier("preconditionFailure"))
    .with(\.leadingTrivia, calledExpression.leadingTrivia)
    )
    )
    )
    )
    }
    }
    return super.visit(node)
    }
    }
    ʮࣜʯͷߏจͷϕʔεϊʔυ
    Ͱ͋Δ&YQS4ZOUBYܕΛฦ͢

    View Slide

  105. class FatalToPreconditionRewriter: SyntaxRewriter {
    override func visit(_ node: FunctionCallExprSyntax) -> ExprSyntax {
    if let calledExpression = node.calledExpression.as(DeclReferenceExprSyntax.self) {
    if calledExpression.baseName.tokenKind == .identifier("fatalError") {
    return super.visit(
    ExprSyntax(
    node.with(
    \.calledExpression,
    ExprSyntax(
    calledExpression
    .with(\.baseName, .identifier("preconditionFailure"))
    .with(\.leadingTrivia, calledExpression.leadingTrivia)
    )
    )
    )
    )
    }
    }
    return super.visit(node)
    }
    }

    View Slide

  106. class FatalToPreconditionRewriter: SyntaxRewriter {
    override func visit(_ node: FunctionCallExprSyntax) -> ExprSyntax {
    if let calledExpression = node.calledExpression.as(DeclReferenceExprSyntax.self) {
    if calledExpression.baseName.tokenKind == .identifier("fatalError") {
    return super.visit(
    ExprSyntax(
    node.with(
    \.calledExpression,
    ExprSyntax(
    calledExpression
    .with(\.baseName, .identifier("preconditionFailure"))
    .with(\.leadingTrivia, calledExpression.leadingTrivia)
    )
    )
    )
    )
    }
    }
    return super.visit(node)
    }
    }
    TVQFSΛݺͿͱࢠཁૉͷ୳ࡧΛଓ͚Δ

    View Slide

  107. 4ZOUBY3FXSJUFS
    #BTF/PEFT
    r %FDM4ZOUBYએݴ
    ‣ ม਺ɺؔ਺ɺΫϥεͷએݴͳͲ
    r &YQS4ZOUBYࣜ ஋Λฦ͢

    ‣ ஋Λฦ͢ߏจɻؔ਺ݺͼग़͠ɺϓϩύςΟΞΫηεɺΫϩʔδϟͳͲ
    r 1BUUFSO4ZOUBY
    ‣ 4XJUDIจͷ$BTFઅ΍ม਺એݴ಺ͷม਺ͷఆٛͳͲ
    r 5ZQF4ZOUBY
    ‣ ܕΞϊςʔγϣϯͳͲ

    View Slide

  108. 4ZOUBY3FXSJUFS
    #BTF/PEFT
    // Non-failable initializer
    let expr = ExprSyntax(MemberAccessExprSyntax(name: .identifier("red")))
    .red

    View Slide

  109. 4ZOUBY3FXSJUFS
    #BTF/PEFT
    // Non-failable initializer
    let expr = ExprSyntax(MemberAccessExprSyntax(name: .identifier("red")))
    // Failable initializer
    let decl = DeclSyntax(MemberAccessExprSyntax(name: .identifier("red")))
    .red
    nil

    View Slide

  110. 4PVSDF-PDBUJPO
    4PVSDF-PDBUJPO$POWFSUFS
    let sourceFile = Parser.parse(source: "let number = 100")
    let locationConverter =
    SourceLocationConverter(fileName: "", tree: sourceFile)
    ...
    let identifier = pattern.identifier
    let start = identifier.startLocation(converter: locationConverter)
    let end = identifier.endLocation(converter: locationConverter)
    print("\(start.line):\(start.column)...\(end.line):\(end.column)")
    1:5...1:11

    View Slide

  111. 4PVSDF-PDBUJPO
    4PVSDF-PDBUJPO$POWFSUFS
    let sourceFile = Parser.parse(source: "let number = 100")
    let locationConverter =
    SourceLocationConverter(fileName: "", tree: sourceFile)
    ...
    let identifier = pattern.identifier
    let start = identifier.startLocation(converter: locationConverter)
    let end = identifier.endLocation(converter: locationConverter)
    print("\(start.line):\(start.column)...\(end.line):\(end.column)")
    1:5...1:11
    let start = identifier.startLocation(converter: locationConverter)
    let end = identifier.endLocation(converter: locationConverter)
    print("\(start.line):\(start.column)...\(end.line):\(end.column)")
    let number = 100
    1:5 1:11

    View Slide

  112. 4PVSDF-PDBUJPO
    4PVSDF-PDBUJPO$POWFSUFS
    func startLocation(
    converter: SourceLocationConverter, afterLeadingTrivia: Bool = true
    ) -> SourceLocation
    func endLocation(
    converter: SourceLocationConverter, afterTrailingTrivia: Bool = false
    ) -> SourceLocation
    number

    View Slide

  113. 4XJGU4ZOUBYJT0OMZ"CPVU4ZOUBY
    *U%PFTO`U,OPX"OZUIJOH"CPVU5ZQFTBOE*OIFSJUBODF
    r 4XJGU4ZOUBY͸ߏจ ςΩετ
    ͷΈΛѻ͏
    r ܕ৘ใ΍ίϯύΠϧՄೳ͔Ͳ͏͔ͳͲ͸4XJGU4ZOUBY͸Θ͔Βͳ͍
    ‣ ಉ໊͡લͷผͷؔ਺ͷ۠ผ͕͔ͭͳ͍
    ‣ ܕΞϊςʔγϣϯ͕লུ͞Εͨม਺΍Ҿ਺ͷܕ͕Θ͔Βͳ͍

    View Slide

  114. 4XJGU4ZOUBYJT0OMZ"CPVU4ZOUBY
    *U%PFTO`U,OPX"OZUIJOH"CPVU5ZQFTBOE*OIFSJUBODF
    let result = run()
    IdentifierPatternSyntax

    View Slide

  115. let result = run()
    : Int
    TypeAnnotationSyntax
    4XJGU4ZOUBYJT0OMZ"CPVU4ZOUBY
    *U%PFTO`U,OPX"OZUIJOH"CPVU5ZQFTBOE*OIFSJUBODF
    IdentifierPatternSyntax

    View Slide

  116. 1BSTFS3FTJMJFODZ
    1BSTFS/FWFS'BJMT
    r จ๏Τϥʔ͕͋ͬͯ΋ࣗಈతʹͭͭ͡·ͷ߹͏τʔΫϯΛ௥Ճͯ͠ଓߦ͢Δ
    r ਖ਼͘͠ͳ͍จ๏ɺίϯύΠϧͰ͖ͳ͍ιʔείʔυͰ΋1BSTFͰ͖Δ

    View Slide

  117. 1BSTFS3FTJMJFODZ
    1BSTFS/FWFS'BJMT

    View Slide

  118. 1BSTFS3FTJMJFODZ
    1BSTFS/FWFS'BJMT
    1BSTFS͕ิ׬ͨ͠τʔΫϯ

    View Slide

  119. 1BSTFS3FTJMJFODZ
    1BSTFS/FWFS'BJMT
    SyntaxVisitor(viewMode: .sourceAccurate)
    enum SyntaxTreeViewMode {
    case sourceAccurate
    case fixedUp
    case all
    }
    ιʔείʔυʹදࣔ͞ΕΔτʔΫϯͷΈ
    1BSTFS͕ิ׬ͨ͠τʔΫϯ΋८ճ͢Δ
    1BSTFS͕ิ׬ͨ͠τʔΫϯ͓Αͼ༧ظ͠ͳ͍ϊʔυ΋८ճ͢Δ

    View Slide

  120. 1BSTFS3FTJMJFODZ
    1BSTFS/FWFS'BJMT
    r จ๏Τϥʔ͕͋ͬͯ΋ࣗಈతʹͭͭ͡·ͷ߹͏τʔΫϯΛ௥Ճͯ͠ଓߦ͢Δ
    r ਖ਼͘͠ͳ͍จ๏ɺίϯύΠϧͰ͖ͳ͍ιʔείʔυͰ΋1BSTFͰ͖Δ
    r ϚΫϩʹ͸ίϯύΠϥͷνΣοΫޙͷΤϥʔͷͳ͍4ZOUBY5SFF͕౉͞ΕΔ
    r ෆ׬શͳίʔυɺΤϥʔΛؚΉ4ZOUBY5SFF IBT&SSPSϓϩύςΟΛνΣοΫ

    ͷѻ͍ʹ஫ҙ

    View Slide

  121. &YFSDJTFT4XJGU.BDSPT
    *OJU.BDSP

    View Slide

  122. 4XJGU.BDSPT
    *OJU.BDSP
    @Init
    class Person {
    let name: String
    let age: Int
    let birthday: Date?
    }
    class Person {
    let name: String
    let age: Int
    let birthday: Date?
    }

    View Slide

  123. 4XJGU.BDSPT
    *OJU.BDSP
    @Init
    class Person {
    let name: String
    let age: Int
    let birthday: Date?
    }
    class Person {
    let name: String
    let age: Int
    let birthday: Date?
    }
    }
    init(
    name: String,
    age: Int,
    birthday: Date?
    ) {
    self.name = name
    self.age = age
    self.birthday = birthday
    }

    View Slide

  124. 4XJGU.BDSPT
    *OJU.BDSP
    struct InitMacro: MemberMacro {
    static func expansion(
    of node: AttributeSyntax,
    providingMembersOf declaration: some DeclGroupSyntax,
    in context: some MacroExpansionContext
    ) throws -> [DeclSyntax] {
    ...
    }
    }

    View Slide

  125. 4XJGU.BDSPT
    *OJU.BDSP
    struct InitMacro: MemberMacro {
    static func expansion(
    of node: AttributeSyntax,
    providingMembersOf declaration: some DeclGroupSyntax,
    in context: some MacroExpansionContext
    ) throws -> [DeclSyntax] {
    ...
    }
    }
    @Init

    View Slide

  126. 4XJGU.BDSPT
    *OJU.BDSP
    struct InitMacro: MemberMacro {
    static func expansion(
    of node: AttributeSyntax,
    providingMembersOf declaration: some DeclGroupSyntax,
    in context: some MacroExpansionContext
    ) throws -> [DeclSyntax] {
    ...
    }
    }
    @Init
    class Person {
    let name: String
    let age: Int
    let birthday: Date?
    }

    View Slide

  127. let variableDecls: [(pattern: IdentifierPatternSyntax, typeAnnotation: TypeAnnotationSyntax)]
    variableDecls = declaration.memberBlock.members.compactMap {
    guard let decl = $0.decl.as(VariableDeclSyntax.self) else { return nil }
    guard let binding = decl.bindings.first else { return nil }
    guard let pattern = binding.pattern.as(IdentifierPatternSyntax.self) else { return nil }
    guard let typeAnnotation = binding.typeAnnotation else { return nil }
    return (pattern, typeAnnotation)
    }
    return [
    """
    init(
    \(raw: variableDecls.map { "\($0.)\($0.)" }.joined(separator: ",\n"))
    ) {
    \(raw: variableDecls.map { "self.\($0.) = \($0.)" }.joined(separator: "\n"))
    }
    """
    ]

    View Slide

  128. let variableDecls: [(pattern: IdentifierPatternSyntax, typeAnnotation: TypeAnnotationSyntax)]
    variableDecls = declaration.memberBlock.members.compactMap {
    guard let decl = $0.decl.as(VariableDeclSyntax.self) else { return nil }
    guard let binding = decl.bindings.first else { return nil }
    guard let pattern = binding.pattern.as(IdentifierPatternSyntax.self) else { return nil }
    guard let typeAnnotation = binding.typeAnnotation else { return nil }
    return (pattern, typeAnnotation)
    }
    return [
    """
    init(
    \(raw: variableDecls.map { "\($0.)\($0.)" }.joined(separator: ",\n"))
    ) {
    \(raw: variableDecls.map { "self.\($0.) = \($0.)" }.joined(separator: "\n"))
    }
    """
    ]
    class Person {
    let name: String
    let age: Int
    let birthday: Date?
    }

    View Slide

  129. let variableDecls: [(pattern: IdentifierPatternSyntax, typeAnnotation: TypeAnnotationSyntax)]
    variableDecls = declaration.memberBlock.members.compactMap {
    guard let decl = $0.decl.as(VariableDeclSyntax.self) else { return nil }
    guard let binding = decl.bindings.first else { return nil }
    guard let pattern = binding.pattern.as(IdentifierPatternSyntax.self) else { return nil }
    guard let typeAnnotation = binding.typeAnnotation else { return nil }
    return (pattern, typeAnnotation)
    }
    return [
    """
    init(
    \(raw: variableDecls.map { "\($0.)\($0.)" }.joined(separator: ",\n"))
    ) {
    \(raw: variableDecls.map { "self.\($0.) = \($0.)" }.joined(separator: "\n"))
    }
    """
    ]
    (name, String)
    (age, Int)
    (birthday, Date?)

    View Slide

  130. let variableDecls: [(pattern: IdentifierPatternSyntax, typeAnnotation: TypeAnnotationSyntax)]
    variableDecls = declaration.memberBlock.members.compactMap {
    guard let decl = $0.decl.as(VariableDeclSyntax.self) else { return nil }
    guard let binding = decl.bindings.first else { return nil }
    guard let pattern = binding.pattern.as(IdentifierPatternSyntax.self) else { return nil }
    guard let typeAnnotation = binding.typeAnnotation else { return nil }
    return (pattern, typeAnnotation)
    }
    return [
    """
    init(
    \(raw: variableDecls.map { "\($0.)\($0.)" }.joined(separator: ",\n"))
    ) {
    \(raw: variableDecls.map { "self.\($0.) = \($0.)" }.joined(separator: "\n"))
    }
    """
    ]
    (pattern, typeAnnotation)

    View Slide

  131. let variableDecls: [(pattern: IdentifierPatternSyntax, typeAnnotation: TypeAnnotationSyntax)]
    variableDecls = declaration.memberBlock.members.compactMap {
    guard let decl = $0.decl.as(VariableDeclSyntax.self) else { return nil }
    guard let binding = decl.bindings.first else { return nil }
    guard let pattern = binding.pattern.as(IdentifierPatternSyntax.self) else { return nil }
    guard let typeAnnotation = binding.typeAnnotation else { return nil }
    return (pattern, typeAnnotation)
    }
    return [
    """
    init(
    \(raw: variableDecls.map { "\(pattern)\(typeAnnotation)" }.joined(separator: ",\n"))
    ) {
    \(raw: variableDecls.map { "self.\(pattern) = \(pattern)" }.joined(separator: "\n"))
    }
    """
    ]

    View Slide

  132. let variableDecls: [(pattern: IdentifierPatternSyntax, typeAnnotation: TypeAnnotationSyntax)]
    variableDecls = declaration.memberBlock.members.compactMap {
    guard let decl = $0.decl.as(VariableDeclSyntax.self) else { return nil }
    guard let binding = decl.bindings.first else { return nil }
    guard let pattern = binding.pattern.as(IdentifierPatternSyntax.self) else { return nil }
    guard let typeAnnotation = binding.typeAnnotation else { return nil }
    return (pattern, typeAnnotation)
    }
    init(
    pattern: typeAnnotation,
    ) {
    self.pattern = pattern
    }

    View Slide

  133. let variableDecls: [(pattern: IdentifierPatternSyntax, typeAnnotation: TypeAnnotationSyntax)]
    variableDecls = declaration.memberBlock.members.compactMap {
    guard let decl = $0.decl.as(VariableDeclSyntax.self) else { return nil }
    guard let binding = decl.bindings.first else { return nil }
    guard let pattern = binding.pattern.as(IdentifierPatternSyntax.self) else { return nil }
    guard let typeAnnotation = binding.typeAnnotation else { return nil }
    return (pattern, typeAnnotation)
    }
    return [
    """
    init(
    \(raw: variableDecls.map { "\(pattern)\(typeAnnotation)" }.joined(separator: ",\n"))
    ) {
    \(raw: variableDecls.map { "self.\(pattern) = \(pattern)" }.joined(separator: "\n"))
    }
    """
    ]
    return [
    """
    init(
    \(raw: variableDecls.map { "\(pattern)\(typeAnnotation)" }.joined(separator: ",\n"))
    ) {
    \(raw: variableDecls.map { "self.\(pattern) = \(pattern)" }.joined(separator: "\n"))
    }
    """
    ]
    import SwiftSyntaxBuilder
    ...

    View Slide

  134. let variableDecls: [(pattern: IdentifierPatternSyntax, typeAnnotation: TypeAnnotationSyntax)]
    variableDecls = declaration.memberBlock.members.compactMap {
    guard let decl = $0.decl.as(VariableDeclSyntax.self) else { return nil }
    guard let binding = decl.bindings.first else { return nil }
    guard let pattern = binding.pattern.as(IdentifierPatternSyntax.self) else { return nil }
    guard let typeAnnotation = binding.typeAnnotation else { return nil }
    return (pattern, typeAnnotation)
    }
    return [
    """
    init(
    \(raw: variableDecls.map { "\(pattern)\(typeAnnotation)" }.joined(separator: ",\n"))
    ) {
    \(raw: variableDecls.map { "self.\(pattern) = \(pattern)" }.joined(separator: "\n"))
    }
    """
    ]
    return [
    DeclSyntax(
    InitializerDeclSyntax(
    signature: FunctionSignatureSyntax(
    input: ParameterClauseSyntax(
    parameterList: FunctionParameterListSyntax(
    variableDecls.map { (pattern, typeAnnotation) in
    FunctionParameterSyntax(firstName: pattern.identifier, type: typeAnnotation.type)
    }
    )
    )
    ),
    body: CodeBlockSyntax(
    statements: CodeBlockItemListSyntax(
    variableDecls.map { (pattern, typeAnnotation) in
    CodeBlockItemSyntax(
    item: CodeBlockItemSyntax.Item(
    InfixOperatorExprSyntax(
    leftOperand: MemberAccessExprSyntax(
    base: IdentifierExprSyntax(identifier: .keyword(.self)),
    name: pattern.identifier
    ),
    operatorOperand: AssignmentExprSyntax(),
    rightOperand: IdentifierExprSyntax(identifier: pattern.identifier)
    )
    )
    )
    }
    )
    )
    )
    )
    ]

    View Slide

  135. let variableDecls: [(pattern: IdentifierPatternSyntax, typeAnnotation: TypeAnnotationSyntax)]
    variableDecls = declaration.memberBlock.members.compactMap {
    guard let decl = $0.decl.as(VariableDeclSyntax.self) else { return nil }
    guard let binding = decl.bindings.first else { return nil }
    guard let pattern = binding.pattern.as(IdentifierPatternSyntax.self) else { return nil }
    guard let typeAnnotation = binding.typeAnnotation else { return nil }
    return (pattern, typeAnnotation)
    }
    return [
    """
    init(
    \(raw: variableDecls.map { "\(pattern)\(typeAnnotation)" }.joined(separator: ",\n"))
    ) {
    \(raw: variableDecls.map { "self.\(pattern) = \(pattern)" }.joined(separator: "\n"))
    }
    """
    ]
    init(name:Stringage:Intbirthday:Date?){self.name=nameself.age=ageself.birthday=birthday}

    View Slide

  136. let variableDecls: [(pattern: IdentifierPatternSyntax, typeAnnotation: TypeAnnotationSyntax)]
    variableDecls = declaration.memberBlock.members.compactMap {
    guard let decl = $0.decl.as(VariableDeclSyntax.self) else { return nil }
    guard let binding = decl.bindings.first else { return nil }
    guard let pattern = binding.pattern.as(IdentifierPatternSyntax.self) else { return nil }
    guard let typeAnnotation = binding.typeAnnotation else { return nil }
    return (pattern, typeAnnotation)
    }
    return [
    """
    init(
    \(raw: variableDecls.map { "\(pattern)\(typeAnnotation)" }.joined(separator: ",\n"))
    ) {
    \(raw: variableDecls.map { "self.\(pattern) = \(pattern)" }.joined(separator: "\n"))
    }
    """
    ]
    init(name: String age: Int birthday: Date?) {
    self.name = name
    self.age = age
    self.birthday = birthday
    }
    .formatted(using: BasicFormat())

    View Slide

  137. let variableDecls: [(pattern: IdentifierPatternSyntax, typeAnnotation: TypeAnnotationSyntax)]
    variableDecls = declaration.memberBlock.members.compactMap {
    guard let decl = $0.decl.as(VariableDeclSyntax.self) else { return nil }
    guard let binding = decl.bindings.first else { return nil }
    guard let pattern = binding.pattern.as(IdentifierPatternSyntax.self) else { return nil }
    guard let typeAnnotation = binding.typeAnnotation else { return nil }
    return (pattern, typeAnnotation)
    }
    return [
    """
    init(
    \(raw: variableDecls.map { "\(pattern)\(typeAnnotation)" }.joined(separator: ",\n"))
    ) {
    \(raw: variableDecls.map { "self.\(pattern) = \(pattern)" }.joined(separator: "\n"))
    }
    """
    ]
    init(name: String age: Int birthday: Date?) {
    self.name = name
    self.age = age
    self.birthday = birthday
    }
    .formatted(using: BasicFormat())
    struct InitMacro: MemberMacro {
    static var formatMode: FormatMode = .disabled
    ...

    View Slide

  138. 'PMEJOH4FRVFODF&YQSFTTJPOT
    4XJGU0QFSBUPST
    SequenceExprSyntax
    ExprListSyntax
    DeclReferenceExprSyntax
    BinaryOperatorExpr
    a
    +
    DeclReferenceExprSyntax
    BinaryOperatorExpr
    *
    b
    DeclReferenceExprSyntax
    c
    InfixOperatorExprSyntax
    DeclReferenceExprSyntax
    BinaryOperatorExpr
    a
    +
    DeclReferenceExprSyntax
    BinaryOperatorExpr
    *
    b
    DeclReferenceExprSyntax
    c
    InfixOperatorExprSyntax
    if a + b * c == d {
    ...
    }
    1BSTF 'PME

    View Slide

  139. 'PMEJOH4FRVFODF&YQSFTTJPOT
    4XJGU0QFSBUPST
    r ԋࢉࢠͷ༏ઌॱҐʹैͬͯ4ZOUBY5SFFΛʮંΓͨͨΉʯ
    r ԋࢉࢠͷ༏ઌॱҐͷ௨Γʹ८ճͰ͖ΔπϦʔߏ଄͕ੜ੒͞ΕΔ
    r ԋࢉࢠͷ༏ઌॱҐ͸ԋࢉࢠςʔϒϧʹ͋Β͔͡Ίొ࿥͢Δ
    r ະ஌ͷԋࢉࢠ ಠࣗఆٛͷԋࢉࢠ
    ͕ԋࢉࢠ͕͋Δͱࣦഊ͢Δ
    r ϚΫϩͷ৔߹ɺࣗಈͰંΓͨͨΜͩ4ZOUBY5SFF͕౉͞ΕΔ
    ‣ ࣗ෼Ͱ1BSTFͨ͠4ZOUBY5SFFͱҟͳ͍ͬͯͯ΋͋Θͯͳ͍
    r ϚΫϩͷ৔߹ɺಠࣗఆٛͷԋࢉࢠ͕͋ͬͯ΋ڧҾʹંΓͨͨΉ
    ‣ ༏ઌॱҐ͕ؒҧͬͯંΓͨͨ·ΕΔ ৔߹͕͋Δ

    r ໰୊͕͋Δ৔߹͸4ZOUBY5SFF͔ΒιʔείʔυΛੜ੒ͯࣗ͠෼Ͱ࠶1BSTF͢Δ

    View Slide

  140. "OBUPNZPG4XJGU4ZOUBY
    4XJGU4ZOUBY
    4XJGU4ZOUBY.BDSPT
    4XJGU0QFSBUPST
    4XJGU4ZOUBY#VJMEFS
    4XJGU1BSTFS
    4XJGU$PNQJMFS1MVHJO
    4XJGU#BTJD'PSNBU
    4ZOUBY5SFFͷσʔλߏ଄ ܕ
    ͱ7JTJUPSɾ
    3FXSJUFSͳͲجຊతͳ"1*Λఏڙ
    ߏจղੳͱ4ZOUBY5SFFͷੜ੒
    จࣈྻϦςϥϧ &YQSFTTJCMF#Z4USJOH-JUFSBM

    ΍3FTVMU#VJMEFS͔Β4ZOUBY5SFFΛੜ੒͢Δ
    "1*Λఏڙ ϚΫϩͷ࣮૷ʹඞཁͳ"1*Λఏڙ
    ඞཁͳۭന΍վߦΛࣗಈతʹૠೖ͢Δ
    ԋࢉࢠͷ༏ઌॱҐʹैͬͯࣜΛʮંΓͨͨΉʯ

    View Slide

  141. *OJU.BDSP
    "EWBODFE&YFSDJTFT
    r $MBTTɺ4USVDUɺ"DUPSҎ֎ͷܕʹద༻͠ͳ͍
    r ܕʹͭ͘ΞΫηεम০ࢠ QVCMJDɺQSJWBUFͳͲ
    ͱΠχγϟϥΠβͷΞΫηεϨ
    ϕϧΛҰகͤ͞Δ
    r $PNQVUFE1SPQFSUZΛআ֎͢Δ
    r ؔ਺ܕͷϓϩύςΟʹରԠ͢Δ
    r αϯϓϧίʔυ
    IUUQTHJUIVCDPNLJTIJLBXBLBUTVNJJPTEDTBNQMFT

    View Slide

  142. &YFSDJTFT4XJGU-JOU
    0QFOJOH#SBDF4QBDJOH3VMF

    View Slide

  143. 4XJGU-JOU0QFOJOH#SBDF4QBDJOH3VMF
    PQFOJOH@CSBDF
    r 0QFOJOHCSBDFTTIPVMECFQSFDFEFECZBTJOHMFTQBDFBOEPOUIFTBNF
    MJOFBTUIFEFDMBSBUJPO
    func run() {
    }
    if a == b {
    }
    func run(){
    }
    func run() {
    }
    if a == b
    {
    }
    guard let a = b else {
    }

    View Slide

  144. 4XJGU-JOU0QFOJOH#SBDF4QBDJOH3VMF
    'BMDF1PTUJWF
    let pattern = /(\{(?\w+)\})/
    Opening Brace Spacing Violation

    View Slide

  145. 4XJGU-JOU0QFOJOH#SBDF4QBDJOH3VMF
    %FUFDUWJPMBUJPO🔎
    override func visit(_ node: TokenSyntax) -> SyntaxVisitorContinueKind {
    if node.tokenKind == .leftBrace {
    if let previousToken = node.previousToken(viewMode: .sourceAccurate),
    previousToken.trailingTrivia == .space {
    let position = node.positionAfterSkippingLeadingTrivia
    violations.append(position)
    }
    }
    return .visitChildren
    }

    View Slide

  146. 4XJGU-JOU0QFOJOH#SBDF4QBDJOH3VMF
    %FUFDUWJPMBUJPO🔎
    override func visit(_ node: TokenSyntax) -> SyntaxVisitorContinueKind {
    if node.tokenKind == .leftBrace {
    if let previousToken = node.previousToken(viewMode: .sourceAccurate),
    previousToken.trailingTrivia == .space {
    let position = node.positionAfterSkippingLeadingTrivia
    violations.append(position)
    }
    }
    return .visitChildren
    }

    View Slide

  147. 4XJGU-JOU0QFOJOH#SBDF4QBDJOH3VMF
    %FUFDUWJPMBUJPO🔎
    override func visit(_ node: TokenSyntax) -> SyntaxVisitorContinueKind {
    if node.tokenKind == .leftBrace {
    if let previousToken = node.previousToken(viewMode: .sourceAccurate),
    previousToken.trailingTrivia == .space {
    let position = node.positionAfterSkippingLeadingTrivia
    violations.append(position)
    }
    }
    return .visitChildren
    }

    View Slide

  148. 4XJGU-JOU0QFOJOH#SBDF4QBDJOH3VMF
    "VUPDPSSFDUJPO🪄
    func run() {
    }
    func run(){
    }
    func run() {
    }
    if a == b
    {
    }
    func run() {
    }
    if a == b {
    }

    View Slide

  149. 5IF5SJWJB"UUSJCVUJPO3VMF
    "UPLFOPXOTBMMPGJUTUSBJMJOHUSJWJBVQUP CVUOPUJODMVEJOH UIFOFYU
    OFXMJOFDIBSBDUFS
    -PPLJOHCBDLXBSEJOUIFUFYU BUPLFOPXOTBMMPGUIFMFBEJOHUSJWJBVQ
    UPBOEJODMVEJOHUIF
    fi
    STUDPOUJHVPVTTFRVFODFPGOFXMJOFTDIBSBDUFST
    IUUQTHJUIVCDPNBQQMFTXJGUTZOUBYJTTVFT

    View Slide

  150. 4XJGU-JOU0QFOJOH#SBDF4QBDJOH3VMF
    5IF5SJWJB"UUSJCVUJPO3VMF
    func run()␣␣{↲
    ␣␣***
    }

    View Slide

  151. func run()␣␣↲
    {↲
    ␣␣***
    }
    func run()↲
    {↲
    ␣␣***
    }
    func run()␣␣{↲
    ␣␣***
    }
    4XJGU-JOU0QFOJOH#SBDF4QBDJOH3VMF
    5IF5SJWJB"UUSJCVUJPO3VMF

    View Slide

  152. SourceFileSyntax
    CodeBlockItemListSyntax
    CodeBlockItemSyntax
    FunctionDeclSyntax
    FunctionSignatureSyntax
    FunctionParameterSyntax
    FunctionParameterClauseSyntax
    CodeBlockSyntax
    CodeBlockItemListSyntax
    endOfFile
    (
    {
    }
    func
    run
    FunctionParameterListSyntax
    )

    View Slide

  153. override func visit(_ node: FunctionDeclSyntax) -> DeclSyntax {
    guard let body = node.body else {
    return super.visit(node)
    }
    let openingBrace = body.leftBrace
    if !openingBrace.hasSingleSpaceLeading() {
    let position = openingBrace.positionAfterSkippingLeadingTrivia
    correctionPositions.append(position)
    return super.visit(
    node
    .with(\.signature, node.signature.with(\.trailingTrivia, .space))
    .with(\.leftBrace, node.leftBrace.with(\.leadingTrivia, []))
    )
    }
    return super.visit(node)
    }

    View Slide

  154. override func visit(_ node: FunctionDeclSyntax) -> DeclSyntax {
    guard let body = node.body else {
    return super.visit(node)
    }
    let openingBrace = body.leftBrace
    if !openingBrace.hasSingleSpaceLeading() {
    let position = openingBrace.positionAfterSkippingLeadingTrivia
    correctionPositions.append(position)
    return super.visit(
    node
    .with(\.signature, node.signature.with(\.trailingTrivia, .space))
    .with(\.leftBrace, node.leftBrace.with(\.leadingTrivia, []))
    )
    }
    return super.visit(node)
    }

    View Slide

  155. override func visit(_ node: FunctionDeclSyntax) -> DeclSyntax {
    guard let body = node.body else {
    return super.visit(node)
    }
    let openingBrace = body.leftBrace
    if !openingBrace.hasSingleSpaceLeading() {
    let position = openingBrace.positionAfterSkippingLeadingTrivia
    correctionPositions.append(position)
    return super.visit(
    node
    .with(\.signature, node.signature.with(\.trailingTrivia, .space))
    .with(\.leftBrace, node.leftBrace.with(\.leadingTrivia, []))
    )
    }
    return super.visit(node)
    }

    View Slide

  156. override func visit(_ node: FunctionDeclSyntax) -> DeclSyntax {
    guard let body = node.body else {
    return super.visit(node)
    }
    let openingBrace = body.leftBrace
    if !openingBrace.hasSingleSpaceLeading() {
    let position = openingBrace.positionAfterSkippingLeadingTrivia
    correctionPositions.append(position)
    return super.visit(
    node
    .with(\.signature, node.signature.with(\.trailingTrivia, .space))
    .with(\.leftBrace, node.leftBrace.with(\.leadingTrivia, []))
    )
    }
    return super.visit(node)
    }
    private extension TokenSyntax {
    func hasSingleSpaceLeading() -> Bool {
    if let previousToken = previousToken(viewMode: .sourceAccurate),
    previousToken.trailingTrivia == .space {
    return true
    } else {
    return false
    }
    }
    }

    View Slide

  157. override func visit(_ node: FunctionDeclSyntax) -> DeclSyntax {
    guard let body = node.body else {
    return super.visit(node)
    }
    let openingBrace = body.leftBrace
    if !openingBrace.hasSingleSpaceLeading() {
    let position = openingBrace.positionAfterSkippingLeadingTrivia
    correctionPositions.append(position)
    return super.visit(
    node
    .with(\.signature, node.signature.with(\.trailingTrivia, .space))
    .with(\.leftBrace, node.leftBrace.with(\.leadingTrivia, []))
    )
    }
    return super.visit(node)
    }

    View Slide

  158. override func visit(_ node: FunctionDeclSyntax) -> DeclSyntax {
    guard let body = node.body else {
    return super.visit(node)
    }
    let openingBrace = body.leftBrace
    if !openingBrace.hasSingleSpaceLeading() {
    let position = openingBrace.positionAfterSkippingLeadingTrivia
    correctionPositions.append(position)
    return super.visit(
    node
    .with(\.signature, node.signature.with(\.trailingTrivia, .space))
    .with(\.leftBrace, node.leftBrace.with(\.leadingTrivia, []))
    )
    }
    return super.visit(node)
    }
    func run(name: T) {
    }
    ??????????????

    View Slide

  159. View Slide

  160. View Slide

  161. View Slide

  162. func run(name: T) where T: StringProtocol {
    }

    View Slide

  163. override func visit(_ node: FunctionDeclSyntax) -> SyntaxVisitorContinueKind {
    guard let body = node.body else {
    return super.visit(node)
    }
    let openingBrace = body.leftBrace
    if !openingBrace.hasSingleSpaceLeading() {
    let position = openingBrace.positionAfterSkippingLeadingTrivia
    correctionPositions.append(position)
    if let genericWhereClause = node.genericWhereClause {
    return super.visit(
    node
    .with(\.genericWhereClause, node.signature.with(\.trailingTrivia, .space))
    .with(\.leftBrace, node.leftBrace.with(\.leadingTrivia, []))
    )
    }
    return super.visit(
    node
    .with(\.signature, node.signature.with(\.trailingTrivia, .space))
    .with(\.leftBrace, node.leftBrace.with(\.leadingTrivia, []))
    )
    }
    return super.visit(node)
    }

    View Slide

  164. override func visit(_ node: FunctionDeclSyntax) -> SyntaxVisitorContinueKind {
    guard let body = node.body else {
    return super.visit(node)
    }
    let openingBrace = body.leftBrace
    if !openingBrace.hasSingleSpaceLeading() {
    let position = openingBrace.positionAfterSkippingLeadingTrivia
    correctionPositions.append(position)
    if let genericWhereClause = node.genericWhereClause {
    return super.visit(
    node
    .with(\.genericWhereClause, node.signature.with(\.trailingTrivia, .space))
    .with(\.leftBrace, node.leftBrace.with(\.leadingTrivia, []))
    )
    }
    return super.visit(
    node
    .with(\.signature, node.signature.with(\.trailingTrivia, .space))
    .with(\.leftBrace, node.leftBrace.with(\.leadingTrivia, []))
    )
    }
    return super.visit(node)
    }

    View Slide

  165. 4XJGU-JOU0QFOJOH#SBDF4QBDJOH3VMF
    "VUPDPSSFDUJPO🪄
    func run(name: T) where T: StringProtocol {
    }
    func run() {
    }
    func run(){
    }
    func run(name: T) where T: StringProtocol {
    }
    if a == b
    {
    }
    if a == b {
    }
    *Gจ΋ର৅ɻଞʹ͸ʁ

    View Slide

  166. -BOHVBHF3FGFSFODF
    4VNNBSZPGUIF(SBNNBS
    IUUQTEPDTTXJGUPSHTXJGUCPPLEPDVNFOUBUJPOUIFTXJGUQSPHSBNNJOHMBOHVBHFTVNNBSZPGUIFHSBNNBS

    View Slide

  167. -BOHVBHF3FGFSFODF
    "CPVUUIF-BOHVBHF3FGFSFODF
    IUUQTEPDTTXJGUPSHTXJGUCPPLEPDVNFOUBUJPOUIFTXJGUQSPHSBNNJOHMBOHVBHFBCPVUUIFMBOHVBHFSFGFSFODF

    View Slide

  168. -BOHVBHF3FGFSFODF
    "CPVUUIF-BOHVBHF3FGFSFODF
    r "OBSSPX ˠ
    JTVTFEUPNBSLHSBNNBSQSPEVDUJPOTBOEDBOCFSFBEBTlDBODPOTJTUPGz
    r 4ZOUBDUJDDBUFHPSJFTBSFJOEJDBUFECZJUBMJDUFYUBOEBQQFBSPOCPUITJEFTPGBHSBNNBS
    QSPEVDUJPOSVMF
    r -JUFSBMXPSETBOEQVODUVBUJPOBSFJOEJDBUFECZCPMEGBDFDPOTUBOUXJEUIUFYUBOEBQQFBSPOMZPO
    UIFSJHIUIBOETJEFPGBHSBNNBSQSPEVDUJPOSVMF
    r "MUFSOBUJWFHSBNNBSQSPEVDUJPOTBSFTFQBSBUFECZWFSUJDBMCBST c
    8IFOBMUFSOBUJWF
    QSPEVDUJPOTBSFUPPMPOHUPSFBEFBTJMZ UIFZ`SFCSPLFOJOUPNVMUJQMFHSBNNBSQSPEVDUJPOSVMFT
    POOFXMJOFT
    r *OBGFXDBTFT SFHVMBSGPOUUFYUJTVTFEUPEFTDSJCFUIFSJHIUIBOETJEFPGBHSBNNBSQSPEVDUJPO
    SVMF
    r 0QUJPOBMTZOUBDUJDDBUFHPSJFTBOEMJUFSBMTBSFNBSLFECZBUSBJMJOHRVFTUJPONBSL

    View Slide

  169. -BOHVBHF3FGFSFODF
    "CPVUUIF-BOHVBHF3FGFSFODF
    r ໼ҹ ˠ
    ͸ߏจΛࣔ͠ɺʮࠨล͸ӈลͷʓʓͰߏ੒͞ΕΔʯͱಡΈ·͢
    r ߏจΧςΰϦ͸ΠλϦοΫͷςΩετͰࣔ͞Εɺߏจϧʔϧͷ྆ଆʹݱΕ·͢
    r ୯ޠϦςϥϧͱه߸͸ɺଠࣈͰ౳෯ͷॻମͰࣔ͞ΕɺߏจϧʔϧͷӈଆʹͷΈݱΕ
    ·͢
    r ෳ਺ͷॻ͖ํ͕͋Δߏจ͸ύΠϓ c
    Ͱ۠੾ΒΕ·͢ɻԣʹ௕͘ಡΈʹ͘͘ͳΔ৔߹
    ͸৽͍͠ߦͰෳ਺ͷߏจϧʔϧʹ෼ׂ͞Ε·͢
    r ௨ৗͷॻମΛ࢖༻ͯ͠จ๏نଇͷӈଆΛهड़͢Δ͜ͱ͕͋Γ·͢
    r ඞਢͰ͸ͳ͍ߏจΧςΰϦͱϦςϥϧ͸ɺ຤ඌʹΫΤενϣϯϚʔΫA A͕෇͖·͢

    View Slide

  170. -BOHVBHF3FGFSFODF
    "CPVUUIF-BOHVBHF3FGFSFODF
    Grammar of a getter-setter block
    getter-setter-block → { getter-clause setter-clause? } | { setter-clause getter-clause }
    var name: String {
    get {
    ...
    }
    set {
    ...
    }
    }
    var name: String {
    get {
    ...
    }
    }

    View Slide

  171. Grammar of a function declaration
    function-declaration → function-head function-name generic-parameter-clause?

    function-signature generic-where-clause? function-body?

    function-head → attributes? declaration-modi
    fi
    ers? func

    function-name → identi
    fi
    er | operator

    function-signature → parameter-clause async? throws? function-result?

    function-signature → parameter-clause async? rethrows function-result?

    function-result → -> attributes? type

    function-body → code-block

    parameter-clause → ( ) | ( parameter-list )

    parameter-list → parameter | parameter , parameter-list

    parameter → external-parameter-name? local-parameter-name type-annotation default-argument-clause?

    parameter → external-parameter-name? local-parameter-name type-annotation

    parameter → external-parameter-name? local-parameter-name type-annotation ...

    external-parameter-name → identi
    fi
    er

    local-parameter-name → identi
    fi
    er

    default-argument-clause → = expression

    View Slide

  172. Grammar of a function declaration
    function-declaration → function-head function-name generic-parameter-clause?

    function-signature generic-where-clause? function-body?

    function-head → attributes? declaration-modi
    fi
    ers? func

    function-name → identi
    fi
    er | operator

    function-signature → parameter-clause async? throws? function-result?

    function-signature → parameter-clause async? rethrows function-result?

    function-result → -> attributes? type

    function-body → code-block

    parameter-clause → ( ) | ( parameter-list )

    parameter-list → parameter | parameter , parameter-list

    parameter → external-parameter-name? local-parameter-name type-annotation default-argument-clause?

    parameter → external-parameter-name? local-parameter-name type-annotation

    parameter → external-parameter-name? local-parameter-name type-annotation ...

    external-parameter-name → identi
    fi
    er

    local-parameter-name → identi
    fi
    er

    default-argument-clause → = expression

    View Slide

  173. Grammar of a function declaration
    function-declaration → function-head function-name generic-parameter-clause?

    function-signature generic-where-clause? function-body?

    function-head → attributes? declaration-modi
    fi
    ers? func

    function-name → identi
    fi
    er | operator

    function-signature → parameter-clause async? throws? function-result?

    function-signature → parameter-clause async? rethrows function-result?

    function-result → -> attributes? type

    function-body → code-block

    parameter-clause → ( ) | ( parameter-list )

    parameter-list → parameter | parameter , parameter-list

    parameter → external-parameter-name? local-parameter-name type-annotation default-argument-clause?

    parameter → external-parameter-name? local-parameter-name type-annotation

    parameter → external-parameter-name? local-parameter-name type-annotation ...

    external-parameter-name → identi
    fi
    er

    local-parameter-name → identi
    fi
    er

    default-argument-clause → = expression

    View Slide

  174. -BOHVBHF3FGFSFODF
    4VNNBSZPGUIF(SBNNBS
    Grammar of a code block
    code-block → { statements? }

    View Slide

  175. -BOHVBHF3FGFSFODF
    4VNNBSZPGUIF(SBNNBS
    r ม਺એݴ
    r $PNQVUFE1SPQFSUZ
    r HFUUFSTFUUFS
    r XJMM4FUEJE4FU
    r GPSJOXIJMFSFQFBUXIJMFJGHVBSETXJUDI
    r EFGFS
    r EPEPDBUDI

    View Slide

  176. -BOHVBHF3FGFSFODF
    4VNNBSZPGUIF(SBNNBS
    Grammar of a structure declaration
    struct-declaration → attributes? access-level-modi
    fi
    er? struct struct-name generic-parameter-clause?

    type-inheritance-clause? generic-where-clause? struct-body

    struct-name → identi
    fi
    er

    struct-body → { struct-members? }

    struct-members → struct-member struct-members?

    struct-member → declaration | compiler-control-statement

    View Slide

  177. -BOHVBHF3FGFSFODF
    4VNNBSZPGUIF(SBNNBS
    r FOVNTUSVDUDMBTTBDUPSQSPUPDPMFYUFOTJPOએݴ
    r QSFDFEFODFHSPVQએݴ
    r $MPTVSF
    r 6OJDPEF&TDBQF
    r "UUSJCVUFT

    View Slide

  178. -BOHVBHF3FGFSFODF
    4VNNBSZPGUIF(SBNNBS
    r FOVNTUSVDUDMBTTBDUPSQSPUPDPMFYUFOTJPOએݴ
    r QSFDFEFODFHSPVQએݴ
    r $MPTVSF
    r 6OJDPEF&TDBQF
    r "UUSJCVUFT

    View Slide

  179. -BOHVBHF3FGFSFODF
    4VNNBSZPGUIF(SBNNBS
    r ม਺એݴ
    r $PNQVUFE1SPQFSUZ
    r HFUUFSTFUUFS
    r XJMM4FUEJE4FU
    r GPSJOXIJMFSFQFBUXIJMFJGHVBSETXJUDI
    r EFGFS
    r EPEPDBUDI
    r FOVNTUSVDUDMBTTBDUPSQSPUPDPMFYUFOTJPOએݴ
    r QSFDFEFODFHSPVQએݴ
    r $MPTVSF

    View Slide

  180. -BOHVBHF3FGFSFODF
    4VNNBSZPGUIF(SBNNBS
    r ม਺એݴ
    r $PNQVUFE1SPQFSUZ
    r HFUUFSTFUUFS
    r XJMM4FUEJE4FU
    r GPSJOXIJMFSFQFBUXIJMFJGHVBSEEFGFSEPEPDBUDI
    r FOVNTUSVDUDMBTTBDUPSQSPUPDPMFYUFOTJPOએݴ
    r QSFDFEFODFHSPVQએݴ
    r TXJUDI$MPTVSF
    %FDM(SPVQ4ZOUBY
    8JUI$PEF#MPDL4ZOUBY
    8JUI4UBUFNFOUT4ZOUBY

    View Slide

  181. 4XJGU-JOU0QFOJOH#SBDF4QBDJOH3VMF
    "VUPDPSSFDUJPO🪄
    extension DeclGroupSyntax {
    func violationPosition() -> AbsolutePosition? {
    let openingBrace = memberBlock.leftBrace
    if !openingBrace.hasSingleSpaceLeading() {
    return openingBrace.positionAfterSkippingLeadingTrivia
    }
    return nil
    }
    }

    View Slide

  182. override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind {
    if let position = node.violationPosition() {
    violations.append(position)
    }
    return .visitChildren
    }
    override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind {
    if let position = node.violationPosition() {
    violations.append(position)
    }
    return .visitChildren
    }
    ...

    View Slide

  183. private extension DeclGroupSyntax {
    ...
    func correct(keyPath: WritableKeyPath) -> Self {
    return self
    .with(keyPath, self[keyPath: keyPath].with(\.trailingTrivia, .space))
    .with(\.memberBlock, memberBlock.with(\.leadingTrivia, []))
    }
    func correct(keyPath: WritableKeyPath) -> Self? {
    guard let value = self[keyPath: keyPath] else {
    return nil
    }
    return self
    .with(keyPath, value.with(\.trailingTrivia, .space))
    .with(\.memberBlock, memberBlock.with(\.leadingTrivia, []))
    }
    }

    View Slide

  184. 4XJGU-JOU0QFOJOH#SBDF4QBDJOH3VMF
    -FTTPOT-FBSOFE
    r 4ZOUBY7JTJUPSΛ࢖ͬͯҧ൓ΛνΣοΫ
    r 4ZOUBY3FXSJUFSΛ࢖ͬͯҧ൓ΛνΣοΫˍࣗಈमਖ਼
    r 1SPUPDPM&YUFOTJPOΛ࢖ͬͯॲཧΛ·ͱΊΔ
    r 4XJGU-BOHVBHF3FGFSFODF
    r 1VMM3FRVFTU
    IUUQTHJUIVCDPNSFBMN4XJGU-JOUQVMM

    View Slide

  185. &YFSDJTFT4XJGU-JOU
    $PMMFDUJPO&MFNFOU"MJHONFOU

    View Slide

  186. 4XJGU-JOU$PMMFDUJPO&MFNFOU"MJHONFOU
    DPMMFDUJPO@BMJHONFOU
    r "MMFMFNFOUTJOBDPMMFDUJPOMJUFSBMTIPVMECFWFSUJDBMMZBMJHOFE
    let abc = [
    "alpha": "a",
    "beta": "b",
    "gamma": "g",
    "delta": "d"
    ]
    let abc = [
    "alpha": "a",
    "beta": "b",
    "gamma": "g",
    "delta": "d"
    ]
    let abc = [
    "alpha": "a",
    "beta": "b",
    "gamma": "g",
    "delta": "d"
    ]
    let abc = [
    "alpha": "a",
    "beta": "b",
    "gamma": "g",
    "delta": "d"
    ]

    View Slide

  187. 4XJGU-JOU$PMMFDUJPO&MFNFOU"MJHONFOU
    DPMMFDUJPO@BMJHONFOU
    r "MMFMFNFOUTJOBDPMMFDUJPOMJUFSBMTIPVMECFWFSUJDBMMZBMJHOFE
    r "MMFMFNFOUTJOBDPMMFDUJPOMJUFSBMTIPVMECFWFSUJDBMMZBMJHOFE
    let abc = [
    "alpha": "a",
    "beta": "b",
    "gamma": "g",
    "delta": "d"
    ]
    let abc = [
    "alpha": "a",
    "beta": "b",
    "gamma": "g",
    "delta": "d"
    ]
    let abc = [
    "alpha": "a",
    "beta": "b",
    "gamma": "g",
    "delta": "d"
    ]
    let abc = [
    "alpha": "a",
    "beta": "b",
    "gamma": "g",
    "delta": "d"
    ]

    View Slide

  188. 4XJGU-JOU$PMMFDUJPO&MFNFOU"MJHONFOU
    'BMTF1PTJUJWF
    NSAttributedString(
    string: "…", attributes: [.font: UIFont.systemFont(ofSize: 12, weight: .regular),
    .foregroundColor: UIColor(white: 0, alpha: 0.2)]
    )
    IUUQTHJUIVCDPNSFBMN4XJGU-JOUJTTVFT

    View Slide

  189. 4XJGU-JOU$PMMFDUJPO&MFNFOU"MJHONFOU
    'BMTF1PTJUJWF
    NSAttributedString(
    string: "…", attributes: [.font: UIFont.systemFont(ofSize: 12, weight: .regular),
    .foregroundColor: UIColor(white: 0, alpha: 0.2)]
    )
    IUUQTHJUIVCDPNSFBMN4XJGU-JOUJTTVFT
    31
    29

    View Slide

  190. 4XJGU-JOU$PMMFDUJPO&MFNFOU"MJHONFOU
    DPMMFDUJPO@BMJHONFOU
    "…".utf8.count
    3

    View Slide

  191. 4XJGU-JOU$PMMFDUJPO&MFNFOU"MJHONFOU
    DPMMFDUJPO@BMJHONFOU
    override func visitPost(_ node: DictionaryElementListSyntax) {
    let locations = node.map { element in
    let position = alignColons ?
    element.colon.positionAfterSkippingLeadingTrivia :
    element.keyExpression.positionAfterSkippingLeadingTrivia
    return locationConverter.location(for: position)
    }
    violations.append(contentsOf: validate(keyLocations: locations))
    }
    "4$** 65'ͰCZUF
    ͳΒ੒Γཱͭ

    View Slide

  192. override func visitPost(_ node: DictionaryElementListSyntax) {
    let locations = node.map { element in
    let position = alignColons ? element.colon.positionAfterSkippingLeadingTrivia :
    element.keyExpression.positionAfterSkippingLeadingTrivia
    let location = locationConverter.location(for: position)
    let graphemeColumn: Int
    let graphemeClusters = String(
    locationConverter.sourceLines[location.line - 1].utf8.prefix(location.column - 1)
    )
    if let graphemeClusters {
    graphemeColumn = graphemeClusters.count + 1
    } else {
    graphemeColumn = location.column
    }
    return SourceLocation(
    line: location.line,
    column: graphemeColumn,
    offset: location.offset,
    file: location.file
    )
    }
    violations.append(contentsOf: validate(keyLocations: locations))
    }

    View Slide

  193. override func visitPost(_ node: DictionaryElementListSyntax) {
    let locations = node.map { element in
    let position = alignColons ? element.colon.positionAfterSkippingLeadingTrivia :
    element.keyExpression.positionAfterSkippingLeadingTrivia
    let location = locationConverter.location(for: position)
    let graphemeColumn: Int
    let graphemeClusters = String(
    locationConverter.sourceLines[location.line - 1].utf8.prefix(location.column - 1)
    )
    if let graphemeClusters {
    graphemeColumn = graphemeClusters.count + 1
    } else {
    graphemeColumn = location.column
    }
    return SourceLocation(
    line: location.line,
    column: graphemeColumn,
    offset: location.offset,
    file: location.file
    )
    }
    violations.append(contentsOf: validate(keyLocations: locations))
    }
    locationConverter.sourceLines[location.line - 1].utf8.prefix(location.column - 1)
    ݩͷιʔείʔυ͕ߦ͝ͱʹ֨ೲ͞Εͨ഑ྻ

    View Slide

  194. override func visitPost(_ node: DictionaryElementListSyntax) {
    let locations = node.map { element in
    let position = alignColons ? element.colon.positionAfterSkippingLeadingTrivia :
    element.keyExpression.positionAfterSkippingLeadingTrivia
    let location = locationConverter.location(for: position)
    let graphemeColumn: Int
    let graphemeClusters = String(
    locationConverter.sourceLines[location.line - 1].utf8.prefix(location.column - 1)
    )
    if let graphemeClusters {
    graphemeColumn = graphemeClusters.count + 1
    } else {
    graphemeColumn = location.column
    }
    return SourceLocation(
    line: location.line,
    column: graphemeColumn,
    offset: location.offset,
    file: location.file
    )
    }
    violations.append(contentsOf: validate(keyLocations: locations))
    }
    29
    31

    View Slide

  195. 4XJGU-JOU$PMMFDUJPO&MFNFOU"MJHONFOU
    'BMTF1PTJUJWF
    NSAttributedString(
    string: "…", attributes: [.font: UIFont.systemFont(ofSize: 12, weight: .regular),
    .foregroundColor: UIColor(white: 0, alpha: 0.2)]
    )
    IUUQTHJUIVCDPNSFBMN4XJGU-JOUJTTVFT

    View Slide

  196. View Slide

  197. let graphemeStartColumn: Int
    if let prefix = String(locationConverter.sourceLines[start.line - 1].utf8.prefix(start.column - 1)) {
    graphemeStartColumn = prefix.utf16.count + 1
    } else {
    graphemeStartColumn = start.column
    }
    let graphemeEndColumn: Int
    if let prefix = String(locationConverter.sourceLines[end.line - 1].utf8.prefix(end.column - 1)) {
    graphemeEndColumn = prefix.utf16.count + 1
    } else {
    graphemeEndColumn = end.column
    }
    4XJGU"45&YQMPSFS
    +BWB4DSJQU4USJOH-FOHUI*TJO65'$PEF6OJUT

    View Slide

  198. 4XJGU-JOU$PMMFDUJPO&MFNFOU"MJHONFOU
    -FTTPOT-FBSOFE
    r 4XJGU4ZOUBYͷߏจϊʔυͷҐஔ৘ใ͸65'ͷόΠτ਺
    r ࣮ࡍͷιʔείʔυͷҐஔͱ͸ඞͣ͠΋Ұக͠ͳ͍ "4$**Ҏ֎ͷจࣈͳͲ

    r ࠷ऴతͳग़ྗઌʹ߹Θͤͯ޻෉͕ඞཁ
    ‣ ݟͨ໨ͷจࣈ਺ˠॻهૉΫϥελ
    ‣ +BWB4DSJQUˠ65'
    r 1VMM3FRVFTU
    IUUQTHJUIVCDPNSFBMN4XJGU-JOUJTTVFT

    View Slide

  199. &YFSDJTFT4PVSDF&EJUPS&YUFOTJPOT

    View Slide

  200. &YFSDJTFT4PVSDF&EJUPS&YUFOTJPOT
    $POWFSU9$5"TTFSU'VODUJPOTUP1PXFS"TTFSU.BDSPT
    XCTAssert(actual == expected)
    XCTAssertEqual(actual, expected)
    XCTAssertNotEqual(actual, expected)
    XCTAssertGreaterThan(a, b)
    XCTAssertGreaterThanOrEqual(a, b)
    XCTAssertIdentical(actual, expected)
    XCTAssertNotIdentical(actual, expected)
    #assert(actual == expected)
    #assert(actual == expected)
    #assert(actual != expected)
    #assert(a > b)
    #assert(a >= b)
    #assert(actual === expected)
    #assert(actual !== expected)

    View Slide

  201. &YFSDJTFT4PVSDF&EJUPS&YUFOTJPOT
    $POWFSU9$5"TTFSU'VODUJPOTUP1PXFS"TTFSU.BDSPT
    r 9$5"TTFSUؔ਺ΛBTTFSU
    ϚΫϩʹม׵͢Δ
    r ςετͷ࣮ߦ݁Ռ͕มΘΒͳ͍Α͏ʹ͢Δ
    r NFTTBHFɺ
    fi
    MFɺMJOFͷσϑΥϧτҾ਺ʹ஫ҙ͢Δ
    r ුಈখ਺Λൺֱ͢Δ9$5"TTFSU&RVBM @@BDDVSBDZ@
    fi
    MFMJOF
    ؔ਺͸ର৅֎
    r ෆ׬શͳιʔείʔυ͕౉ͬͯ͘Δ৔߹͕͋Δ
    r ղ౴ྫ
    IUUQTHJUIVCDPNLJTIJLBXBLBUTVNJTXJGUQPXFSBTTFSUQVMM

    View Slide

  202. 1VTI:PVS-JNJUT
    4XJGU4ZOUBYΛϚελʔ͢ΔͨΊʹ
    r 4XJGU-JOUͷϧʔϧΛ4XJGU4ZOUBYΛ࢖ͬͯॻ͖׵͑Δ
    r 4XJGUϚΫϩΛ࡞ͬͯΈΔ
    r 4XJGU4ZOUBYΛ࢖ͬͯ9DPEF4PVSDF&EJUPS&YUFOTJPOΛ࡞Δ
    r 4XJGU"45&YQMPSFSͷ։ൃʹڠྗ͢Δ
    r 4XJGU1PXFS"TTFSUͷ։ൃʹڠྗ͢Δ

    View Slide

  203. 8SBQ6Q
    r 4XJGU1BSTFSͰιʔείʔυΛߏจղੳ͠ɺ4ZOUBY5SFFΛ୳ࡧ͢Δ
    r 4ZOUBY5SFFΛม׵ͯ͠ιʔείʔυΛॻ͖׵͑Δ
    r ϚΫϩɺ-JOUɺ'PSNBUUFSɺ9DPEF&YUFOTJPOɺͳͲ͕࡞ΕΔ
    r 4ZOUBY3FXSJUFS΍4PVSDF-PDBUJPO$POWFSUFSͳͲಠಛͷ"1*ʹ׳ΕΕ͹ҙ֎
    ͱγϯϓϧʹॻ͚Δ
    r ίϯύΠϥͰ͸ͳ͍ͷͰܕ৘ใͳͲߏจ͔ΒಡΈऔΕͳ͍΋ͷ͸ѻ͑ͳ͍
    r 4XJGU4ZOUBYΛ༗ޮʹ࢖͏ʹ͸4XJGUʹؔ͢Δ෯޿͍஌͕ࣝॏཁ

    View Slide

  204. 3FGFSFODFT
    r 4BNQMF$PEF
    IUUQTHJUIVCDPNLJTIJLBXBLBUTVNJJPTEDTBNQMFT
    r 4XJGU4ZOUBY
    IUUQTHJUIVCDPNBQQMFTXJGUTZOUBY
    r 4XJGU"45&YQMPSFS
    IUUQTTXJGUBTUFYQMPSFSDPN
    r 4XJGU-BOHVBHF3FGFSFODF
    IUUQTEPDTTXJGUPSHTXJGUCPPLEPDVNFOUBUJPOUIFTXJGUQSPHSBNNJOHMBOHVBHFBCPVUUIFMBOHVBHFSFGFSFODF
    r 4XJGU.BDSPT
    IUUQTHJUIVCDPNLS[ZT[UPG[BCMPDLJ4XJGU.BDSPT

    View Slide