Swift コードのための Swift プログラミング / Swift programming for Swift code

Swift コードのための Swift プログラミング / Swift programming for Swift code

2019/4/24に行われたLINE Developer meetup #53 in KYOTOでの登壇資料です

53850955f15249a1a9dc49df6113e400?s=128

LINE Developers

April 24, 2019
Tweet

Transcript

  1. 14.

    SwiftSyntax ͱ͸ • ϕʔε͸ Swift compiler ಺Ͱ࢖ΘΕ͍ͯΔ libSyntax • AST

    (ந৅ߏจ໦) ͷղੳɾ࡞੒ɾॻ͖׵͕͑Ͱ͖Δ • SwiftPM ͷύοέʔδͱͯ͠ఏڙ͞Ε͍ͯΔ
  2. 15.

    ίʔυ ࣈ۟
 ղੳ ߏจ ղੳ ҙຯ
 ղੳ AST AST struct

    MyStruct { var v = "Meetup" func f() { print(self) } }
  3. 25.
  4. 29.

    ߏจ৘ใ͔Β෼͔Δ͜ͱ͸ SwiftSyntax ΋෼͔Δ ͲΜͳ property ͕
 ͋Δʁ Class ͷએݴ͸Ͳ͜ʁ ͜ͷ

    struct ͸Կʹ
 ४ڌͯ͠Δʁ ͜ͷ struct ͸ͲΜͳ function Λ࣋ͬͯΔʁ integer literal ࢖ͬͯΔ
 ͱ͜Ζશ෦ڭ͑ͯ
  5. 30.

    AST Λૢ࡞Ͱ͖Δ ಛఆͷ property ʹ private ͚͍ͭͨ Class ʹ final

    ͚͍ͭͨ ४ڌͯ͠Δ protocol ͷ એݴΛฒͼସ͍͑ͨ function Λফ͍ͨ͠ integer literal ͷ
 ϑΥʔϚοτΛ౷Ұ͍ͨ͠
  6. 35.

    open class SyntaxVisitor { public init() open func visit(_ node:

    InOutExprSyntax) -> SyntaxVisitorContinu open func visit(_ node: PoundColumnExprSyntax) -> SyntaxVisitorC open func visit(_ node: FunctionCallArgumentListSyntax) -> Synta open func visit(_ node: ArrayElementListSyntax) -> SyntaxVisitor open func visit(_ node: DictionaryElementListSyntax) -> SyntaxVi open func visit(_ node: StringInterpolationExprSyntax) -> Syntax open func visit(_ node: TryExprSyntax) -> SyntaxVisitorContinueK "hello!\(name)"
  7. 36.

    open func visit(_ node: ArrayExprSyntax) -> SyntaxVisitorContinu open func visit(_

    node: DictionaryExprSyntax) -> SyntaxVisitorCo open func visit(_ node: FunctionCallArgumentSyntax) -> SyntaxVis open func visit(_ node: TupleElementSyntax) -> SyntaxVisitorCont open func visit(_ node: ArrayElementSyntax) -> SyntaxVisitorCont open func visit(_ node: DictionaryElementSyntax) -> SyntaxVisito open func visit(_ node: IntegerLiteralExprSyntax) -> SyntaxVisit open func visit(_ node: StringLiteralExprSyntax) -> SyntaxVisito open func visit(_ node: BooleanLiteralExprSyntax) -> SyntaxVisit open func visit(_ node: TernaryExprSyntax) -> SyntaxVisitorConti
  8. 37.

    class TextVisitor: SyntaxVisitor { override func visit(_ node: StringLiteralExprSyntax) ->

    SyntaxVisitorContinueKind {
 
 return .skipChildren } override func visit(_ node: StringInterpolationExprSyntax) -> SyntaxVisitorContinueKind {
 
 return .skipChildren } } ܧঝ
  9. 38.

    class TextVisitor: SyntaxVisitor { override func visit(_ node: StringLiteralExprSyntax) ->

    SyntaxVisitorContinueKind {
 
 return .skipChildren } override func visit(_ node: StringInterpolationExprSyntax) -> SyntaxVisitorContinueKind {
 
 return .skipChildren } } print(node.stringLiteral.withoutTrivia().text)
 print(node.segments) "Hello, world!\n" hello!\(name)
  10. 40.

    let syntaxTree = try SyntaxTreeParser.parse(fileURL) let textVisitor = TextVisitor() syntaxTree.walk(textVisitor)

    "couldn't find the products directory" "testExample" "textoru" "Hello, world!\n" prefix_____\(pipe)_____suffix Output
  11. 43.

    override func visit(_ node: StringLiteralExprSyntax) -> SyntaxVisitorContinueKind {
 
 let

    text = node.stringLiteral.withoutTrivia().text print("\(filePath):\(node.position.line):\(node.position.column):
 warning: `\(text)` is here!") return .skipChildren }
  12. 46.
  13. 48.

    protocol SampleViewModeling { var inputs: SampleViewModelInputs { get } var

    outputs: SampleViewModelOutputs { get } var coordinatorOutputs: SampleViewModelCoordinatorOutputs { get } } protocol SampleViewModelInputs { var viewWillAppear: PublishRelay<Void> { get } var okButtonDidTap: PublishRelay<Void> { get } } protocol SampleViewModelOutputs { var isOkButtonEnabled: Driver<Bool> { get } var showError: Signal<Sample.Error> { get } } protocol SampleViewModelCoordinatorOutputs { var show: Signal<SampleViewModel.RequestScreen> { get } } final class SampleViewModel: SampleViewModelInputs, SampleViewModelOutputs, SampleViewModelCoordinatorOutputs, SampleViewModeling { var inputs: SampleViewModelInputs { return self } var outputs: SampleViewModelOutputs { return self } var coordinatorOutputs: SampleViewModelCoordinatorOutputs { return self } // MARK: - SampleViewModelInputs let viewWillAppear: PublishRelay<Void> = PublishRelay() let okButtonDidTap: PublishRelay<Void> = PublishRelay() // MARK: - SampleViewModelOutputs let isOkButtonEnabled: Driver<Bool> let showError: Signal<Sample.Error> // MARK: - SampleViewModelCoordinatorOutputs let show: Observable<RequestScreen> // MARK: -
  14. 49.

    protocol SampleViewModeling { var inputs: SampleViewModelInputs { get } var

    outputs: SampleViewModelOutputs { get } var coordinatorOutputs: SampleViewModelCoordinatorOutputs { get } } protocol SampleViewModelInputs { var viewWillAppear: PublishRelay<Void> { get } var okButtonDidTap: PublishRelay<Void> { get } } protocol SampleViewModelOutputs { var isOkButtonEnabled: Driver<Bool> { get } var showError: Signal<Sample.Error> { get } } protocol SampleViewModelCoordinatorOutputs { var show: Signal<SampleViewModel.RequestScreen> { get } } final class SampleViewModel: SampleViewModelInputs, SampleViewModelOutputs, SampleViewModelCoordinatorOutputs, SampleViewModeling { var inputs: SampleViewModelInputs { return self }
  15. 50.

    } protocol SampleViewModelCoordinatorOutputs { var show: Signal<SampleViewModel.RequestScreen> { get }

    } final class SampleViewModel: SampleViewModelInputs, SampleViewModelOutputs, SampleViewModelCoordinatorOutputs, SampleViewModeling { var inputs: SampleViewModelInputs { return self } var outputs: SampleViewModelOutputs { return self } var coordinatorOutputs: SampleViewModelCoordinatorOutputs { return self } // MARK: - SampleViewModelInputs let viewWillAppear: PublishRelay<Void> = PublishRelay() let okButtonDidTap: PublishRelay<Void> = PublishRelay() // MARK: - SampleViewModelOutputs let isOkButtonEnabled: Driver<Bool> let showError: Signal<Sample.Error> // MARK: - SampleViewModelCoordinatorOutputs let show: Observable<RequestScreen> // MARK: - // ... டংΛकΔͨΊͷ࿑ྗͷൃੜ
  16. 51.

    } protocol SampleViewModelCoordinatorOutputs { var show: Signal<SampleViewModel.RequestScreen> { get }

    } final class SampleViewModel: SampleViewModelInputs, SampleViewModelOutputs, SampleViewModelCoordinatorOutputs, SampleViewModeling { var inputs: SampleViewModelInputs { return self } var outputs: SampleViewModelOutputs { return self } var coordinatorOutputs: SampleViewModelCoordinatorOutputs { return self } // MARK: - SampleViewModelInputs let viewWillAppear: PublishRelay<Void> = PublishRelay() let okButtonDidTap: PublishRelay<Void> = PublishRelay() // MARK: - SampleViewModelOutputs let isOkButtonEnabled: Driver<Bool> let showError: Signal<Sample.Error> // MARK: - SampleViewModelCoordinatorOutputs let show: Observable<RequestScreen> // MARK: - // ...
  17. 52.

    } protocol SampleViewModelCoordinatorOutputs { var show: Signal<SampleViewModel.RequestScreen> { get }

    } final class SampleViewModel: SampleViewModelInputs, SampleViewModelOutputs, SampleViewModelCoordinatorOutputs, SampleViewModeling { var inputs: SampleViewModelInputs { return self } var outputs: SampleViewModelOutputs { return self } var coordinatorOutputs: SampleViewModelCoordinatorOutputs { return self } // MARK: - SampleViewModelInputs let viewWillAppear: PublishRelay<Void> = PublishRelay() let okButtonDidTap: PublishRelay<Void> = PublishRelay() // MARK: - SampleViewModelOutputs let isOkButtonEnabled: Driver<Bool> let showError: Signal<Sample.Error> // MARK: - SampleViewModelCoordinatorOutputs let show: Observable<RequestScreen> let showError: Signal<Sample.Error> // MARK: - ৔ॴͷޡΓ
  18. 53.

    } protocol SampleViewModelCoordinatorOutputs { var show: Signal<SampleViewModel.RequestScreen> { get }

    } final class SampleViewModel: SampleViewModelInputs, SampleViewModelOutputs, SampleViewModelCoordinatorOutputs, SampleViewModeling { var inputs: SampleViewModelInputs { return self } var outputs: SampleViewModelOutputs { return self } var coordinatorOutputs: SampleViewModelCoordinatorOutputs { return self } // MARK: - SampleViewModelInputs let viewWillAppear: PublishRelay<Void> = PublishRelay() let okButtonDidTap: PublishRelay<Void> = PublishRelay() // MARK: - SampleViewModelOutputs let isOkButtonEnabled: Driver<Bool> let showError: Signal<Sample.Error> // MARK: - SampleViewModelCoordinatorOutputs let show: Observable<RequestScreen> // MARK: - // ...
  19. 54.

    } protocol SampleViewModelCoordinatorOutputs { var show: Signal<SampleViewModel.RequestScreen> { get }

    } final class SampleViewModel: SampleViewModelInputs, SampleViewModelOutputs, SampleViewModelCoordinatorOutputs, SampleViewModeling { var inputs: SampleViewModelInputs { return self } var outputs: SampleViewModelOutputs { return self } var coordinatorOutputs: SampleViewModelCoordinatorOutputs { return self } // MARK: - SampleViewModelInputs let viewWillAppear: PublishRelay<Void> = PublishRelay() let okButtonDidTap: PublishRelay<Void> = PublishRelay() // MARK: - SampleViewModelOutputs let showError: Signal<Sample.Error> let isOkButtonEnabled: Driver<Bool> // MARK: - SampleViewModelCoordinatorOutputs let show: Observable<RequestScreen> // MARK: - // ... protocol ͷதͱॱ൪͕ҧ͏
 (௚͢΄Ͳ͡Όͳ͍͚Ͳɺͪΐͬͱؾʹͳͬͯ ἧ͑ͪΌ͏…)
  20. 58.

    struct `Protocol` { let name: String /// store `VariableDeclSyntax` let

    memberVariables: [String] } class InputOutputProtocolVisitor: SyntaxVisitor { var foundProtocols: [Protocol] = [] override func visit(_ node: ProtocolDeclSyntax) -> SyntaxVisitorContinueKind {
 let members = node.members.members
 ɹɹɹɹɹɹɹɹɹɹɹɹɹɹɹɹɹɹ.compactMap { ($0.decl as? VariableDeclSyntax)?.variableName } 
 if node.identifier.text.hasSuffix("Inputs") || node.identifier.text.hasSuffix("Outputs") { foundProtocols.append(Interface(name: node.identifier.text, memberVariables: members) } return .skipChildren } }
  21. 59.

    struct `Protocol` { let name: String /// store `VariableDeclSyntax` let

    memberVariables: [String] } class InputOutputProtocolVisitor: SyntaxVisitor { var foundProtocols: [Protocol] = [] override func visit(_ node: ProtocolDeclSyntax) -> SyntaxVisitorContinueKind {
 let members = node.members.members
 ɹɹɹɹɹɹɹɹɹɹɹɹɹɹɹɹɹɹ.compactMap { ($0.decl as? VariableDeclSyntax)?.variableName } 
 if node.identifier.text.hasSuffix("Inputs") || node.identifier.text.hasSuffix("Outputs") { foundProtocols.append(Interface(name: node.identifier.text, memberVariables: members) } return .skipChildren } }
  22. 60.

    struct `Protocol` { let name: String /// store `VariableDeclSyntax` let

    memberVariables: [String] } class InputOutputProtocolVisitor: SyntaxVisitor { var foundProtocols: [Protocol] = [] override func visit(_ node: ProtocolDeclSyntax) -> SyntaxVisitorContinueKind {
 let members = node.members.members
 ɹɹɹɹɹɹɹɹɹɹɹɹɹɹɹɹɹɹ.compactMap { ($0.decl as? VariableDeclSyntax)?.variableName } 
 if node.identifier.text.hasSuffix("Inputs") || node.identifier.text.hasSuffix("Outputs") { foundProtocols.append(Interface(name: node.identifier.text, memberVariables: members) } return .skipChildren } }
  23. 64.

    open class SyntaxRewriter { public init() open func visit(_ node:

    UnknownDeclSyntax) -> DeclSyntax open func visit(_ node: UnknownExprSyntax) -> ExprSyntax open func visit(_ node: UnknownStmtSyntax) -> StmtSyntax open func visit(_ node: UnknownTypeSyntax) -> TypeSyntax open func visit(_ node: UnknownPatternSyntax) -> PatternSynt open func visit(_ node: CodeBlockItemSyntax) -> Syntax open func visit(_ node: CodeBlockItemListSyntax) -> Syntax
  24. 65.

    open func visit(_ node: DeclModifierSyntax) -> Syntax open func visit(_

    node: InheritedTypeSyntax) -> Syntax open func visit(_ node: InheritedTypeListSyntax) -> Syntax open func visit(_ node: TypeInheritanceClauseSyntax) -> Synt open func visit(_ node: ClassDeclSyntax) -> DeclSyntax open func visit(_ node: StructDeclSyntax) -> DeclSyntax open func visit(_ node: ProtocolDeclSyntax) -> DeclSyntax open func visit(_ node: ExtensionDeclSyntax) -> DeclSyntax open func visit(_ node: MemberDeclBlockSyntax) -> Syntax
  25. 66.

    class ImplSectionRewriter: SyntaxRewriter { let protocols: [Protocol] init(protocols: [Protocol]) {

    self.protocols = protocols } override func visit(_ node: ClassDeclSyntax) -> DeclSyntax { // ४ڌ͍ͯ͠Δ protocol Λ֬ೝ // property Λฒͼସ͑ͨϊʔυΛ࡞੒ // ࡞ͬͨϊʔυΛฦ͢ }
  26. 67.

    class ImplSectionRewriter: SyntaxRewriter { let protocols: [Protocol] init(protocols: [Protocol]) {

    self.protocols = protocols } override func visit(_ node: ClassDeclSyntax) -> DeclSyntax { guard let inheritance = node.inheritanceClause?.inheritedTypeCollection .compactMap({ $0.typeName.description 
 .trimmingCharacters(in: .whitespacesAndNewlines) }) else { return node } let targetProtocols = protocols.filter { inheritance.contains($0.name) } return targetProtocols.reduce(node) { (result, `protocol`) -> ClassDeclSyntax in grouping(for: `protocol`, in: result) } } private func grouping(for targetProtocol: Protocol, in node: ClassDeclSyntax) -> ClassDeclSyntax { var sourceMembers = node.members.members var protocolImplMembers: [MemberDeclListItemSyntax] = []
  27. 68.

    private func grouping(for targetProtocol: Protocol, in node: ClassDeclSyntax) -> ClassDeclSyntax

    { var sourceMembers = node.members.members var protocolImplMembers: [MemberDeclListItemSyntax] = [] var sectionHeaderMember: MemberDeclListItemSyntax? = nil var triviaPircesOfSectionComment: [TriviaPiece] = [] for targetVariableName in targetProtocol.memberVariables { guard let index = sourceMembers.firstIndex(where: { guard let originMember = $0.decl as? VariableDeclSyntax else { return false } return originMember.variableName == targetVariableName }) else { continue } let foundDecl = sourceMembers[index] let sectionCommentIndex = foundDecl.leadingTrivia?.firstIndex(where: { (piece) -> Bool in return piece.comment?.contains("MARK: - \(targetProtocol.name)") == true }) if let sectionCommentIndex = sectionCommentIndex, let leadingTrivia = foundDecl.leadingTrivia { sectionHeaderMember = foundDecl let startTriviasOfDeclIndex = leadingTrivia.dropFirst(sectionCommentIndex + 1)
  28. 69.

    } let foundDecl = sourceMembers[index] let sectionCommentIndex = foundDecl.leadingTrivia?.firstIndex(where: {

    (piece) -> Bool in return piece.comment?.contains("MARK: - \(targetProtocol.name)") == true }) if let sectionCommentIndex = sectionCommentIndex, let leadingTrivia = foundDecl.leadingTrivia { sectionHeaderMember = foundDecl let startTriviasOfDeclIndex = leadingTrivia.dropFirst(sectionCommentIndex + 1) .firstIndex { $0.isNewline } let triviaPircesOfDecl: [TriviaPiece] = (startTriviasOfDeclIndex != nil) ? Array(leadingTrivia.dropFirst(startTriviasOfDeclIndex! + 1)) : [] triviaPircesOfSectionComment = (startTriviasOfDeclIndex != nil) ? Array(leadingTrivia.prefix(upTo: startTriviasOfDeclIndex!)) : leadingTrivia.map { $0 } let trimedDecl = FirstTokenRewriter { token in token.withLeadingTrivia(Trivia.init(pieces: [.newlines(1)] + triviaPircesOfDecl)) } .visit(foundDecl) as! MemberDeclListItemSyntax protocolImplMembers.append(trimedDecl) } else { sourceMembers = sourceMembers.removing(childAt: index) protocolImplMembers.append(foundDecl) }
  29. 70.

    guard let _sectionHeaderMember = sectionHeaderMember, let sectionHeaderMemberIndex = sourceMembers.firstIndex(where: {

    ($0.decl as? VariableDeclSyntax)?.variableName == (_sectionHeaderMember.decl as? VariableDeclSyntax)?.variableName }) else { return node } // restore section header comment let headDecl = FirstTokenRewriter { (token) -> TokenSyntax in let originLeadingTrivia = token.leadingTrivia.map { $0 } return token.withLeadingTrivia(Trivia.init(pieces: triviaPircesOfSectionComment + originLeadingTrivia)) } .visit(protocolImplMembers.first!) as! MemberDeclListItemSyntax protocolImplMembers[0] = headDecl sourceMembers = sourceMembers.removing(childAt: sectionHeaderMemberIndex) let formedMember = protocolImplMembers.reversed().reduce(sourceMembers) { (result, member) in result.inserting(member, at: sectionHeaderMemberIndex) } let block = node.members.withMembers(formedMember) return node.withMembers(block) } }
  30. 71.

    let syntaxTree = try! SyntaxTreeParser.parse(fileURL)
 let protocolCollector = InputOutputProtocolVisitor()
 syntaxTree.walk(protocolCollector)

    let rewriter = ImplSectionRewriter(
 protocols: protocolCollector.foundProtocols
 )
 let formedTree = rewriter.visit(syntaxTree)
 try! formedTree.description .write(to: fileURL, atomically: false, encoding: .utf8)
  31. 72.
  32. 73.
  33. 74.

    ߏจ৘ใ͔Β෼͔Δ͜ͱ͸ SwiftSyntax ΋෼͔Δ ͲΜͳ property ͕
 ͋Δʁ Class ͷએݴ͸Ͳ͜ʁ ͜ͷ

    struct ͸Կʹ
 ४ڌͯ͠Δʁ ͜ͷ struct ͸ͲΜͳ function Λ࣋ͬͯΔʁ integer literal ࢖ͬͯΔ
 ͱ͜Ζશ෦ڭ͑ͯ
  34. 75.

    AST Λૢ࡞Ͱ͖Δ ಛఆͷ property ʹ private ͚͍ͭͨ Class ʹ final

    ͚͍ͭͨ ४ڌͯ͠Δ protocol ͷ એݴΛฒͼସ͍͑ͨ function Λফ͍ͨ͠ integer literal ͷ
 ϑΥʔϚοτΛ౷Ұ͍ͨ͠