diff --git a/Package.swift b/Package.swift index 63a58c69..9ada335f 100644 --- a/Package.swift +++ b/Package.swift @@ -24,21 +24,39 @@ let package = Package( .package(url: "https://github.com/pointfreeco/swift-macro-testing", from: "0.2.0"), ], targets: [ + .target(name: "SafeDI", dependencies: [ + "SafeDIMacros" + ]), .macro( name: "SafeDIMacros", dependencies: [ + "SafeDICore", .product(name: "SwiftSyntaxMacros", package: "swift-syntax"), .product(name: "SwiftCompilerPlugin", package: "swift-syntax") ] ), - .target(name: "SafeDI", dependencies: ["SafeDIMacros"]), .testTarget( name: "SafeDIMacrosTests", dependencies: [ "SafeDIMacros", + "SafeDICore", .product(name: "SwiftSyntaxMacrosTestSupport", package: "swift-syntax"), .product(name: "MacroTesting", package: "swift-macro-testing"), // TODO: write tests that use this! ] ), + .target( + name: "SafeDICore", + dependencies: [ + .product(name: "SwiftSyntax", package: "swift-syntax"), + .product(name: "SwiftSyntaxBuilder", package: "swift-syntax"), + .product(name: "SwiftSyntaxMacros", package: "swift-syntax"), + ] + ), + .testTarget( + name: "SafeDICoreTests", + dependencies: [ + "SafeDICore" + ] + ), ] ) diff --git a/Sources/SafeDIMacros/Internal/BuilderVisitor.swift b/Sources/SafeDICore/BuilderVisitor.swift similarity index 81% rename from Sources/SafeDIMacros/Internal/BuilderVisitor.swift rename to Sources/SafeDICore/BuilderVisitor.swift index 4469c648..922bf9c1 100644 --- a/Sources/SafeDIMacros/Internal/BuilderVisitor.swift +++ b/Sources/SafeDICore/BuilderVisitor.swift @@ -21,17 +21,17 @@ import SwiftDiagnostics import SwiftSyntax -final class BuilderVisitor: SyntaxVisitor { +public final class BuilderVisitor: SyntaxVisitor { // MARK: Initialization - init() { + public init() { super.init(viewMode: .sourceAccurate) } // MARK: SyntaxVisitor - override func visit(_ node: VariableDeclSyntax) -> SyntaxVisitorContinueKind { + public override func visit(_ node: VariableDeclSyntax) -> SyntaxVisitorContinueKind { guard let parent = node.parent, let typedParent = MemberBlockItemSyntax(parent), @@ -57,7 +57,7 @@ final class BuilderVisitor: SyntaxVisitor { return .skipChildren } - override func visit(_ node: InitializerDeclSyntax) -> SyntaxVisitorContinueKind { + public override func visit(_ node: InitializerDeclSyntax) -> SyntaxVisitorContinueKind { guard let parent = node.parent, let typedParent = MemberBlockItemSyntax(parent), @@ -83,7 +83,7 @@ final class BuilderVisitor: SyntaxVisitor { return .skipChildren } - override func visit(_ node: FunctionDeclSyntax) -> SyntaxVisitorContinueKind { + public override func visit(_ node: FunctionDeclSyntax) -> SyntaxVisitorContinueKind { guard let parent = node.parent, let typedParent = MemberBlockItemSyntax(parent), @@ -109,24 +109,28 @@ final class BuilderVisitor: SyntaxVisitor { return .skipChildren } - override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind { - if node.name.text == DependenciesMacro.decoratedStructName { + public override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind { + if node.name.text == DependenciesVisitor.decoratedStructName { didFindDependencies = true dependenciesVisitor.walk(node) } return .skipChildren } - // MARK: Internal + // MARK: Public - var dependencies: [Dependency] { + public var dependencies: [Dependency] { dependenciesVisitor.dependencies } - var builtType: String? { + public var builtType: String? { dependenciesVisitor.builtType } - private(set) var didFindDependencies = false - private(set) var diagnostics = [Diagnostic]() + public private(set) var didFindDependencies = false + public private(set) var diagnostics = [Diagnostic]() + + public static let macroName = "builder" + public static let decoratedStructName = "Builder" + public static let getDependenciesClosureName = "getDependencies" // MARK: Private diff --git a/Sources/SafeDIMacros/Internal/DependenciesVisitor.swift b/Sources/SafeDICore/DependenciesVisitor.swift similarity index 91% rename from Sources/SafeDIMacros/Internal/DependenciesVisitor.swift rename to Sources/SafeDICore/DependenciesVisitor.swift index 8ec72404..b0ebf724 100644 --- a/Sources/SafeDIMacros/Internal/DependenciesVisitor.swift +++ b/Sources/SafeDICore/DependenciesVisitor.swift @@ -21,17 +21,17 @@ import SwiftDiagnostics import SwiftSyntax -final class DependenciesVisitor: SyntaxVisitor { +public final class DependenciesVisitor: SyntaxVisitor { // MARK: Initialization - init() { + public init() { super.init(viewMode: .sourceAccurate) } // MARK: SyntaxVisitor - override func visit(_ node: VariableDeclSyntax) -> SyntaxVisitorContinueKind { + public override func visit(_ node: VariableDeclSyntax) -> SyntaxVisitorContinueKind { // Check attributes and extract dependency source. let dependencySources = node.attributes.dependencySources guard dependencySources.isEmpty || dependencySources.count == 1 else { @@ -42,7 +42,7 @@ final class DependenciesVisitor: SyntaxVisitor { replacementNode = Syntax(AttributeSyntax( attributeName: IdentifierTypeSyntax( name: TokenSyntax( - TokenKind.identifier(ConstructedMacro.name), + TokenKind.identifier(Dependency.Source.constructedAttributeName), presence: .present ) ) @@ -157,7 +157,7 @@ final class DependenciesVisitor: SyntaxVisitor { return .skipChildren } - override func visit(_ node: InitializerDeclSyntax) -> SyntaxVisitorContinueKind { + public override func visit(_ node: InitializerDeclSyntax) -> SyntaxVisitorContinueKind { guard let parent = node.parent, let typedParent = MemberBlockItemSyntax(parent), @@ -183,8 +183,8 @@ final class DependenciesVisitor: SyntaxVisitor { return .skipChildren } - override func visit(_ node: FunctionDeclSyntax) -> SyntaxVisitorContinueKind { - if node.name.text == DependenciesMacro.buildMethodName { + public override func visit(_ node: FunctionDeclSyntax) -> SyntaxVisitorContinueKind { + if node.name.text == DependenciesVisitor.buildMethodName { if didFindBuildMethod { // We've already found a `build` method! if @@ -246,8 +246,8 @@ final class DependenciesVisitor: SyntaxVisitor { return .skipChildren } - override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind { - if node.name.text == DependenciesMacro.decoratedStructName { + public override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind { + if node.name.text == DependenciesVisitor.decoratedStructName { guard node.modifiers.containsPublic else { diagnostics.append(Diagnostic( node: node.attributes, @@ -272,7 +272,7 @@ final class DependenciesVisitor: SyntaxVisitor { newAttributes.append(.attribute( AttributeSyntax( attributeName: IdentifierTypeSyntax( - name: .identifier(DependenciesMacro.decoratedStructName) + name: .identifier(DependenciesVisitor.decoratedStructName) ) ) )) @@ -296,12 +296,16 @@ final class DependenciesVisitor: SyntaxVisitor { } } - // MARK: Internal + // MARK: Public - private(set) var didFindBuildMethod = false - private(set) var dependencies = [Dependency]() - private(set) var builtType: String? - private(set) var diagnostics = [Diagnostic]() + public private(set) var didFindBuildMethod = false + public private(set) var dependencies = [Dependency]() + public private(set) var builtType: String? + public private(set) var diagnostics = [Diagnostic]() + + public static let macroName = "dependencies" + public static let decoratedStructName = "Dependencies" + public static let buildMethodName = "build" // MARK: Private diff --git a/Sources/SafeDIMacros/Internal/Dependency.swift b/Sources/SafeDICore/Dependency.swift similarity index 76% rename from Sources/SafeDIMacros/Internal/Dependency.swift rename to Sources/SafeDICore/Dependency.swift index 8f6d1e3b..2be44c76 100644 --- a/Sources/SafeDIMacros/Internal/Dependency.swift +++ b/Sources/SafeDICore/Dependency.swift @@ -18,11 +18,11 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -struct Dependency: Codable, Equatable { - let property: Property - let source: Source +public struct Dependency: Codable, Equatable { + public let property: Property + public let source: Source - var isVariant: Bool { + public var isVariant: Bool { switch source { case .constructedInvariant, .providedInvariant, .singletonInvariant: return false @@ -31,7 +31,7 @@ struct Dependency: Codable, Equatable { } } - var isInvariant: Bool { + public var isInvariant: Bool { switch source { case .constructedInvariant, .providedInvariant, .singletonInvariant: return true @@ -40,16 +40,19 @@ struct Dependency: Codable, Equatable { } } - enum Source: Codable, Equatable { + public enum Source: Codable, Equatable { case constructedInvariant case providedInvariant case singletonInvariant case variant - init?(_ attributeText: String) { - if attributeText == ConstructedMacro.name { + public static let constructedAttributeName = "constructed" + public static let singletonAttributeName = "singleton" + + public init?(_ attributeText: String) { + if attributeText == Self.constructedAttributeName { self = .constructedInvariant - } else if attributeText == SingletonMacro.name { + } else if attributeText == Self.singletonAttributeName { self = .singletonInvariant } else { return nil diff --git a/Sources/SafeDICore/Errors/DiagnosticError.swift b/Sources/SafeDICore/Errors/DiagnosticError.swift new file mode 100644 index 00000000..456f989b --- /dev/null +++ b/Sources/SafeDICore/Errors/DiagnosticError.swift @@ -0,0 +1,28 @@ +// Distributed under the MIT License +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +import SwiftDiagnostics +import SwiftSyntax + +public protocol DiagnosticError: Error, CustomStringConvertible { + var diagnostic: DiagnosticMessage { get } + + var fixIt: FixItMessage { get } +} diff --git a/Sources/SafeDIMacros/Internal/FixableBuilderError.swift b/Sources/SafeDICore/Errors/FixableBuilderError.swift similarity index 75% rename from Sources/SafeDIMacros/Internal/FixableBuilderError.swift rename to Sources/SafeDICore/Errors/FixableBuilderError.swift index 701d7823..fd7e5a78 100644 --- a/Sources/SafeDIMacros/Internal/FixableBuilderError.swift +++ b/Sources/SafeDICore/Errors/FixableBuilderError.swift @@ -20,37 +20,36 @@ import SwiftDiagnostics -enum FixableBuilderError: DiagnosticError { +public enum FixableBuilderError: DiagnosticError { case missingDependencies case unexpectedVariableDeclaration case unexpectedInitializer case unexpectedFuncationDeclaration - var description: String { + public var description: String { switch self { case .missingDependencies: - return "Missing nested `@\(DependenciesMacro.name) public struct \(DependenciesMacro.decoratedStructName)` declaration" + return "Missing nested `@\(DependenciesVisitor.macroName) public struct \(DependenciesVisitor.decoratedStructName)` declaration" case .unexpectedVariableDeclaration: - return "Found unexpected variable declaration in `\(BuilderMacro.decoratedStructName)`" + return "Found unexpected variable declaration in `\(BuilderVisitor.decoratedStructName)`" case .unexpectedInitializer: - return "Found unexpected initializer in `\(BuilderMacro.decoratedStructName)`" + return "Found unexpected initializer in `\(BuilderVisitor.decoratedStructName)`" case .unexpectedFuncationDeclaration: - return "Found unexpected function declaration in `\(BuilderMacro.decoratedStructName)`" + return "Found unexpected function declaration in `\(BuilderVisitor.decoratedStructName)`" } } - var diagnostic: DiagnosticMessage { - DiagnosticMessage(error: self) + public var diagnostic: SwiftDiagnostics.DiagnosticMessage { + BuilderDiagnosticMessage(error: self) } - var fixIt: FixItMessage { - FixItMessage(error: self) + public var fixIt: SwiftDiagnostics.FixItMessage { + BuilderFixItMessage(error: self) } - struct DiagnosticMessage: SwiftDiagnostics.DiagnosticMessage { - - let error: FixableBuilderError + // MARK: - BuilderDiagnosticMessage + private struct BuilderDiagnosticMessage: DiagnosticMessage { var diagnosticID: MessageID { MessageID(domain: "FixableBuilderError.DiagnosticMessage", id: error.description) } @@ -68,13 +67,17 @@ enum FixableBuilderError: DiagnosticError { var message: String { error.description } + + let error: FixableBuilderError } - struct FixItMessage: SwiftDiagnostics.FixItMessage { + // MARK: - BuilderFixItMessage + + private struct BuilderFixItMessage: FixItMessage { var message: String { switch error { case .missingDependencies: - return "Create nested `@\(DependenciesMacro.name) struct \(DependenciesMacro.decoratedStructName)`" + return "Create nested `@\(DependenciesVisitor.macroName) struct \(DependenciesVisitor.decoratedStructName)`" case .unexpectedVariableDeclaration: return "Delete variable declaration" case .unexpectedInitializer: diff --git a/Sources/SafeDIMacros/Internal/FixableDependenciesError.swift b/Sources/SafeDICore/Errors/FixableDependenciesError.swift similarity index 75% rename from Sources/SafeDIMacros/Internal/FixableDependenciesError.swift rename to Sources/SafeDICore/Errors/FixableDependenciesError.swift index 9ddcffa7..be56f7c2 100644 --- a/Sources/SafeDIMacros/Internal/FixableDependenciesError.swift +++ b/Sources/SafeDICore/Errors/FixableDependenciesError.swift @@ -20,7 +20,7 @@ import SwiftDiagnostics -enum FixableDependenciesError: DiagnosticError { +public enum FixableDependenciesError: DiagnosticError { case missingDependenciesAttribute case missingPublicAttributeOnDependencies case dependencyHasTooManyAttributes @@ -33,14 +33,14 @@ enum FixableDependenciesError: DiagnosticError { case multipleBuildMethods case duplicateDependency - var description: String { + public var description: String { switch self { case .missingDependenciesAttribute: - return "Missing `@\(DependenciesMacro.name)` attached macro on `public struct Dependencies`" + return "Missing `@\(DependenciesVisitor.macroName)` attached macro on `public struct Dependencies`" case .missingPublicAttributeOnDependencies: return "Missing `public` modifier on `struct Dependencies`" case .dependencyHasTooManyAttributes: - return "Dependency can have at most one `@\(ConstructedMacro.name)` or `@\(SingletonMacro.name)` attached macro" + return "Dependency can have at most one `@\(Dependency.Source.constructedAttributeName)` or `@\(Dependency.Source.singletonAttributeName)` attached macro" case .dependencyIsStatic: return "Dependency must not be `static`" case .dependencyIsNotPrivate: @@ -50,27 +50,27 @@ enum FixableDependenciesError: DiagnosticError { case .unexpectedInitializer: return "Dependency must not have hand-written initializer" case .missingBuildMethod: - return "@\(DependenciesMacro.name)-decorated type must have `func build(...) -> BuiltProduct` method" + return "@\(DependenciesVisitor.macroName)-decorated type must have `func build(...) -> BuiltProduct` method" case .missingBuildMethodReturnClause: - return "@\(DependenciesMacro.name)-decorated type's `func build(...)` method must return a type" + return "@\(DependenciesVisitor.macroName)-decorated type's `func build(...)` method must return a type" case .multipleBuildMethods: - return "@\(DependenciesMacro.name)-decorated type must have a single `func build(...) -> BuiltProduct` method" + return "@\(DependenciesVisitor.macroName)-decorated type must have a single `func build(...) -> BuiltProduct` method" case .duplicateDependency: return "Every declared dependency must have a unique name" } } - var diagnostic: DiagnosticMessage { - DiagnosticMessage(error: self) + public var diagnostic: DiagnosticMessage { + DependenciesDiagnosticMessage(error: self) } - var fixIt: FixItMessage { - FixItMessage(error: self) + public var fixIt: FixItMessage { + DependenciesFixItMessage(error: self) } - struct DiagnosticMessage: SwiftDiagnostics.DiagnosticMessage { + // MARK: - DependenciesDiagnosticMessage - let error: FixableDependenciesError + private struct DependenciesDiagnosticMessage: DiagnosticMessage { var diagnosticID: MessageID { MessageID(domain: "FixableDependenciesError.DiagnosticMessage", id: error.description) @@ -96,17 +96,21 @@ enum FixableDependenciesError: DiagnosticError { var message: String { error.description } + + let error: FixableDependenciesError } - struct FixItMessage: SwiftDiagnostics.FixItMessage { + // MARK: - DependenciesFixItMessage + + struct DependenciesFixItMessage: SwiftDiagnostics.FixItMessage { var message: String { switch error { case .missingDependenciesAttribute: - return "Attach `@\(DependenciesMacro.name)` macro" + return "Attach `@\(DependenciesVisitor.macroName)` macro" case .missingPublicAttributeOnDependencies: - return "Make `struct \(DependenciesMacro.decoratedStructName)` have an access level of `public`" + return "Make `struct \(DependenciesVisitor.decoratedStructName)` have an access level of `public`" case .dependencyHasTooManyAttributes: - return "Remove all but first `@\(ConstructedMacro.name)` or `@\(SingletonMacro.name)` attached macro" + return "Remove all but first `@\(Dependency.Source.constructedAttributeName)` or `@\(Dependency.Source.singletonAttributeName)` attached macro" case .dependencyIsStatic: return "Remove `static` from property" case .dependencyIsNotPrivate: diff --git a/Sources/SafeDIMacros/Extensions/ArrayExtensions.swift b/Sources/SafeDICore/Extensions/ArrayExtensions.swift similarity index 88% rename from Sources/SafeDIMacros/Extensions/ArrayExtensions.swift rename to Sources/SafeDICore/Extensions/ArrayExtensions.swift index 4b892ea3..10350bca 100644 --- a/Sources/SafeDIMacros/Extensions/ArrayExtensions.swift +++ b/Sources/SafeDICore/Extensions/ArrayExtensions.swift @@ -23,7 +23,7 @@ import SwiftSyntaxBuilder extension Array where Element == Dependency { - var variantUnlabeledParameterList: FunctionParameterListSyntax { + public var variantUnlabeledParameterList: FunctionParameterListSyntax { FunctionParameterListSyntax( filter { $0.source == .variant } .map { "\(raw: $0.property.type)" } @@ -36,7 +36,7 @@ extension Array where Element == Dependency { ) } - var variantParameterList: FunctionParameterListSyntax { + public var variantParameterList: FunctionParameterListSyntax { FunctionParameterListSyntax( filter { $0.source == .variant } .map { "\(raw: $0.property.label): \(raw: $0.property.type)" } @@ -49,19 +49,19 @@ extension Array where Element == Dependency { ) } - var variantUnlabeledExpressionList: String { + public var variantUnlabeledExpressionList: String { filter { $0.isVariant } .map { "\($0.property.label)" } .joined(separator: ", ") } - var variantLabeledExpressionList: String { + public var variantLabeledExpressionList: String { filter { $0.isVariant } .map { "\($0.property.label): \($0.property.label)" } .joined(separator: ", ") } - var invariantParameterList: FunctionParameterListSyntax { + public var invariantParameterList: FunctionParameterListSyntax { FunctionParameterListSyntax( filter { $0.isInvariant } .map { "\(raw: $0.property.label): \(raw: $0.property.type)" } @@ -74,7 +74,7 @@ extension Array where Element == Dependency { ) } - var invariantAssignmentExpressionList: String { + public var invariantAssignmentExpressionList: String { """ \(filter(\.isInvariant) .map { "self.\($0.property.label) = \($0.property.label)" } @@ -89,7 +89,7 @@ extension Array { /// Returns an array with all of the items in the array except for the last transformed. /// - Parameter transform: A transforming closure. `transform` accepts an element of this sequence as its parameter and returns a transformed value of the same type. /// - Returns: An array containing the transformed elements of this sequence, plus the untransfomred last element. - func transformUntilLast(_ transform: (Element) throws -> Element) rethrows -> [Element] { + fileprivate func transformUntilLast(_ transform: (Element) throws -> Element) rethrows -> [Element] { var arrayToTransform = self guard let lastItem = arrayToTransform.popLast() else { // Array is empty. diff --git a/Sources/SafeDIMacros/Extensions/AttributeListSyntaxExtensions.swift b/Sources/SafeDICore/Extensions/AttributeListSyntaxExtensions.swift similarity index 87% rename from Sources/SafeDIMacros/Extensions/AttributeListSyntaxExtensions.swift rename to Sources/SafeDICore/Extensions/AttributeListSyntaxExtensions.swift index 3e2f5e9f..b64927a0 100644 --- a/Sources/SafeDIMacros/Extensions/AttributeListSyntaxExtensions.swift +++ b/Sources/SafeDICore/Extensions/AttributeListSyntaxExtensions.swift @@ -22,18 +22,18 @@ import SwiftSyntax extension AttributeListSyntax { - var isDecoratedWithDependenciesMacro: Bool { + public var isDecoratedWithDependenciesMacro: Bool { contains(where: { element in switch element { case let .attribute(attribute): - return attribute.attributeName.as(IdentifierTypeSyntax.self)?.name.text == DependenciesMacro.name + return attribute.attributeName.as(IdentifierTypeSyntax.self)?.name.text == DependenciesVisitor.macroName case .ifConfigDecl: return false } }) } - var attributedNodes: [(attribute: String, node: AttributeListSyntax.Element)] { + public var attributedNodes: [(attribute: String, node: AttributeListSyntax.Element)] { compactMap { element in switch element { case let .attribute(attribute): @@ -47,7 +47,7 @@ extension AttributeListSyntax { } } - var dependencySources: [(source: Dependency.Source, node: AttributeListSyntax.Element)] { + public var dependencySources: [(source: Dependency.Source, node: AttributeListSyntax.Element)] { attributedNodes.compactMap { guard let source = Dependency.Source.init($0.attribute) else { return nil diff --git a/Sources/SafeDIMacros/Extensions/AttributeSyntaxArgumentsExtensions.swift b/Sources/SafeDICore/Extensions/AttributeSyntaxArgumentsExtensions.swift similarity index 98% rename from Sources/SafeDIMacros/Extensions/AttributeSyntaxArgumentsExtensions.swift rename to Sources/SafeDICore/Extensions/AttributeSyntaxArgumentsExtensions.swift index c3577176..14e26032 100644 --- a/Sources/SafeDIMacros/Extensions/AttributeSyntaxArgumentsExtensions.swift +++ b/Sources/SafeDICore/Extensions/AttributeSyntaxArgumentsExtensions.swift @@ -21,7 +21,7 @@ import SwiftSyntax extension AttributeSyntax.Arguments { - var string: String? { + public var string: String? { switch self { case let .argumentList(labeledExprListSyntax): return labeledExprListSyntax diff --git a/Sources/SafeDIMacros/Extensions/DeclModifierListSyntaxExtensions.swift b/Sources/SafeDICore/Extensions/DeclModifierListSyntaxExtensions.swift similarity index 94% rename from Sources/SafeDIMacros/Extensions/DeclModifierListSyntaxExtensions.swift rename to Sources/SafeDICore/Extensions/DeclModifierListSyntaxExtensions.swift index ed04841b..883d306c 100644 --- a/Sources/SafeDIMacros/Extensions/DeclModifierListSyntaxExtensions.swift +++ b/Sources/SafeDICore/Extensions/DeclModifierListSyntaxExtensions.swift @@ -22,13 +22,13 @@ import SwiftSyntax extension DeclModifierListSyntax { - var containsPublic: Bool { + public var containsPublic: Bool { contains(where: { modifier in modifier.name.text == "public" }) } - var staticModifier: Element? { + public var staticModifier: Element? { first(where: { modifier in modifier.name.text == "static" }) diff --git a/Sources/SafeDIMacros/Internal/DiagnosticError.swift b/Sources/SafeDICore/Extensions/DiagnosticExtensions.swift similarity index 85% rename from Sources/SafeDIMacros/Internal/DiagnosticError.swift rename to Sources/SafeDICore/Extensions/DiagnosticExtensions.swift index d4e55c44..20a9dfd5 100644 --- a/Sources/SafeDIMacros/Internal/DiagnosticError.swift +++ b/Sources/SafeDICore/Extensions/DiagnosticExtensions.swift @@ -21,16 +21,8 @@ import SwiftDiagnostics import SwiftSyntax -protocol DiagnosticError: Error, CustomStringConvertible { - associatedtype DiagnosticErrorMessage: DiagnosticMessage - var diagnostic: DiagnosticErrorMessage { get } - - associatedtype DiagnosticErrorFixIt: FixItMessage - var fixIt: DiagnosticErrorFixIt { get } -} - extension Diagnostic { - init( + public init( node: some SyntaxProtocol, position: AbsolutePosition? = nil, error: some DiagnosticError, diff --git a/Sources/SafeDIMacros/Extensions/FunctionDeclSyntaxExtensions.swift b/Sources/SafeDICore/Extensions/FunctionDeclSyntaxExtensions.swift similarity index 79% rename from Sources/SafeDIMacros/Extensions/FunctionDeclSyntaxExtensions.swift rename to Sources/SafeDICore/Extensions/FunctionDeclSyntaxExtensions.swift index 995b1f51..69115b06 100644 --- a/Sources/SafeDIMacros/Extensions/FunctionDeclSyntaxExtensions.swift +++ b/Sources/SafeDICore/Extensions/FunctionDeclSyntaxExtensions.swift @@ -3,7 +3,7 @@ import SwiftSyntaxBuilder extension FunctionDeclSyntax { - static var buildTemplate: Self { + public static var buildTemplate: Self { try! FunctionDeclSyntax(""" func build(<#T##parameter#>: <#T##ParameterType#>) \(returnClauseTemplate) { <#T##ConcreteBuiltProductType#>(<#T##parameter#>: <#T##ParameterType#>) @@ -11,7 +11,7 @@ extension FunctionDeclSyntax { """) } - static var returnClauseTemplate: ReturnClauseSyntax { + public static var returnClauseTemplate: ReturnClauseSyntax { ReturnClauseSyntax( type: TypeSyntax(" <#T##BuiltProductType#>") ) diff --git a/Sources/SafeDIMacros/Extensions/StructDeclSyntaxExtensions.swift b/Sources/SafeDICore/Extensions/StructDeclSyntaxExtensions.swift similarity index 96% rename from Sources/SafeDIMacros/Extensions/StructDeclSyntaxExtensions.swift rename to Sources/SafeDICore/Extensions/StructDeclSyntaxExtensions.swift index 40f4d93f..aae2f66f 100644 --- a/Sources/SafeDIMacros/Extensions/StructDeclSyntaxExtensions.swift +++ b/Sources/SafeDICore/Extensions/StructDeclSyntaxExtensions.swift @@ -23,7 +23,7 @@ import SwiftSyntaxBuilder extension StructDeclSyntax { - static var dependenciesTemplate: Self { + public static var dependenciesTemplate: Self { try! StructDeclSyntax(""" @dependencies public struct Dependencies { diff --git a/Sources/SafeDIMacros/Internal/Property.swift b/Sources/SafeDICore/Property.swift similarity index 92% rename from Sources/SafeDIMacros/Internal/Property.swift rename to Sources/SafeDICore/Property.swift index 97b0b0da..bd1864ae 100644 --- a/Sources/SafeDIMacros/Internal/Property.swift +++ b/Sources/SafeDICore/Property.swift @@ -20,9 +20,9 @@ /// A representation of a property. /// e.g. `let myDependency: MyDependency` -struct Property: Codable, Equatable { +public struct Property: Codable, Equatable { /// The label by which the property is referenced. - let label: String + public let label: String /// The type to which the property conforms. - let type: String + public let type: String } diff --git a/Sources/SafeDIMacros/Macros/BuilderMacro.swift b/Sources/SafeDIMacros/Macros/BuilderMacro.swift index e8186626..9bde9b2f 100644 --- a/Sources/SafeDIMacros/Macros/BuilderMacro.swift +++ b/Sources/SafeDIMacros/Macros/BuilderMacro.swift @@ -18,6 +18,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +import SafeDICore import SwiftDiagnostics import SwiftSyntax import SwiftSyntaxBuilder @@ -86,30 +87,26 @@ public struct BuilderMacro: MemberMacro { return [] } let builtPropertyDescription = "let \(builtPropertyName): \(builtType)" - let builderPropertyDescription = "let \(builtPropertyName)\(Self.decoratedStructName): \(structDelcaration.name.text)" + let builderPropertyDescription = "let \(builtPropertyName)\(BuilderVisitor.decoratedStructName): \(structDelcaration.name.text)" return [ """ - // Inject this builder as a dependency by adding `\(raw: builderPropertyDescription)` to your @\(raw: DependenciesMacro.name) type - public init(\(raw: Self.getDependenciesClosureName): @escaping (\(variantUnlabeledParameterList)) -> \(raw: DependenciesMacro.decoratedStructName)) { - self.\(raw: Self.getDependenciesClosureName) = \(raw: Self.getDependenciesClosureName) + // Inject this builder as a dependency by adding `\(raw: builderPropertyDescription)` to your @\(raw: DependenciesVisitor.macroName) type + public init(\(raw: BuilderVisitor.getDependenciesClosureName): @escaping (\(variantUnlabeledParameterList)) -> \(raw: DependenciesVisitor.decoratedStructName)) { + self.\(raw: BuilderVisitor.getDependenciesClosureName) = \(raw: BuilderVisitor.getDependenciesClosureName) } """, """ - // Inject this built product as a dependency by adding `\(raw: builtPropertyDescription)` to your @\(raw: DependenciesMacro.name) type + // Inject this built product as a dependency by adding `\(raw: builtPropertyDescription)` to your @\(raw: DependenciesVisitor.macroName) type public func build(\(variantParameterList)) -> \(raw: builtType) { - \(raw: Self.getDependenciesClosureName)(\(raw: variantUnlabeledExpressionList)).build(\(raw: variantLabeledExpressionList)) + \(raw: BuilderVisitor.getDependenciesClosureName)(\(raw: variantUnlabeledExpressionList)).build(\(raw: variantLabeledExpressionList)) } """, """ - private let \(raw: Self.getDependenciesClosureName): (\(variantUnlabeledParameterList)) -> \(raw: DependenciesMacro.decoratedStructName) + private let \(raw: BuilderVisitor.getDependenciesClosureName): (\(variantUnlabeledParameterList)) -> \(raw: DependenciesVisitor.decoratedStructName) """, ] } - static let name = "builder" - static let decoratedStructName = "Builder" - static let getDependenciesClosureName = "getDependencies" - // MARK: - BuilderError private enum BuilderError: Error, CustomStringConvertible { @@ -120,11 +117,11 @@ public struct BuilderMacro: MemberMacro { var description: String { switch self { case .notPublic: - return "@\(BuilderMacro.name) struct must be `public`" + return "@\(BuilderVisitor.macroName) struct must be `public`" case .notStruct: - return "@\(BuilderMacro.name) must decorate a `struct`" + return "@\(BuilderVisitor.macroName) must decorate a `struct`" case .notTopLevelDeclaration: - return "@\(BuilderMacro.name) struct is not declared at the top level" + return "@\(BuilderVisitor.macroName) struct is not declared at the top level" } } } diff --git a/Sources/SafeDIMacros/Macros/ConstructedMacro.swift b/Sources/SafeDIMacros/Macros/ConstructedMacro.swift index 2b370d4e..221de1b0 100644 --- a/Sources/SafeDIMacros/Macros/ConstructedMacro.swift +++ b/Sources/SafeDIMacros/Macros/ConstructedMacro.swift @@ -18,6 +18,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +import SafeDICore import SwiftSyntax import SwiftSyntaxMacros @@ -37,8 +38,6 @@ public struct ConstructedMacro: PeerMacro { return [] } - static let name = "constructed" - // MARK: - ConstructedError private enum ConstructedError: Error, CustomStringConvertible { @@ -47,7 +46,7 @@ public struct ConstructedMacro: PeerMacro { var description: String { switch self { case .notDecoratingBinding: - return "@\(ConstructedMacro.name) must decorate a instance variable" + return "@\(Dependency.Source.constructedAttributeName) must decorate a instance variable" } } } diff --git a/Sources/SafeDIMacros/Macros/DependenciesMacro.swift b/Sources/SafeDIMacros/Macros/DependenciesMacro.swift index a4e7df90..7f44d6bb 100644 --- a/Sources/SafeDIMacros/Macros/DependenciesMacro.swift +++ b/Sources/SafeDIMacros/Macros/DependenciesMacro.swift @@ -18,6 +18,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +import SafeDICore import SwiftDiagnostics import SwiftSyntax import SwiftSyntaxBuilder @@ -38,7 +39,7 @@ public struct DependenciesMacro: MemberMacro { throw DependenciesError.notStruct // TODO: add fixit instead } - guard structDelcaration.name.text == Self.decoratedStructName else { + guard structDelcaration.name.text == DependenciesVisitor.decoratedStructName else { throw DependenciesError.notNamedDependencies // TODO: add fixit instead } @@ -79,10 +80,6 @@ public struct DependenciesMacro: MemberMacro { ] } - static let name = "dependencies" - static let decoratedStructName = "Dependencies" - static let buildMethodName = "build" - // MARK: - DependenciesError private enum DependenciesError: Error, CustomStringConvertible { @@ -93,11 +90,11 @@ public struct DependenciesMacro: MemberMacro { var description: String { switch self { case .notPublic: - return "@\(DependenciesMacro.name) struct must be `public`" + return "@\(DependenciesVisitor.macroName) struct must be `public`" case .notStruct: - return "@\(DependenciesMacro.name) must decorate a `struct`" + return "@\(DependenciesVisitor.macroName) must decorate a `struct`" case .notNamedDependencies: - return "@\(DependenciesMacro.name) must decorate a `struct` with the name `Dependencies`" + return "@\(DependenciesVisitor.macroName) must decorate a `struct` with the name `Dependencies`" } } } diff --git a/Sources/SafeDIMacros/Macros/SingletonMacro.swift b/Sources/SafeDIMacros/Macros/SingletonMacro.swift index 3c7e3ab0..3e8e236f 100644 --- a/Sources/SafeDIMacros/Macros/SingletonMacro.swift +++ b/Sources/SafeDIMacros/Macros/SingletonMacro.swift @@ -18,6 +18,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +import SafeDICore import SwiftSyntax import SwiftSyntaxMacros @@ -37,8 +38,6 @@ public struct SingletonMacro: PeerMacro { return [] } - static let name = "singleton" - // MARK: - SingletonError private enum SingletonError: Error, CustomStringConvertible { @@ -47,7 +46,7 @@ public struct SingletonMacro: PeerMacro { var description: String { switch self { case .notDecoratingBinding: - return "@\(SingletonMacro.name) must decorate a instance variable" + return "@\(Dependency.Source.singletonAttributeName) must decorate a instance variable" } } } diff --git a/Tests/SafeDIMacrosTests/ArrayExtensionsTests.swift b/Tests/SafeDICoreTests/ArrayExtensionsTests.swift similarity index 56% rename from Tests/SafeDIMacrosTests/ArrayExtensionsTests.swift rename to Tests/SafeDICoreTests/ArrayExtensionsTests.swift index 250cbc71..24dfea0b 100644 --- a/Tests/SafeDIMacrosTests/ArrayExtensionsTests.swift +++ b/Tests/SafeDICoreTests/ArrayExtensionsTests.swift @@ -20,12 +20,12 @@ import XCTest -@testable import SafeDIMacros +@testable import SafeDICore final class ArrayExtensionsTests: XCTestCase { func test_variantUnlabeledParameterList_withSingleVariant() throws { - let dependencies = [Dependency(variableName: "int", type: "Int", source: .variant)] + let dependencies = [Dependency(property: Property(label: "int", type: "Int"), source: .variant)] XCTAssertEqual( dependencies.variantUnlabeledParameterList.description, "Int" @@ -34,10 +34,10 @@ final class ArrayExtensionsTests: XCTestCase { func test_variantUnlabeledParameterList_withMultipleVariants() throws { let dependencies = [ - Dependency(variableName: "int", type: "Int", source: .variant), - Dependency(variableName: "string", type: "String", source: .variant), - Dependency(variableName: "double", type: "Double", source: .variant), - Dependency(variableName: "invariant", type: "Invariant", source: .providedInvariant) + Dependency(property: Property(label: "int", type: "Int"), source: .variant), + Dependency(property: Property(label: "string", type: "String"), source: .variant), + Dependency(property: Property(label: "double", type: "Double"), source: .variant), + Dependency(property: Property(label: "invariant", type: "Invariant"), source: .providedInvariant) ] XCTAssertEqual( dependencies.variantUnlabeledParameterList.description, @@ -46,7 +46,7 @@ final class ArrayExtensionsTests: XCTestCase { } func test_variantParameterList_withSingleVariant() throws { - let dependencies = [Dependency(variableName: "int", type: "Int", source: .variant)] + let dependencies = [Dependency(property: Property(label: "int", type: "Int"), source: .variant)] XCTAssertEqual( dependencies.variantParameterList.description, "int: Int" @@ -55,10 +55,10 @@ final class ArrayExtensionsTests: XCTestCase { func test_variantParameterList_withMultipleVariants() throws { let dependencies = [ - Dependency(variableName: "int", type: "Int", source: .variant), - Dependency(variableName: "string", type: "String", source: .variant), - Dependency(variableName: "double", type: "Double", source: .variant), - Dependency(variableName: "invariant", type: "Invariant", source: .providedInvariant) + Dependency(property: Property(label: "int", type: "Int"), source: .variant), + Dependency(property: Property(label: "string", type: "String"), source: .variant), + Dependency(property: Property(label: "double", type: "Double"), source: .variant), + Dependency(property: Property(label: "invariant", type: "Invariant"), source: .providedInvariant) ] XCTAssertEqual( dependencies.variantParameterList.description, @@ -67,7 +67,7 @@ final class ArrayExtensionsTests: XCTestCase { } func test_variantUnlabeledExpressionList_withSingleVariant() throws { - let dependencies = [Dependency(variableName: "int", type: "Int", source: .variant)] + let dependencies = [Dependency(property: Property(label: "int", type: "Int"), source: .variant)] XCTAssertEqual( dependencies.variantUnlabeledExpressionList, "int" @@ -76,10 +76,10 @@ final class ArrayExtensionsTests: XCTestCase { func test_variantUnlabeledExpressionList_withMultipleVariants() throws { let dependencies = [ - Dependency(variableName: "int", type: "Int", source: .variant), - Dependency(variableName: "string", type: "String", source: .variant), - Dependency(variableName: "double", type: "Double", source: .variant), - Dependency(variableName: "invariant", type: "Invariant", source: .providedInvariant) + Dependency(property: Property(label: "int", type: "Int"), source: .variant), + Dependency(property: Property(label: "string", type: "String"), source: .variant), + Dependency(property: Property(label: "double", type: "Double"), source: .variant), + Dependency(property: Property(label: "invariant", type: "Invariant"), source: .providedInvariant) ] XCTAssertEqual( dependencies.variantUnlabeledExpressionList, @@ -89,7 +89,7 @@ final class ArrayExtensionsTests: XCTestCase { func test_variantLabeledExpressionList_withSingleVariant() throws { - let dependencies = [Dependency(variableName: "int", type: "Int", source: .variant)] + let dependencies = [Dependency(property: Property(label: "int", type: "Int"), source: .variant)] XCTAssertEqual( dependencies.variantLabeledExpressionList, "int: int" @@ -98,10 +98,10 @@ final class ArrayExtensionsTests: XCTestCase { func test_variantLabeledExpressionList_withMultipleVariants() throws { let dependencies = [ - Dependency(variableName: "int", type: "Int", source: .variant), - Dependency(variableName: "string", type: "String", source: .variant), - Dependency(variableName: "double", type: "Double", source: .variant), - Dependency(variableName: "invariant", type: "Invariant", source: .providedInvariant) + Dependency(property: Property(label: "int", type: "Int"), source: .variant), + Dependency(property: Property(label: "string", type: "String"), source: .variant), + Dependency(property: Property(label: "double", type: "Double"), source: .variant), + Dependency(property: Property(label: "invariant", type: "Invariant"), source: .providedInvariant) ] XCTAssertEqual( dependencies.variantLabeledExpressionList, @@ -110,7 +110,7 @@ final class ArrayExtensionsTests: XCTestCase { } func test_invariantParameterList_withSingleInvariant() throws { - let dependencies = [Dependency(variableName: "int", type: "Int", source: .providedInvariant)] + let dependencies = [Dependency(property: Property(label: "int", type: "Int"), source: .providedInvariant)] XCTAssertEqual( dependencies.invariantParameterList.description, "int: Int" @@ -119,10 +119,10 @@ final class ArrayExtensionsTests: XCTestCase { func test_invariantParameterList_withMultipleInvariants() throws { let dependencies = [ - Dependency(variableName: "int", type: "Int", source: .singletonInvariant), - Dependency(variableName: "string", type: "String", source: .constructedInvariant), - Dependency(variableName: "double", type: "Double", source: .providedInvariant), - Dependency(variableName: "variant", type: "Variant", source: .variant) + Dependency(property: Property(label: "int", type: "Int"), source: .singletonInvariant), + Dependency(property: Property(label: "string", type: "String"), source: .constructedInvariant), + Dependency(property: Property(label: "double", type: "Double"), source: .providedInvariant), + Dependency(property: Property(label: "variant", type: "Variant"), source: .variant) ] XCTAssertEqual( dependencies.invariantParameterList.description, @@ -131,7 +131,7 @@ final class ArrayExtensionsTests: XCTestCase { } func test_invariantAssignmentExpressionList_withSingleInvariant() throws { - let dependencies = [Dependency(variableName: "int", type: "Int", source: .providedInvariant)] + let dependencies = [Dependency(property: Property(label: "int", type: "Int"), source: .providedInvariant)] XCTAssertEqual( dependencies.invariantAssignmentExpressionList, "self.int = int" @@ -140,10 +140,10 @@ final class ArrayExtensionsTests: XCTestCase { func test_invariantAssignmentExpressionList_withMultipleInvariants() throws { let dependencies = [ - Dependency(variableName: "int", type: "Int", source: .singletonInvariant), - Dependency(variableName: "string", type: "String", source: .constructedInvariant), - Dependency(variableName: "double", type: "Double", source: .providedInvariant), - Dependency(variableName: "variant", type: "Variant", source: .variant) + Dependency(property: Property(label: "int", type: "Int"), source: .singletonInvariant), + Dependency(property: Property(label: "string", type: "String"), source: .constructedInvariant), + Dependency(property: Property(label: "double", type: "Double"), source: .providedInvariant), + Dependency(property: Property(label: "variant", type: "Variant"), source: .variant) ] XCTAssertEqual( dependencies.invariantAssignmentExpressionList, diff --git a/Tests/SafeDIMacrosTests/MacroTests.swift b/Tests/SafeDIMacrosTests/MacroTests.swift index e5c52fc7..37a4e9ea 100644 --- a/Tests/SafeDIMacrosTests/MacroTests.swift +++ b/Tests/SafeDIMacrosTests/MacroTests.swift @@ -22,13 +22,16 @@ import SwiftSyntaxMacros import SwiftSyntaxMacrosTestSupport import XCTest +import SafeDICore + +#if canImport(SafeDIMacros) @testable import SafeDIMacros let testMacros: [String: Macro.Type] = [ - BuilderMacro.name: BuilderMacro.self, - DependenciesMacro.name: DependenciesMacro.self, - ConstructedMacro.name: ConstructedMacro.self, - SingletonMacro.name: SingletonMacro.self, + BuilderVisitor.macroName: BuilderMacro.self, + DependenciesVisitor.macroName: DependenciesMacro.self, + Dependency.Source.constructedAttributeName: ConstructedMacro.self, + Dependency.Source.singletonAttributeName: SingletonMacro.self, ] final class MacroTests: XCTestCase { @@ -502,3 +505,4 @@ final class MacroTests: XCTestCase { ) } } +#endif