diff --git a/Sources/DependenciesMacros/Macros.swift b/Sources/DependenciesMacros/Macros.swift index 59982228..926a7c63 100644 --- a/Sources/DependenciesMacros/Macros.swift +++ b/Sources/DependenciesMacros/Macros.swift @@ -209,6 +209,10 @@ public macro DependencyEndpoint(method: String = "") = module: "DependenciesMacrosPlugin", type: "DependencyEndpointMacro" ) +@attached(accessor, names: named(willSet)) +public macro DependencyEndpointIgnored() = + #externalMacro(module: "DependenciesMacrosPlugin", type: "DependencyEndpointIgnoredMacro") + /// The error thrown by "unimplemented" closures produced by ``DependencyEndpoint(method:)`` public struct Unimplemented: Error { let endpoint: String diff --git a/Sources/DependenciesMacrosPlugin/DependencyClientMacro.swift b/Sources/DependenciesMacrosPlugin/DependencyClientMacro.swift index ff9250f7..a68cf67f 100644 --- a/Sources/DependenciesMacrosPlugin/DependencyClientMacro.swift +++ b/Sources/DependenciesMacrosPlugin/DependencyClientMacro.swift @@ -12,6 +12,10 @@ public enum DependencyClientMacro: MemberAttributeMacro, MemberMacro { providingAttributesFor member: M, in context: C ) throws -> [AttributeSyntax] { + if member.as(VariableDeclSyntax.self)?.isIgnored == true { + return [] + } + guard let property = member.as(VariableDeclSyntax.self), property.bindingSpecifier.tokenKind != .keyword(.let), @@ -62,6 +66,7 @@ public enum DependencyClientMacro: MemberAttributeMacro, MemberMacro { } ) } + return attributes } @@ -94,6 +99,7 @@ public enum DependencyClientMacro: MemberAttributeMacro, MemberMacro { let isEndpoint = property.hasDependencyEndpointMacroAttached || property.bindingSpecifier.tokenKind != .keyword(.let) && property.isClosure + let propertyAccess = Access(modifiers: property.modifiers) guard var binding = property.bindings.first, @@ -117,6 +123,8 @@ public enum DependencyClientMacro: MemberAttributeMacro, MemberMacro { if propertyAccess == .private, binding.initializer != nil { continue } accesses.insert(propertyAccess ?? .internal) + if property.isIgnored { continue } + guard let type = binding.typeAnnotation?.type ?? binding.initializer?.value.literalType else { context.diagnose( @@ -156,6 +164,7 @@ public enum DependencyClientMacro: MemberAttributeMacro, MemberMacro { ) return [] } + if var attributedTypeSyntax = type.as(AttributedTypeSyntax.self), attributedTypeSyntax.baseType.is(FunctionTypeSyntax.self) { @@ -258,23 +267,43 @@ private struct Property { var isEndpoint: Bool } -extension VariableDeclSyntax { - fileprivate var isStatic: Bool { +fileprivate extension VariableDeclSyntax { + var isStatic: Bool { self.modifiers.contains { modifier in modifier.name.tokenKind == .keyword(.static) } } - fileprivate var hasDependencyEndpointMacroAttached: Bool { + static let dependencyEndpointName = "DependencyEndpoint" + static let dependencyEndpointIgnoredName = "DependencyEndpointIgnored" + static let dependencyName = "Dependency" + + func hasMacroAttached(_ macro: String) -> Bool { self.attributes.contains { guard case let .attribute(attribute) = $0, let attributeName = attribute.attributeName.as(IdentifierTypeSyntax.self)?.name.text, - ["DependencyEndpoint"].qualified("DependenciesMacros").contains(attributeName) + [macro].qualified("DependenciesMacros").contains(attributeName) else { return false } return true } } + + var hasDependencyEndpointMacroAttached: Bool { + hasMacroAttached(Self.dependencyEndpointName) + } + + var hasDependencyEndpointIgnoredMacroAttached: Bool { + hasMacroAttached(Self.dependencyEndpointIgnoredName) + } + + var hasDependencyMacroAttached: Bool { + hasMacroAttached(Self.dependencyName) + } + + var isIgnored: Bool { + hasDependencyMacroAttached || hasDependencyEndpointIgnoredMacroAttached + } } extension ExprSyntax { diff --git a/Sources/DependenciesMacrosPlugin/DependencyEndpointMacro.swift b/Sources/DependenciesMacrosPlugin/DependencyEndpointMacro.swift index 4abdb0d9..44a330dc 100644 --- a/Sources/DependenciesMacrosPlugin/DependencyEndpointMacro.swift +++ b/Sources/DependenciesMacrosPlugin/DependencyEndpointMacro.swift @@ -376,3 +376,16 @@ extension TupleTypeElementSyntax { .tokenKind == .keyword(.inout) } } + +public struct DependencyEndpointIgnoredMacro: AccessorMacro { + public static func expansion< + Context: MacroExpansionContext, + Declaration: DeclSyntaxProtocol + >( + of node: AttributeSyntax, + providingAccessorsOf declaration: Declaration, + in context: Context + ) throws -> [AccessorDeclSyntax] { + return [] + } +} diff --git a/Sources/DependenciesMacrosPlugin/Plugins.swift b/Sources/DependenciesMacrosPlugin/Plugins.swift index 4bacd940..56eb262d 100644 --- a/Sources/DependenciesMacrosPlugin/Plugins.swift +++ b/Sources/DependenciesMacrosPlugin/Plugins.swift @@ -6,5 +6,6 @@ struct MacrosPlugin: CompilerPlugin { let providingMacros: [Macro.Type] = [ DependencyClientMacro.self, DependencyEndpointMacro.self, + DependencyEndpointIgnoredMacro.self ] } diff --git a/Sources/DependenciesMacrosPlugin/Support.swift b/Sources/DependenciesMacrosPlugin/Support.swift index 13bef324..8d74a554 100644 --- a/Sources/DependenciesMacrosPlugin/Support.swift +++ b/Sources/DependenciesMacrosPlugin/Support.swift @@ -175,6 +175,20 @@ extension VariableDeclSyntax { var isClosure: Bool { self.asClosureType != nil } + + func hasMacroApplication(_ name: String) -> Bool { + for attribute in attributes { + switch attribute { + case .attribute(let attr): + if attr.attributeName.tokens(viewMode: .all).map({ $0.tokenKind }) == [.identifier(name)] { + return true + } + default: + break + } + } + return false + } } extension MacroExpansionContext { diff --git a/Tests/DependenciesMacrosPluginTests/DependencyClientMacroTests.swift b/Tests/DependenciesMacrosPluginTests/DependencyClientMacroTests.swift index 60c2dfeb..01522069 100644 --- a/Tests/DependenciesMacrosPluginTests/DependencyClientMacroTests.swift +++ b/Tests/DependenciesMacrosPluginTests/DependencyClientMacroTests.swift @@ -13,6 +13,7 @@ final class DependencyClientMacroTests: BaseTestCase { } func testBasics() { + assertMacro { """ @DependencyClient @@ -616,6 +617,82 @@ final class DependencyClientMacroTests: BaseTestCase { } } + func testWithDependencyEndpointIgnored() { + assertMacro { + """ + @DependencyClient + struct Client: Sendable { + + let id = UUID() + var endpoint: @Sendable () -> Void + + @DependencyEndpointIgnored + var ignoredVar: @Sendable () -> Void + } + """ + } expansion: { + """ + struct Client: Sendable { + + let id = UUID() + @DependencyEndpoint + var endpoint: @Sendable () -> Void + + @DependencyEndpointIgnored + var ignoredVar: @Sendable () -> Void + + init( + endpoint: @Sendable @escaping () -> Void + ) { + self.endpoint = endpoint + } + + init() { + } + } + """ + } + } + + func testWithDependencyMacro() { + assertMacro { + """ + @DependencyClient + struct Client: Sendable { + @Dependency(TypedDependency.self) var typedDependency + @Dependency(TypedDependency.self) var typedDependency: TypedDependency + @Dependency(\\.dependency1) var dependency1 + @Dependency(\\.dependency2) var dependency2: DependencyTwo + + let id = UUID() + var endpoint: @Sendable () -> Void + } + """ + } expansion: { + #""" + struct Client: Sendable { + @Dependency(TypedDependency.self) var typedDependency + @Dependency(TypedDependency.self) var typedDependency: TypedDependency + @Dependency(\.dependency1) var dependency1 + @Dependency(\.dependency2) var dependency2: DependencyTwo + + let id = UUID() + @DependencyEndpoint + var endpoint: @Sendable () -> Void + + init( + endpoint: @Sendable @escaping () -> Void + ) { + self.endpoint = endpoint + } + + init() { + } + } + """# + } + } + func testLet_WithDefault() { assertMacro { """