Skip to content

Commit

Permalink
Merge pull request #1 from Ryu0118/feature/dependency-value-macro
Browse files Browse the repository at this point in the history
add DependencyValue Macro
  • Loading branch information
Ryu0118 committed Sep 30, 2023
2 parents c1581b7 + 2fc88c2 commit 5367bac
Show file tree
Hide file tree
Showing 10 changed files with 281 additions and 27 deletions.
24 changes: 14 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import DependenciesMacro
public struct TestClient {
public var request: @Sendable (_ request: Request) -> Void
}

@DependencyValue(TestClient.self)
public extension DependencyValues {}
```
`@PublicInit` is a Member Macro and provides a public initializer.
This macro can be applied only to public structs.
Expand All @@ -31,17 +34,18 @@ extension TestClient: TestDependencyKey {
request: unimplemented("\(Self.self).request")
)
}

public extension DependencyValues {
var testClient: TestClient {
get {
self[TestClient.self]
}
set {
self[TestClient.self] = newValue
}
}
}
```
> [!WARNING]
> The following DependencyValues extension are not generated by macro
> ```Swift
> public extension DependencyValues {
> var testClient: TestClient {
> get { self[TestClient.self] }
> set { self[TestClient.self] = newValue }
> }
> }
> ```

## Installation
This library can only be installed from swift package manager.
Expand Down
3 changes: 3 additions & 0 deletions Sources/DependenciesMacro/DependenciesMacro.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,6 @@ public macro PublicInit() = #externalMacro(module: "PublicInitMacroPlugin", type

@attached(extension, conformances: TestDependencyKey, names: named(testValue))
public macro Dependencies() = #externalMacro(module: "DependenciesMacroPlugin", type: "DependenciesMacro")

@attached(member, names: arbitrary)
public macro DependencyValue<T: TestDependencyKey>(_ type: T.Type) = #externalMacro(module: "DependenciesMacroPlugin", type: "DependencyValuesMacro")
5 changes: 1 addition & 4 deletions Sources/DependenciesMacroClient/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,6 @@ public struct TestClient {
public var request: @Sendable (_ request: String) -> Void
}

@DependencyValue(TestClient.self)
public extension DependencyValues {
var testClient: TestClient {
get { self[TestClient.self] }
set { self[TestClient.self] = newValue }
}
}
9 changes: 1 addition & 8 deletions Sources/DependenciesMacroPlugin/DependenciesMacro.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@ import SwiftSyntax
import SwiftSyntaxBuilder
import SwiftSyntaxMacros

public struct DependenciesMacro {

}
public struct DependenciesMacro {}

