Lock in $30 Savings on PRO—Offer Ends Soon! ⏳

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. r *OUSPEVDUJPO r 4XJGU4ZOUBY0WFSWJFX r "OBUPNZPG4ZOUBY5SFF r 4XJGU4ZOUBY"1* r &YFSDJTFT

    ‣ 4XJGU.BDSPT ‣ 4XJGU-JOU ‣ 4PVSDF&EJUPS&YUFOTJPOT 0VUMJOF ໨࣍
  2. let number = 100 SourceFileSyntax CodeBlockItemListSyntax CodeBlockItemSyntax VariableDeclSyntax PatternBindingListSyntax PatternBindingSyntax

    IdentifierPatternSyntax InitializerClauseSyntax IntegerLiteralExprSyntax endOfFile number = 100 let 4ZOUBY5SFF
  3. 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
  4. 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
  5. 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
  6. 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
  7. let number = 100 SourceFileSyntax CodeBlockItemListSyntax CodeBlockItemSyntax VariableDeclSyntax PatternBindingListSyntax PatternBindingSyntax

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

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

    IdentifierPatternSyntax InitializerClauseSyntax IntegerLiteralExprSyntax endOfFile number = 100 let
  10. πϦʔߏ଄ΛԼํ޲ʹͨͲΔ 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")
  11. SourceFileSyntax ├─statements │ ╰─[0] │ ╰─item │ ├─attributes │ ├─modifiers

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

    │ ├─attributes │ ├─modifiers │ ├─bindingSpecifier │ ╰─bindings │ ╰─[0] │ ├─pattern │ │ ╰─identifier: "number" │ ╰─initializer │ ├─equal: equal │ ╰─value │ ╰─literal: "100" ╰─endOfFileToken
  13. 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
  14. 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
  15. 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)
  16. 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
  17. 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)
  18. 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) }
  19. 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
  20. 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
  21. 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 }
  22. let number = 100 SourceFileSyntax CodeBlockItemListSyntax CodeBlockItemSyntax VariableDeclSyntax PatternBindingListSyntax PatternBindingSyntax

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

    IdentifierPatternSyntax InitializerClauseSyntax IntegerLiteralExprSyntax endOfFile number = 100 let
  24. 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 πϦʔͷܗΛߏ଄తʹϚονϯά͢Δ
  25. πϦʔߏ଄Λ্ํ޲ʹͨͲΔ 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
  26. let result = decl.with( \.bindings, [ binding.with( \.pattern, PatternSyntax( pattern.with(

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

    \.identifier, identifier.with( \.tokenKind, .identifier("amount") ) ) ) ) ] ) let number = 100 let amount = 100 ݩͷཁૉ ݩͷཁૉ ݩͷཁૉ ͜ͷཁૉ͚ͩม͑Δ
  28. let /* let number defines a constant. */ number =

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

    100 WT3FHVMBS&YQSFTTJPO /\s*let\s+(number)\s*/
  30. 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)
  31. 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
  32. 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
  33. 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
  34. @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
  35. @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
  36. 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"
  37. if let decl = item.as(VariableDeclSyntax.self) { ... } if let

    decl = item as? VariableDeclSyntax { } ϊʔυͷܕΛม׵͢Δ ܕม׵ͷؔ਺Λ࢖͏
  38. ϊʔυͷܕΛม׵͢Δ 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 ܕม׵ͷؔ਺Λ࢖͏
  39. ϊʔυͷܕΛม׵͢Δ 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 ܕม׵ͷؔ਺Λ࢖͏
  40. ϊʔυͷܕΛม׵͢Δ 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 ܕม׵ͷؔ਺Λ࢖͏
  41. ϊʔυͷܕΛม׵͢Δ 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 ܕม׵ͷؔ਺Λ࢖͏
  42. r 4ZOUBY5SFFͷ֤ϊʔυ͸ͳΜΒ͔ͷ4ZOUBY 4ZOUBY1SPUPDPM  r ۩ମతͳܕΛௐ΂Δ͜ͱͰߏจͷछྨ͕Θ͔Δ FH7BSJBCMF%FDM4ZOUBYɺ'VODUJPO$BMM&YQS4ZOUBYɺ'PS4UNU4ZOUBY r 4ZOUBYͷ࠷খ୯Ґ͸τʔΫϯ 5PLFO4ZOUBY

     r πϦʔߏ଄ΛԼํ޲ʹͨͲΔͱ͖͸ܕͷϓϩύςΟ͕࢖͑Δ FHsourceFile.statementsɺpattern.identifier ˞ෳ਺ͷܕͷީิ͕͋Δ৔߹͸μ΢ϯΩϟετ͕ඞཁ r ্ํ޲ʹཁૉΛͨͲΔͱ͖͸దٓμ΢ϯΩϟετ͢Δ "OBUPNZPG4ZOUBY5SFF
  43. r એݴ ‣ ม਺ɺؔ਺ɺΫϥεͷએݴͳͲ ‣ $MBTT%FDM4ZOUBYɺ'VODUJPO%FDM4ZOUBYɺ7BSJBCMF%FDM4ZOUBYɺ r ࣜ ஋Λฦ͢ 

    ‣ ஋Λฦ͢ߏจɻؔ਺ݺͼग़͠ɺϓϩύςΟΞΫηεɺΫϩʔδϟͳͲ ‣ $MPTVSF&YQS4ZOUBYɺ'VODUJPO$BMM&YQS4ZOUBYɺ 4USJOH-JUFSBM&YQS4ZOUBYɺ r ͦͷଞ ‣ ܕΞϊςʔγϣϯͳͲ ‣ *EFOUJ fi FS5ZQF4ZOUBYɺ.FNCFS5ZQF4ZOUBYɺ0QUJPOBM5ZQF4ZOUBYɺ ͍Ζ͍Ζͳߏจϊʔυͷܕ
  44. ␣␣␣␣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
  45. r 4ZOUBY7JTJUPS ‣ 4ZOUBY5SFFΛਂ͞༏ઌ୳ࡧ %'4 ͰͨͲΔ αϒΫϥεͰॲཧΛ͢Δର৅ͷϊʔυͷWJTJUؔ਺ΛΦʔόʔϥΠυ͢Δ r 4ZOUBY3FXSJUFS ‣

    4ZOUBY7JTJUPSͷػೳʹՃ͑ͯɺWJTJUؔ਺ͰͨͲͬͨϊʔυΛผͷϊʔυʹॻ ͖׵͑Δ͜ͱ͕Ͱ͖Δ ‣ ϚΫϩ΍ϦϑΝΫλϦϯάɺϑΥʔϚολʔͳͲ4ZOUBY5SFFΛ༻͍ͯιʔε ίʔυΛม׵͢Δ༻్ʹ෯޿͘࢖ΘΕΔ 4XJGU4ZOUBY"1* 4ZOUBY7JTJUPS4ZOUBY3FXSJUFS
  46. 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) ... }
  47. 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ؔ਺ΛΦʔόʔϥΠυ͢Δ
  48. &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 } }
  49. &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 } }
  50. &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 } }
  51. ιʔείʔυͷςΩετΛൺֱ if calledExpression.baseName.tokenKind == .identifier("fatalError") { ... } if calledExpression.baseName.trimmed.description

    == "fatalError" { ... } if calledExpression.baseName.text == "fatalError" { ... } if calledExpression.baseName.description == "fatalError" { ... }
  52. ιʔείʔυͷςΩετΛൺֱ 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, []) } ... }
  53. ιʔείʔυͷςΩετΛൺֱ 4XJGU4ZOUBY#VJMEFS if calledExpression.baseName == "fatalError" { ... } import

    SwiftSyntaxBuilder ... 4USJOHϦςϥϧ͔Β4ZOUBY͕࡞ΒΕΔͷͰ ίϯύΠϧΤϥʔʹͳΒͳ͍
  54. 4ZOUBY"OZ7JTJUPS &YBNQMF$POWFSU4ZOUBY5SFFUP)5.- class HTMLConverter: SyntaxAnyVisitor { var html = "<!DOCTYPE

    html>" override func visitAny(_ node: Syntax) -> SyntaxVisitorContinueKind { html += #"<span class="\#(node.syntaxNodeType)">"# return .visitChildren } override func visitAnyPost(_ node: Syntax) { html += "</span>" } }
  55. 4ZOUBY"OZ7JTJUPS &YBNQMF$POWFSU4ZOUBY5SFFUP)5.- class HTMLConverter: SyntaxAnyVisitor { var html = "<!DOCTYPE

    html>" override func visitAny(_ node: Syntax) -> SyntaxVisitorContinueKind { html += #"<span class="\#(node.syntaxNodeType)">"# return .visitChildren } override func visitAnyPost(_ node: Syntax) { html += "</span>" } }
  56. class HTMLConverter: SyntaxAnyVisitor { var html = "<!DOCTYPE html>" override

    func visitAny(_ node: Syntax) -> SyntaxVisitorContinueKind { html += #"<span class="\#(node.syntaxNodeType)">"# return .visitChildren } override func visitAnyPost(_ node: Syntax) { html += "</span>" } override func visit(_ node: TokenSyntax) -> SyntaxVisitorContinueKind { html += node.description return .visitChildren } override func visitPost(_ node: TokenSyntax) {} }
  57. class HTMLConverter: SyntaxAnyVisitor { var html = "<!DOCTYPE html>" override

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

    func visitAny(_ node: Syntax) -> SyntaxVisitorContinueKind { html += #"<span class="\#(node.syntaxNodeType)">"# return .visitChildren } override func visitAnyPost(_ node: Syntax) { html += "</span>" } 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 += """ <span class="\(kind.htmlEscaped())">\(node.text.htmlEscaped())</span> """ html += node.trailingTrivia.pieces.map { $0.htmlEscaped() }.joined() return .visitChildren
  59. ... <span class="SourceFileSyntax"> <span class="CodeBlockItemListSyntax"> <span class="CodeBlockItemSyntax"> <span class="VariableDeclSyntax"> <span

    class="AttributeListSyntax"></span> <span class="DeclModifierListSyntax"></span> <span class="keyword">let</span>&nbsp; <span class="PatternBindingListSyntax"> <span class="PatternBindingSyntax"> <span class="IdentifierPatternSyntax"> <span class="identifier">number</span>&nbsp;</span> <span class="InitializerClauseSyntax"> <span class="equal">=</span>&nbsp; <span class="IntegerLiteralExprSyntax"> <span class="integerLiteral">100</span></span> </span> </span> </span> </span> </span> </span> <span class="endOfFile"></span> </span> ...
  60. 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 ...
  61. 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͕ॻ͖׵ΘΔ
  62. 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 @
  63. 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 @
  64. 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) } }
  65. 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) } }
  66. 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) } }
  67. 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Λ ͦͷ··෮ݩ͢Δ
  68. 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ܕΛฦ͢
  69. 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) } }
  70. 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ΛݺͿͱࢠཁૉͷ୳ࡧΛଓ͚Δ
  71. 4ZOUBY3FXSJUFS #BTF/PEFT r %FDM4ZOUBYએݴ ‣ ม਺ɺؔ਺ɺΫϥεͷએݴͳͲ r &YQS4ZOUBYࣜ ஋Λฦ͢ 

    ‣ ஋Λฦ͢ߏจɻؔ਺ݺͼग़͠ɺϓϩύςΟΞΫηεɺΫϩʔδϟͳͲ r 1BUUFSO4ZOUBY ‣ 4XJUDIจͷ$BTFઅ΍ม਺એݴ಺ͷม਺ͷఆٛͳͲ r 5ZQF4ZOUBY ‣ ܕΞϊςʔγϣϯͳͲ
  72. 4ZOUBY3FXSJUFS #BTF/PEFT // Non-failable initializer let expr = ExprSyntax(MemberAccessExprSyntax(name: .identifier("red")))

    // Failable initializer let decl = DeclSyntax(MemberAccessExprSyntax(name: .identifier("red"))) .red nil
  73. 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
  74. 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
  75. 4PVSDF-PDBUJPO 4PVSDF-PDBUJPO$POWFSUFS func startLocation( converter: SourceLocationConverter, afterLeadingTrivia: Bool = true

    ) -> SourceLocation func endLocation( converter: SourceLocationConverter, afterTrailingTrivia: Bool = false ) -> SourceLocation number
  76. 1BSTFS3FTJMJFODZ 1BSTFS/FWFS'BJMT SyntaxVisitor(viewMode: .sourceAccurate) enum SyntaxTreeViewMode { case sourceAccurate case

    fixedUp case all } ιʔείʔυʹදࣔ͞ΕΔτʔΫϯͷΈ 1BSTFS͕ิ׬ͨ͠τʔΫϯ΋८ճ͢Δ 1BSTFS͕ิ׬ͨ͠τʔΫϯ͓Αͼ༧ظ͠ͳ͍ϊʔυ΋८ճ͢Δ
  77. 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? }
  78. 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 }
  79. 4XJGU.BDSPT *OJU.BDSP struct InitMacro: MemberMacro { static func expansion( of

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

    node: AttributeSyntax, providingMembersOf declaration: some DeclGroupSyntax, in context: some MacroExpansionContext ) throws -> [DeclSyntax] { ... } } @Init
  81. 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? }
  82. 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")) } """ ]
  83. 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? }
  84. 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?)
  85. 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)
  86. 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")) } """ ]
  87. 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 }
  88. 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 ...
  89. 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) ) ) ) } ) ) ) ) ]
  90. 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}
  91. 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())
  92. 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 ...
  93. '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
  94. 'PMEJOH4FRVFODF&YQSFTTJPOT 4XJGU0QFSBUPST r ԋࢉࢠͷ༏ઌॱҐʹैͬͯ4ZOUBY5SFFΛʮંΓͨͨΉʯ r ԋࢉࢠͷ༏ઌॱҐͷ௨Γʹ८ճͰ͖ΔπϦʔߏ଄͕ੜ੒͞ΕΔ r ԋࢉࢠͷ༏ઌॱҐ͸ԋࢉࢠςʔϒϧʹ͋Β͔͡Ίొ࿥͢Δ r ະ஌ͷԋࢉࢠ

    ಠࣗఆٛͷԋࢉࢠ ͕ԋࢉࢠ͕͋Δͱࣦഊ͢Δ r ϚΫϩͷ৔߹ɺࣗಈͰંΓͨͨΜͩ4ZOUBY5SFF͕౉͞ΕΔ ‣ ࣗ෼Ͱ1BSTFͨ͠4ZOUBY5SFFͱҟͳ͍ͬͯͯ΋͋Θͯͳ͍ r ϚΫϩͷ৔߹ɺಠࣗఆٛͷԋࢉࢠ͕͋ͬͯ΋ڧҾʹંΓͨͨΉ ‣ ༏ઌॱҐ͕ؒҧͬͯંΓͨͨ·ΕΔ ৔߹͕͋Δ  r ໰୊͕͋Δ৔߹͸4ZOUBY5SFF͔ΒιʔείʔυΛੜ੒ͯࣗ͠෼Ͱ࠶1BSTF͢Δ
  95. "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*Λఏڙ ඞཁͳۭന΍վߦΛࣗಈతʹૠೖ͢Δ ԋࢉࢠͷ༏ઌॱҐʹैͬͯࣜΛʮંΓͨͨΉʯ
  96. *OJU.BDSP "EWBODFE&YFSDJTFT r $MBTTɺ4USVDUɺ"DUPSҎ֎ͷܕʹద༻͠ͳ͍ r ܕʹͭ͘ΞΫηεम০ࢠ QVCMJDɺQSJWBUFͳͲ ͱΠχγϟϥΠβͷΞΫηεϨ ϕϧΛҰகͤ͞Δ r

    $PNQVUFE1SPQFSUZΛআ֎͢Δ r ؔ਺ܕͷϓϩύςΟʹରԠ͢Δ r αϯϓϧίʔυ IUUQTHJUIVCDPNLJTIJLBXBLBUTVNJJPTEDTBNQMFT
  97. 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 }
  98. 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 }
  99. 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 }
  100. func run()␣␣↲ {↲ ␣␣*** } func run()↲ {↲ ␣␣*** }

    func run()␣␣{↲ ␣␣*** } 4XJGU-JOU0QFOJOH#SBDF4QBDJOH3VMF 5IF5SJWJB"UUSJCVUJPO3VMF
  101. 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) }
  102. 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) }
  103. 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) }
  104. 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 } } }
  105. 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) }
  106. 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) { } ??????????????
  107. 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) }
  108. 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) }
  109. 4XJGU-JOU0QFOJOH#SBDF4QBDJOH3VMF "VUPDPSSFDUJPO🪄 func run<T>(name: T) where T: StringProtocol { }

    func run() { } func run(){ } func run<T>(name: T) where T: StringProtocol { } if a == b { } if a == b { } *Gจ΋ର৅ɻଞʹ͸ʁ
  110. -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  
  111. -BOHVBHF3FGFSFODF "CPVUUIF-BOHVBHF3FGFSFODF r ໼ҹ ˠ ͸ߏจΛࣔ͠ɺʮࠨล͸ӈลͷʓʓͰߏ੒͞ΕΔʯͱಡΈ·͢ r ߏจΧςΰϦ͸ΠλϦοΫͷςΩετͰࣔ͞Εɺߏจϧʔϧͷ྆ଆʹݱΕ·͢ r ୯ޠϦςϥϧͱه߸͸ɺଠࣈͰ౳෯ͷॻମͰࣔ͞ΕɺߏจϧʔϧͷӈଆʹͷΈݱΕ

    ·͢ r ෳ਺ͷॻ͖ํ͕͋Δߏจ͸ύΠϓ c Ͱ۠੾ΒΕ·͢ɻԣʹ௕͘ಡΈʹ͘͘ͳΔ৔߹ ͸৽͍͠ߦͰෳ਺ͷߏจϧʔϧʹ෼ׂ͞Ε·͢ r ௨ৗͷॻମΛ࢖༻ͯ͠จ๏نଇͷӈଆΛهड़͢Δ͜ͱ͕͋Γ·͢ r ඞਢͰ͸ͳ͍ߏจΧςΰϦͱϦςϥϧ͸ɺ຤ඌʹΫΤενϣϯϚʔΫA A͕෇͖·͢
  112. -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 { ... } }
  113. 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
  114. 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
  115. 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
  116. -BOHVBHF3FGFSFODF 4VNNBSZPGUIF(SBNNBS r ม਺એݴ r $PNQVUFE1SPQFSUZ r HFUUFSTFUUFS r XJMM4FUEJE4FU

    r GPSJOXIJMFSFQFBUXIJMFJGHVBSETXJUDI r EFGFS r EPEPDBUDI
  117. -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
  118. -BOHVBHF3FGFSFODF 4VNNBSZPGUIF(SBNNBS r ม਺એݴ r $PNQVUFE1SPQFSUZ r HFUUFSTFUUFS r XJMM4FUEJE4FU

    r GPSJOXIJMFSFQFBUXIJMFJGHVBSETXJUDI r EFGFS r EPEPDBUDI r FOVNTUSVDUDMBTTBDUPSQSPUPDPMFYUFOTJPOએݴ r QSFDFEFODFHSPVQએݴ r $MPTVSF
  119. -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
  120. 4XJGU-JOU0QFOJOH#SBDF4QBDJOH3VMF "VUPDPSSFDUJPO🪄 extension DeclGroupSyntax { func violationPosition() -> AbsolutePosition? {

    let openingBrace = memberBlock.leftBrace if !openingBrace.hasSingleSpaceLeading() { return openingBrace.positionAfterSkippingLeadingTrivia } return nil } }
  121. 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 } ...
  122. private extension DeclGroupSyntax { ... func correct<T: SyntaxProtocol>(keyPath: WritableKeyPath<Self, T>)

    -> Self { return self .with(keyPath, self[keyPath: keyPath].with(\.trailingTrivia, .space)) .with(\.memberBlock, memberBlock.with(\.leadingTrivia, [])) } func correct<T: SyntaxProtocol>(keyPath: WritableKeyPath<Self, T?>) -> Self? { guard let value = self[keyPath: keyPath] else { return nil } return self .with(keyPath, value.with(\.trailingTrivia, .space)) .with(\.memberBlock, memberBlock.with(\.leadingTrivia, [])) } }
  123. 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" ]
  124. 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" ]
  125. 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
  126. 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
  127. 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 ͳΒ੒Γཱͭ
  128. 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)) }
  129. 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) ݩͷιʔείʔυ͕ߦ͝ͱʹ֨ೲ͞Εͨ഑ྻ
  130. 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
  131. 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
  132. 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
  133. &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)
  134. &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
  135. 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