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

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

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

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

LINE Developers

April 24, 2019
Tweet

More Decks by LINE Developers

Other Decks in Technology

Transcript

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

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

    MyStruct { var v = "Meetup" func f() { print(self) } }
  3. ߏจ৘ใ͔Β෼͔Δ͜ͱ͸ SwiftSyntax ΋෼͔Δ ͲΜͳ property ͕
 ͋Δʁ Class ͷએݴ͸Ͳ͜ʁ ͜ͷ

    struct ͸Կʹ
 ४ڌͯ͠Δʁ ͜ͷ struct ͸ͲΜͳ function Λ࣋ͬͯΔʁ integer literal ࢖ͬͯΔ
 ͱ͜Ζશ෦ڭ͑ͯ
  4. AST Λૢ࡞Ͱ͖Δ ಛఆͷ property ʹ private ͚͍ͭͨ Class ʹ final

    ͚͍ͭͨ ४ڌͯ͠Δ protocol ͷ એݴΛฒͼସ͍͑ͨ function Λফ͍ͨ͠ integer literal ͷ
 ϑΥʔϚοτΛ౷Ұ͍ͨ͠
  5. 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)"
  6. 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
  7. class TextVisitor: SyntaxVisitor { override func visit(_ node: StringLiteralExprSyntax) ->

    SyntaxVisitorContinueKind {
 
 return .skipChildren } override func visit(_ node: StringInterpolationExprSyntax) -> SyntaxVisitorContinueKind {
 
 return .skipChildren } } ܧঝ
  8. 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)
  9. 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
  10. 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 }
  11. 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: -
  12. 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 }
  13. } 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. } 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: - // ...
  15. } 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: - ৔ॴͷޡΓ
  16. } 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. } 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 ͷதͱॱ൪͕ҧ͏
 (௚͢΄Ͳ͡Όͳ͍͚Ͳɺͪΐͬͱؾʹͳͬͯ ἧ͑ͪΌ͏…)
  18. 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 } }
  19. 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 } }
  20. 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. 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
  22. 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
  23. class ImplSectionRewriter: SyntaxRewriter { let protocols: [Protocol] init(protocols: [Protocol]) {

    self.protocols = protocols } override func visit(_ node: ClassDeclSyntax) -> DeclSyntax { // ४ڌ͍ͯ͠Δ protocol Λ֬ೝ // property Λฒͼସ͑ͨϊʔυΛ࡞੒ // ࡞ͬͨϊʔυΛฦ͢ }
  24. 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] = []
  25. 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)
  26. } 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) }
  27. 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) } }
  28. 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)
  29. ߏจ৘ใ͔Β෼͔Δ͜ͱ͸ SwiftSyntax ΋෼͔Δ ͲΜͳ property ͕
 ͋Δʁ Class ͷએݴ͸Ͳ͜ʁ ͜ͷ

    struct ͸Կʹ
 ४ڌͯ͠Δʁ ͜ͷ struct ͸ͲΜͳ function Λ࣋ͬͯΔʁ integer literal ࢖ͬͯΔ
 ͱ͜Ζશ෦ڭ͑ͯ
  30. AST Λૢ࡞Ͱ͖Δ ಛఆͷ property ʹ private ͚͍ͭͨ Class ʹ final

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