extension DependenciesMacro: ExtensionMacro {
public static func expansion(
Expand Down Expand Up @@ -42,8 +40,3 @@ extension DependenciesMacro: ExtensionMacro {
]
}
}
extension String {
func initialLowerCased() -> String {
return self.prefix(1).lowercased() + self.dropFirst()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ extension DependenciesMacroDiagnostic: DiagnosticMessage {
public var message: String {
switch self {
case .notStruct:
"PublicInit Macro can only be applied to struct."
"Dependencies Macro can only be applied to struct."
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import SwiftSyntaxMacros
@main
struct DependenciesMacroPlugin: CompilerPlugin {
let providingMacros: [Macro.Type] = [
DependenciesMacro.self
DependenciesMacro.self,
DependencyValuesMacro.self
]
}
110 changes: 110 additions & 0 deletions Sources/DependenciesMacroPlugin/DependencyValueMacroDiagnostic.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import SwiftSyntax
import SwiftSyntaxMacros
import SwiftDiagnostics

public enum DependencyValuesMacroDiagnostic {
case notExtension
case notDependencyValues
case invalidArgument
}

extension DependencyValuesMacroDiagnostic: DiagnosticMessage {
func diagnose(at node: some SyntaxProtocol) -> Diagnostic {
Diagnostic(node: Syntax(node), message: self)
}

public var message: String {
switch self {
case .notExtension:
"DependencyValue Macro can only be applied to extension."

case .invalidArgument:
"Invalid argument."

case .notDependencyValues:
"DependencyValue Macro can only be applied to extension of DependencyValues"
}
}

public var severity: DiagnosticSeverity { .error }

public var diagnosticID: MessageID {
switch self {
case .notExtension:
MessageID(domain: "DependencyValuesMacroDiagnostic", id: "notExtension")

case .invalidArgument:
MessageID(domain: "DependencyValuesMacroDiagnostic", id: "invalidArgument")

case .notDependencyValues:
MessageID(domain: "DependencyValuesMacroDiagnostic", id: "invalidArgument")
}
}
}

public extension DependencyValuesMacro {
static func decodeExpansion(
of syntax: AttributeSyntax,
attachedTo declaration: some DeclGroupSyntax,
in context: some MacroExpansionContext
) throws -> (decl: ExtensionDeclSyntax, type: String) {
guard let extensionDecl = declaration.as(ExtensionDeclSyntax.self) else {
if let actorDecl = declaration.as(ActorDeclSyntax.self) {
throw DiagnosticsError(
diagnostics: [
DependencyValuesMacroDiagnostic.notExtension.diagnose(at: actorDecl.actorKeyword)
]
)
}
else if let classDecl = declaration.as(ClassDeclSyntax.self) {
throw DiagnosticsError(
diagnostics: [
DependencyValuesMacroDiagnostic.notExtension.diagnose(at: classDecl.classKeyword)
]
)
}
else if let enumDecl = declaration.as(EnumDeclSyntax.self) {
throw DiagnosticsError(
diagnostics: [
DependencyValuesMacroDiagnostic.notExtension.diagnose(at: enumDecl.enumKeyword)
]
)
}
else if let structDecl = declaration.as(StructDeclSyntax.self) {
throw DiagnosticsError(
diagnostics: [
DependencyValuesMacroDiagnostic.notExtension.diagnose(at: structDecl.structKeyword)
]
)
}
else {
throw DiagnosticsError(
diagnostics: [
DependencyValuesMacroDiagnostic.notExtension.diagnose(at: declaration)
]
)
}
}

guard extensionDecl.extendedType.as(IdentifierTypeSyntax.self)?.name.text == "DependencyValues" else {
throw DiagnosticsError(
diagnostics: [
DependencyValuesMacroDiagnostic.notDependencyValues.diagnose(at: extensionDecl.extendedType)
]
)
}

guard case .argumentList(let arguments) = syntax.arguments,
let type = arguments.first?.expression.as(MemberAccessExprSyntax.self)?.base?.as(DeclReferenceExprSyntax.self)?.baseName.text,
arguments.count == 1
else {
throw DiagnosticsError(
diagnostics: [
DependencyValuesMacroDiagnostic.invalidArgument.diagnose(at: declaration)
]
)
}

return (extensionDecl, type)
}
}
38 changes: 38 additions & 0 deletions Sources/DependenciesMacroPlugin/DependencyValuesMacro.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import SwiftSyntax
import SwiftSyntaxBuilder
import SwiftSyntaxMacros
import SwiftDiagnostics
import SwiftCompilerPluginMessageHandling

public struct DependencyValuesMacro: MemberMacro {
public static func expansion(
of node: AttributeSyntax,
providingMembersOf declaration: some DeclGroupSyntax,
in context: some MacroExpansionContext
) throws -> [DeclSyntax] {
let (_, typeName) = try decodeExpansion(
of: node,
attachedTo: declaration,
in: context
)

let variableName = typeName.initialLowerCased()

return [
DeclSyntax(
"""
var \(raw: variableName): \(raw: typeName) {
get { self[\(raw: typeName).self] }
set { self[\(raw: typeName).self] = newValue }
}
"""
)
]
}
}

extension String {
func initialLowerCased() -> String {
return prefix(1).lowercased() + dropFirst()
}
}
6 changes: 3 additions & 3 deletions Tests/DependenciesMacroTests/DependenciesMacroTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ final class DependenciesMacroTests: XCTestCase {
@Dependencies
class Test {}
┬────
╰─ 🛑 PublicInit Macro can only be applied to struct.
╰─ 🛑 Dependencies Macro can only be applied to struct.
"""
}
assertMacro {
Expand All @@ -36,7 +36,7 @@ final class DependenciesMacroTests: XCTestCase {
@Dependencies
enum Test {}
┬───
╰─ 🛑 PublicInit Macro can only be applied to struct.
╰─ 🛑 Dependencies Macro can only be applied to struct.
"""
}
assertMacro {
Expand All @@ -49,7 +49,7 @@ final class DependenciesMacroTests: XCTestCase {
@Dependencies
actor Test {}
┬────
╰─ 🛑 PublicInit Macro can only be applied to struct.
╰─ 🛑 Dependencies Macro can only be applied to struct.
"""
}
}
Expand Down
108 changes: 108 additions & 0 deletions Tests/DependenciesMacroTests/DependencyValueMacroTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import DependenciesMacro
import DependenciesMacroPlugin
import MacroTesting
import XCTest

final class DependencyValueMacroTests: XCTestCase {
override func invokeTest() {
withMacroTesting(
macros: ["DependencyValue": DependencyValuesMacro.self]
) {
super.invokeTest()
}
}

func testDiagnostic() {
assertMacro {
"""
@DependencyValue(TestClient.self)
public struct DependencyValues {}
"""
} matches: {
"""
@DependencyValue(TestClient.self)
public struct DependencyValues {}
┬─────
╰─ 🛑 DependencyValue Macro can only be applied to extension.
"""
}

assertMacro {
"""
@DependencyValue(TestClient.self)
public class DependencyValues {}
"""
} matches: {
"""
@DependencyValue(TestClient.self)
public class DependencyValues {}
┬────
╰─ 🛑 DependencyValue Macro can only be applied to extension.
"""
}

assertMacro {
"""
@DependencyValue(TestClient.self)
public actor DependencyValues {}
"""
} matches: {
"""
@DependencyValue(TestClient.self)
public actor DependencyValues {}
┬────
╰─ 🛑 DependencyValue Macro can only be applied to extension.
"""
}

assertMacro {
"""
@DependencyValue(TestClient.self)
public enum DependencyValues {}
"""
} matches: {
"""
@DependencyValue(TestClient.self)
public enum DependencyValues {}
┬───
╰─ 🛑 DependencyValue Macro can only be applied to extension.
"""
}

assertMacro {
"""
@DependencyValue(TestClient.self)
public extension Test {}
"""
} matches: {
"""
@DependencyValue(TestClient.self)
public extension Test {}
┬───
╰─ 🛑 DependencyValue Macro can only be applied to extension of DependencyValues
"""
}
}

func testMacro() {
assertMacro {
"""
@DependencyValue(TestClient.self)
public extension DependencyValues {}
"""
} matches: {
"""
public extension DependencyValues {
var testClient: TestClient {
get {
self [TestClient.self]
}
set {
self [TestClient.self] = newValue
}
}}
"""
}
}
}

0 comments on commit 5367bac

Please sign in to comment.