Slide 1

Slide 1 text

6OMPDLUIF1PUFOUJBMPG4XJGU$PEF(FOFSBUJPO  USZ4XJGU5PLZP !SPDLOBNF

Slide 2

Slide 2 text

 4NBSU#BOL *OD .PCJMF"QQMJDBUJPO%FWFMPQFS SPDLOBNF !@SPDLOBNF *OUSPEVDUJPO !SPDLOBNF

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

 6OMPDLUIF1PUFOUJBMPG4XJGU$PEF(FOFSBUJPO $PEF(FOFSBUJPO-JCSBSJFTJO"OESPJE%FWFMPQNFOU 3PPN %BHHFS

Slide 6

Slide 6 text

 6OMPDLUIF1PUFOUJBMPG4XJGU$PEF(FOFSBUJPO $PEF(FOFSBUJPO-JCSBSJFTJO"OESPJE%FWFMPQNFOU 3PPN %BHHFS "OOPUBUJPO1SPDFTTJOH

Slide 7

Slide 7 text

 6OMPDLUIF1PUFOUJBMPG4XJGU$PEF(FOFSBUJPO $PEF(FOFSBUJPO-JCSBSJFTJO"OESPJE%FWFMPQNFOU 3PPN %BHHFS

Slide 8

Slide 8 text

@Dao interface UserDao { @Query("SELECT * FROM users") fun getAll(): List }

Slide 9

Slide 9 text

@Dao interface UserDao { @Query("SELECT * FROM users") fun getAll(): List }

Slide 10

Slide 10 text

// Generated code with simplified class UserDao_Impl(private val __db: RoomDatabase) : UserDao { override fun getAll(): List { val statement = RoomSQLiteQuery.acquire("SELECT * FROM users", 0) __db.assertNotSuspendingTransaction() return query(__db, statement, false, null).use { cursor -> mutableListOf().apply { while (cursor.moveToNext()) { add(User(cursor.getInt(getColumnIndexOrThrow(cursor, "id")))) } } }.also { statement.release() } } }

Slide 11

Slide 11 text

No content

Slide 12

Slide 12 text

import SwiftData @Model final class User { var name: String init(name: String) { self.name = name } }

Slide 13

Slide 13 text

// Generated code with simplified final class User { var name: String { get { persistentBackingData.getValue(forKey: \.name) } set { persistentBackingData.setValue( forKey: \.name, to: newValue ) } } var persistentBackingData: any BackingData { ... } init(name: String) { … } }

Slide 14

Slide 14 text

 6OMPDLUIF1PUFOUJBMPG4XJGU$PEF(FOFSBUJPO $PEF(FOFSBUJPO-JCSBSJFTJO"OESPJE%FWFMPQNFOU 3PPN %BHHFS

Slide 15

Slide 15 text

@Component interface ApplicationComponent { fun inject(activity: LoginActivity) } class LoginActivity: Activity() { @Inject lateinit var loginViewModel: LoginViewModel } class LoginViewModel @Inject constructor( private val userRepository: UserRepository ) {} class UserRepository @Inject constructor( private val localDataSource: UserLocalDataSource, private val remoteDataSource: UserRemoteDataSource ) {} class UserLocalDataSource @Inject constructor() {} class UserRemoteDataSource @Inject constructor() {}

Slide 16

Slide 16 text

@Component interface ApplicationComponent { fun inject(activity: LoginActivity) } class LoginActivity: Activity() { @Inject lateinit var loginViewModel: LoginViewModel } class LoginViewModel @Inject constructor( private val userRepository: UserRepository ) {} class UserRepository @Inject constructor( private val localDataSource: UserLocalDataSource, private val remoteDataSource: UserRemoteDataSource ) {} class UserLocalDataSource @Inject constructor() {} class UserRemoteDataSource @Inject constructor() {}

Slide 17

Slide 17 text

@Component interface ApplicationComponent { fun inject(activity: LoginActivity) } class LoginActivity: Activity() { @Inject lateinit var loginViewModel: LoginViewModel } class LoginViewModel @Inject constructor( private val userRepository: UserRepository ) {} class UserRepository @Inject constructor( private val localDataSource: UserLocalDataSource, private val remoteDataSource: UserRemoteDataSource ) {} class UserLocalDataSource @Inject constructor() {} class UserRemoteDataSource @Inject constructor() {}

Slide 18

Slide 18 text

// Generated code with simplified class DaggerApplicationComponent : ApplicationComponent { fun inject(activity: LoginActivity) { activity.loginViewModel = loginViewModel() } private fun loginViewModel() = LoginViewModel(userRepository()) private fun userRepository() = UserRepository( UserLocalDataSource(), UserRemoteDataSource() ) }

Slide 19

Slide 19 text

 6OMPDLUIF1PUFOUJBMPG4XJGU$PEF(FOFSBUJPO w /05QPTTJCMF w .BDSPTPOMZQSPDFTTFT"45BSPVOEUIFNBDSP EFDMBSBUJPO w 4PZPVDBOOPUHFOFSBUFHMPCBMDPEFUIBUTQBOTNVMUJQMF UZQFT *TJUQPTTJCMFUPSFQMJDBUF%BHHFSXJUIKVTUVTJOH4XJGU.BDSPT

Slide 20

Slide 20 text

 6OMPDLUIF1PUFOUJBMPG4XJGU$PEF(FOFSBUJPO w /05QPTTJCMF w .BDSPTPOMZQSPDFTTFT"45BSPVOEUIFNBDSP EFDMBSBUJPO w 4PZPVDBOOPUHFOFSBUFHMPCBMDPEFUIBUTQBOTNVMUJQMF UZQFT *TJUQPTTJCMFUPSFQMJDBUF%BHHFSXJUIKVTUVTJOH4XJGU.BDSPT

Slide 21

Slide 21 text

4XJGU.BDSPT

Slide 22

Slide 22 text

4XJGU.BDSPT 4XJGU1BDLBHFQMVHJOT 9

Slide 23

Slide 23 text

4XJGU.BDSPT 4XJGU1BDLBHFQMVHJOT 4XJGU4ZOUBY 9 9

Slide 24

Slide 24 text

1SBDUJDBM&YBNQMF $PEF(FOFSBUJPOGPS%*$POUBJOFS

Slide 25

Slide 25 text

Sword IUUQTHJUIVCDPNSPDLOBNFTXPSE

Slide 26

Slide 26 text

No content

Slide 27

Slide 27 text

// ComponentApp/AppComponent.swift @Component final class AppComponent { }

Slide 28

Slide 28 text

// DataLocal/UserLocalDataSource.swift @Dependency(registeredTo: AppComponent.self) struct UserLocalDataSource { @Injected init() {} }

Slide 29

Slide 29 text

// DataLocal/UserRemoteDataSource.swift @Dependency(registeredTo: AppComponent.self) struct UserRemoteDataSource { @Injected init() {} }

Slide 30

Slide 30 text

// DataRepository/UserRepository.swift @Dependency(registeredTo: AppComponent.self) struct UserRepository { private let localDataSource: UserLocalDataSource private let remoteDataSource: UserRemoteDataSource @Injected init( localDataSource: UserLocalDataSource, remoteDataSource: UserRemoteDataSource ) { … } }

Slide 31

Slide 31 text

// FeatureLogin/LoginViewModel.swift @Dependency(registeredTo: AppComponent.self) final class LoginViewModel { private let userRepository: UserRepository @Injected init(userRepository: UserRepository) { self.userRepository = userRepository } } // FeatureLogin/LoginScreen.swift struct LoginScreen: View { let viewModel: LoginViewModel var body: some View { … } }

Slide 32

Slide 32 text

// SceneRoot/RootScene.swift struct RootScene: Scene { private let component = AppComponent() var body: some Scene { WindowGroup { ... } } }

Slide 33

Slide 33 text

extension AppComponent { var userLocalDataSource: UserLocalDataSource { UserLocalDataSource() } var userRemoteDataSource: UserRemoteDataSource { UserRemoteDataSource() } var userRepository: UserRepository { UserRepository( localDataSource: self.userLocalDataSource, remoteDataSource: self.userRemoteDataSource ) } var loginViewModel: LoginViewModel { LoginViewModel(userRepository: self.userRepository) } }

Slide 34

Slide 34 text

 1SBDUJDBM&YBNQMF$PEF(FOFSBUJPOGPS%*$POUBJOFS %F fi OF .BDSPT $PO fi HVSF 1MVHJO 1BSTF 7BMJEBUF (FOFSBUF $PEF

Slide 35

Slide 35 text

 1SBDUJDBM&YBNQMF$PEF(FOFSBUJPOGPS%*$POUBJOFS %F fi OF .BDSPT $PO fi HVSF 1MVHJO 1BSTF 7BMJEBUF (FOFSBUF $PEF

Slide 36

Slide 36 text

public protocol Component: AnyObject {} @attached( extension, conformances: Component ) public macro Component() = #externalMacro( module: "SwordMacros", type: “ComponentMacro" )

Slide 37

Slide 37 text

public protocol Component: AnyObject {} @attached( extension, conformances: Component ) public macro Component() = #externalMacro( module: "SwordMacros", type: “ComponentMacro" )

Slide 38

Slide 38 text

public protocol Component: AnyObject {} @attached( extension, conformances: Component ) public macro Component() = #externalMacro( module: "SwordMacros", type: “ComponentMacro" )

Slide 39

Slide 39 text

struct ComponentMacro: ExtensionMacro { static func expansion( of node: AttributeSyntax, attachedTo declaration: some DeclGroupSyntax, providingExtensionsOf type: some TypeSyntaxProtocol, conformingTo protocols: [TypeSyntax], in context: some MacroExpansionContext ) throws -> [ExtensionDeclSyntax] { let decl: DeclSyntax = """ extension \(raw: type.trimmedDescription): Sword.Component { } """ let extensionDecl = decl.cast(ExtensionDeclSyntax.self) return [extensionDecl] } }

Slide 40

Slide 40 text

struct ComponentMacro: ExtensionMacro { static func expansion( of node: AttributeSyntax, attachedTo declaration: some DeclGroupSyntax, providingExtensionsOf type: some TypeSyntaxProtocol, conformingTo protocols: [TypeSyntax], in context: some MacroExpansionContext ) throws -> [ExtensionDeclSyntax] { let decl: DeclSyntax = """ extension \(raw: type.trimmedDescription): Sword.Component { } """ let extensionDecl = decl.cast(ExtensionDeclSyntax.self) return [extensionDecl] } }

Slide 41

Slide 41 text

@Component public final class AppComponent { }

Slide 42

Slide 42 text

public final class AppComponent { } extension AppComponent: Sword.Component { } !$PNQPOFOU

Slide 43

Slide 43 text

@attached(peer) public macro Dependency( registeredTo component: any Component.Type ) = #externalMacro( module: ”SwordMacros", type: “DependencyMacro" )

Slide 44

Slide 44 text

@attached(peer) public macro Dependency( registeredTo component: any Component.Type ) = #externalMacro( module: ”SwordMacros", type: “DependencyMacro" )

Slide 45

Slide 45 text

@attached(peer) public macro Dependency( registeredTo component: any Component.Type ) = #externalMacro( module: ”SwordMacros", type: “DependencyMacro" )

Slide 46

Slide 46 text

struct DependencyMacro: PeerMacro { static func expansion( of node: AttributeSyntax, providingPeersOf declaration: some DeclSyntaxProtocol, in context: some MacroExpansionContext ) throws -> [DeclSyntax] { guard declaration.is(StructDeclSyntax.self) || declaration.is(ClassDeclSyntax.self) || declaration.is(ActorDeclSyntax.self) else { throw DependencyError.invalidApplication } return [] } }

Slide 47

Slide 47 text

struct DependencyMacro: PeerMacro { static func expansion( of node: AttributeSyntax, providingPeersOf declaration: some DeclSyntaxProtocol, in context: some MacroExpansionContext ) throws -> [DeclSyntax] { guard declaration.is(StructDeclSyntax.self) || declaration.is(ClassDeclSyntax.self) || declaration.is(ActorDeclSyntax.self) else { throw DependencyError.invalidApplication } return [] } }

Slide 48

Slide 48 text

@Dependency(registeredTo: AppComponent.self) enum UserRepository { }

Slide 49

Slide 49 text

@Dependency(registeredTo: AppComponent.self) enum UserRepository { }

Slide 50

Slide 50 text

@attached(peer) public macro Injected() = #externalMacro( module: "SwordMacros", type: “InjectedMacro" ) struct InjectedMacro: PeerMacro { static func expansion( of node: AttributeSyntax, providingPeersOf declaration: some DeclSyntaxProtocol, in context: some MacroExpansionContext ) throws -> [DeclSyntax] { guard declaration.is(InitializerDeclSyntax.self) else { throw InjectedError.invalidApplication } return [] } }

Slide 51

Slide 51 text

 1SBDUJDBM&YBNQMF$PEF(FOFSBUJPOGPS%*$POUBJOFS %F fi OF .BDSPT $PO fi HVSF 1MVHJO 1BSTF 7BMJEBUF (FOFSBUF $PEF

Slide 52

Slide 52 text

 1SBDUJDBM&YBNQMF$PEF(FOFSBUJPOGPS%*$POUBJOFS %F fi OF .BDSPT $PO fi HVSF 1MVHJO 1BSTF 7BMJEBUF (FOFSBUF $PEF

Slide 53

Slide 53 text

let package = Package( … targets: [ .target( name: “ComponentApp", dependencies: [.product(name: "Sword", package: “sword")] ), .target(name: “DataLocal", dependencies: [“ComponentApp”, .product(name: "Sword", package: “sword")] ), .target(name: “DataRemote", dependencies: […]), .target(name: “DataRepository", dependencies: […]), .target(name: “FeatureLogin", dependencies: […]), .target( name: "SceneRoot", dependencies: […] ) ] )

Slide 54

Slide 54 text

let package = Package( … targets: [ .target( name: “ComponentApp", dependencies: [.product(name: "Sword", package: “sword")] ), .target(name: “DataLocal", dependencies: [“ComponentApp”, .product(name: "Sword", package: “sword")] ), .target(name: “DataRemote", dependencies: […]), .target(name: “DataRepository", dependencies: […]), .target(name: “FeatureLogin", dependencies: […]), .target( name: "SceneRoot", dependencies: […], plugins: [ .plugin(name: "SwordBuildToolPlugin", package: "sword") ] ) ] )

Slide 55

Slide 55 text

struct SwordBuildToolPlugin: BuildToolPlugin { func createBuildCommands(context: PluginContext, target: Target) throws -> [Command] { } }

Slide 56

Slide 56 text

struct SwordBuildToolPlugin: BuildToolPlugin { func createBuildCommands(context: PluginContext, target: Target) throws -> [Command] { } }

Slide 57

Slide 57 text

struct SwordBuildToolPlugin: BuildToolPlugin { func createBuildCommands(context: PluginContext, target: Target) throws -> [Command] { } } let package = Package( … targets: [ …, .target( name: "SceneRoot", dependencies: [ “ComponentApp", “DataLocal", …, .product(name: "Sword", package: "sword")], plugins: [ .plugin(name: "SwordBuildToolPlugin", package: "sword") ] ) ] )

Slide 58

Slide 58 text

struct SwordBuildToolPlugin: BuildToolPlugin { func createBuildCommands(context: PluginContext, target: Target) throws -> [Command] { } } let package = Package( … targets: [ …, .target( name: "SceneRoot", dependencies: [ “ComponentApp", “DataLocal", …, .product(name: "Sword", package: "sword")], plugins: [ .plugin(name: "SwordBuildToolPlugin", package: "sword") ] ) ] )

Slide 59

Slide 59 text

struct SwordBuildToolPlugin: BuildToolPlugin { func createBuildCommands(context: PluginContext, target: Target) throws -> [Command] { } private func recursiveSamePackageDependencies(for target: Target) -> [Target] { } }

Slide 60

Slide 60 text

struct SwordBuildToolPlugin: BuildToolPlugin { func createBuildCommands(context: PluginContext, target: Target) throws -> [Command] { } private func recursiveSamePackageDependencies(for target: Target) -> [Target] { guard let sourceModule = target.sourceModule } }

Slide 61

Slide 61 text

struct SwordBuildToolPlugin: BuildToolPlugin { func createBuildCommands(context: PluginContext, target: Target) throws -> [Command] { } private func recursiveSamePackageDependencies(for target: Target) -> [Target] { guard let sourceModule = target.sourceModule, case .generic = sourceModule.kind else { return [] } } }

Slide 62

Slide 62 text

struct SwordBuildToolPlugin: BuildToolPlugin { func createBuildCommands(context: PluginContext, target: Target) throws -> [Command] { } private func recursiveSamePackageDependencies(for target: Target) -> [Target] { guard let sourceModule = target.sourceModule, case .generic = sourceModule.kind else { return [] } return sourceModule.dependencies.reduce([target]) { result, dependency in } } }

Slide 63

Slide 63 text

struct SwordBuildToolPlugin: BuildToolPlugin { func createBuildCommands(context: PluginContext, target: Target) throws -> [Command] { } private func recursiveSamePackageDependencies(for target: Target) -> [Target] { guard let sourceModule = target.sourceModule, case .generic = sourceModule.kind else { return [] } return sourceModule.dependencies.reduce([target]) { result, dependency in switch dependency { case .target(let dependencyTarget): result + recursiveSamePackageDependencies(for: dependencyTarget) case .product: result @unknown default: result } } } }

Slide 64

Slide 64 text

struct SwordBuildToolPlugin: BuildToolPlugin { func createBuildCommands(context: PluginContext, target: Target) throws -> [Command] { let dependencies = recursiveSamePackageDependencies(for: target) } }

Slide 65

Slide 65 text

struct SwordBuildToolPlugin: BuildToolPlugin { func createBuildCommands(context: PluginContext, target: Target) throws -> [Command] { let dependencies = recursiveSamePackageDependencies(for: target) let currentDirectory = context.package.directoryURL.path() let inputDirectories = dependencies.map { dependency in dependency.directory.string.replacingOccurrences( of: currentDirectory, with: "" ) } } }

Slide 66

Slide 66 text

struct SwordBuildToolPlugin: BuildToolPlugin { func createBuildCommands(context: PluginContext, target: Target) throws -> [Command] { let dependencies = recursiveSamePackageDependencies(for: target) let currentDirectory = context.package.directoryURL.path() let inputDirectories = dependencies.map { dependency in dependency.directory.string.replacingOccurrences( of: currentDirectory, with: "" ) } let output = context.pluginWorkDirectoryURL.appending( path: “Sword.generated.swift" ) } }

Slide 67

Slide 67 text

struct SwordBuildToolPlugin: BuildToolPlugin { func createBuildCommands(context: PluginContext, target: Target) throws -> [Command] { let dependencies = recursiveSamePackageDependencies(for: target) let currentDirectory = context.package.directoryURL.path() let inputDirectories = dependencies.map { dependency in dependency.directory.string.replacingOccurrences( of: currentDirectory, with: "" ) } let output = context.pluginWorkDirectoryURL.appending( path: “Sword.generated.swift" ) return [.buildCommand( displayName: "Run SwordCommand", executable: try context.tool(named: "SwordCommand").url, arguments: ["--inputs"] + inputDirectories + ["--output", output.relativePath], outputFiles: [output] )] } }

Slide 68

Slide 68 text

struct SwordCommand: ParsableCommand { @Option(parsing: .upToNextOption) var inputs: [String] = [] @Option var output: String mutating func run() throws { } }

Slide 69

Slide 69 text

 1SBDUJDBM&YBNQMF$PEF(FOFSBUJPOGPS%*$POUBJOFS w :PVDBOTVQQPSU9DPEF1SPKFDU fi MFTXJUI 9DPEF#VJME5PPM1MVHJO 1PJOUTUPOPUF

Slide 70

Slide 70 text

 1SBDUJDBM&YBNQMF$PEF(FOFSBUJPOGPS%*$POUBJOFS %F fi OF .BDSPT $PO fi HVSF 1MVHJO 1BSTF 7BMJEBUF (FOFSBUF $PEF

Slide 71

Slide 71 text

 1SBDUJDBM&YBNQMF$PEF(FOFSBUJPOGPS%*$POUBJOFS %F fi OF .BDSPT $PO fi HVSF 1MVHJO 1BSTF 7BMJEBUF (FOFSBUF $PEF

Slide 72

Slide 72 text

struct SwordCommand: ParsableCommand { @Option(parsing: .upToNextOption) var inputs: [String] = [] @Option var output: String mutating func run() throws { } }

Slide 73

Slide 73 text

struct SwordCommand: ParsableCommand { @Option(parsing: .upToNextOption) var inputs: [String] = [] @Option var output: String mutating func run() throws { } } ["Sources/SceneRoot", "Sources/ComponentApp", ”Sources/FeatureLogin”, "Sources/DataRepository", "Sources/DataLocal", "Sources/DataRemote"]

Slide 74

Slide 74 text

struct SwordCommand: ParsableCommand { … mutating func run() throws { let sourceFilePaths: [URL] = try inputs.flatMap { input in // using https://github.com/kylef/PathKit.git try Path(input).recursiveChildren().compactMap { child in guard child.extension == "swift" else { return nil } return child.absolute().url } } } }

Slide 75

Slide 75 text

struct SwordCommand: ParsableCommand { … mutating func run() throws { let sourceFilePaths: [URL] = try inputs.flatMap { input in // using https://github.com/kylef/PathKit.git try Path(input).recursiveChildren().compactMap { child in guard child.extension == "swift" else { return nil } return child.absolute().url } } } }

Slide 76

Slide 76 text

struct SwordCommand: ParsableCommand { … mutating func run() throws { let sourceFilePaths: [URL] = try inputs.flatMap { input in // using https://github.com/kylef/PathKit.git try Path(input).recursiveChildren().compactMap { child in guard child.extension == "swift" else { return nil } return child.absolute().url } } } } […/ SampleProject/Package/Sources/SceneRoot/RootScene.swift, …/SampleProject/Package/Sources/ComponentApp/AppComponent.swift, …/SampleProject/Package/Sources/DataLocal/UserLocalDataSource.swift, …/SampleProject/Package/Sources/DataRemote/UserRemoteDataSource.swift, …/SampleProject/Package/Sources/DataRepository/UserRepository.swift, …/SampleProject/Package/Sources/DataRepository/UserRepository.swift, …/SampleProject/Package/Sources/FeatureLogin/LoginViewModel.swift, …/SampleProject/Package/Sources/FeatureLogin/LoginScreen.swift]

Slide 77

Slide 77 text

struct SwordCommand: ParsableCommand { … mutating func run() throws { let sourceFilePaths: [URL] = … let sourceFileSyntaxes = try sourceFilePaths.map { sourceFilePath in let source = try String(contentsOf: sourceFilePath, encoding: .utf8) return SwiftParser.Parser.parse(source: source) } } }

Slide 78

Slide 78 text

struct SwordCommand: ParsableCommand { … mutating func run() throws { let sourceFilePaths: [URL] = … let sourceFileSyntaxes = try sourceFilePaths.map { sourceFilePath in let source = try String(contentsOf: sourceFilePath, encoding: .utf8) return SwiftParser.Parser.parse(source: source) } } }

Slide 79

Slide 79 text

struct SwordCommand: ParsableCommand { … mutating func run() throws { let sourceFilePaths: [URL] = … let sourceFileSyntaxes = try sourceFilePaths.map { sourceFilePath in let source = try String(contentsOf: sourceFilePath, encoding: .utf8) return SwiftParser.Parser.parse(source: source) } } }

Slide 80

Slide 80 text

SourceFileSyntax ├─statements: CodeBlockItemListSyntax │ ╰─[1]: CodeBlockItemSyntax │ ╰─item: ClassDeclSyntax │ ├─attributes: AttributeListSyntax │ │ ╰─[0]: AttributeSyntax │ │ ├─atSign: atSign │ │ ╰─attributeName: IdentifierTypeSyntax │ │ ╰─name: identifier("Component") │ ├─modifiers: DeclModifierListSyntax │ │ ╰─[0]: DeclModifierSyntax │ │ ╰─name: keyword(SwiftSyntax.Keyword.final) │ ├─classKeyword: keyword(SwiftSyntax.Keyword.class) │ ├─name: identifier("AppComponent") │ ╰─memberBlock:… ╰─endOfFileToken: endOfFile

Slide 81

Slide 81 text

SourceFileSyntax ├─statements: CodeBlockItemListSyntax │ ╰─[1]: CodeBlockItemSyntax │ ╰─item: ClassDeclSyntax │ ├─attributes: AttributeListSyntax │ │ ╰─[0]: AttributeSyntax │ │ ├─atSign: atSign │ │ ╰─attributeName: IdentifierTypeSyntax │ │ ╰─name: identifier("Component") │ ├─modifiers: DeclModifierListSyntax │ │ ╰─[0]: DeclModifierSyntax │ │ ╰─name: keyword(SwiftSyntax.Keyword.final) │ ├─classKeyword: keyword(SwiftSyntax.Keyword.class) │ ├─name: identifier("AppComponent") │ ╰─memberBlock:… ╰─endOfFileToken: endOfFile @Component final class AppComponent { }

Slide 82

Slide 82 text

SourceFileSyntax ├─statements: CodeBlockItemListSyntax │ ╰─[1]: CodeBlockItemSyntax │ ╰─item: ClassDeclSyntax │ ├─attributes: AttributeListSyntax │ │ ╰─[0]: AttributeSyntax │ │ ├─atSign: atSign │ │ ╰─attributeName: IdentifierTypeSyntax │ │ ╰─name: identifier("Component") │ ├─modifiers: DeclModifierListSyntax │ │ ╰─[0]: DeclModifierSyntax │ │ ╰─name: keyword(SwiftSyntax.Keyword.final) │ ├─classKeyword: keyword(SwiftSyntax.Keyword.class) │ ├─name: identifier("AppComponent") │ ╰─memberBlock:… ╰─endOfFileToken: endOfFile @Component final class AppComponent { }

Slide 83

Slide 83 text

SourceFileSyntax ├─statements: CodeBlockItemListSyntax │ ╰─[1]: CodeBlockItemSyntax │ ╰─item: ClassDeclSyntax │ ├─attributes: AttributeListSyntax │ │ ╰─[0]: AttributeSyntax │ │ ├─atSign: atSign │ │ ╰─attributeName: IdentifierTypeSyntax │ │ ╰─name: identifier("Component") │ ├─modifiers: DeclModifierListSyntax │ │ ╰─[0]: DeclModifierSyntax │ │ ╰─name: keyword(SwiftSyntax.Keyword.final) │ ├─classKeyword: keyword(SwiftSyntax.Keyword.class) │ ├─name: identifier("AppComponent") │ ╰─memberBlock:… ╰─endOfFileToken: endOfFile @Component final class AppComponent { }

Slide 84

Slide 84 text

struct ComponentDescriptor { let name: String }

Slide 85

Slide 85 text

final class ComponentVisitor: SyntaxVisitor { var results = [ComponentDescriptor]() override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind { } }

Slide 86

Slide 86 text

final class ComponentVisitor: SyntaxVisitor { var results = [ComponentDescriptor]() override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind { } }

Slide 87

Slide 87 text

final class ComponentVisitor: SyntaxVisitor { var results = [ComponentDescriptor]() override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind { } }

Slide 88

Slide 88 text

final class ComponentVisitor: SyntaxVisitor { var results = [ComponentDescriptor]() override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind { } }

Slide 89

Slide 89 text

final class ComponentVisitor: SyntaxVisitor { var results = [ComponentDescriptor]() override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind { } } ClassDeclSyntax ├─attributes: AttributeListSyntax │ ╰─[0]: AttributeSyntax │ ├─atSign: atSign │ ╰─attributeName: IdentifierTypeSyntax │ ╰─name: identifier("Component") ├─modifiers: DeclModifierListSyntax │ ╰─[0]: DeclModifierSyntax │ ╰─name: keyword(SwiftSyntax.Keyword.final) ├─classKeyword: keyword(SwiftSyntax.Keyword.class) ├─name: identifier("AppComponent") ╰─memberBlock:…

Slide 90

Slide 90 text

final class ComponentVisitor: SyntaxVisitor { var results = [ComponentDescriptor]() override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind { } } ClassDeclSyntax ├─attributes: AttributeListSyntax │ ╰─[0]: AttributeSyntax │ ├─atSign: atSign │ ╰─attributeName: IdentifierTypeSyntax │ ╰─name: identifier("Component") ├─modifiers: DeclModifierListSyntax │ ╰─[0]: DeclModifierSyntax │ ╰─name: keyword(SwiftSyntax.Keyword.final) ├─classKeyword: keyword(SwiftSyntax.Keyword.class) ├─name: identifier("AppComponent") ╰─memberBlock:…

Slide 91

Slide 91 text

final class ComponentVisitor: SyntaxVisitor { var results = [ComponentDescriptor]() override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind { let componentAttribute = node.attributes.first { attribute in guard let attribute = attribute.as(AttributeSyntax.self), let attributeName = attribute.attributeName.as(IdentifierTypeSyntax.self) else { return false } return attributeName.name.text == "Component" } } }

Slide 92

Slide 92 text

final class ComponentVisitor: SyntaxVisitor { var results = [ComponentDescriptor]() override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind { let componentAttribute = node.attributes.first { attribute in guard let attribute = attribute.as(AttributeSyntax.self), let attributeName = attribute.attributeName.as(IdentifierTypeSyntax.self) else { return false } return attributeName.name.text == "Component" } } }

Slide 93

Slide 93 text

final class ComponentVisitor: SyntaxVisitor { var results = [ComponentDescriptor]() override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind { let componentAttribute = node.attributes.first { attribute in guard let attribute = attribute.as(AttributeSyntax.self), let attributeName = attribute.attributeName.as(IdentifierTypeSyntax.self) else { return false } return attributeName.name.text == "Component" } } }

Slide 94

Slide 94 text

final class ComponentVisitor: SyntaxVisitor { var results = [ComponentDescriptor]() override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind { let componentAttribute = node.attributes.first(named: “Component") } } extension AttributeListSyntax { func first(named name: String) -> AttributeSyntax? { self.first { attribute in guard let attribute = attribute.as(AttributeSyntax.self), let attributeName = attribute.attributeName.as(IdentifierTypeSyntax.self) else { return false } return attributeName.name.text == name }?.as(AttributeSyntax.self) } }

Slide 95

Slide 95 text

final class ComponentVisitor: SyntaxVisitor { var results = [ComponentDescriptor]() override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind { let componentAttribute = node.attributes.first(named: "Component") guard componentAttribute != nil else { return .skipChildren } } }

Slide 96

Slide 96 text

final class ComponentVisitor: SyntaxVisitor { var results = [ComponentDescriptor]() override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind { let componentAttribute = node.attributes.first(named: "Component") guard componentAttribute != nil else { return .skipChildren } } } ClassDeclSyntax ├─attributes: AttributeListSyntax │ ╰─[0]: AttributeSyntax │ ├─atSign: atSign │ ╰─attributeName: IdentifierTypeSyntax │ ╰─name: identifier("Component") ├─modifiers: DeclModifierListSyntax │ ╰─[0]: DeclModifierSyntax │ ╰─name: keyword(SwiftSyntax.Keyword.final) ├─classKeyword: keyword(SwiftSyntax.Keyword.class) ├─name: identifier("AppComponent") ╰─memberBlock:…

Slide 97

Slide 97 text

final class ComponentVisitor: SyntaxVisitor { var results = [ComponentDescriptor]() override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind { let componentAttribute = node.attributes.first(named: "Component") guard componentAttribute != nil else { return .skipChildren } results.append(ComponentDescriptor(name: node.name.text)) return .skipChildren } }

Slide 98

Slide 98 text

struct SwordCommand: ParsableCommand { … mutating func run() throws { … let sourceFileSyntaxes = … } }

Slide 99

Slide 99 text

struct SwordCommand: ParsableCommand { … mutating func run() throws { … let sourceFileSyntaxes = … var componentDescriptors = [ComponentDescriptor]() for sourceFileSyntax in sourceFileSyntaxes { let componentVisitor = ComponentVisitor(viewMode: .sourceAccurate) componentVisitor.walk(sourceFileSyntax) componentDescriptors.append(contentsOf: componentVisitor.results) } } }

Slide 100

Slide 100 text

struct SwordCommand: ParsableCommand { … mutating func run() throws { … let sourceFileSyntaxes = … var componentDescriptors = [ComponentDescriptor]() var dependencyDescriptors = [DependencyDescriptor]() for sourceFileSyntax in sourceFileSyntaxes { let componentVisitor = ComponentVisitor(viewMode: .sourceAccurate) componentVisitor.walk(sourceFileSyntax) componentDescriptors.append(contentsOf: componentVisitor.results) let dependencyVisitor = DependencyVisitor(viewMode: .sourceAccurate) dependencyVisitor.walk(sourceFileSyntax) dependencyDescriptors.append(contentsOf: dependencyVisitor.results) } } }

Slide 101

Slide 101 text

 1SBDUJDBM&YBNQMF$PEF(FOFSBUJPOGPS%*$POUBJOFS %F fi OF .BDSPT $PO fi HVSF 1MVHJO 1BSTF 7BMJEBUF (FOFSBUF $PEF

Slide 102

Slide 102 text

 1SBDUJDBM&YBNQMF$PEF(FOFSBUJPOGPS%*$POUBJOFS %F fi OF .BDSPT $PO fi HVSF 1MVHJO 1BSTF 7BMJEBUF (FOFSBUF $PEF

Slide 103

Slide 103 text

1 @Dependency(registeredTo: AppComponent.self) 2 struct UserRepository { 3 private let localDataSource: UserLocalDataSource 4 private let remoteDataSource: UserRemoteDataSource 5 6 init( 7 localDataSource: UserLocalDataSource, 8 remoteDataSource: UserRemoteDataSource 9 ) { 10 self.localDataSource = localDataSource 11 self.remoteDataSource = remoteDataSource 12 } 13 }

Slide 104

Slide 104 text

1 @Dependency(registeredTo: AppComponent.self) 2 struct UserRepository { 3 private let localDataSource: UserLocalDataSource 4 private let remoteDataSource: UserRemoteDataSource 5 6 init( 7 localDataSource: UserLocalDataSource, 8 remoteDataSource: UserRemoteDataSource 9 ) { 10 self.localDataSource = localDataSource 11 self.remoteDataSource = remoteDataSource 12 } 13 }

Slide 105

Slide 105 text

let file = ".../UserRepository.swift" let line = 1 let column = 1 let severity = "error" // or “warning" let message = "'@Dependency' requires an '@Injected' initializer" FileHandle.standardOutput.write( Data(“\(file):\(line):\(column): \(severity): \(message)”.utf8) )

Slide 106

Slide 106 text

let file = ".../UserRepository.swift" let line = 1 let column = 1 let severity = "error" // or “warning" let message = "'@Dependency' requires an '@Injected' initializer" FileHandle.standardOutput.write( Data(“\(file):\(line):\(column): \(severity): \(message)”.utf8) )

Slide 107

Slide 107 text

let file = ".../UserRepository.swift" let line = 1 let column = 1 let severity = "error" // or “warning" let message = "'@Dependency' requires an '@Injected' initializer" FileHandle.standardOutput.write( Data(“\(file):\(line):\(column): \(severity): \(message)”.utf8) )

Slide 108

Slide 108 text

let file = ".../UserRepository.swift" let line = 1 let column = 1 let severity = "error" // or “warning" let message = "'@Dependency' requires an '@Injected' initializer" FileHandle.standardOutput.write( Data(“\(file):\(line):\(column): \(severity): \(message)”.utf8) )

Slide 109

Slide 109 text

let file = ".../UserRepository.swift" let line = 1 let column = 1 let severity = "error" // or “warning" let message = "'@Dependency' requires an '@Injected' initializer" FileHandle.standardOutput.write( Data(“\(file):\(line):\(column): \(severity): \(message)”.utf8) )

Slide 110

Slide 110 text

let file = ".../UserRepository.swift" let line = 1 let column = 1 let severity = "error" // or “warning" let message = "'@Dependency' requires an '@Injected' initializer" FileHandle.standardOutput.write( Data(“\(file):\(line):\(column): \(severity): \(message)”.utf8) )

Slide 111

Slide 111 text

let file = ".../UserRepository.swift" let line = 1 let column = 1 let severity = "error" // or “warning" let message = "'@Dependency' requires an '@Injected' initializer" FileHandle.standardOutput.write( Data(“\(file):\(line):\(column): \(severity): \(message)”.utf8) )

Slide 112

Slide 112 text

struct DependencyDescriptor { struct Initializer { struct Parameter { let type: String let name: String } let parameters: [Parameter] } let componentName: String let type: String let injectedInitializers: [Initializer] let location: SourceLocation }

Slide 113

Slide 113 text

struct DependencyDescriptor { struct Initializer { struct Parameter { let type: String let name: String } let parameters: [Parameter] } let componentName: String let type: String let injectedInitializers: [Initializer] let location: SourceLocation } public struct SourceLocation: Hashable, Codable, Sendable { /// The fi le in which this location resides. public let file: String /// The line in the fi le where this location resides. 1-based. public var line: Int /// The UTF-8 byte o ff set from the beginning of the line where this location /// resides. 1-based. public let column: Int … }

Slide 114

Slide 114 text

final class DependencyVisitor: SyntaxVisitor { … private let locationConverter: SourceLocationConverter init(locationConverter: SourceLocationConverter) { self.locationConverter = locationConverter super.init(viewMode: .sourceAccurate) } override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind { … let dependencyDescriptor = DependencyDescriptor( componentName: componentArgument.baseName.text, type: node.name.text, injectedInitializers: injectedInitializers, location: node.attributes.startLocation(converter: locationConverter) ) results.append(dependencyDescriptor) return .skipChildren } }

Slide 115

Slide 115 text

final class DependencyVisitor: SyntaxVisitor { … private let locationConverter: SourceLocationConverter init(locationConverter: SourceLocationConverter) { self.locationConverter = locationConverter super.init(viewMode: .sourceAccurate) } override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind { … let dependencyDescriptor = DependencyDescriptor( componentName: componentArgument.baseName.text, type: node.name.text, injectedInitializers: injectedInitializers, location: node.attributes.startLocation(converter: locationConverter) ) results.append(dependencyDescriptor) return .skipChildren } }

Slide 116

Slide 116 text

final class DependencyVisitor: SyntaxVisitor { … private let locationConverter: SourceLocationConverter init(locationConverter: SourceLocationConverter) { self.locationConverter = locationConverter super.init(viewMode: .sourceAccurate) } override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind { … let dependencyDescriptor = DependencyDescriptor( componentName: componentArgument.baseName.text, type: node.name.text, injectedInitializers: injectedInitializers, location: node.attributes.startLocation(converter: locationConverter) ) results.append(dependencyDescriptor) return .skipChildren } }

Slide 117

Slide 117 text

struct SwordCommand: ParsableCommand { … mutating func run() throws { … } }

Slide 118

Slide 118 text

struct SwordCommand: ParsableCommand { … mutating func run() throws { … let dependencies = try dependencyDescriptors.compactMap { descriptor in return Dependency( ) } } }

Slide 119

Slide 119 text

struct SwordCommand: ParsableCommand { … mutating func run() throws { … let dependencies = try dependencyDescriptors.compactMap { descriptor in guard let initializer = descriptor.injectedInitializers.first else { return nil } return Dependency( type: descriptor.type, componentName: descriptor.componentName, initializer: initializer ) } } }

Slide 120

Slide 120 text

struct SwordCommand: ParsableCommand { … mutating func run() throws { … let dependencies = try dependencyDescriptors.compactMap { descriptor in guard let initializer = descriptor.injectedInitializers.first else { let location = descriptor.location try FileHandle.standardOutput.write(contentsOf: Data( "\(location.file):\(location.line):\(location.column): error: '@Dependency' requires an '@Injected' initializer".utf8 )) return nil } return Dependency( type: descriptor.type, componentName: descriptor.componentName, initializer: initializer ) } } }

Slide 121

Slide 121 text

struct SwordCommand: ParsableCommand { … mutating func run() throws { … guard let componentDescriptor = componentDescriptors.first else { try FileHandle.standardOutput.write(contentsOf: Data( "warning: '@Component' must be declared".utf8 )) return } let component = Component(name: componentDescriptor.name) } }

Slide 122

Slide 122 text

 1SBDUJDBM&YBNQMF$PEF(FOFSBUJPOGPS%*$POUBJOFS %F fi OF .BDSPT $PO fi HVSF 1MVHJO 1BSTF 7BMJEBUF (FOFSBUF $PEF

Slide 123

Slide 123 text

 1SBDUJDBM&YBNQMF$PEF(FOFSBUJPOGPS%*$POUBJOFS %F fi OF .BDSPT $PO fi HVSF 1MVHJO 1BSTF 7BMJEBUF (FOFSBUF $PEF

Slide 124

Slide 124 text

// Sword.generated.swift extension AppComponent { var userLocalDataSource: UserLocalDataSource { UserLocalDataSource() } var userRemoteDataSource: UserRemoteDataSource { UserRemoteDataSource() } var userRepository: UserRepository { UserRepository(localDataSource: self.userLocalDataSource, remoteDataSource: self.userRemoteDataSource) } var loginViewModel: LoginViewModel { LoginViewModel(userRepository: self.userRepository) } }

Slide 125

Slide 125 text

// using only String var output = "extension \(component.name) {" output += "\n" for dependency in dependencies { output.append(" var \(dependency.key): \(dependency.type) {") output.append("\n") var dependencyGetter = "" dependencyGetter.append(" \(dependency.type)") dependencyGetter.append("(") for (i, parameter) in dependency.initializer.parameters.enumerated() { dependencyGetter.append("\(parameter.name): self.\(parameter.type)") if i < dependency.initializer.parameters.count - 1 { dependencyGetter.append(", ") } } dependencyGetter.append(")") output.append(dependencyGetter) output.append("\n") output.append(" }") output.append("\n") }

Slide 126

Slide 126 text

// using only String var output = "extension \(component.name) {" output += "\n" for dependency in dependencies { output.append(" var \(dependency.key): \(dependency.type) {") output.append("\n") var dependencyGetter = "" dependencyGetter.append(" \(dependency.type)") dependencyGetter.append("(") for (i, parameter) in dependency.initializer.parameters.enumerated() { dependencyGetter.append("\(parameter.name): self.\(parameter.type)") if i < dependency.initializer.parameters.count - 1 { dependencyGetter.append(", ") } } dependencyGetter.append(")") output.append(dependencyGetter) output.append("\n") output.append(" }") output.append("\n") }

Slide 127

Slide 127 text

// using SwiftSyntaxBuilder CodeBlockItemListSyntax(itemsBuilder: { ExtensionDeclSyntax( extendedType: IdentifierTypeSyntax(name: .identifier(component.name)), memberBlockBuilder: { for dependency in dependencies { VariableDeclSyntax(bindingSpecifier: .keyword(.var)) { PatternBindingSyntax( pattern: IdentifierPatternSyntax( identifier: .identifier(dependency.key.value)), typeAnnotation: TypeAnnotationSyntax( type: IdentifierTypeSyntax( name: .identifier(dependency.type) ) ), accessorBlock: AccessorBlockSyntax(accessors: .getter(…)) ) } } ) })

Slide 128

Slide 128 text

// using SwiftSyntaxBuilder CodeBlockItemListSyntax(itemsBuilder: { ExtensionDeclSyntax( extendedType: IdentifierTypeSyntax(name: .identifier(component.name)), memberBlockBuilder: { for dependency in dependencies { VariableDeclSyntax(bindingSpecifier: .keyword(.var)) { PatternBindingSyntax( pattern: IdentifierPatternSyntax( identifier: .identifier(dependency.key.value)), typeAnnotation: TypeAnnotationSyntax( type: IdentifierTypeSyntax( name: .identifier(dependency.type) ) ), accessorBlock: AccessorBlockSyntax(accessors: .getter(…)) ) } } ) }) CodeBlockItemListSyntax ╰─[0]: CodeBlockItemSyntax ╰─item: ExtensionDeclSyntax ├─… ├─extendedType: IdentifierTypeSyntax │ ╰─name: identifier("AppComponent") ╰─memberBlock: MemberBlockSyntax ├─leftBrace: leftBrace ├─members: MemberBlockItemListSyntax │ ╰─[1]: MemberBlockItemSyntax │ ╰─decl: VariableDeclSyntax │ ├─… │ ╰─bindings: PatternBindingListSyntax │ ╰─[0]: PatternBindingSyntax │ ├─pattern: IdentifierPatternSyntax │ │ ╰─identifier: identifier("userRemoteDataSource") │ ╰─… ╰─rightBrace: rightBrace

Slide 129

Slide 129 text

// using SwiftSyntaxBuilder CodeBlockItemListSyntax(itemsBuilder: { ExtensionDeclSyntax( extendedType: IdentifierTypeSyntax(name: .identifier(component.name)), memberBlockBuilder: { for dependency in dependencies { VariableDeclSyntax(bindingSpecifier: .keyword(.var)) { PatternBindingSyntax( pattern: IdentifierPatternSyntax( identifier: .identifier(dependency.key.value)), typeAnnotation: TypeAnnotationSyntax( type: IdentifierTypeSyntax( name: .identifier(dependency.type) ) ), accessorBlock: AccessorBlockSyntax(accessors: .getter(…)) ) } } ) }) CodeBlockItemListSyntax ╰─[0]: CodeBlockItemSyntax ╰─item: ExtensionDeclSyntax ├─… ├─extendedType: IdentifierTypeSyntax │ ╰─name: identifier("AppComponent") ╰─memberBlock: MemberBlockSyntax ├─leftBrace: leftBrace ├─members: MemberBlockItemListSyntax │ ╰─[1]: MemberBlockItemSyntax │ ╰─decl: VariableDeclSyntax │ ├─… │ ╰─bindings: PatternBindingListSyntax │ ╰─[0]: PatternBindingSyntax │ ├─pattern: IdentifierPatternSyntax │ │ ╰─identifier: identifier("userRemoteDataSource") │ ╰─… ╰─rightBrace: rightBrace

Slide 130

Slide 130 text

// using SwiftSyntaxBuilder CodeBlockItemListSyntax(itemsBuilder: { ExtensionDeclSyntax( extendedType: IdentifierTypeSyntax(name: .identifier(component.name)), memberBlockBuilder: { for dependency in dependencies { VariableDeclSyntax(bindingSpecifier: .keyword(.var)) { PatternBindingSyntax( pattern: IdentifierPatternSyntax( identifier: .identifier(dependency.key.value)), typeAnnotation: TypeAnnotationSyntax( type: IdentifierTypeSyntax( name: .identifier(dependency.type) ) ), accessorBlock: AccessorBlockSyntax(accessors: .getter(…)) ) } } ) })

Slide 131

Slide 131 text

// using SwiftSyntaxBuilder CodeBlockItemListSyntax(itemsBuilder: { ExtensionDeclSyntax( extendedType: IdentifierTypeSyntax(name: .identifier(component.name)), memberBlockBuilder: { for dependency in dependencies { VariableDeclSyntax(bindingSpecifier: .keyword(.var)) { PatternBindingSyntax( pattern: IdentifierPatternSyntax( identifier: .identifier(dependency.key.value)), typeAnnotation: TypeAnnotationSyntax( type: IdentifierTypeSyntax( name: .identifier(dependency.type) ) ), accessorBlock: AccessorBlockSyntax(accessors: .getter(…)) ) } } ) }) CodeBlockItemListSyntax ╰─[0]: CodeBlockItemSyntax ╰─item: ExtensionDeclSyntax ├─… ├─extendedType: IdentifierTypeSyntax │ ╰─name: identifier("AppComponent") ╰─memberBlock: MemberBlockSyntax ├─leftBrace: leftBrace ├─members: MemberBlockItemListSyntax │ ╰─[1]: MemberBlockItemSyntax │ ╰─decl: VariableDeclSyntax │ ├─… │ ╰─bindings: PatternBindingListSyntax │ ╰─[0]: PatternBindingSyntax │ ├─pattern: IdentifierPatternSyntax │ │ ╰─identifier: identifier("userRemoteDataSource") │ ╰─… ╰─rightBrace: rightBrace

Slide 132

Slide 132 text

// using SwiftSyntaxBuilder CodeBlockItemListSyntax(itemsBuilder: { ExtensionDeclSyntax( extendedType: IdentifierTypeSyntax(name: .identifier(component.name)), memberBlockBuilder: { for dependency in dependencies { VariableDeclSyntax(bindingSpecifier: .keyword(.var)) { PatternBindingSyntax( pattern: IdentifierPatternSyntax( identifier: .identifier(dependency.key.value)), typeAnnotation: TypeAnnotationSyntax( type: IdentifierTypeSyntax( name: .identifier(dependency.type) ) ), accessorBlock: AccessorBlockSyntax(accessors: .getter(…)) ) } } ) })

Slide 133

Slide 133 text

// using SwiftSyntaxBuilder CodeBlockItemListSyntax(itemsBuilder: { ExtensionDeclSyntax( extendedType: IdentifierTypeSyntax(name: .identifier(component.name)), memberBlockBuilder: { for dependency in dependencies { VariableDeclSyntax(bindingSpecifier: .keyword(.var)) { PatternBindingSyntax( pattern: IdentifierPatternSyntax( identifier: .identifier(dependency.key.value)), typeAnnotation: TypeAnnotationSyntax( type: IdentifierTypeSyntax( name: .identifier(dependency.type) ) ), accessorBlock: AccessorBlockSyntax(accessors: .getter(…)) ) } } ) }) CodeBlockItemListSyntax ╰─[0]: CodeBlockItemSyntax ╰─item: ExtensionDeclSyntax ├─… ├─extendedType: IdentifierTypeSyntax │ ╰─name: identifier("AppComponent") ╰─memberBlock: MemberBlockSyntax ├─leftBrace: leftBrace ├─members: MemberBlockItemListSyntax │ ╰─[1]: MemberBlockItemSyntax │ ╰─decl: VariableDeclSyntax │ ├─… │ ╰─bindings: PatternBindingListSyntax │ ╰─[0]: PatternBindingSyntax │ ├─pattern: IdentifierPatternSyntax │ │ ╰─identifier: identifier("userRemoteDataSource") │ ╰─… ╰─rightBrace: rightBrace

Slide 134

Slide 134 text

// using SwiftSyntaxBuilder CodeBlockItemListSyntax(itemsBuilder: { ExtensionDeclSyntax( extendedType: IdentifierTypeSyntax(name: .identifier(component.name)), memberBlockBuilder: { for dependency in dependencies { VariableDeclSyntax(bindingSpecifier: .keyword(.var)) { PatternBindingSyntax( pattern: IdentifierPatternSyntax( identifier: .identifier(dependency.key.value)), typeAnnotation: TypeAnnotationSyntax( type: IdentifierTypeSyntax( name: .identifier(dependency.type) ) ), accessorBlock: AccessorBlockSyntax(accessors: .getter(…)) ) } } ) })

Slide 135

Slide 135 text

struct SwordCommand: ParsableCommand { … @Option var output: String mutating func run() throws { … let renderResult = render(component, dependencies) } private func render(_ component: Component, _ dependencies: [Dependency]) -> CodeBlockItemListSyntax { CodeBlockItemListSyntax { … } } }

Slide 136

Slide 136 text

struct SwordCommand: ParsableCommand { … @Option var output: String mutating func run() throws { … let renderResult = render(component, dependencies) var renderResultOutput = "" renderResult.formatted().write(to: &renderResultOutput) } private func render(_ component: Component, _ dependencies: [Dependency]) -> CodeBlockItemListSyntax { CodeBlockItemListSyntax { … } } }

Slide 137

Slide 137 text

struct SwordCommand: ParsableCommand { … @Option var output: String mutating func run() throws { … let renderResult = render(component, dependencies) var renderResultOutput = "" renderResult.formatted().write(to: &renderResultOutput) } private func render(_ component: Component, _ dependencies: [Dependency]) -> CodeBlockItemListSyntax { CodeBlockItemListSyntax { … } } }

Slide 138

Slide 138 text

struct SwordCommand: ParsableCommand { … @Option var output: String mutating func run() throws { … let renderResult = render(component, dependencies) var renderResultOutput = "" renderResult.formatted().write(to: &renderResultOutput) try renderResultOutput.data(using: .utf8)?.write( to: URL(filePath: output), options: .atomic ) } private func render(_ component: Component, _ dependencies: [Dependency]) -> CodeBlockItemListSyntax { CodeBlockItemListSyntax { … } } }

Slide 139

Slide 139 text

 1SBDUJDBM&YBNQMF$PEF(FOFSBUJPOGPS%*$POUBJOFS %F fi OF .BDSPT $PO fi HVSF 1MVHJO 1BSTF 7BMJEBUF (FOFSBUF $PEF

Slide 140

Slide 140 text

No content

Slide 141

Slide 141 text

No content

Slide 142

Slide 142 text

No content

Slide 143

Slide 143 text

🎉

Slide 144

Slide 144 text

 8SBQVQ

Slide 145

Slide 145 text

 8SBQVQ w %F fi OF4XJGU.BDSPTBT"OOPUBUJPOT w $PO fi HVSF1MVHJOTUPQSPDFTT fi MFTPOFBDICVJME w 1BSTFTPVSDFDPEFVTJOH4XJGU1BSTFSBOE4ZOUBY7JTJUPS w 7BMJEBUFEBUBBOESFQPSUXJUI4PVSDF-PDBUJPO w (FOFSBUFDPEFXJUI4XJGU4ZOUBY#VJMEFS

Slide 146

Slide 146 text

5IBOLZPV