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

Unlock the Potential of Swift Code Generation

Unlock the Potential of Swift Code Generation

try! Swift Tokyo 2025における発表資料です。

書き起こし記事: https://blog.smartbank.co.jp/entry/2025/04/11/unlock-the-potential-of-swift-code-generation

rockname

April 11, 2025
Tweet

More Decks by rockname

Other Decks in Programming

Transcript

  1. // Generated code with simplified class UserDao_Impl(private val __db: RoomDatabase)

    : UserDao { override fun getAll(): List<User> { val statement = RoomSQLiteQuery.acquire("SELECT * FROM users", 0) __db.assertNotSuspendingTransaction() return query(__db, statement, false, null).use { cursor -> mutableListOf<User>().apply { while (cursor.moveToNext()) { add(User(cursor.getInt(getColumnIndexOrThrow(cursor, "id")))) } } }.also { statement.release() } } }
  2. import SwiftData @Model final class User { var name: String

    init(name: String) { self.name = name } }
  3. // 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<User> { ... } init(name: String) { … } }
  4. @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() {}
  5. @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() {}
  6. @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() {}
  7. // 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() ) }
  8. // DataRepository/UserRepository.swift @Dependency(registeredTo: AppComponent.self) struct UserRepository { private let localDataSource:

    UserLocalDataSource private let remoteDataSource: UserRemoteDataSource @Injected init( localDataSource: UserLocalDataSource, remoteDataSource: UserRemoteDataSource ) { … } }
  9. // 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 { … } }
  10. // SceneRoot/RootScene.swift struct RootScene: Scene { private let component =

    AppComponent() var body: some Scene { WindowGroup { ... } } }
  11. 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) } }
  12. public protocol Component: AnyObject {} @attached( extension, conformances: Component )

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

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

    public macro Component() = #externalMacro( module: "SwordMacros", type: “ComponentMacro" )
  15. 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] } }
  16. 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] } }
  17. @attached(peer) public macro Dependency( registeredTo component: any Component.Type ) =

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

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

    #externalMacro( module: ”SwordMacros", type: “DependencyMacro" )
  20. 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 [] } }
  21. 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 [] } }
  22. @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 [] } }
  23. 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: […] ) ] )
  24. 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") ] ) ] )
  25. 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") ] ) ] )
  26. 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") ] ) ] )
  27. struct SwordBuildToolPlugin: BuildToolPlugin { func createBuildCommands(context: PluginContext, target: Target) throws

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

    -> [Command] { } private func recursiveSamePackageDependencies(for target: Target) -> [Target] { guard let sourceModule = target.sourceModule } }
  29. 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 [] } } }
  30. 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 } } }
  31. 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 } } } }
  32. struct SwordBuildToolPlugin: BuildToolPlugin { func createBuildCommands(context: PluginContext, target: Target) throws

    -> [Command] { let dependencies = recursiveSamePackageDependencies(for: target) } }
  33. 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: "" ) } } }
  34. 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" ) } }
  35. 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] )] } }
  36. struct SwordCommand: ParsableCommand { @Option(parsing: .upToNextOption) var inputs: [String] =

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

    [] @Option var output: String mutating func run() throws { } }
  38. 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"]
  39. 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 } } } }
  40. 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 } } } }
  41. 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]
  42. 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) } } }
  43. 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) } } }
  44. 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) } } }
  45. 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
  46. 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 { }
  47. 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 { }
  48. 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 { }
  49. final class ComponentVisitor: SyntaxVisitor { var results = [ComponentDescriptor]() override

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

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

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

    func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind { } }
  53. 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:…
  54. 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:…
  55. 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" } } }
  56. 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" } } }
  57. 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" } } }
  58. 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) } }
  59. 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 } } }
  60. 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:…
  61. 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 } }
  62. 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) } } }
  63. 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) } } }
  64. 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 }
  65. 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 }
  66. 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) )
  67. 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) )
  68. 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) )
  69. 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) )
  70. 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) )
  71. 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) )
  72. 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) )
  73. 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 }
  74. 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 … }
  75. 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 } }
  76. 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 } }
  77. 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 } }
  78. struct SwordCommand: ParsableCommand { … mutating func run() throws {

    … let dependencies = try dependencyDescriptors.compactMap { descriptor in return Dependency( ) } } }
  79. 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 ) } } }
  80. 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 ) } } }
  81. 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) } }
  82. // 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) } }
  83. // 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") }
  84. // 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") }
  85. // 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(…)) ) } } ) })
  86. // 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
  87. // 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
  88. // 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(…)) ) } } ) })
  89. // 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
  90. // 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(…)) ) } } ) })
  91. // 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
  92. // 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(…)) ) } } ) })
  93. 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 { … } } }
  94. 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 { … } } }
  95. 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 { … } } }
  96. 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 { … } } }
  97.  8SBQVQ w %F fi OF4XJGU.BDSPTBT"OOPUBUJPOT w $PO fi HVSF1MVHJOTUPQSPDFTT

    fi MFTPOFBDICVJME w 1BSTFTPVSDFDPEFVTJOH4XJGU1BSTFSBOE4ZOUBY7JTJUPS w 7BMJEBUFEBUBBOESFQPSUXJUI4PVSDF-PDBUJPO w (FOFSBUFDPEFXJUI4XJGU4ZOUBY#VJMEFS