├── .github └── FUNDING.yml ├── .gitignore ├── LICENSE ├── Package.resolved ├── Package.swift ├── README.md ├── Sources ├── Client │ └── main.swift ├── Macros │ ├── Access.swift │ ├── AddAssociatedValueVariable.swift │ ├── AddInit.swift │ ├── AddPublisher.swift │ ├── BuildDate.swift │ ├── BuildURL.swift │ ├── BuildURLRequest.swift │ ├── ConformToEquatable.swift │ ├── ConformToHashable.swift │ ├── FormatDate.swift │ ├── FormatDateComponents.swift │ ├── FormatDateInterval.swift │ ├── JSONEncoderrDecoder.swift │ ├── MacroDiagnostics.swift │ ├── MacroPlugin.swift │ ├── Mock.swift │ ├── Mockable.swift │ ├── PostNotification.swift │ └── Singleton.swift └── SwiftMacros │ ├── AccessContentType.swift │ ├── DateBuilder.swift │ ├── SwiftMacros.docc │ ├── Resources │ │ └── documentation-art │ │ │ └── macro-icon.png │ └── SwiftMacros.md │ ├── SwiftMacros.swift │ ├── URLBuilder.swift │ └── URLRequestBuilder.swift └── Tests └── MacroTests ├── AccessTests.swift ├── AddAssociatedValueVariableTests.swift ├── AddInitTests.swift ├── AddPublisherTests.swift ├── BuildDateTests.swift ├── BuildURLRequestTests.swift ├── BuildURLTests.swift ├── ConformToEqutableTests.swift ├── ConformToHashableTests.swift ├── FormatDateComponentsTests.swift ├── FormatDateIntervalTests.swift ├── FormatDateTests.swift ├── JSONEncoderDecoderTests.swift ├── MockTests.swift ├── NotificationTests.swift └── SingletonTests.swift /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [ShenghaiWang] 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | xcuserdata/ 5 | DerivedData/ 6 | .swiftpm/configuration/registries.json 7 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata 8 | .netrc 9 | .swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Tim Wang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "originHash" : "a882106e67ac01286c6ad774a7a05817d03e5bee8d8727c3138597a6ccb203b3", 3 | "pins" : [ 4 | { 5 | "identity" : "swift-docc-plugin", 6 | "kind" : "remoteSourceControl", 7 | "location" : "https://github.com/swiftlang/swift-docc-plugin.git", 8 | "state" : { 9 | "revision" : "85e4bb4e1cd62cec64a4b8e769dcefdf0c5b9d64", 10 | "version" : "1.4.3" 11 | } 12 | }, 13 | { 14 | "identity" : "swift-docc-symbolkit", 15 | "kind" : "remoteSourceControl", 16 | "location" : "https://github.com/swiftlang/swift-docc-symbolkit", 17 | "state" : { 18 | "revision" : "b45d1f2ed151d057b54504d653e0da5552844e34", 19 | "version" : "1.0.0" 20 | } 21 | }, 22 | { 23 | "identity" : "swift-syntax", 24 | "kind" : "remoteSourceControl", 25 | "location" : "https://github.com/swiftlang/swift-syntax.git", 26 | "state" : { 27 | "revision" : "0687f71944021d616d34d922343dcef086855920", 28 | "version" : "600.0.1" 29 | } 30 | }, 31 | { 32 | "identity" : "swiftkeychain", 33 | "kind" : "remoteSourceControl", 34 | "location" : "https://github.com/ShenghaiWang/SwiftKeychain.git", 35 | "state" : { 36 | "revision" : "34be6816cab951f20d3ec78d8cb598043af4b3f0", 37 | "version" : "0.2.0" 38 | } 39 | } 40 | ], 41 | "version" : 3 42 | } 43 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 6.0 2 | 3 | import PackageDescription 4 | import CompilerPluginSupport 5 | 6 | let package = Package( 7 | name: "SwiftMacros", 8 | platforms: [.macOS(.v10_15), .iOS(.v13), .tvOS(.v13), .watchOS(.v6), .macCatalyst(.v13)], 9 | products: [ 10 | .library( 11 | name: "SwiftMacros", 12 | targets: ["SwiftMacros"] 13 | ), 14 | ], 15 | dependencies: [ 16 | .package(url: "https://github.com/swiftlang/swift-syntax.git", from: "600.0.1"), 17 | .package(url: "https://github.com/swiftlang/swift-docc-plugin.git", from: "1.4.3"), 18 | .package(url: "https://github.com/ShenghaiWang/SwiftKeychain.git", from: "0.2.0") 19 | ], 20 | targets: [ 21 | .macro( 22 | name: "Macros", 23 | dependencies: [ 24 | .product(name: "SwiftSyntaxMacros", package: "swift-syntax"), 25 | .product(name: "SwiftCompilerPlugin", package: "swift-syntax"), 26 | ] 27 | ), 28 | .target(name: "SwiftMacros", dependencies: ["Macros"]), 29 | 30 | .executableTarget(name: "Client", dependencies: ["SwiftMacros", "SwiftKeychain"]), 31 | 32 | .testTarget( 33 | name: "MacroTests", 34 | dependencies: [ 35 | "Macros", 36 | .product(name: "SwiftSyntaxMacrosTestSupport", package: "swift-syntax"), 37 | ] 38 | ), 39 | ] 40 | ) 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SwiftMacros 2 | 3 | A practical collection of Swift Macros that help code correctly and swiftly. 4 | 5 | ## Install 6 | 7 | .package(url: "https://github.com/ShenghaiWang/SwiftMacros.git", from: "2.0.0") 8 | 9 | ## Macros [API Doc](https://m.timwang.au/swiftmacros/documentation/swiftmacros/) 10 | 11 | | Macro | Description | 12 | |------------|------------------------------------------------------------| 13 | | @Access |An easy API to access UserDefaults, Keychain, NSCache and NSMapTable. | 14 | | |
struct TestAccess {
static let cache = NSCache()

// Please make sure the generic type is the same as the type of the variable
// Without defalut value
@Access(.userDefaults())
var isPaidUser: Bool?

// With default value
@Access< Bool>(.userDefaults())
var isPaidUser2: Bool = false

@Access(.nsCache(TestAccess.cache))
var hasPaid: NSObject?

@Access(.nsMapTable(TestAccess.mapTable))
var hasPaid2: NSObject?

@Access(.keychain)
var keychainValue: TestStruct?
}
| 15 | |@AddAssociatedValueVariable|Add variables to retrieve the associated values| 16 | | |
@AddAssociatedValueVariable
enum MyEnum {
case first
case second(Int)
case third(String, Int)
case forth(a: String, b: Int), forth2(String, Int)
case fifth(() -> Void)
}
| 17 | | @AddInit |Generate initialiser for the class/struct/actor. The variables with optional types will have nil as default values. Using `withMock: true` if want to generate mock data.
For custmoised data type, it will use `Type.mock`. In case there is no this value, need to define this yourself or use `@Mock` or `@AddInit(withMock: true)` to generate this variable. | 18 | | @AddPublisher |Generate a Combine publisher to a Combine subject in order to avoid overexposing subject variable | 19 | | |
@AddPublisher
private let mySubject = PassthroughSubject()
| 20 | | |
@AddInit
struct InitStruct {
let a: Int
let b: Int?
let c: (Int?) -> Void
let d: ((Int?) -> Void)?
}
@AddInit(withMock: true)
class AStruct {
let a: Float
}
| 21 | | #buildDate |Build a Date from components
This solution addes in a resultBulder `DateBuilder`, which can be used directly if prefer builder pattern.
Note: this is for a simpler API. Please use it with caution in places that require efficiency.| 22 | | |
let date = #buildDate(DateString("03/05/2003", dateFormat: "MM/dd/yyyy"),
Date(),
Month(10),
Year(1909),
YearForWeekOfYear(2025))
| 23 | | #buildURL |Build a url from components.
This solution addes in a resultBulder `URLBuilder`, which can be used directly if prefer builder pattern. | 24 | | |
let url = #buildURL("http://google.com",
URLScheme.https,
URLQueryItems([.init(name: "q1", value: "q1v"), .init(name: "q2", value: "q2v")]))
let url2 = buildURL {
"http://google.com"
URLScheme.https
URLQueryItems([.init(name: "q1", value: "q1v"), .init(name: "q2", value: "q2v")])
}
| 25 | | #buildURLRequest |Build a URLRequest from components.
This solution addes in a resultBulder `URLRequestBuilder`, which can be used directly if prefer builder pattern. | 26 | | |
let urlrequest = #buildURLRequest(url!, RequestTimeOutInterval(100))
let urlRequest2 = buildURLRequest {
url!
RequestTimeOutInterval(100)
}
| 27 | | @ConformToEquatable|Add Equtable conformance to a class type
Use it with caution per https://github.com/apple/swift-evolution/blob/main/proposals/0185-synthesize-equatable-hashable.md#synthesis-for-class-types-and-tuples| 28 | | |
@AddInit
@ConformToEquatable
class AClass {
let a: Int?
let b: () -> Void
}
| 29 | | @ConformToHashable|Add Hashable conformance to a class type
Use it with caution per https://github.com/apple/swift-evolution/blob/main/proposals/0185-synthesize-equatable-hashable.md#synthesis-for-class-types-and-tuples| 30 | | |
@AddInit
@ConformToHashable
class AClass {
let a: Int?
let b: () -> Void
}
| 31 | | #encode |Encode an Encodable to data using JSONEncoder | 32 | | |
#encode(value)
| 33 | | #decode |Decode a Decodable to a typed value using JSONDecoder | 34 | | |
#decode(TestStruct.self, from: data)
| 35 | | #formatDate |Format date to a string | 36 | | |
#formatDate(Date(), dateStyle: .full)
| 37 | | #formatDateComponents |Format date differences/timeinterval/date components to a string | 38 | | |
#formatDateComponents(from: Date(), to: Date(), allowedUnits: [.day, .hour, .minute, .second])
#formatDateComponents(fromInterval: 100, allowedUnits: [.day, .hour, .minute, .second])
#formatDateComponents(fromComponents: DateComponents(hour: 10), allowedUnits: [.day, .hour, .minute, .second])
| 39 | | #formatDateInterval |Format two dates into interval string | 40 | | |
#formatDateInterval(from: Date(), to: Date(), dateStyle: .short)
| 41 | | @Mock |Generate a static variable `mock` using the attached initializer.
For custmoised data type, it will use `Type.mock`. In case there is no this value, need to define this yourself or use `@Mock` or `@AddInit(withMock: true)` to generate this variable. | 42 | | |
class AStruct {
let a: Float
@Mock(type: AStruct.self)
init(a: Float) {
self.a = a
}
}
| 43 | | #postNotification | An easy way to post notifications | 44 | | |
#postNotification(.NSCalendarDayChanged)
| 45 | | @Singleton |Generate Swift singleton code for struct and class types | 46 | | |
@Singleton
struct A {}
| 47 | 48 | ## To be added 49 | 50 | Please feel free to add the macros you want here. All PRs(with or without implementations) are welcome. 51 | 52 | | Macro | Description | 53 | |------------|------------------------------------------------------------| 54 | | @ | Your new macro ideas | 55 | -------------------------------------------------------------------------------- /Sources/Client/main.swift: -------------------------------------------------------------------------------- 1 | import SwiftMacros 2 | import Foundation 3 | import Combine 4 | import SwiftUI 5 | import SwiftKeychain 6 | 7 | @Singleton 8 | struct TestSingletonMacro { 9 | let name = "Tim Wang" 10 | } 11 | 12 | @MainActor 13 | @Singleton 14 | public struct TestPublicSingletonMacro { 15 | let name = "Tim Wang" 16 | } 17 | 18 | @MainActor 19 | @Singleton 20 | class TestPublicSingletonNSOjbectMacro: NSObject { 21 | let name = "Tim Wang" 22 | } 23 | 24 | struct TestStruct: Codable { 25 | var name = "Tim Wang" 26 | } 27 | 28 | @Singleton 29 | struct A { 30 | 31 | } 32 | 33 | let data = try #encode(TestStruct(), dateEncodingStrategy: .iso8601, dataEncodingStrategy: .base64) 34 | let value = try #decode(TestStruct.self, from: data, dateDecodingStrategy: .deferredToDate) 35 | 36 | struct MyType { 37 | @AddPublisher 38 | private let mySubject = PassthroughSubject() 39 | } 40 | 41 | _ = MyType().mySubjectPublisher.sink { _ in 42 | 43 | } 44 | 45 | struct TestNotification { 46 | func post() { 47 | #postNotification(.NSCalendarDayChanged, object: NSObject(), userInfo: ["value": 0]) 48 | } 49 | } 50 | 51 | @AddInit 52 | struct InitStruct { 53 | let a: Int 54 | let b: Int? 55 | let c: (Int?) -> Void 56 | let d: ((Int?) -> Void)? 57 | } 58 | 59 | @AddInit(withMock: true) 60 | actor InitActor { 61 | let a: Int 62 | let b: Int? 63 | let c: (Int?) -> Void 64 | let d: ((Int?) -> Void)? 65 | } 66 | 67 | @AddAssociatedValueVariable 68 | enum MyEnum { 69 | case first 70 | case second(Int) 71 | case third(String, Int) 72 | case forth(a: String, b: Int), forth2(String, Int) 73 | case seventh(() -> Void) 74 | } 75 | 76 | @AddAssociatedValueVariable 77 | enum TestEnum { 78 | case test(Int) 79 | } 80 | 81 | assert(MyEnum.first.forth2Value == nil) 82 | 83 | let url = #buildURL("http://google.com", 84 | URLScheme.https, 85 | URLQueryItems([.init(name: "q1", value: "q1v"), .init(name: "q2", value: "q2v")])) 86 | 87 | let url2 = buildURL { 88 | "http://google.com" 89 | URLScheme.https 90 | URLQueryItems([.init(name: "q1", value: "q1v"), .init(name: "q2", value: "q2v")]) 91 | } 92 | 93 | let urlrequest = #buildURLRequest(url!, RequestTimeOutInterval(100)) 94 | let urlRequest2 = buildURLRequest { 95 | url! 96 | RequestTimeOutInterval(100) 97 | } 98 | 99 | @MainActor 100 | class AClassForMock { 101 | let a: Float 102 | @Mock(type: AClassForMock.self) 103 | init(a: Float) { 104 | self.a = a 105 | } 106 | } 107 | 108 | @MainActor 109 | @AddInit(withMock: true) 110 | class BClassForMock { 111 | let a: Float 112 | let b: CStruct 113 | } 114 | 115 | struct CStruct { 116 | static let mock = CStruct() 117 | } 118 | print(AClassForMock.mock.a) 119 | print(BClassForMock.mock.a) 120 | 121 | let date = #buildDate(DateString("03/05/2003", dateFormat: "MM/dd/yyyy"), 122 | Date(), 123 | Month(10), 124 | Year(1909), 125 | YearForWeekOfYear(2025)) 126 | let string = #formatDate(Date(), dateStyle: .full) 127 | 128 | let intervalString = #formatDateInterval(from: Date(), to: Date(timeIntervalSinceNow: 200)) 129 | let componentsString = #formatDateComponents(from: Date(), to: Date(timeIntervalSinceNow: 200), allowedUnits: [.day, .hour, .minute, .second]) 130 | 131 | @MainActor 132 | struct TestAccess { 133 | static let cache = NSCache() 134 | static let mapTable = NSMapTable(keyOptions: .copyIn, valueOptions: .weakMemory) 135 | 136 | // without defalut value 137 | // make sure the generic type is the same as the type of the variable 138 | @Access(.userDefaults()) 139 | var isPaidUser: Bool? 140 | 141 | // with default value 142 | @Access(.userDefaults()) 143 | var isPaidUser2: Bool = false 144 | 145 | @Access(.nsCache(TestAccess.cache)) 146 | var hasPaid: NSObject? 147 | 148 | @Access(.nsMapTable(TestAccess.mapTable)) 149 | var hasPaid2: NSObject? 150 | 151 | @Access(.keychain) 152 | var keychainValue: TestStruct? 153 | } 154 | 155 | @AddInit 156 | @ConformToEquatable 157 | @ConformToHashable 158 | class AClass { 159 | let a: Int? 160 | let b: () -> Void 161 | } 162 | -------------------------------------------------------------------------------- /Sources/Macros/Access.swift: -------------------------------------------------------------------------------- 1 | import SwiftSyntax 2 | import SwiftSyntaxBuilder 3 | import SwiftSyntaxMacros 4 | import Foundation 5 | 6 | public struct Access: AccessorMacro { 7 | public static func expansion(of node: AttributeSyntax, 9 | providingAccessorsOf declaration: Declaration, 10 | in context: Context) throws -> [AccessorDeclSyntax] { 11 | guard let firstArg = node.arguments?.as(LabeledExprListSyntax.self)?.first, 12 | let type = firstArg.type else { 13 | throw MacroDiagnostics.errorMacroUsage(message: "Must specify a content type") 14 | } 15 | if type == "userDefaults", 16 | let dataType = node.attributeName.as(IdentifierTypeSyntax.self)?.type { 17 | return processUserDefaults(for: declaration, 18 | userDefaults: firstArg.userDefaults, 19 | type: "\(dataType)") 20 | } else if ["nsCache", "nsMapTable"].contains(type), 21 | let object = firstArg.object, 22 | let dataType = node.attributeName.as(IdentifierTypeSyntax.self)?.type { 23 | let isOptionalType = node.attributeName.as(IdentifierTypeSyntax.self)?.genericArgumentClause?.arguments 24 | .first?.argument.is(OptionalTypeSyntax.self) ?? false 25 | return processNSCacheAndNSMapTable(for: declaration, 26 | object: object, 27 | type: "\(dataType)", 28 | isOptionalType: isOptionalType) 29 | } else if type == "keychain" { 30 | return processKeychain(for: declaration) 31 | } 32 | 33 | return [] 34 | } 35 | 36 | private static func processKeychain(for declaration: DeclSyntaxProtocol) -> [AccessorDeclSyntax] { 37 | guard let binding = declaration.as(VariableDeclSyntax.self)?.bindings.first, 38 | let identifier = binding.pattern.as(IdentifierPatternSyntax.self)?.identifier.text, 39 | binding.accessorBlock == nil else { return [] } 40 | let getAccessor: AccessorDeclSyntax = 41 | """ 42 | get { 43 | try? SwiftKeychain.search(key: "AccessKey_\(raw: identifier)") 44 | } 45 | """ 46 | 47 | let setAccessor: AccessorDeclSyntax = 48 | """ 49 | set { 50 | if let value = newValue { 51 | SwiftKeychain.delete(key: "AccessKey_\(raw: identifier)") 52 | try? SwiftKeychain.add(value: value, for: "AccessKey_\(raw: identifier)") 53 | } else { 54 | SwiftKeychain.delete(key: "AccessKey_\(raw: identifier)") 55 | } 56 | } 57 | """ 58 | return [getAccessor, setAccessor] 59 | } 60 | 61 | private static func processUserDefaults(for declaration: DeclSyntaxProtocol, 62 | userDefaults: ExprSyntax, 63 | type: String) -> [AccessorDeclSyntax] { 64 | guard let binding = declaration.as(VariableDeclSyntax.self)?.bindings.first, 65 | let identifier = binding.pattern.as(IdentifierPatternSyntax.self)?.identifier.text, 66 | binding.accessorBlock == nil else { return [] } 67 | var defaultValue = "" 68 | if let value = binding.initializer?.value { 69 | defaultValue = " ?? \(value)" 70 | } 71 | let getAccessor: AccessorDeclSyntax = 72 | """ 73 | get { 74 | (\(userDefaults).object(forKey: "AccessKey_\(raw: identifier)") as? \(raw: type))\(raw: defaultValue) 75 | } 76 | """ 77 | 78 | let setAccessor: AccessorDeclSyntax = 79 | """ 80 | set { 81 | \(userDefaults).set(newValue, forKey: "AccessKey_\(raw: identifier)") 82 | } 83 | """ 84 | return [getAccessor, setAccessor] 85 | } 86 | 87 | private static func processNSCacheAndNSMapTable(for declaration: DeclSyntaxProtocol, 88 | object: ExprSyntax, 89 | type: String, 90 | isOptionalType: Bool) -> [AccessorDeclSyntax] { 91 | guard let binding = declaration.as(VariableDeclSyntax.self)?.bindings.first, 92 | let identifier = binding.pattern.as(IdentifierPatternSyntax.self)?.identifier.text, 93 | binding.accessorBlock == nil else { return [] } 94 | var defaultValue = "" 95 | if let value = binding.initializer?.value { 96 | defaultValue = " ?? \(value)" 97 | } 98 | let getAccessor: AccessorDeclSyntax = 99 | """ 100 | get { 101 | (\(object).object(forKey: "AccessKey_\(raw: identifier)") as? \(raw: type))\(raw: defaultValue) 102 | } 103 | """ 104 | let setAccessor: AccessorDeclSyntax 105 | if isOptionalType { 106 | setAccessor = 107 | """ 108 | set { 109 | if let value = newValue { 110 | \(object).setObject(value, forKey: "AccessKey_\(raw: identifier)") 111 | } else { 112 | \(object).removeObject(forKey: "AccessKey_\(raw: identifier)") 113 | } 114 | } 115 | """ 116 | } else { 117 | setAccessor = 118 | """ 119 | set { 120 | \(object).setObject(newValue, forKey: "AccessKey_\(raw: identifier)") 121 | } 122 | """ 123 | } 124 | return [getAccessor, setAccessor] 125 | } 126 | } 127 | 128 | private extension LabeledExprSyntax { 129 | var type: String? { 130 | expression.as(MemberAccessExprSyntax.self)?.declName.baseName.text 131 | ?? expression.as(FunctionCallExprSyntax.self)?.calledExpression.as(MemberAccessExprSyntax.self)?.declName.baseName.text 132 | } 133 | } 134 | 135 | private extension LabeledExprSyntax { 136 | var userDefaults: ExprSyntax { 137 | if expression.is(MemberAccessExprSyntax.self) { 138 | return "UserDefaults.standard" 139 | } 140 | if let memeberAceess = expression.as(FunctionCallExprSyntax.self)?.arguments.first?.expression.as(MemberAccessExprSyntax.self) { 141 | return "UserDefaults.\(raw: memeberAceess.declName.baseName.text)" 142 | } else { 143 | return expression.as(FunctionCallExprSyntax.self)?.arguments.first?.expression ?? "UserDefaults.standard" 144 | } 145 | } 146 | 147 | var object: ExprSyntax? { 148 | expression.as(FunctionCallExprSyntax.self)?.arguments.first?.expression 149 | } 150 | } 151 | 152 | private extension IdentifierTypeSyntax { 153 | var type: SyntaxProtocol? { 154 | genericArgumentClause?.arguments.first?.argument.as(OptionalTypeSyntax.self)?.wrappedType 155 | ?? genericArgumentClause?.arguments.first 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /Sources/Macros/AddAssociatedValueVariable.swift: -------------------------------------------------------------------------------- 1 | import SwiftSyntax 2 | import SwiftSyntaxBuilder 3 | import SwiftSyntaxMacros 4 | 5 | public struct AddAssociatedValueVariable: MemberMacro { 6 | public static func expansion(of node: AttributeSyntax, 8 | providingMembersOf declaration: Declaration, 9 | in context: Context) throws -> [DeclSyntax] { 10 | guard let members = declaration.as(EnumDeclSyntax.self)? 11 | .memberBlock.members.compactMap({ $0.decl.as(EnumCaseDeclSyntax.self)?.elements }) else { 12 | throw MacroDiagnostics.errorMacroUsage(message: "Can only be applied to an Enum type") 13 | } 14 | return try members.flatMap { list-> [DeclSyntax] in 15 | try list.compactMap { element -> DeclSyntax? in 16 | guard let associatedValue = element.parameterClause else { return nil } 17 | let typeValue: String 18 | if associatedValue.parameters.isOneSimpleTypeIdentifierSyntax { 19 | typeValue = "\(associatedValue.parameters.first ?? "")" 20 | } else { 21 | typeValue = "\(associatedValue)" 22 | } 23 | let varSyntax = try VariableDeclSyntax("\(declaration.modifiers )var \(element.name)Value: \(raw: typeValue)?") { 24 | try IfExprSyntax( 25 | "if case let .\(element.name)(\(raw: associatedValue.parameters.toVariableNames)) = self", 26 | bodyBuilder: { 27 | if associatedValue.parameters.count == 1 { 28 | StmtSyntax("return \(raw: associatedValue.parameters.toVariableNames)") 29 | } else { 30 | StmtSyntax("return (\(raw: associatedValue.parameters.toVariableNames))") 31 | } 32 | }) 33 | StmtSyntax(#"return nil"#) 34 | } 35 | return DeclSyntax(varSyntax) 36 | } 37 | } 38 | } 39 | } 40 | 41 | extension EnumCaseParameterListSyntax { 42 | var toVariableNames: String { 43 | (0..(of node: AttributeSyntax, 9 | providingMembersOf declaration: Declaration, 10 | in context: Context) throws -> [DeclSyntax] { 11 | guard [SwiftSyntax.SyntaxKind.classDecl, .structDecl, .actorDecl].contains(declaration.kind) else { 12 | throw MacroDiagnostics.errorMacroUsage(message: "Can only be applied to a struct, class or actor") 13 | } 14 | let (parameters, body) = initBodyAndParams(for: declaration) 15 | let bodyExpr: ExprSyntax = "\(raw: body.joined(separator: "\n"))" 16 | var parametersLiteral = "init(\(parameters.joined(separator: ", ")))" 17 | parametersLiteral = "\(declaration.modifiers)\(parametersLiteral)" 18 | let initDecl = try InitializerDeclSyntax(SyntaxNodeString(stringLiteral: parametersLiteral), 19 | bodyBuilder: { bodyExpr }) 20 | var result = [DeclSyntax(initDecl)] 21 | if node.argument(for: "withMock")?.as(BooleanLiteralExprSyntax.self)?.literal.tokenKind.keyword == .true { 22 | let randomValue = node.argument(for: "randomMockValue")?.as(BooleanLiteralExprSyntax.self)? 23 | .literal.tokenKind.keyword != .false 24 | result.append(mock(basedOn: declaration, randomValue: randomValue)) 25 | } 26 | return result 27 | } 28 | 29 | private static func initBodyAndParams(for declaration: DeclGroupSyntax) -> (params: [String], body: [String]) { 30 | var parameters: [String] = [] 31 | var body: [String] = [] 32 | declaration.memberBlock.members.forEach { member in 33 | if let patternBinding = member.decl.as(VariableDeclSyntax.self)?.bindings.first, 34 | let identifier = patternBinding.pattern.as(IdentifierPatternSyntax.self)?.identifier, 35 | let type = patternBinding.typeAnnotation?.type, 36 | patternBinding.accessorBlock == nil { 37 | var parameter = "\(identifier): " 38 | if type.is(FunctionTypeSyntax.self) { 39 | parameter += "@escaping " 40 | } 41 | parameter += "\(type)" 42 | if type.is(OptionalTypeSyntax.self) { 43 | parameter += " = nil" 44 | } 45 | parameters.append(parameter) 46 | body.append(" self.\(identifier) = \(identifier)") 47 | } 48 | } 49 | return (params: parameters, body: body) 50 | } 51 | 52 | private static func mock(basedOn declaration: DeclGroupSyntax, randomValue: Bool) -> DeclSyntax { 53 | let identifier = (declaration as? StructDeclSyntax)?.name.text 54 | ?? (declaration as? ClassDeclSyntax)?.name.text 55 | ?? (declaration as? ActorDeclSyntax)?.name.text ?? "" 56 | let parameters = declaration.memberBlock.members.compactMap { member -> String? in 57 | guard let patternBinding = member.decl.as(VariableDeclSyntax.self)?.bindings.first, 58 | let identifier = patternBinding.pattern.as(IdentifierPatternSyntax.self)?.identifier, 59 | let type = patternBinding.typeAnnotation?.type, 60 | patternBinding.accessorBlock == nil else { return nil } 61 | let mockValue = type.mockValue(randomValue: randomValue) 62 | ?? type.as(OptionalTypeSyntax.self)?.mockValue(randomValue: randomValue) 63 | ?? "nil" 64 | return "\(identifier): \(mockValue)" 65 | } 66 | var varDelcaration: DeclSyntax = "static let mock = \(raw: identifier)(\(raw: parameters.joined(separator: ", ")))" 67 | varDelcaration = "\(declaration.modifiers)\(varDelcaration)" 68 | varDelcaration = "#if DEBUG\n\(varDelcaration)\n#endif" 69 | return varDelcaration 70 | } 71 | } 72 | 73 | extension AttributeSyntax { 74 | func argument(for label: String) -> ExprSyntax? { 75 | arguments?.as(LabeledExprListSyntax.self)?.filter({ $0.label?.text == label }).first?.expression 76 | } 77 | } 78 | 79 | extension IdentifierTypeSyntax { 80 | func mockValue(randomValue: Bool) -> String? { 81 | let mockFunctions: [String: @Sendable (Bool) -> String] = { 82 | let common = [ 83 | "Int": Int.mock(random:), 84 | "Int8": Int8.mock(random:), 85 | "Int32": Int32.mock(random:), 86 | "Int64": Int64.mock(random:), 87 | "UInt": UInt.mock(random:), 88 | "UInt32": UInt32.mock(random:), 89 | "UInt64": UInt64.mock(random:), 90 | "UInt8": UInt8.mock(random:), 91 | "Bool": Bool.mock(random:), 92 | "String": String.mock(random:), 93 | "Double": Double.mock(random:), 94 | "Float": Float.mock(random:), 95 | "Character": Character.mock(random:), 96 | "CharacterSet": CharacterSet.mock(random:), 97 | ] 98 | if #available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) { 99 | return common.merging(["Image": Image.mock(random:)]) { first, _ in first } 100 | } else { 101 | return common 102 | } 103 | }() 104 | let type = self.name.text 105 | if let fun = mockFunctions[type] { 106 | return fun(randomValue) 107 | } else if name.text == "Void" { 108 | return "return" 109 | } else if name.text != "Set" { 110 | return "\(type).mock" 111 | } 112 | return nil 113 | } 114 | } 115 | 116 | extension OptionalTypeSyntax { 117 | func mockValue(randomValue: Bool) -> String? { 118 | if randomValue, 119 | let mockValue = wrappedType.mockValue(randomValue: randomValue) { 120 | return mockValue 121 | } else { 122 | return "nil" 123 | } 124 | } 125 | } 126 | 127 | extension FunctionTypeSyntax { 128 | func mockValue(randomValue: Bool) -> String? { 129 | let args = repeatElement("_", count: max(parameters.count, 1)).joined(separator: ", ") 130 | let returnValue = returnClause.type.mockValue(randomValue: randomValue) ?? "" 131 | return "{ \(args) in return \(returnValue) }" 132 | } 133 | } 134 | 135 | extension TypeSyntax { 136 | func mockValue(randomValue: Bool) -> String? { 137 | if let mockValue = self.as(IdentifierTypeSyntax.self)?.mockValue(randomValue: randomValue) { 138 | return mockValue 139 | } else if let type = self.as(DictionaryTypeSyntax.self) { 140 | let mockKeyValue = type.key.mockValue(randomValue: randomValue) ?? "" 141 | let mockValueValue = type.value.mockValue(randomValue: randomValue) ?? "nil" 142 | return "[\(mockKeyValue): \(mockValueValue)]" 143 | } else if let mockValue = self.as(FunctionTypeSyntax.self)?.mockValue(randomValue: randomValue) { 144 | return mockValue 145 | } else if let mockValue = self.as(TupleTypeSyntax.self)?.elements.first?.type 146 | .as(FunctionTypeSyntax.self)?.mockValue(randomValue: randomValue) { 147 | return mockValue 148 | } else if let type = self.as(ArrayTypeSyntax.self)?.element { 149 | return "[" + (type.mockValue(randomValue: randomValue) ?? "nil") + "]" 150 | } else if let type = self.as(IdentifierTypeSyntax.self), 151 | type.name.text == "Set", 152 | let genericType = type.genericArgumentClause?.arguments.first?.argument { 153 | return "[" + (genericType.mockValue(randomValue: randomValue) ?? "nil") + "]" 154 | } 155 | return nil 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /Sources/Macros/AddPublisher.swift: -------------------------------------------------------------------------------- 1 | import SwiftSyntax 2 | import SwiftSyntaxBuilder 3 | import SwiftSyntaxMacros 4 | import Foundation 5 | 6 | public struct AddPublisher: PeerMacro { 7 | public static func expansion(of node: AttributeSyntax, 9 | providingPeersOf declaration: Declaration, 10 | in context: Context) throws -> [DeclSyntax] { 11 | guard let variableDecl = declaration.as(VariableDeclSyntax.self), 12 | variableDecl.modifiers.map({ $0.name.text }).contains("private") else { 13 | throw MacroDiagnostics.errorMacroUsage(message: "Please make the subject private and use the automated generated publisher variable outsite of this type") 14 | } 15 | guard let binding = variableDecl.bindings.first, 16 | let identifier = binding.pattern.as(IdentifierPatternSyntax.self)?.identifier, 17 | let genericArgumentClause = binding.genericArgumentClause, 18 | ["PassthroughSubject", "CurrentValueSubject"].contains(binding.typeName) else { 19 | throw MacroDiagnostics.errorMacroUsage(message: "Can only be applied to a subject(PassthroughSubject/CurrentValueSubject) variable declaration") 20 | } 21 | 22 | let publisher: DeclSyntax = 23 | """ 24 | var \(raw: identifier.text)Publisher: AnyPublisher<\(genericArgumentClause.arguments)> { 25 | \(raw: identifier.text).eraseToAnyPublisher() 26 | } 27 | """ 28 | return [publisher] 29 | } 30 | } 31 | 32 | extension PatternBindingListSyntax.Element { 33 | var genericArgumentClause: GenericArgumentClauseSyntax? { 34 | initializer?.value.as(FunctionCallExprSyntax.self)? 35 | .calledExpression.as(GenericSpecializationExprSyntax.self)? 36 | .genericArgumentClause 37 | ?? typeAnnotation?.type.as(IdentifierTypeSyntax.self)?.genericArgumentClause 38 | } 39 | 40 | var typeName: String? { 41 | initializer?.value.as(FunctionCallExprSyntax.self)?.calledExpression.as(GenericSpecializationExprSyntax.self)?.expression.as(DeclReferenceExprSyntax.self)?.baseName.text 42 | ?? typeAnnotation?.type.as(IdentifierTypeSyntax.self)?.name.text 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Sources/Macros/BuildDate.swift: -------------------------------------------------------------------------------- 1 | import SwiftCompilerPlugin 2 | import SwiftSyntax 3 | import SwiftSyntaxBuilder 4 | import SwiftSyntaxMacros 5 | 6 | public struct BuildDate: ExpressionMacro { 7 | public static func expansion(of node: Node, 9 | in context: Context) throws -> ExprSyntax { 10 | guard node.arguments.count > 0 else { 11 | throw MacroDiagnostics.errorMacroUsage(message: "Must specify arguments") 12 | } 13 | 14 | let arguments = node.arguments.map { 15 | "\($0.expression)".trimmingCharacters(in: .whitespacesAndNewlines) 16 | } 17 | 18 | let expr: ExprSyntax = 19 | """ 20 | buildDate { 21 | \(raw: arguments.joined(separator: "\n")) 22 | } 23 | """ 24 | return ExprSyntax(expr) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Sources/Macros/BuildURL.swift: -------------------------------------------------------------------------------- 1 | import SwiftCompilerPlugin 2 | import SwiftSyntax 3 | import SwiftSyntaxBuilder 4 | import SwiftSyntaxMacros 5 | 6 | public struct BuildURL: ExpressionMacro { 7 | public static func expansion(of node: Node, 9 | in context: Context) throws -> ExprSyntax { 10 | guard node.arguments.count > 0 else { 11 | throw MacroDiagnostics.errorMacroUsage(message: "Must specify arguments") 12 | } 13 | 14 | let arguments = node.arguments.map { 15 | "\($0.expression)".trimmingCharacters(in: .whitespacesAndNewlines) 16 | } 17 | 18 | let expr: ExprSyntax = 19 | """ 20 | buildURL { 21 | \(raw: arguments.joined(separator: "\n")) 22 | } 23 | """ 24 | return ExprSyntax(expr) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Sources/Macros/BuildURLRequest.swift: -------------------------------------------------------------------------------- 1 | import SwiftCompilerPlugin 2 | import SwiftSyntax 3 | import SwiftSyntaxBuilder 4 | import SwiftSyntaxMacros 5 | 6 | public struct BuildURLRequest: ExpressionMacro { 7 | public static func expansion(of node: Node, 9 | in context: Context) throws -> ExprSyntax { 10 | guard node.arguments.count > 0 else { 11 | throw MacroDiagnostics.errorMacroUsage(message: "Must specify arguments") 12 | } 13 | 14 | let arguments = node.arguments.map { 15 | "\($0.expression)".trimmingCharacters(in: .whitespacesAndNewlines) 16 | } 17 | 18 | let expr: ExprSyntax = 19 | """ 20 | buildURLRequest { 21 | \(raw: arguments.joined(separator: "\n")) 22 | } 23 | """ 24 | return ExprSyntax(expr) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Sources/Macros/ConformToEquatable.swift: -------------------------------------------------------------------------------- 1 | import SwiftSyntax 2 | import SwiftSyntaxBuilder 3 | import SwiftSyntaxMacros 4 | import Foundation 5 | 6 | public struct ConformToEquatable: ExtensionMacro { 7 | public static func expansion(of node: SwiftSyntax.AttributeSyntax, 8 | attachedTo declaration: some SwiftSyntax.DeclGroupSyntax, 9 | providingExtensionsOf type: some SwiftSyntax.TypeSyntaxProtocol, 10 | conformingTo protocols: [SwiftSyntax.TypeSyntax], 11 | in context: some SwiftSyntaxMacros.MacroExpansionContext) throws -> [SwiftSyntax.ExtensionDeclSyntax] { 12 | guard [SwiftSyntax.SyntaxKind.classDecl].contains(declaration.kind) else { 13 | throw MacroDiagnostics.errorMacroUsage(message: "Can only be applied to a class type") 14 | } 15 | let equatableProtocol = InheritanceClauseSyntax(inheritedTypes: InheritedTypeListSyntax( 16 | arrayLiteral: InheritedTypeSyntax(type: TypeSyntax(stringLiteral: "Equatable"))) 17 | ) 18 | 19 | let mambers = declaration.memberBlock.members.compactMap { member in 20 | if let patternBinding = member.decl.as(VariableDeclSyntax.self)?.bindings.first, 21 | let identifier = patternBinding.pattern.as(IdentifierPatternSyntax.self)?.identifier, 22 | let type = patternBinding.typeAnnotation?.type { 23 | if type.is(IdentifierTypeSyntax.self) { 24 | return "lhs.\(identifier) == rhs.\(identifier)" 25 | } 26 | if let wrappedType = type.as(OptionalTypeSyntax.self)?.wrappedType, 27 | wrappedType.is(IdentifierTypeSyntax.self) { 28 | return "lhs.\(identifier) == rhs.\(identifier)" 29 | } 30 | } 31 | return nil 32 | }.joined(separator: "\n && ") 33 | 34 | let equtableFunction = """ 35 | static func == (lhs: \(type), rhs: \(type)) -> Bool { 36 | \(mambers) 37 | } 38 | """ 39 | 40 | let member = MemberBlockSyntax(members: MemberBlockItemListSyntax(stringLiteral: equtableFunction)) 41 | let extensionDecl = ExtensionDeclSyntax(extendedType: type, 42 | inheritanceClause: equatableProtocol, 43 | memberBlock: member) 44 | return [extensionDecl] 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Sources/Macros/ConformToHashable.swift: -------------------------------------------------------------------------------- 1 | import SwiftSyntax 2 | import SwiftSyntaxBuilder 3 | import SwiftSyntaxMacros 4 | import Foundation 5 | 6 | public struct ConformToHashable: ExtensionMacro { 7 | public static func expansion(of node: SwiftSyntax.AttributeSyntax, 8 | attachedTo declaration: some SwiftSyntax.DeclGroupSyntax, 9 | providingExtensionsOf type: some SwiftSyntax.TypeSyntaxProtocol, 10 | conformingTo protocols: [SwiftSyntax.TypeSyntax], 11 | in context: some SwiftSyntaxMacros.MacroExpansionContext) throws -> [SwiftSyntax.ExtensionDeclSyntax] { 12 | guard [SwiftSyntax.SyntaxKind.classDecl].contains(declaration.kind) else { 13 | throw MacroDiagnostics.errorMacroUsage(message: "Can only be applied to a class type") 14 | } 15 | let equatableProtocol = InheritanceClauseSyntax(inheritedTypes: InheritedTypeListSyntax( 16 | arrayLiteral: InheritedTypeSyntax(type: TypeSyntax(stringLiteral: "Hashable"))) 17 | ) 18 | 19 | let mambers = declaration.memberBlock.members.compactMap { member in 20 | if let patternBinding = member.decl.as(VariableDeclSyntax.self)?.bindings.first, 21 | let identifier = patternBinding.pattern.as(IdentifierPatternSyntax.self)?.identifier, 22 | let type = patternBinding.typeAnnotation?.type { 23 | if type.is(IdentifierTypeSyntax.self) { 24 | return "hasher.combine(\(identifier))" 25 | } 26 | if let wrappedType = type.as(OptionalTypeSyntax.self)?.wrappedType, 27 | wrappedType.is(IdentifierTypeSyntax.self) { 28 | return "hasher.combine(\(identifier))" 29 | } 30 | } 31 | return nil 32 | }.joined(separator: "\n ") 33 | 34 | let equtableFunction = """ 35 | func hash(into hasher: inout Hasher) { 36 | \(mambers) 37 | } 38 | """ 39 | 40 | let member = MemberBlockSyntax(members: MemberBlockItemListSyntax(stringLiteral: equtableFunction)) 41 | let extensionDecl = ExtensionDeclSyntax(extendedType: type, 42 | inheritanceClause: equatableProtocol, 43 | memberBlock: member) 44 | return [extensionDecl] 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Sources/Macros/FormatDate.swift: -------------------------------------------------------------------------------- 1 | import SwiftCompilerPlugin 2 | import SwiftSyntax 3 | import SwiftSyntaxBuilder 4 | import SwiftSyntaxMacros 5 | 6 | public struct FormatDate: ExpressionMacro { 7 | public static func expansion(of node: Node, 9 | in context: Context) throws -> ExprSyntax { 10 | guard let date = node.arguments.first else { 11 | throw MacroDiagnostics.errorMacroUsage(message: "Must specify arguments") 12 | } 13 | 14 | let formatter: DeclSyntax = "let formatter = DateFormatter()" 15 | let formatterStatement = CodeBlockItemSyntax(item: .decl(formatter)) 16 | let arguments = node.arguments.filter { $0.label != nil } 17 | .compactMap { tupleExprElementSyntax in 18 | if let parameter = tupleExprElementSyntax.label?.text, 19 | !tupleExprElementSyntax.expression.is(NilLiteralExprSyntax.self) { 20 | let stmt: StmtSyntax = "\n formatter.\(raw: parameter) = \(tupleExprElementSyntax.expression)" 21 | return CodeBlockItemSyntax(item: .stmt(stmt)) 22 | } 23 | return nil 24 | } 25 | let returnValue: ExprSyntax = "\n return formatter.string(from: \(date.expression))" 26 | let returnblock = CodeBlockItemSyntax(item: .expr(returnValue)) 27 | let statementList = CodeBlockItemListSyntax([formatterStatement] + arguments + [returnblock]) 28 | let closure = ClosureExprSyntax(statements: statementList) 29 | let function = FunctionCallExprSyntax(callee: closure) 30 | return ExprSyntax(function) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Sources/Macros/FormatDateComponents.swift: -------------------------------------------------------------------------------- 1 | import SwiftCompilerPlugin 2 | import SwiftSyntax 3 | import SwiftSyntaxBuilder 4 | import SwiftSyntaxMacros 5 | 6 | public struct FormatDateComponents: ExpressionMacro { 7 | public static func expansion(of node: Node, 9 | in context: Context) throws -> ExprSyntax { 10 | let statementList: CodeBlockItemListSyntax 11 | if let fromDate = node.argument(for: "from"), 12 | let toDate = node.argument(for: "to") { 13 | statementList = parseFromToFunction(for: node.arguments, from: fromDate, to: toDate) 14 | } else if let fromInterval = node.argument(for: "fromInterval") { 15 | statementList = parseFromFunction(for: node.arguments, from: fromInterval) 16 | } else if let fromComponents = node.argument(for: "fromComponents") { 17 | statementList = parseFromFunction(for: node.arguments, from: fromComponents) 18 | } else { 19 | throw MacroDiagnostics.errorMacroUsage(message: "Not supported parameters") 20 | } 21 | let closure = ClosureExprSyntax(statements: statementList) 22 | let function = FunctionCallExprSyntax(callee: closure) 23 | return ExprSyntax(function) 24 | } 25 | 26 | private static func parseFromToFunction(for argumentList: LabeledExprListSyntax, 27 | from fromDate: ExprSyntax, 28 | to toDate: ExprSyntax) -> CodeBlockItemListSyntax { 29 | let formatter: DeclSyntax = "let formatter = DateComponentsFormatter()" 30 | let formatterStatement = CodeBlockItemSyntax(item: .decl(formatter)) 31 | let arguments = argumentList.dropFirst(2).compactMap { codeblock(for: $0) } 32 | let returnValue: ExprSyntax = "\n return formatter.string(from: \(fromDate), to: \(toDate))" 33 | let returnblock = CodeBlockItemSyntax(item: .expr(returnValue)) 34 | return CodeBlockItemListSyntax([formatterStatement] + arguments + [returnblock]) 35 | } 36 | 37 | private static func parseFromFunction(for argumentList: LabeledExprListSyntax, 38 | from fromInterval: ExprSyntax) -> CodeBlockItemListSyntax { 39 | let formatter: DeclSyntax = "let formatter = DateComponentsFormatter()" 40 | let formatterStatement = CodeBlockItemSyntax(item: .decl(formatter)) 41 | let arguments = argumentList.dropFirst(1).compactMap { codeblock(for: $0) } 42 | let returnValue: ExprSyntax = "\n return formatter.string(from: \(fromInterval))" 43 | let returnblock = CodeBlockItemSyntax(item: .expr(returnValue)) 44 | return CodeBlockItemListSyntax([formatterStatement] + arguments + [returnblock]) 45 | } 46 | 47 | private static func codeblock(for tupleExprElementSyntax: LabeledExprSyntax) -> CodeBlockItemSyntax? { 48 | if let parameter = tupleExprElementSyntax.label?.text, 49 | !tupleExprElementSyntax.expression.is(NilLiteralExprSyntax.self) { 50 | let stmt: StmtSyntax = "\n formatter.\(raw: parameter) = \(tupleExprElementSyntax.expression)" 51 | let codeblock = CodeBlockItemSyntax(item: .stmt(stmt)) 52 | return codeblock 53 | } 54 | return nil 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Sources/Macros/FormatDateInterval.swift: -------------------------------------------------------------------------------- 1 | import SwiftCompilerPlugin 2 | import SwiftSyntax 3 | import SwiftSyntaxBuilder 4 | import SwiftSyntaxMacros 5 | 6 | public struct FormatDateInterval: ExpressionMacro { 7 | public static func expansion(of node: Node, 9 | in context: Context) throws -> ExprSyntax { 10 | guard let fromDate = node.argument(for: "from"), 11 | let toDate = node.argument(for: "to") else { 12 | throw MacroDiagnostics.errorMacroUsage(message: "Must specify dates") 13 | } 14 | 15 | let formatter: DeclSyntax = "let formatter = DateIntervalFormatter()" 16 | let formatterStatement = CodeBlockItemSyntax(item: .decl(formatter)) 17 | let arguments = node.arguments.dropFirst(2).compactMap { tupleExprElementSyntax in 18 | if let parameter = tupleExprElementSyntax.label?.text, 19 | !tupleExprElementSyntax.expression.is(NilLiteralExprSyntax.self) { 20 | let stmt: StmtSyntax = "\n formatter.\(raw: parameter) = \(tupleExprElementSyntax.expression)" 21 | return CodeBlockItemSyntax(item: .stmt(stmt)) 22 | } 23 | return nil 24 | } 25 | let returnValue: ExprSyntax = "\n return formatter.string(from: \(fromDate), to: \(toDate))" 26 | let returnblock = CodeBlockItemSyntax(item: .expr(returnValue)) 27 | let statementList = CodeBlockItemListSyntax([formatterStatement] + arguments + [returnblock]) 28 | let closure = ClosureExprSyntax(statements: statementList) 29 | let function = FunctionCallExprSyntax(callee: closure) 30 | return ExprSyntax(function) 31 | } 32 | } 33 | 34 | extension FreestandingMacroExpansionSyntax { 35 | func argument(for label: String) -> ExprSyntax? { 36 | arguments.filter({ $0.label?.text == label }).first?.expression 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Sources/Macros/JSONEncoderrDecoder.swift: -------------------------------------------------------------------------------- 1 | import SwiftCompilerPlugin 2 | import SwiftSyntax 3 | import SwiftSyntaxBuilder 4 | import SwiftSyntaxMacros 5 | 6 | public struct Encode: ExpressionMacro { 7 | public static func expansion(of node: Node, 9 | in context: Context) throws -> ExprSyntax { 10 | let defaults = [ 11 | "outputFormatting": "[]", 12 | "dateEncodingStrategy": "deferredToDate", 13 | "dataEncodingStrategy": "deferredToData", 14 | "nonConformingFloatEncodingStrategy": "throw", 15 | "keyEncodingStrategy": "useDefaultKeys", 16 | "userInfo": "[:]"] 17 | 18 | guard let value = node.arguments.first(where: { $0.label == nil })?.expression else { 19 | throw MacroDiagnostics.errorMacroUsage(message: "Must specify the value to encode") 20 | } 21 | let encoder: DeclSyntax = "let encoder = JSONEncoder()" 22 | let encoderStatement = CodeBlockItemSyntax(item: .decl(encoder)) 23 | let arguments = node.arguments.filter { $0.label != nil } 24 | .compactMap { tupleExprElementSyntax in 25 | if let parameter = tupleExprElementSyntax.label?.text, 26 | defaults[parameter] != "\(tupleExprElementSyntax.expression)" { 27 | let stmt: StmtSyntax = "\n encoder.\(raw: parameter) = \(tupleExprElementSyntax.expression)" 28 | return CodeBlockItemSyntax(item: .stmt(stmt)) 29 | } 30 | return nil 31 | } 32 | let returnValue: ExprSyntax = "\n return try encoder.encode(\(value))" 33 | let returnblock = CodeBlockItemSyntax(item: .expr(returnValue)) 34 | let statementList = CodeBlockItemListSyntax([encoderStatement] + arguments + [returnblock]) 35 | let closure = ClosureExprSyntax(statements: statementList) 36 | let function = FunctionCallExprSyntax(callee: closure) 37 | return ExprSyntax(function) 38 | } 39 | } 40 | 41 | public struct Decode: ExpressionMacro { 42 | public static func expansion(of node: Node, 44 | in context: Context) throws -> ExprSyntax { 45 | let defaults = [ 46 | "dateDecodingStrategy": "deferredToDate", 47 | "dataDecodingStrategy": "deferredToData", 48 | "nonConformingFloatDecodingStrategy": "throw", 49 | "keyDecodingStrategy": "useDefaultKeys", 50 | "userInfo": "[:]", 51 | "allowsJSON5": "false", 52 | "assumesTopLevelDictionary": "false"] 53 | guard let type = node.arguments.first(where: { $0.label == nil })?.expression, 54 | let data = node.arguments.first(where: { $0.label?.text == "from" })?.expression else { 55 | throw MacroDiagnostics.errorMacroUsage(message: "Must specify the type and the value to decode") 56 | } 57 | let decoder: DeclSyntax = "let decoder = JSONDecoder()" 58 | let decoderStatement = CodeBlockItemSyntax(item: .decl(decoder)) 59 | let arguments = node.arguments 60 | .filter { $0.label != nil && $0.label?.text != "from" } 61 | .compactMap { tupleExprElementSyntax in 62 | if let parameter = tupleExprElementSyntax.label?.text, 63 | defaults[parameter] != "\(tupleExprElementSyntax.expression)" { 64 | let stmt: StmtSyntax = "\n decoder.\(raw: tupleExprElementSyntax.label?.text ?? "") = \(tupleExprElementSyntax.expression)" 65 | return CodeBlockItemSyntax(item: .stmt(stmt)) 66 | } 67 | return nil 68 | } 69 | let returnValue: ExprSyntax = "\n return try decoder.decode(\(type), from: \(data))" 70 | let returnblock = CodeBlockItemSyntax(item: .expr(returnValue)) 71 | let statementList = CodeBlockItemListSyntax([decoderStatement] + arguments + [returnblock]) 72 | let closure = ClosureExprSyntax(statements: statementList) 73 | let function = FunctionCallExprSyntax(callee: closure) 74 | return ExprSyntax(function) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Sources/Macros/MacroDiagnostics.swift: -------------------------------------------------------------------------------- 1 | import SwiftSyntax 2 | import SwiftDiagnostics 3 | 4 | enum MacroDiagnostics { 5 | struct Message: DiagnosticMessage, Error { 6 | let message: String 7 | let diagnosticID: MessageID 8 | let severity: DiagnosticSeverity 9 | } 10 | 11 | enum ErrorMacroUsage: Error, CustomStringConvertible { 12 | case message(String) 13 | 14 | var description: String { 15 | switch self { 16 | case .message(let text): return text 17 | } 18 | } 19 | } 20 | 21 | static func diagnostic(node: Syntax, 22 | position: AbsolutePosition? = nil, 23 | message: Message, 24 | highlights: [Syntax]? = nil, 25 | notes: [Note] = [], 26 | fixIts: [FixIt] = []) -> Diagnostic { 27 | Diagnostic(node: node, message: message) 28 | } 29 | 30 | static func errorMacroUsage(message: String) -> ErrorMacroUsage { 31 | .message(message) 32 | } 33 | } 34 | 35 | extension MacroDiagnostics.Message: FixItMessage { 36 | var fixItID: MessageID { diagnosticID } 37 | } 38 | -------------------------------------------------------------------------------- /Sources/Macros/MacroPlugin.swift: -------------------------------------------------------------------------------- 1 | import SwiftCompilerPlugin 2 | import SwiftSyntaxMacros 3 | 4 | @main 5 | struct MacroPlugin: CompilerPlugin { 6 | let providingMacros: [Macro.Type] = [ 7 | Access.self, 8 | AddAssociatedValueVariable.self, 9 | AddInit.self, 10 | AddPublisher.self, 11 | BuildDate.self, 12 | BuildURL.self, 13 | BuildURLRequest.self, 14 | Encode.self, 15 | Decode.self, 16 | FormatDate.self, 17 | FormatDateComponents.self, 18 | FormatDateInterval.self, 19 | Mock.self, 20 | PostNotification.self, 21 | Singleton.self, 22 | ConformToEquatable.self, 23 | ConformToHashable.self 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /Sources/Macros/Mock.swift: -------------------------------------------------------------------------------- 1 | import SwiftSyntax 2 | import SwiftSyntaxBuilder 3 | import SwiftSyntaxMacros 4 | import Foundation 5 | 6 | import Foundation 7 | public struct Mock: PeerMacro { 8 | public static func expansion(of node: AttributeSyntax, 10 | providingPeersOf declaration: Declaration, 11 | in context: Context) throws -> [DeclSyntax] { 12 | guard let initializer = declaration.as(InitializerDeclSyntax.self) else { 13 | throw MacroDiagnostics.errorMacroUsage(message: "Can only apply to an initializer") 14 | } 15 | guard let typeName = node.argument(for: "type")?.as(MemberAccessExprSyntax.self)?.base else { 16 | throw MacroDiagnostics.errorMacroUsage(message: "Must specify type name") 17 | } 18 | 19 | let randomValue = node.argument(for: "randomMockValue")? 20 | .as(BooleanLiteralExprSyntax.self)?.literal.tokenKind.keyword != .false 21 | let parameters = initializer.signature.parameterClause.parameters.compactMap { parameter -> String in 22 | let name = parameter.firstName 23 | let mockValue = parameter.type.mockValue(randomValue: randomValue) ?? "nil" 24 | return "\(name): \(mockValue)" 25 | } 26 | var varDelcaration: DeclSyntax = "static let mock = \(raw: typeName)(\(raw: parameters.joined(separator: ", ")))" 27 | varDelcaration = "\(initializer.modifiers)\(varDelcaration)" 28 | varDelcaration = "#if DEBUG\n\(varDelcaration)\n#endif" 29 | return [varDelcaration] 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Sources/Macros/Mockable.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import SwiftUI 3 | 4 | public protocol Mockable { 5 | static func mock(random: Bool) -> String 6 | } 7 | 8 | extension Int: Mockable { 9 | public static func mock(random: Bool) -> String { 10 | String(random ? Int.random(in: Int.min.. String { 16 | String(random ? Int8.random(in: Int8.min.. String { 22 | String(random ? Int32.random(in: Int32.min.. String { 28 | String(String(random ? Int64.random(in: Int64.min.. String { 34 | String(random ? UInt.random(in: UInt.min.. String { 40 | String(random ? UInt8.random(in: UInt8.min.. String { 46 | String(random ? UInt32.random(in: UInt32.min.. String { 52 | String(random ? UInt64.random(in: UInt64.min.. String { 58 | String(random ? Bool.random() : true) 59 | } 60 | } 61 | 62 | extension String: Mockable { 63 | public static func mock(random: Bool) -> String { 64 | let string = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" 65 | return random 66 | ? "\"\(String(repeatElement(string.randomElement() ?? "a", count: Int.random(in: 0..<50))))\"" 67 | : #""abcd""# 68 | } 69 | } 70 | 71 | extension Double: Mockable { 72 | public static func mock(random: Bool) -> String { 73 | String(random ? Double.random(in: Double.leastNormalMagnitude.. String { 79 | String(random ? Float.random(in: Float.leastNormalMagnitude.. String { 86 | let names = ["apple.logo", 87 | "applescript", 88 | "applewatch", 89 | "appletv", 90 | "homepodmini.and.appletv", 91 | ] 92 | return random 93 | ? "Image(systemName: \"\(names.randomElement() ?? "apple.logo")\")" 94 | : #"Image(systemName: "apple.logo")"# 95 | } 96 | } 97 | 98 | extension Character: Mockable { 99 | public static func mock(random: Bool) -> String { 100 | "\"" + String("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".randomElement() ?? "A") + "\"" 101 | } 102 | } 103 | 104 | extension CharacterSet: Mockable { 105 | public static func mock(random: Bool) -> String { 106 | "[]" 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /Sources/Macros/PostNotification.swift: -------------------------------------------------------------------------------- 1 | import SwiftSyntax 2 | import SwiftSyntaxBuilder 3 | import SwiftSyntaxMacros 4 | import Foundation 5 | 6 | public struct PostNotification: ExpressionMacro { 7 | public static func expansion(of node: Node, 9 | in context: Context) throws -> ExprSyntax { 10 | guard let name = node.arguments.first(where: { $0.label == nil })?.expression else { 11 | throw MacroDiagnostics.errorMacroUsage(message: "Must specify a Notification name") 12 | } 13 | let object = node.arguments.first(where: { $0.label?.text == "object" })?.expression 14 | let userInfo = node.arguments.first(where: { $0.label?.text == "userInfo" })?.expression 15 | let notificationCenter = node.arguments.first(where: { $0.label?.text == "from" })?.expression 16 | let expr: ExprSyntax = 17 | """ 18 | \(notificationCenter ?? "NotificationCenter.default").post(name: \(name), object: \(object ?? "nil"), userInfo: \(userInfo ?? "nil")) 19 | """ 20 | return expr 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Sources/Macros/Singleton.swift: -------------------------------------------------------------------------------- 1 | import SwiftSyntax 2 | import SwiftSyntaxBuilder 3 | import SwiftSyntaxMacros 4 | 5 | public struct Singleton: MemberMacro { 6 | public static func expansion(of node: AttributeSyntax, 8 | providingMembersOf declaration: Declaration, 9 | in context: Context) throws -> [DeclSyntax] { 10 | guard [SwiftSyntax.SyntaxKind.classDecl, .structDecl].contains(declaration.kind) else { 11 | throw MacroDiagnostics.errorMacroUsage(message: "Can only be applied to a struct or class") 12 | } 13 | let identifier = (declaration as? StructDeclSyntax)?.name ?? (declaration as? ClassDeclSyntax)?.name ?? "" 14 | var override = "" 15 | if let inheritedTypes = (declaration as? ClassDeclSyntax)?.inheritanceClause?.inheritedTypes, 16 | inheritedTypes.contains(where: { inherited in inherited.type.trimmedDescription == "NSObject" }) { 17 | override = "override " 18 | } 19 | 20 | let initializer = try InitializerDeclSyntax("private \(raw: override)init()") {} 21 | 22 | let selfToken: TokenSyntax = "\(raw: identifier.text)()" 23 | let initShared = FunctionCallExprSyntax(calledExpression: DeclReferenceExprSyntax(baseName: selfToken)) {} 24 | let sharedInitializer = InitializerClauseSyntax(equal: .equalToken(trailingTrivia: .space), 25 | value: initShared) 26 | 27 | let staticToken: TokenSyntax = "static" 28 | let staticModifier = DeclModifierSyntax(name: staticToken) 29 | var modifiers = DeclModifierListSyntax([staticModifier]) 30 | 31 | let isPublicACL = declaration.modifiers.compactMap(\.name.tokenKind.keyword).contains(.public) 32 | if isPublicACL { 33 | let publicToken: TokenSyntax = "public" 34 | let publicModifier = DeclModifierSyntax(name: publicToken) 35 | modifiers = DeclModifierListSyntax([publicModifier] + [staticModifier]) 36 | } 37 | 38 | let shared = VariableDeclSyntax(modifiers: modifiers, 39 | .let, name: "shared", 40 | initializer: sharedInitializer) 41 | 42 | return [DeclSyntax(initializer), 43 | DeclSyntax(shared)] 44 | } 45 | } 46 | 47 | extension TokenKind { 48 | var keyword: Keyword? { 49 | switch self { 50 | case let .keyword(keyword): return keyword 51 | default: return nil 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Sources/SwiftMacros/AccessContentType.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public enum AccessContentType { 4 | /// Retrieve value from or store value to [UserDefault](https://developer.apple.com/documentation/foundation/userdefaults). 5 | /// Default to [.standard](https://developer.apple.com/documentation/foundation/userdefaults/1416603-standard). 6 | case userDefaults(UserDefaults = .standard) 7 | /// Retrieve value from or store value to [NSCache](https://developer.apple.com/documentation/foundation/nscache). 8 | /// Have to provide an instance of ****. 9 | case nsCache(NSCache) 10 | /// Retrieve value from or store value to [NSMAPTable](https://developer.apple.com/documentation/foundation/nsmaptable) 11 | /// Have to provide an instance of **NSMapTable**. 12 | case nsMapTable(NSMapTable) 13 | /// Access system keychain 14 | case keychain 15 | } 16 | -------------------------------------------------------------------------------- /Sources/SwiftMacros/DateBuilder.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public protocol DateComponent {} 4 | 5 | extension Calendar: DateComponent {} 6 | 7 | extension TimeZone: DateComponent {} 8 | 9 | extension Date: DateComponent {} 10 | 11 | public struct DateString: DateComponent { 12 | public let string: String 13 | public let locale: Locale? 14 | public let dateFormat: String? 15 | public let timeZone: TimeZone? 16 | public let twoDigitStartDate: Date? 17 | public let isLenient: Bool 18 | 19 | public init(_ string: String, 20 | locale: Locale? = nil, 21 | dateFormat: String? = nil, 22 | timeZone: TimeZone? = nil, 23 | twoDigitStartDate: Date? = nil, 24 | isLenient: Bool = false) { 25 | self.string = string 26 | self.locale = locale 27 | self.dateFormat = dateFormat 28 | self.timeZone = timeZone 29 | self.twoDigitStartDate = twoDigitStartDate 30 | self.isLenient = isLenient 31 | } 32 | } 33 | 34 | public struct ISO8601DateString: DateComponent { 35 | public let string: String 36 | public let timeZone: TimeZone? 37 | public let formatOptions: ISO8601DateFormatter.Options 38 | 39 | public init(_ string: String, 40 | timeZone: TimeZone? = nil, 41 | formatOptions: ISO8601DateFormatter.Options = []) { 42 | self.string = string 43 | self.timeZone = timeZone 44 | self.formatOptions = formatOptions 45 | } 46 | } 47 | 48 | public struct Era: DateComponent { 49 | public let value: Int 50 | 51 | public init(_ value: Int) { 52 | self.value = value 53 | } 54 | } 55 | 56 | public struct Year: DateComponent { 57 | public let value: Int 58 | 59 | public init(_ value: Int) { 60 | self.value = value 61 | } 62 | } 63 | 64 | public struct Month: DateComponent { 65 | public let value: Int 66 | 67 | public init(_ value: Int) { 68 | self.value = value 69 | } 70 | } 71 | 72 | public struct Hour: DateComponent { 73 | public let value: Int 74 | 75 | public init(_ value: Int) { 76 | self.value = value 77 | } 78 | } 79 | 80 | public struct Day: DateComponent { 81 | public let value: Int 82 | 83 | public init(_ value: Int) { 84 | self.value = value 85 | } 86 | } 87 | 88 | public struct Minute: DateComponent { 89 | public let value: Int 90 | 91 | public init(_ value: Int) { 92 | self.value = value 93 | } 94 | } 95 | 96 | public struct Second: DateComponent { 97 | public let value: Int 98 | 99 | public init(_ value: Int) { 100 | self.value = value 101 | } 102 | } 103 | 104 | public struct NanoSecond: DateComponent { 105 | public let value: Int 106 | 107 | public init(_ value: Int) { 108 | self.value = value 109 | } 110 | } 111 | 112 | public struct Weekday: DateComponent { 113 | public let value: Int 114 | 115 | public init(_ value: Int) { 116 | self.value = value 117 | } 118 | } 119 | 120 | public struct WeekdayOrdinal: DateComponent { 121 | public let value: Int 122 | 123 | public init(_ value: Int) { 124 | self.value = value 125 | } 126 | } 127 | 128 | public struct Quarter: DateComponent { 129 | public let value: Int 130 | 131 | public init(_ value: Int) { 132 | self.value = value 133 | } 134 | } 135 | 136 | public struct WeekOfMonth: DateComponent { 137 | public let value: Int 138 | 139 | public init(_ value: Int) { 140 | self.value = value 141 | } 142 | } 143 | 144 | public struct WeekOfYear: DateComponent { 145 | public let value: Int 146 | 147 | public init(_ value: Int) { 148 | self.value = value 149 | } 150 | } 151 | 152 | public struct YearForWeekOfYear: DateComponent { 153 | public let value: Int 154 | 155 | public init(_ value: Int) { 156 | self.value = value 157 | } 158 | } 159 | 160 | public typealias LeapMonth = Bool 161 | extension LeapMonth: DateComponent {} 162 | 163 | @resultBuilder 164 | public struct DateBuilder { 165 | public static func buildBlock(_ parts: [DateComponent]...) -> [DateComponent] { 166 | parts.flatMap { $0 } 167 | } 168 | 169 | public static func buildExpression(_ expression: DateComponent) -> [DateComponent] { 170 | [expression] 171 | } 172 | 173 | public static func buildFinalResult(_ components: [DateComponent]) -> Date? { 174 | var dateComponents = DateComponents() 175 | components.forEach { component in 176 | if let value = component as? Calendar { 177 | dateComponents.calendar = value 178 | } else if let value = component as? TimeZone { 179 | dateComponents.timeZone = value 180 | } else if let value = component as? Era { 181 | dateComponents.era = value.value 182 | } else if let value = component as? Year { 183 | dateComponents.yearForWeekOfYear = nil 184 | dateComponents.year = value.value 185 | } else if let value = component as? Month { 186 | dateComponents.month = value.value 187 | } else if let value = component as? Hour { 188 | dateComponents.hour = value.value 189 | } else if let value = component as? Day { 190 | dateComponents.day = value.value 191 | } else if let value = component as? Minute { 192 | dateComponents.minute = value.value 193 | } else if let value = component as? Second { 194 | dateComponents.second = value.value 195 | } else if let value = component as? NanoSecond { 196 | dateComponents.nanosecond = value.value 197 | } else if let value = component as? Weekday { 198 | dateComponents.weekday = value.value 199 | } else if let value = component as? WeekdayOrdinal { 200 | dateComponents.weekdayOrdinal = value.value 201 | } else if let value = component as? Quarter { 202 | dateComponents.quarter = value.value 203 | } else if let value = component as? WeekOfMonth { 204 | dateComponents.weekOfMonth = value.value 205 | } else if let value = component as? WeekOfYear { 206 | dateComponents.weekOfYear = value.value 207 | } else if let value = component as? YearForWeekOfYear { 208 | dateComponents.year = nil 209 | dateComponents.yearForWeekOfYear = value.value 210 | } else if let value = component as? LeapMonth { 211 | dateComponents.isLeapMonth = value 212 | } else if let value = component as? Date { 213 | let calendar = dateComponents.calendar ?? Calendar.current 214 | dateComponents = calendar.dateComponents(Calendar.Component.all, from: value) 215 | } else if let value = component as? DateString { 216 | let dateFormatter = DateFormatter() 217 | dateFormatter.locale = value.locale 218 | dateFormatter.dateFormat = value.dateFormat 219 | dateFormatter.timeZone = value.timeZone 220 | dateFormatter.twoDigitStartDate = value.twoDigitStartDate 221 | dateFormatter.isLenient = value.isLenient 222 | if let date = dateFormatter.date(from: value.string) { 223 | let calendar = dateComponents.calendar ?? Calendar.current 224 | dateComponents = calendar.dateComponents(Calendar.Component.all, from: date) 225 | } 226 | } else if let value = component as? ISO8601DateString { 227 | let dateFormatter = ISO8601DateFormatter() 228 | dateFormatter.timeZone = value.timeZone 229 | dateFormatter.formatOptions = value.formatOptions 230 | if let date = dateFormatter.date(from: value.string) { 231 | let calendar = dateComponents.calendar ?? Calendar.current 232 | dateComponents = calendar.dateComponents(Calendar.Component.all, from: date) 233 | } 234 | } 235 | } 236 | return dateComponents.date 237 | } 238 | } 239 | 240 | public func buildDate(@DateBuilder builder: () -> Date?) -> Date? { 241 | builder() 242 | } 243 | 244 | extension Calendar.Component { 245 | static let all: Set = { 246 | [.era, 247 | .year, 248 | .month, 249 | .day, 250 | .hour, 251 | .minute, 252 | .second, 253 | .weekday, 254 | .weekdayOrdinal, 255 | .quarter, 256 | .weekOfMonth, 257 | .weekOfYear, 258 | .yearForWeekOfYear, 259 | .nanosecond, 260 | .calendar, 261 | .timeZone] 262 | }() 263 | } 264 | -------------------------------------------------------------------------------- /Sources/SwiftMacros/SwiftMacros.docc/Resources/documentation-art/macro-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShenghaiWang/SwiftMacros/e553e69994cdde627ebd7a481c5b3d53a74ebea5/Sources/SwiftMacros/SwiftMacros.docc/Resources/documentation-art/macro-icon.png -------------------------------------------------------------------------------- /Sources/SwiftMacros/SwiftMacros.docc/SwiftMacros.md: -------------------------------------------------------------------------------- 1 | # ``SwiftMacros`` 2 | 3 | A practical collection of Swift Macros that help code correctly and swiftly. 4 | 5 | @Metadata { 6 | @PageImage(purpose: icon, source: "macro-icon", alt: "Swift-Macros") 7 | @PageColor(green) 8 | } 9 | 10 | ## Overview 11 | 12 | This collection of Swift Macros aims to remove boilerplate code by automatically generating needed code for a rich set of purposes. 13 | 14 | ## Topics 15 | 16 | ### Macros 17 | 18 | - ``Access(_:)`` 19 | - ``AddAssociatedValueVariable`` 20 | - ``AddInit(withMock:randomMockValue:)`` 21 | - ``AddPublisher`` 22 | - ``buildDate(_:)`` 23 | - ``buildURL(_:)`` 24 | - ``buildURLRequest(_:)`` 25 | - ``ConformToEqutable`` 26 | - ``ConformToHashable`` 27 | - ``encode(_:outputFormatting:dateEncodingStrategy:dataEncodingStrategy:nonConformingFloatEncodingStrategy:keyEncodingStrategy:userInfo:)`` 28 | - ``decode(_:from:dateDecodingStrategy:dataDecodingStrategy:nonConformingFloatDecodingStrategy:keyDecodingStrategy:userInfo:allowsJSON5:assumesTopLevelDictionary:)`` 29 | - ``formatDate(_:dateStyle:timeStyle:formattingContext:formatterBehavior:doesRelativeDateFormatting:amSymbol:pmSymbol:weekdaySymbols:shortWeekdaySymbols:veryShortWeekdaySymbols:standaloneWeekdaySymbols:shortStandaloneWeekdaySymbols:veryShortStandaloneWeekdaySymbols:monthSymbols:shortMonthSymbols:veryShortMonthSymbols:standaloneMonthSymbols:shortStandaloneMonthSymbols:veryShortStandaloneMonthSymbols:quarterSymbols:shortQuarterSymbols:standaloneQuarterSymbols:shortStandaloneQuarterSymbols:eraSymbols:longEraSymbols:)`` 30 | - ``formatDateComponents(fromComponents:allowedUnits:allowsFractionalUnits:calendar:collapsesLargestUnit:includesApproximationPhrase:includesTimeRemainingPhrase:maximumUnitCount:unitsStyle:zeroFormattingBehavior:formattingContext:referenceDate:)`` 31 | - ``formatDateComponents(from:to:allowedUnits:allowsFractionalUnits:calendar:collapsesLargestUnit:includesApproximationPhrase:includesTimeRemainingPhrase:maximumUnitCount:unitsStyle:zeroFormattingBehavior:formattingContext:referenceDate:)`` 32 | - ``formatDateComponents(fromInterval:allowedUnits:allowsFractionalUnits:calendar:collapsesLargestUnit:includesApproximationPhrase:includesTimeRemainingPhrase:maximumUnitCount:unitsStyle:zeroFormattingBehavior:formattingContext:referenceDate:)`` 33 | - ``formatDateInterval(from:to:dateStyle:timeStyle:dateTemplate:calendar:locale:timeZone:)`` 34 | - ``Mock(type:randomMockValue:)`` 35 | - ``postNotification(_:object:userInfo:from:)`` 36 | - ``Singleton`` 37 | 38 | -------------------------------------------------------------------------------- /Sources/SwiftMacros/SwiftMacros.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// An easy interface to retrieve value from or store value to ``AccessContentType`` like [UserDefault](https://developer.apple.com/documentation/foundation/userdefaults), [NSCache](https://developer.apple.com/documentation/foundation/nscache), [NSMAPTable](https://developer.apple.com/documentation/foundation/nsmaptable) and [Keychain](https://github.com/ShenghaiWang/SwiftKeychain) 4 | /// 5 | /// - Parameters: 6 | /// - type: One type of ``AccessContentType``. 7 | /// 8 | /// For example: 9 | /// ```swift 10 | /// struct TestAccess { 11 | /// static let cache = NSCache() 12 | /// static let mapTable = NSMapTable(keyOptions: .copyIn, valueOptions: .weakMemory) 13 | /// 14 | /// // without defalut value 15 | /// // make sure the generic type is the same as the type of the variable 16 | /// @Access(.userDefaults()) 17 | /// var isPaidUser: Bool? 18 | /// 19 | /// // with default value 20 | /// @Access(.userDefaults()) 21 | /// var isPaidUser2: Bool = false 22 | /// 23 | /// @Access(.nsCache(TestAccess.cache)) 24 | /// var hasPaid: NSObject? 25 | /// 26 | /// @Access(.nsMapTable(TestAccess.mapTable)) 27 | /// var hasPaid2: NSObject? 28 | /// 29 | /// @Access(.keychain) 30 | /// var value: CodableStruct? 31 | /// } 32 | /// ``` 33 | /// will expand to 34 | /// 35 | /// ```swift 36 | /// struct TestAccess { 37 | /// static let cache = NSCache() 38 | /// static let mapTable = NSMapTable(keyOptions: .copyIn, valueOptions: .weakMemory) 39 | /// 40 | /// // without defalut value 41 | /// // make sure the generic type is the same as the type of the variable 42 | /// var isPaidUser: Bool? 43 | /// { 44 | /// get { 45 | /// (UserDefaults.standard.object(forKey: "AccessKey_isPaidUser") as? Bool) 46 | /// } 47 | /// 48 | /// set { 49 | /// UserDefaults.standard.set(newValue, forKey: "AccessKey_isPaidUser") 50 | /// } 51 | /// } 52 | /// 53 | /// // with default value 54 | /// var isPaidUser2: Bool = false 55 | /// { 56 | /// get { 57 | /// (UserDefaults.standard.object(forKey: "AccessKey_isPaidUser2") as? Bool) ?? false 58 | /// } 59 | /// 60 | /// set { 61 | /// UserDefaults.standard.set(newValue, forKey: "AccessKey_isPaidUser2") 62 | /// } 63 | /// } 64 | /// 65 | /// var hasPaid: NSObject? 66 | /// { 67 | /// get { 68 | /// (TestAccess.cache.object(forKey: "AccessKey_hasPaid") as? NSObject) 69 | /// } 70 | /// 71 | /// set { 72 | /// if let value = newValue { 73 | /// TestAccess.cache.setObject(value, forKey: "AccessKey_hasPaid") 74 | /// } else { 75 | /// TestAccess.cache.removeObject(forKey: "AccessKey_hasPaid") 76 | /// } 77 | /// } 78 | /// } 79 | /// 80 | /// var hasPaid2: NSObject? 81 | /// { 82 | /// get { 83 | /// (TestAccess.mapTable.object(forKey: "AccessKey_hasPaid2") as? NSObject) 84 | /// } 85 | /// 86 | /// set { 87 | /// if let value = newValue { 88 | /// TestAccess.mapTable.setObject(value, forKey: "AccessKey_hasPaid2") 89 | /// } else { 90 | /// TestAccess.mapTable.removeObject(forKey: "AccessKey_hasPaid2") 91 | /// } 92 | /// } 93 | /// } 94 | /// 95 | /// var keychainValue: CodableStruct? 96 | /// { 97 | /// get { 98 | /// try? SwiftKeychain.search(key: "AccessKey_keychainValue") 99 | /// } 100 | /// 101 | /// set { 102 | /// if let value = newValue { 103 | /// SwiftKeychain.delete(key: "AccessKey_keychainValue") 104 | /// try? SwiftKeychain.add(value: value, for: "AccessKey_keychainValue") 105 | /// } else { 106 | /// SwiftKeychain.delete(key: "AccessKey_keychainValue") 107 | /// } 108 | /// } 109 | /// } 110 | /// } 111 | /// ``` 112 | @attached(accessor) 113 | public macro Access(_ type: AccessContentType) = #externalMacro(module: "Macros", type: "Access") 114 | 115 | /// Add variables for cases that have associated values in order to easily retrieve them 116 | /// 117 | /// For example: 118 | /// 119 | /// ```swift 120 | /// @AddAssociatedValueVariable 121 | /// enum TestEnum { 122 | /// case test(Int) 123 | /// } 124 | /// ``` 125 | /// 126 | /// will expand to 127 | /// 128 | /// ```swift 129 | /// enum TestEnum { 130 | /// case test(Int) 131 | /// var testValue: Int? { 132 | /// if case let .test(v0) = self { 133 | /// return v0 134 | /// } 135 | /// return nil 136 | /// } 137 | /// } 138 | /// ``` 139 | @attached(member, names: arbitrary) 140 | public macro AddAssociatedValueVariable() = #externalMacro(module: "Macros", type: "AddAssociatedValueVariable") 141 | 142 | /// Generate initialiser for the class/struct/actor. 143 | /// 144 | /// - Parameters: 145 | /// - withMock: true - if want to add a mock value as the same time. Default to false. 146 | /// - randomMockValue: true - if want to have random value for the mocked variable. Default to true. 147 | /// 148 | /// For example: 149 | /// 150 | /// ```swift 151 | /// @AddInit 152 | /// struct InitStruct { 153 | /// let a: Int 154 | /// let b: Int? 155 | /// let c: (Int?) -> Void 156 | /// let d: ((Int?) -> Void)? 157 | /// } 158 | /// ``` 159 | /// will expand to 160 | /// 161 | /// ```swift 162 | /// struct InitStruct { 163 | /// let a: Int 164 | /// let b: Int? 165 | /// let c: (Int?) -> Void 166 | /// let d: ((Int?) -> Void)? 167 | /// init(a: Int, b: Int? = nil, c: @escaping (Int?) -> Void, d: ((Int?) -> Void)? = nil) { 168 | /// self.a = a 169 | /// self.b = b 170 | /// self.c = c 171 | /// self.d = d 172 | /// } 173 | /// } 174 | ///``` 175 | /// And 176 | /// 177 | /// ```swift 178 | /// @AddInit(withMock: true) 179 | /// struct InitStruct { 180 | /// let a: Int 181 | /// let b: Int? 182 | /// let c: (Int?) -> Void 183 | /// let d: ((Int?) -> Void)? 184 | /// } 185 | /// ``` 186 | /// will expand to 187 | /// 188 | /// ```swift 189 | /// struct InitStruct { 190 | /// let a: Int 191 | /// let b: Int? 192 | /// init(a: Int, b: Int? = nil) { 193 | /// self.a = a 194 | /// self.b = b 195 | /// } 196 | /// #if DEBUG 197 | /// static let mock = InitStruct(a: 4285361067953494500, b: -2664036447940829071) 198 | /// #endif 199 | /// } 200 | /// ``` 201 | @attached(member, names: named(init), named(mock)) 202 | public macro AddInit(withMock: Bool = false, randomMockValue: Bool = true) = #externalMacro(module: "Macros", type: "AddInit") 203 | 204 | /// Generate a Combine publisher to a Combine subject in order to avoid overexposing subject variable 205 | /// 206 | /// For example: 207 | /// 208 | /// ```swift 209 | /// struct MyType { 210 | /// @AddPublisher 211 | /// private let mySubject = PassthroughSubject() 212 | /// } 213 | ///``` 214 | /// will expand to 215 | /// 216 | /// ```swift 217 | /// struct MyType { 218 | /// private let mySubject = PassthroughSubject() 219 | /// var mySubjectPublisher: AnyPublisher { 220 | /// mySubject.eraseToAnyPublisher() 221 | /// } 222 | /// } 223 | /// ``` 224 | @attached(peer, names: suffixed(Publisher)) 225 | public macro AddPublisher() = #externalMacro(module: "Macros", type: "AddPublisher") 226 | 227 | /// Build a Date from components. 228 | /// This solution addes in a resultBulder `DateBuilder`, 229 | /// which can be used directly if prefer builder pattern. 230 | /// 231 | /// For example: 232 | /// 233 | /// ```swift 234 | /// let date = #buildDate(DateString("03/05/2003", dateFormat: "MM/dd/yyyy"), 235 | /// Date(), 236 | /// Month(10), 237 | /// Year(1909), 238 | /// YearForWeekOfYear(2025)) 239 | /// 240 | /// // or use build pattern directly 241 | /// let date = buildDate(DateString("03/05/2003", dateFormat: "MM/dd/yyyy") 242 | /// Date() 243 | /// Month(10) 244 | /// Year(1909) 245 | /// YearForWeekOfYear(2025)) 246 | /// ``` 247 | /// > Note: this is for a simpler API. Please use it with caution in places that require efficiency. 248 | @freestanding(expression) 249 | public macro buildDate(_ components: DateComponent...) -> Date? = #externalMacro(module: "Macros", type: "BuildDate") 250 | 251 | /// Build a url from components. 252 | /// This solution addes in a resultBulder `URLBuilder`, which can be used directly if prefer builder pattern. 253 | /// 254 | /// For exmaple: 255 | /// ```swift 256 | /// let url = #buildURL("http://google.com", 257 | /// URLScheme.https, 258 | /// URLQueryItems([.init(name: "q1", value: "q1v"), .init(name: "q2", value: "q2v")])) 259 | /// 260 | /// // or use build pattern directly: 261 | /// let url2 = buildURL { 262 | /// "http://google.com" 263 | /// URLScheme.https 264 | /// URLQueryItems([.init(name: "q1", value: "q1v"), .init(name: "q2", value: "q2v")]) 265 | /// } 266 | /// ``` 267 | @freestanding(expression) 268 | public macro buildURL(_ components: URLComponent...) -> URL? = #externalMacro(module: "Macros", type: "BuildURL") 269 | 270 | /// Build a URLRequest from components. 271 | /// This solution addes in a resultBulder URLRequestBuilder, which can be used directly if prefer builder pattern. 272 | /// For example: 273 | /// ```swift 274 | /// let urlrequest = #buildURLRequest(url!, RequestTimeOutInterval(100)) 275 | /// 276 | /// // or use build pattern 277 | /// let urlRequest2 = buildURLRequest { url! 278 | /// RequestTimeOutInterval(100) 279 | /// } 280 | /// ``` 281 | @freestanding(expression) 282 | public macro buildURLRequest(_ components: URLRequestComponent...) -> URLRequest? = #externalMacro(module: "Macros", type: "BuildURLRequest") 283 | 284 | /// Encode an Encodable to data using JSONEncoder 285 | /// 286 | /// - Parameters: 287 | /// - value: the value to be encoded. Need to conform Encodable 288 | /// - outputFormatting: [JSONEncoder.OutputFormatting](https://developer.apple.com/documentation/foundation/jsonencoder/outputformatting) 289 | /// - dataEncodingStrategy: [JSONEncoder.DataEncodingStrategy](https://developer.apple.com/documentation/foundation/jsonencoder/dataencodingstrategy) 290 | /// - nonConformingFloatEncodingStrategy: JSONEncoder.NonConformingFloatEncodingStrategy 291 | /// - keyEncodingStrategy: [JSONEncoder.KeyEncodingStrategy](https://developer.apple.com/documentation/foundation/jsonencoder/nonconformingfloatencodingstrategy) 292 | /// - userInfo: [[CodingUserInfoKey: Any]](https://developer.apple.com/documentation/foundation/jsonencoder/2895176-userinfo) 293 | /// 294 | /// For example: 295 | /// ```swift 296 | /// let data = #encode(value) 297 | /// let anotherData = #encode(value, dateEncodingStrategy: .secondsSince1970) 298 | /// ``` 299 | @freestanding(expression) 300 | public macro encode(_ value: Encodable, 301 | outputFormatting: JSONEncoder.OutputFormatting = [], 302 | dateEncodingStrategy: JSONEncoder.DateEncodingStrategy = .deferredToDate, 303 | dataEncodingStrategy: JSONEncoder.DataEncodingStrategy = .deferredToData, 304 | nonConformingFloatEncodingStrategy: JSONEncoder.NonConformingFloatEncodingStrategy = .throw, 305 | keyEncodingStrategy: JSONEncoder.KeyEncodingStrategy = .useDefaultKeys, 306 | userInfo: [CodingUserInfoKey: Any] = [:]) -> Data = #externalMacro(module: "Macros", type: "Encode") 307 | 308 | /// Decode a Decodable to a typed value using JSONDecoder 309 | /// - Parameters: 310 | /// - type: The type of the value to decode from the supplied JSON object. 311 | /// - dateDecodingStrategy: [JSONDecoder.DateDecodingStrategy](https://developer.apple.com/documentation/foundation/jsondecoder/datedecodingstrategy) 312 | /// - dataDecodingStrategy: [JSONDecoder.DataDecodingStrategy](https://developer.apple.com/documentation/foundation/jsondecoder/datadecodingstrategy) 313 | /// - nonConformingFloatDecodingStrategy: [JSONDecoder.NonConformingFloatDecodingStrategy](https://developer.apple.com/documentation/foundation/jsondecoder/nonconformingfloatdecodingstrategy) 314 | /// - keyDecodingStrategy: [JSONDecoder.KeyDecodingStrategy](https://developer.apple.com/documentation/foundation/jsondecoder/keydecodingstrategy) 315 | /// - userInfo: [[CodingUserInfoKey: Any]](https://developer.apple.com/documentation/foundation/jsondecoder/2895340-userinfo) 316 | /// - allowsJSON5: [Bool](https://developer.apple.com/documentation/foundation/jsondecoder/3766916-allowsjson5) 317 | /// - assumesTopLevelDictionary: [Bool](https://developer.apple.com/documentation/foundation/jsondecoder/3766917-assumestopleveldictionary) 318 | /// 319 | /// For exmample: 320 | /// ```swift 321 | /// let value = #decode(TestStruct.self, from: data) 322 | /// let anotherValue = #decode(TestStruct.self, from: data, dateDecodingStrategy: .iso8601) 323 | /// ``` 324 | @freestanding(expression) 325 | public macro decode(_ type: T.Type, 326 | from value: Data, 327 | dateDecodingStrategy: JSONDecoder.DateDecodingStrategy = .deferredToDate, 328 | dataDecodingStrategy: JSONDecoder.DataDecodingStrategy = .base64, 329 | nonConformingFloatDecodingStrategy: JSONDecoder.NonConformingFloatDecodingStrategy = .throw, 330 | keyDecodingStrategy: JSONDecoder.KeyDecodingStrategy = .useDefaultKeys, 331 | userInfo: [CodingUserInfoKey: Any] = [:], 332 | allowsJSON5: Bool = false, 333 | assumesTopLevelDictionary: Bool = false) -> T = #externalMacro(module: "Macros", type: "Decode") 334 | 335 | /// Format date to a string 336 | /// 337 | /// - Parameters: 338 | /// - date: the date to be formatted 339 | /// - dateStyle: [DateFormatter.Style?](https://developer.apple.com/documentation/foundation/dateformatter/1415411-datestyle) 340 | /// - timeStyle: [DateFormatter.Style?](https://developer.apple.com/documentation/foundation/dateformatter/1413467-timestyle) 341 | /// - formattingContext: [Formatter.Context?](https://developer.apple.com/documentation/foundation/dateformatter/1408066-formattingcontext) 342 | /// - formatterBehavior: [DateFormatter.Behavior?](https://developer.apple.com/documentation/foundation/dateformatter/1409720-formatterbehavior) 343 | /// - doesRelativeDateFormatting: [Bool?](https://developer.apple.com/documentation/foundation/dateformatter/1415848-doesrelativedateformatting) 344 | /// - amSymbol: [String?](https://developer.apple.com/documentation/foundation/dateformatter/1409506-amsymbol) 345 | /// - pmSymbol: [String?](https://developer.apple.com/documentation/foundation/dateformatter/1412367-pmsymbol) 346 | /// - weekdaySymbols: [[String]?](https://developer.apple.com/documentation/foundation/dateformatter/1412405-weekdaysymbols) 347 | /// - shortWeekdaySymbols: [[String]?](https://developer.apple.com/documentation/foundation/dateformatter/1416121-shortweekdaysymbols) 348 | /// - veryShortWeekdaySymbols: [[String]?](https://developer.apple.com/documentation/foundation/dateformatter/1415109-veryshortweekdaysymbols) 349 | /// - standaloneWeekdaySymbols: [[String]?](https://developer.apple.com/documentation/foundation/dateformatter/1413618-standaloneweekdaysymbols) 350 | /// - shortStandaloneWeekdaySymbols: [[String]?](https://developer.apple.com/documentation/foundation/dateformatter/1409119-shortstandaloneweekdaysymbols) 351 | /// - veryShortStandaloneWeekdaySymbols: [[String]?](https://developer.apple.com/documentation/foundation/dateformatter/1418238-veryshortstandaloneweekdaysymbol) 352 | /// - monthSymbols: [[String]?](https://developer.apple.com/documentation/foundation/dateformatter/1412049-monthsymbols) 353 | /// - shortMonthSymbols: [[String]?](https://developer.apple.com/documentation/foundation/dateformatter/1409209-shortmonthsymbols) 354 | /// - veryShortMonthSymbols: [[String]?](https://developer.apple.com/documentation/foundation/dateformatter/1413632-veryshortmonthsymbols) 355 | /// - standaloneMonthSymbols: [[String]?](https://developer.apple.com/documentation/foundation/dateformatter/1416227-standalonemonthsymbols) 356 | /// - shortStandaloneMonthSymbols: [[String]?](https://developer.apple.com/documentation/foundation/dateformatter/1414771-shortstandalonemonthsymbols) 357 | /// - veryShortStandaloneMonthSymbols: [[String]?](https://developer.apple.com/documentation/foundation/dateformatter/1413322-veryshortstandalonemonthsymbols) 358 | /// - quarterSymbols: [[String]?](https://developer.apple.com/documentation/foundation/dateformatter/1417587-quartersymbols) 359 | /// - shortQuarterSymbols: [[String]?](https://developer.apple.com/documentation/foundation/dateformatter/1409851-shortquartersymbols) 360 | /// - standaloneQuarterSymbols: [[String]?](https://developer.apple.com/documentation/foundation/dateformatter/1411487-standalonequartersymbols) 361 | /// - shortStandaloneQuarterSymbols: [[String]?](https://developer.apple.com/documentation/foundation/dateformatter/1416421-shortstandalonequartersymbols) 362 | /// - eraSymbols: [[String]?](https://developer.apple.com/documentation/foundation/dateformatter/1418282-erasymbols) 363 | /// - longEraSymbols: [[String]?](https://developer.apple.com/documentation/foundation/dateformatter/1418081-longerasymbols) 364 | /// 365 | /// For example: 366 | /// ```swift 367 | /// let string = #formatDate(Date(), dateStyle: .full) 368 | /// ``` 369 | @freestanding(expression) 370 | public macro formatDate(_ date: Date, 371 | dateStyle: DateFormatter.Style? = nil, 372 | timeStyle: DateFormatter.Style? = nil, 373 | formattingContext: Formatter.Context? = nil, 374 | formatterBehavior: DateFormatter.Behavior? = nil, 375 | doesRelativeDateFormatting: Bool? = nil, 376 | amSymbol: String? = nil, 377 | pmSymbol: String? = nil, 378 | weekdaySymbols: [String]? = nil, 379 | shortWeekdaySymbols: [String]? = nil, 380 | veryShortWeekdaySymbols: [String]? = nil, 381 | standaloneWeekdaySymbols: [String]? = nil, 382 | shortStandaloneWeekdaySymbols: [String]? = nil, 383 | veryShortStandaloneWeekdaySymbols: [String]? = nil, 384 | monthSymbols: [String]? = nil, 385 | shortMonthSymbols: [String]? = nil, 386 | veryShortMonthSymbols: [String]? = nil, 387 | standaloneMonthSymbols: [String]? = nil, 388 | shortStandaloneMonthSymbols: [String]? = nil, 389 | veryShortStandaloneMonthSymbols: [String]? = nil, 390 | quarterSymbols: [String]? = nil, 391 | shortQuarterSymbols: [String]? = nil, 392 | standaloneQuarterSymbols: [String]? = nil, 393 | shortStandaloneQuarterSymbols: [String]? = nil, 394 | eraSymbols: [String]? = nil, 395 | longEraSymbols: [String]? = nil) -> String = #externalMacro(module: "Macros", type: "FormatDate") 396 | 397 | /// Format date differences to a string 398 | /// 399 | /// - Parameters: 400 | /// - from: from date 401 | /// - to: to date 402 | /// - allowedUnits: [NSCalendar.Unit?](https://developer.apple.com/documentation/foundation/nscalendar/unit) 403 | /// - allowsFractionalUnits: [Bool?](https://developer.apple.com/documentation/foundation/datecomponentsformatter/1413084-allowsfractionalunits) 404 | /// - calendar: [Calendar?](https://developer.apple.com/documentation/foundation/calendar) 405 | /// - collapsesLargestUnit: [Bool?](https://developer.apple.com/documentation/foundation/datecomponentsformatter/1410812-collapseslargestunit) 406 | /// - includesApproximationPhrase: [Bool?](https://developer.apple.com/documentation/foundation/datecomponentsformatter/1416387-includesapproximationphrase) 407 | /// - includesTimeRemainingPhrase:[ Bool?](https://developer.apple.com/documentation/foundation/datecomponentsformatter/1416416-includestimeremainingphrase) 408 | /// - maximumUnitCount: [Int?](https://developer.apple.com/documentation/foundation/datecomponentsformatter/1416214-maximumunitcount) 409 | /// - unitsStyle: [DateComponentsFormatter.UnitsStyle?](https://developer.apple.com/documentation/foundation/datecomponentsformatter/unitsstyle) 410 | /// - zeroFormattingBehavior: [DateComponentsFormatter.ZeroFormattingBehavior?](https://developer.apple.com/documentation/foundation/datecomponentsformatter/zeroformattingbehavior) 411 | /// - formattingContext: [Formatter.Context?](https://developer.apple.com/documentation/foundation/formatter/context) 412 | /// - referenceDate:[ Date?](https://developer.apple.com/documentation/foundation/datecomponentsformatter/2878110-referencedate) 413 | /// 414 | /// For example: 415 | /// ```swift 416 | /// let string = #formatDateComponents(from: Date(), to: Date(), allowedUnits: [.day, .hour, .minute, .second]) 417 | /// ``` 418 | @freestanding(expression) 419 | public macro formatDateComponents(from fromDate: Date, 420 | to toDate: Date, 421 | allowedUnits: NSCalendar.Unit? = nil, 422 | allowsFractionalUnits: Bool? = nil, 423 | calendar: Calendar? = nil, 424 | collapsesLargestUnit: Bool? = nil, 425 | includesApproximationPhrase: Bool? = nil, 426 | includesTimeRemainingPhrase: Bool? = nil, 427 | maximumUnitCount: Int? = nil, 428 | unitsStyle: DateComponentsFormatter.UnitsStyle? = nil, 429 | zeroFormattingBehavior: DateComponentsFormatter.ZeroFormattingBehavior? = nil, 430 | formattingContext: Formatter.Context? = nil, 431 | referenceDate: Date? = nil) -> String? = #externalMacro(module: "Macros", type: "FormatDateComponents") 432 | 433 | /// Format a timeInterval components to a string 434 | /// 435 | /// - Parameters: 436 | /// - fromInterval: the time interval to be formatted 437 | /// - allowedUnits: [NSCalendar.Unit?](https://developer.apple.com/documentation/foundation/nscalendar/unit) 438 | /// - allowsFractionalUnits: [Bool?](https://developer.apple.com/documentation/foundation/datecomponentsformatter/1413084-allowsfractionalunits) 439 | /// - calendar: [Calendar?](https://developer.apple.com/documentation/foundation/calendar) 440 | /// - collapsesLargestUnit: [Bool?](https://developer.apple.com/documentation/foundation/datecomponentsformatter/1410812-collapseslargestunit) 441 | /// - includesApproximationPhrase: [Bool?](https://developer.apple.com/documentation/foundation/datecomponentsformatter/1416387-includesapproximationphrase) 442 | /// - includesTimeRemainingPhrase:[ Bool?](https://developer.apple.com/documentation/foundation/datecomponentsformatter/1416416-includestimeremainingphrase) 443 | /// - maximumUnitCount: [Int?](https://developer.apple.com/documentation/foundation/datecomponentsformatter/1416214-maximumunitcount) 444 | /// - unitsStyle: [DateComponentsFormatter.UnitsStyle?](https://developer.apple.com/documentation/foundation/datecomponentsformatter/unitsstyle) 445 | /// - zeroFormattingBehavior: [DateComponentsFormatter.ZeroFormattingBehavior?](https://developer.apple.com/documentation/foundation/datecomponentsformatter/zeroformattingbehavior) 446 | /// - formattingContext: [Formatter.Context?](https://developer.apple.com/documentation/foundation/formatter/context) 447 | /// - referenceDate:[ Date?](https://developer.apple.com/documentation/foundation/datecomponentsformatter/2878110-referencedate) 448 | /// 449 | /// For example: 450 | /// ```swift 451 | /// let string = #formatDateComponents(fromInterval: 100, allowedUnits: [.day, .hour, .minute, .second]) 452 | /// ``` 453 | @freestanding(expression) 454 | public macro formatDateComponents(fromInterval timeInterval: TimeInterval, 455 | allowedUnits: NSCalendar.Unit? = nil, 456 | allowsFractionalUnits: Bool? = nil, 457 | calendar: Calendar? = nil, 458 | collapsesLargestUnit: Bool? = nil, 459 | includesApproximationPhrase: Bool? = nil, 460 | includesTimeRemainingPhrase: Bool? = nil, 461 | maximumUnitCount: Int? = nil, 462 | unitsStyle: DateComponentsFormatter.UnitsStyle? = nil, 463 | zeroFormattingBehavior: DateComponentsFormatter.ZeroFormattingBehavior? = nil, 464 | formattingContext: Formatter.Context? = nil, 465 | referenceDate: Date? = nil) -> String? = #externalMacro(module: "Macros", type: "FormatDateComponents") 466 | 467 | /// Format a date components to a string 468 | /// 469 | /// - Parameters: 470 | /// - fromComponents: the date components to be formatted 471 | /// - allowedUnits: [NSCalendar.Unit?](https://developer.apple.com/documentation/foundation/nscalendar/unit) 472 | /// - allowsFractionalUnits: [Bool?](https://developer.apple.com/documentation/foundation/datecomponentsformatter/1413084-allowsfractionalunits) 473 | /// - calendar: [Calendar?](https://developer.apple.com/documentation/foundation/calendar) 474 | /// - collapsesLargestUnit: [Bool?](https://developer.apple.com/documentation/foundation/datecomponentsformatter/1410812-collapseslargestunit) 475 | /// - includesApproximationPhrase: [Bool?](https://developer.apple.com/documentation/foundation/datecomponentsformatter/1416387-includesapproximationphrase) 476 | /// - includesTimeRemainingPhrase:[ Bool?](https://developer.apple.com/documentation/foundation/datecomponentsformatter/1416416-includestimeremainingphrase) 477 | /// - maximumUnitCount: [Int?](https://developer.apple.com/documentation/foundation/datecomponentsformatter/1416214-maximumunitcount) 478 | /// - unitsStyle: [DateComponentsFormatter.UnitsStyle?](https://developer.apple.com/documentation/foundation/datecomponentsformatter/unitsstyle) 479 | /// - zeroFormattingBehavior: [DateComponentsFormatter.ZeroFormattingBehavior?](https://developer.apple.com/documentation/foundation/datecomponentsformatter/zeroformattingbehavior) 480 | /// - formattingContext: [Formatter.Context?](https://developer.apple.com/documentation/foundation/formatter/context) 481 | /// - referenceDate:[ Date?](https://developer.apple.com/documentation/foundation/datecomponentsformatter/2878110-referencedate) 482 | /// 483 | /// For example: 484 | /// ```swift 485 | /// let string = #formatDateComponents(fromComponents: DateComponents(hour: 10), allowedUnits: [.day, .hour, .minute, .second]) 486 | /// ``` 487 | @freestanding(expression) 488 | public macro formatDateComponents(fromComponents components: DateComponents, 489 | allowedUnits: NSCalendar.Unit? = nil, 490 | allowsFractionalUnits: Bool? = nil, 491 | calendar: Calendar? = nil, 492 | collapsesLargestUnit: Bool? = nil, 493 | includesApproximationPhrase: Bool? = nil, 494 | includesTimeRemainingPhrase: Bool? = nil, 495 | maximumUnitCount: Int? = nil, 496 | unitsStyle: DateComponentsFormatter.UnitsStyle? = nil, 497 | zeroFormattingBehavior: DateComponentsFormatter.ZeroFormattingBehavior? = nil, 498 | formattingContext: Formatter.Context? = nil, 499 | referenceDate: Date? = nil) -> String? = #externalMacro(module: "Macros", type: "FormatDateComponents") 500 | /// Format two dates into interval string 501 | /// 502 | /// - Parameters: 503 | /// - from: From date 504 | /// - to: To date 505 | /// - dateStyle: [DateIntervalFormatter.Style?](https://developer.apple.com/documentation/foundation/dateintervalformatter/style) 506 | /// - timeStyle: [DateIntervalFormatter.Style?](https://developer.apple.com/documentation/foundation/dateintervalformatter/style) 507 | /// - dateTemplate: [String? ](https://developer.apple.com/documentation/foundation/dateintervalformatter/1407373-datetemplate) 508 | /// - calendar: [Calendar?](https://developer.apple.com/documentation/foundation/calendar) 509 | /// - locale: [Locale?](https://developer.apple.com/documentation/foundation/locale) 510 | /// - timeZone: [TimeZone?](https://developer.apple.com/documentation/foundation/timezone) 511 | /// 512 | /// For example: 513 | /// ```swift 514 | /// let string = #formatDateInterval(from: Date(), to: Date(), dateStyle: .short) 515 | /// ``` 516 | @freestanding(expression) 517 | public macro formatDateInterval(from fromDate: Date, 518 | to toDate: Date, 519 | dateStyle: DateIntervalFormatter.Style? = nil, 520 | timeStyle: DateIntervalFormatter.Style? = nil, 521 | dateTemplate: String? = nil, 522 | calendar: Calendar? = nil, 523 | locale: Locale? = nil, 524 | timeZone: TimeZone? = nil) -> String = #externalMacro(module: "Macros", type: "FormatDateInterval") 525 | 526 | /// Generate a static variable mock using the attached initializer. 527 | /// For custmoised data type, it will use Type.mock. In case there is no this value, 528 | /// need to define this yourself or use @Mock or @AddInit(withMock: true) to generate this variable. 529 | /// 530 | /// - Parameters: 531 | /// - type: The value type 532 | /// - randomMockValue: true - if want to have random value for the mocked variable. Default to true. 533 | /// 534 | /// For example: 535 | /// ```swift 536 | /// class AStruct { 537 | /// let a: Float 538 | /// @Mock(type: AStruct.self) 539 | /// init(a: Float) { 540 | /// self.a = a 541 | /// } 542 | /// } 543 | /// ``` 544 | /// will expand to 545 | /// ```swift 546 | /// class AStruct { 547 | /// let a: Float 548 | /// init(a: Float) { 549 | /// self.a = a 550 | /// } 551 | /// #if DEBUG 552 | /// static let mock = AStruct(a: 3.0339055e+37) 553 | /// #endif 554 | /// } 555 | /// ``` 556 | @attached(peer, names: named(mock)) 557 | public macro Mock(type: Any.Type, randomMockValue: Bool = true) = #externalMacro(module: "Macros", type: "Mock") 558 | 559 | /// An easy way to post notifications 560 | /// - Parameters: 561 | /// - name: The notification name to be posted 562 | /// - object: The object posting the notification. 563 | /// - userInfo: A user info dictionary with optional information about the notification. 564 | /// - from: The notificationCenter used to send the notification. Default to [.default](https://developer.apple.com/documentation/foundation/notificationcenter/1414169-default) 565 | /// 566 | /// For example: 567 | /// ```swift 568 | /// #postNotification(.NSCalendarDayChanged) 569 | /// ``` 570 | @freestanding(expression) 571 | public macro postNotification(_ name: NSNotification.Name, 572 | object: Any? = nil, 573 | userInfo: [AnyHashable : Any]? = nil, 574 | from notificationCenter: NotificationCenter = .default) = #externalMacro(module: "Macros", type: "PostNotification") 575 | 576 | /// Generate Swift singleton code for struct and class types 577 | /// 578 | /// For example: 579 | /// ```swift 580 | /// @Singleton 581 | /// struct A { 582 | /// } 583 | /// ``` 584 | /// will expand to 585 | /// ```swift 586 | /// struct A { 587 | /// private init() { 588 | /// } 589 | /// 590 | /// static let shared = A() 591 | /// } 592 | /// ``` 593 | @attached(member, names: named(init), named(shared)) 594 | public macro Singleton() = #externalMacro(module: "Macros", type: "Singleton") 595 | 596 | /// Add Equatable conformance to class 597 | /// 598 | /// For example: 599 | /// ```swift 600 | /// @ConformToEqutable 601 | /// class A { 602 | /// let a: Int 603 | /// init(a: Int) { 604 | /// self.a = a 605 | /// } 606 | /// } 607 | /// ``` 608 | /// will expand to 609 | /// ```swift 610 | /// class A { 611 | /// let a: Int 612 | /// init(a: Int) { 613 | /// self.a = a 614 | /// } 615 | /// } 616 | /// 617 | /// extension A: Equatable { 618 | /// static func == (lhs: A, rhs: A) -> Bool { 619 | /// lhs.a == rhs.a 620 | /// } 621 | /// } 622 | /// ``` 623 | @attached(extension, conformances: Equatable, names: named(==)) 624 | public macro ConformToEquatable() = #externalMacro(module: "Macros", type: "ConformToEquatable") 625 | 626 | /// Add Hashable conformance to class 627 | /// 628 | /// For example: 629 | /// ```swift 630 | /// @ConformToHashable 631 | /// class A { 632 | /// let a: Int 633 | /// init(a: Int) { 634 | /// self.a = a 635 | /// } 636 | /// } 637 | /// ``` 638 | /// will expand to 639 | /// ```swift 640 | /// class A { 641 | /// let a: Int 642 | /// init(a: Int) { 643 | /// self.a = a 644 | /// } 645 | /// } 646 | /// 647 | /// extension A: Hashable { 648 | /// func hash(into hasher: inout Hasher) { 649 | /// hasher.combine(a) 650 | /// } 651 | /// } 652 | /// ``` 653 | @attached(extension, conformances: Hashable, names: named(hash)) 654 | public macro ConformToHashable() = #externalMacro(module: "Macros", type: "ConformToHashable") 655 | -------------------------------------------------------------------------------- /Sources/SwiftMacros/URLBuilder.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public protocol URLComponent {} 4 | 5 | extension String: URLComponent {} 6 | 7 | public struct URLFragment: URLComponent { 8 | public let value: String 9 | 10 | public init(_ value: String) { 11 | self.value = value 12 | } 13 | } 14 | 15 | public struct URLHost: URLComponent { 16 | public let value: String 17 | 18 | public init(_ value: String) { 19 | self.value = value 20 | } 21 | } 22 | 23 | public enum URLScheme: String, URLComponent { 24 | case http 25 | case https 26 | } 27 | 28 | public struct URLPassword: URLComponent { 29 | public let value: String 30 | 31 | public init(_ value: String) { 32 | self.value = value 33 | } 34 | } 35 | 36 | public struct URLPath: URLComponent { 37 | public let value: String 38 | 39 | public init(_ value: String) { 40 | self.value = value 41 | } 42 | } 43 | 44 | public struct URLPort: URLComponent { 45 | public let value: Int 46 | 47 | public init(_ value: Int) { 48 | self.value = value 49 | } 50 | } 51 | 52 | public struct URLQuery: URLComponent { 53 | public let value: String 54 | 55 | public init(_ value: String) { 56 | self.value = value 57 | } 58 | } 59 | 60 | public struct URLQueryItems: URLComponent { 61 | public let value: [URLQueryItem] 62 | 63 | public init(_ value: [URLQueryItem]) { 64 | self.value = value 65 | } 66 | } 67 | 68 | public struct URLUser: URLComponent { 69 | public let value: String 70 | 71 | public init(_ value: String) { 72 | self.value = value 73 | } 74 | } 75 | 76 | @resultBuilder 77 | public struct URLBuilder { 78 | public static func buildBlock(_ parts: [URLComponent]...) -> [URLComponent] { 79 | parts.flatMap { $0 } 80 | } 81 | 82 | public static func buildExpression(_ expression: URLComponent) -> [URLComponent] { 83 | [expression] 84 | } 85 | 86 | public static func buildFinalResult(_ components: [URLComponent]) -> URL? { 87 | let componentDict = Dictionary(grouping: components) { $0 is String } 88 | let urlString = componentDict[true]?.last as? String ?? "" 89 | guard var urlComponents = URLComponents(string: urlString) else { return nil } 90 | componentDict[false]?.forEach { component in 91 | if let fragment = component as? URLFragment { 92 | urlComponents.fragment = fragment.value 93 | } else if let host = component as? URLHost { 94 | urlComponents.host = host.value 95 | } else if let scheme = component as? URLScheme { 96 | urlComponents.scheme = scheme.rawValue 97 | } else if let password = component as? URLPassword { 98 | urlComponents.password = password.value 99 | } else if let path = component as? URLPath { 100 | urlComponents.path = path.value 101 | } else if let port = component as? URLPort { 102 | urlComponents.port = port.value 103 | } else if let query = component as? URLQuery { 104 | urlComponents.query = query.value 105 | } else if let queryItems = component as? URLQueryItems { 106 | urlComponents.queryItems = queryItems.value 107 | } else if let user = component as? URLUser { 108 | urlComponents.user = user.value 109 | } 110 | } 111 | return urlComponents.url 112 | } 113 | } 114 | 115 | public func buildURL(@URLBuilder builder: () -> URL?) -> URL? { 116 | builder() 117 | } 118 | -------------------------------------------------------------------------------- /Sources/SwiftMacros/URLRequestBuilder.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public protocol URLRequestComponent {} 4 | 5 | extension URL: URLRequestComponent {} 6 | 7 | public struct RequestCachePolicy: URLRequestComponent { 8 | public let value: URLRequest.CachePolicy 9 | 10 | public init(_ value: URLRequest.CachePolicy) { 11 | self.value = value 12 | } 13 | } 14 | 15 | public struct RequestTimeOutInterval: URLRequestComponent { 16 | public let value: TimeInterval 17 | 18 | public init(_ value: TimeInterval) { 19 | self.value = value 20 | } 21 | } 22 | 23 | public enum RequestHTTPMethod: String, URLRequestComponent { 24 | case get = "GET" 25 | case put = "PUT" 26 | case post = "POST" 27 | case delete = "DELETE" 28 | case head = "HEAD" 29 | case options = "OPTIONS" 30 | case trace = "TRACE" 31 | case connect = "CONNECT" 32 | } 33 | 34 | public struct RequestHTTPBody: URLRequestComponent { 35 | public let value: Data 36 | 37 | public init(_ value: Data) { 38 | self.value = value 39 | } 40 | } 41 | 42 | public struct RequestHTTPBodyStream: URLRequestComponent { 43 | public let value: InputStream 44 | 45 | public init(_ value: InputStream) { 46 | self.value = value 47 | } 48 | } 49 | 50 | public struct RequestMainDocumentURL: URLRequestComponent { 51 | public let value: URL 52 | 53 | public init(_ value: URL) { 54 | self.value = value 55 | } 56 | } 57 | 58 | public struct RequestHTTPHeaderFields: URLRequestComponent { 59 | public let value: [String : String] 60 | 61 | public init(_ value: [String : String]) { 62 | self.value = value 63 | } 64 | } 65 | 66 | public struct RequestShouldHandleCookies: URLRequestComponent { 67 | public let value: Bool 68 | 69 | public init(_ value: Bool) { 70 | self.value = value 71 | } 72 | } 73 | 74 | public struct RequestShouldUsePipelining: URLRequestComponent { 75 | public let value: Bool 76 | 77 | public init(_ value: Bool) { 78 | self.value = value 79 | } 80 | } 81 | 82 | public struct RequestAllowsCellularAccess: URLRequestComponent { 83 | public let value: Bool 84 | 85 | public init(_ value: Bool) { 86 | self.value = value 87 | } 88 | } 89 | 90 | public struct RequestAllowsConstrainedNetworkAccess: URLRequestComponent { 91 | public let value: Bool 92 | 93 | public init(_ value: Bool) { 94 | self.value = value 95 | } 96 | } 97 | 98 | public struct RequestAllowsExpensiveNetworkAccess: URLRequestComponent { 99 | public let value: Bool 100 | 101 | public init(_ value: Bool) { 102 | self.value = value 103 | } 104 | } 105 | 106 | public struct RequestNetworkServiceType: URLRequestComponent { 107 | public let value: URLRequest.NetworkServiceType 108 | 109 | public init(_ value: URLRequest.NetworkServiceType) { 110 | self.value = value 111 | } 112 | } 113 | 114 | @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *) 115 | public struct RequestAttribution: URLRequestComponent { 116 | public let value: URLRequest.Attribution 117 | 118 | public init(_ value: URLRequest.Attribution) { 119 | self.value = value 120 | } 121 | } 122 | 123 | public struct RequestAssumesHTTP3Capable: URLRequestComponent { 124 | public let value: Bool 125 | 126 | public init(_ value: Bool) { 127 | self.value = value 128 | } 129 | } 130 | 131 | public struct RequestRequiresDNSSECValidation: URLRequestComponent { 132 | public let value: Bool 133 | 134 | public init(_ value: Bool) { 135 | self.value = value 136 | } 137 | } 138 | 139 | @resultBuilder 140 | public struct URLRequestBuilder { 141 | public static func buildBlock(_ parts: [URLRequestComponent]...) -> [URLRequestComponent] { 142 | parts.flatMap { $0 } 143 | } 144 | 145 | public static func buildExpression(_ expression: URLRequestComponent) -> [URLRequestComponent] { 146 | [expression] 147 | } 148 | 149 | public static func buildFinalResult(_ components: [URLRequestComponent]) -> URLRequest? { 150 | let componentDict = Dictionary(grouping: components) { $0 is URL } 151 | guard let url = componentDict[true]?.last as? URL else { return nil } 152 | var request = URLRequest(url: url) 153 | componentDict[false]?.forEach { component in 154 | if let value = component as? RequestCachePolicy { 155 | request.cachePolicy = value.value 156 | } else if let value = component as? RequestTimeOutInterval { 157 | request.timeoutInterval = value.value 158 | } else if let value = component as? RequestHTTPMethod { 159 | request.httpMethod = value.rawValue 160 | } else if let value = component as? RequestHTTPBody { 161 | request.httpBody = value.value 162 | } else if let value = component as? RequestHTTPBodyStream { 163 | request.httpBodyStream = value.value 164 | } else if let value = component as? RequestMainDocumentURL { 165 | request.mainDocumentURL = value.value 166 | } else if let value = component as? RequestHTTPHeaderFields { 167 | request.allHTTPHeaderFields = value.value 168 | } else if let value = component as? RequestShouldHandleCookies { 169 | request.httpShouldHandleCookies = value.value 170 | } else if let value = component as? RequestShouldUsePipelining { 171 | request.httpShouldUsePipelining = value.value 172 | } else if let value = component as? RequestAllowsCellularAccess { 173 | request.allowsCellularAccess = value.value 174 | } else if let value = component as? RequestAllowsConstrainedNetworkAccess { 175 | request.allowsConstrainedNetworkAccess = value.value 176 | } else if let value = component as? RequestAllowsExpensiveNetworkAccess { 177 | request.allowsExpensiveNetworkAccess = value.value 178 | } else if let value = component as? RequestNetworkServiceType { 179 | request.networkServiceType = value.value 180 | } 181 | if #available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *), 182 | let value = component as? RequestAttribution { 183 | request.attribution = value.value 184 | } 185 | 186 | if #available(macOS 11.3, iOS 14.5, watchOS 7.4, tvOS 14.5, *), 187 | let value = component as? RequestAssumesHTTP3Capable { 188 | request.assumesHTTP3Capable = value.value 189 | } 190 | 191 | if #available(macOS 13.0, iOS 16.1, watchOS 9.1, tvOS 16.1, *), 192 | let value = component as? RequestRequiresDNSSECValidation { 193 | request.requiresDNSSECValidation = value.value 194 | } 195 | } 196 | return request 197 | } 198 | } 199 | 200 | public func buildURLRequest(@URLRequestBuilder builder: () -> URLRequest?) -> URLRequest? { 201 | builder() 202 | } 203 | -------------------------------------------------------------------------------- /Tests/MacroTests/AccessTests.swift: -------------------------------------------------------------------------------- 1 | import SwiftSyntaxMacros 2 | import SwiftSyntaxMacrosTestSupport 3 | import XCTest 4 | import Macros 5 | 6 | final class AccessTests: XCTestCase { 7 | let testMacros: [String: Macro.Type] = [ 8 | "Access": Access.self, 9 | ] 10 | 11 | func testAccessUserDefaultsMacro() { 12 | assertMacroExpansion( 13 | """ 14 | @Access(.userDefaults) 15 | var isPaidUser1: Bool = false 16 | @Access(.userDefaults) 17 | var isPaidUser2: Bool = false 18 | @Access(.userDefaults) 19 | var isPaidUser3: Bool? 20 | """, 21 | expandedSource: 22 | """ 23 | 24 | var isPaidUser1: Bool { 25 | get { 26 | (UserDefaults.standard.object(forKey: "AccessKey_isPaidUser1") as? Bool) ?? false 27 | } 28 | set { 29 | UserDefaults.standard.set(newValue, forKey: "AccessKey_isPaidUser1") 30 | } 31 | } 32 | var isPaidUser2: Bool { 33 | get { 34 | (UserDefaults.standard.object(forKey: "AccessKey_isPaidUser2") as? Bool) ?? false 35 | } 36 | set { 37 | UserDefaults.standard.set(newValue, forKey: "AccessKey_isPaidUser2") 38 | } 39 | } 40 | var isPaidUser3: Bool? { 41 | get { 42 | (UserDefaults.standard.object(forKey: "AccessKey_isPaidUser3") as? Bool) 43 | } 44 | set { 45 | UserDefaults.standard.set(newValue, forKey: "AccessKey_isPaidUser3") 46 | } 47 | } 48 | """, 49 | macros: testMacros 50 | ) 51 | } 52 | 53 | func testAccessUserDefaultsWithValueMacro() { 54 | assertMacroExpansion( 55 | """ 56 | @Access(.userDefaults(.standard)) 57 | var isPaidUser1: Bool = false 58 | @Access(.userDefaults(UserDefaults())) 59 | var isPaidUser2: Bool = false 60 | """, 61 | expandedSource: 62 | """ 63 | 64 | var isPaidUser1: Bool { 65 | get { 66 | (UserDefaults.standard.object(forKey: "AccessKey_isPaidUser1") as? Bool) ?? false 67 | } 68 | set { 69 | UserDefaults.standard.set(newValue, forKey: "AccessKey_isPaidUser1") 70 | } 71 | } 72 | var isPaidUser2: Bool { 73 | get { 74 | (UserDefaults().object(forKey: "AccessKey_isPaidUser2") as? Bool) ?? false 75 | } 76 | set { 77 | UserDefaults().set(newValue, forKey: "AccessKey_isPaidUser2") 78 | } 79 | } 80 | """, 81 | macros: testMacros 82 | ) 83 | } 84 | 85 | func testAccessNSCacheMacro() { 86 | assertMacroExpansion( 87 | """ 88 | @Access(.nsCache(cache)) 89 | var isPaidUser1: NSObject = NSObject() 90 | @Access(.nsCache(cache)) 91 | var isPaidUser2: NSObject? 92 | """, 93 | expandedSource: 94 | """ 95 | 96 | var isPaidUser1: NSObject { 97 | get { 98 | (cache.object(forKey: "AccessKey_isPaidUser1") as? NSObject) ?? NSObject() 99 | } 100 | set { 101 | cache.setObject(newValue, forKey: "AccessKey_isPaidUser1") 102 | } 103 | } 104 | var isPaidUser2: NSObject? { 105 | get { 106 | (cache.object(forKey: "AccessKey_isPaidUser2") as? NSObject) 107 | } 108 | set { 109 | if let value = newValue { 110 | cache.setObject(value, forKey: "AccessKey_isPaidUser2") 111 | } else { 112 | cache.removeObject(forKey: "AccessKey_isPaidUser2") 113 | } 114 | } 115 | } 116 | """, 117 | macros: testMacros 118 | ) 119 | } 120 | 121 | func testAccessNSMapTableMacro() { 122 | assertMacroExpansion( 123 | """ 124 | @Access(.nsMapTable(cache)) 125 | var isPaidUser1: NSObject = NSObject() 126 | @Access(.nsMapTable(cache)) 127 | var isPaidUser2: NSObject? 128 | """, 129 | expandedSource: 130 | """ 131 | 132 | var isPaidUser1: NSObject { 133 | get { 134 | (cache.object(forKey: "AccessKey_isPaidUser1") as? NSObject) ?? NSObject() 135 | } 136 | set { 137 | cache.setObject(newValue, forKey: "AccessKey_isPaidUser1") 138 | } 139 | } 140 | var isPaidUser2: NSObject? { 141 | get { 142 | (cache.object(forKey: "AccessKey_isPaidUser2") as? NSObject) 143 | } 144 | set { 145 | if let value = newValue { 146 | cache.setObject(value, forKey: "AccessKey_isPaidUser2") 147 | } else { 148 | cache.removeObject(forKey: "AccessKey_isPaidUser2") 149 | } 150 | } 151 | } 152 | """, 153 | macros: testMacros 154 | ) 155 | } 156 | 157 | func testAccessKeychainMacro() { 158 | assertMacroExpansion( 159 | """ 160 | @Access(.keychain) 161 | var keychainValue: CodableStruct? 162 | """, 163 | expandedSource: 164 | """ 165 | 166 | var keychainValue: CodableStruct? { 167 | get { 168 | try? SwiftKeychain.search(key: "AccessKey_keychainValue") 169 | } 170 | set { 171 | if let value = newValue { 172 | SwiftKeychain.delete(key: "AccessKey_keychainValue") 173 | try? SwiftKeychain.add(value: value, for: "AccessKey_keychainValue") 174 | } else { 175 | SwiftKeychain.delete(key: "AccessKey_keychainValue") 176 | } 177 | } 178 | } 179 | """, 180 | macros: testMacros 181 | ) 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /Tests/MacroTests/AddAssociatedValueVariableTests.swift: -------------------------------------------------------------------------------- 1 | import SwiftSyntaxMacros 2 | import SwiftSyntaxMacrosTestSupport 3 | import XCTest 4 | import Macros 5 | 6 | final class AddAssociatedValueVairiableTests: XCTestCase { 7 | let testMacros: [String: Macro.Type] = [ 8 | "AddAssociatedValueVariable": AddAssociatedValueVariable.self, 9 | ] 10 | 11 | func testdAssociatedValueVairiableMacro() { 12 | assertMacroExpansion( 13 | """ 14 | @AddAssociatedValueVariable 15 | enum A { 16 | case first 17 | case second(Int) 18 | case third(String, Int), third2(String, Int) 19 | case forth(a: String, b: Int) 20 | case fifth(a: String, Int) 21 | case sixth(a: () -> Void) 22 | case seventh(() -> Void) 23 | } 24 | """, 25 | expandedSource: 26 | """ 27 | 28 | enum A { 29 | case first 30 | case second(Int) 31 | case third(String, Int), third2(String, Int) 32 | case forth(a: String, b: Int) 33 | case fifth(a: String, Int) 34 | case sixth(a: () -> Void) 35 | case seventh(() -> Void) 36 | 37 | var secondValue: Int? { 38 | if case let .second(v0) = self { 39 | return v0 40 | } 41 | return nil 42 | } 43 | 44 | var thirdValue: (String, Int)? { 45 | if case let .third(v0, v1) = self { 46 | return (v0, v1) 47 | } 48 | return nil 49 | } 50 | 51 | var third2Value: (String, Int)? { 52 | if case let .third2(v0, v1) = self { 53 | return (v0, v1) 54 | } 55 | return nil 56 | } 57 | 58 | var forthValue: (a: String, b: Int)? { 59 | if case let .forth(v0, v1) = self { 60 | return (v0, v1) 61 | } 62 | return nil 63 | } 64 | 65 | var fifthValue: (a: String, Int)? { 66 | if case let .fifth(v0, v1) = self { 67 | return (v0, v1) 68 | } 69 | return nil 70 | } 71 | 72 | var sixthValue: (a: () -> Void)? { 73 | if case let .sixth(v0) = self { 74 | return v0 75 | } 76 | return nil 77 | } 78 | 79 | var seventhValue: (() -> Void)? { 80 | if case let .seventh(v0) = self { 81 | return v0 82 | } 83 | return nil 84 | } 85 | } 86 | """, 87 | macros: testMacros 88 | ) 89 | } 90 | 91 | func testdAssociatedValueVairiableModifierMacro() { 92 | assertMacroExpansion( 93 | """ 94 | @AddAssociatedValueVariable 95 | public enum A { 96 | case first 97 | case second(Int) 98 | } 99 | """, 100 | expandedSource: 101 | """ 102 | 103 | public enum A { 104 | case first 105 | case second(Int) 106 | 107 | public var secondValue: Int? { 108 | if case let .second(v0) = self { 109 | return v0 110 | } 111 | return nil 112 | } 113 | } 114 | """, 115 | macros: testMacros 116 | ) 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /Tests/MacroTests/AddInitTests.swift: -------------------------------------------------------------------------------- 1 | import SwiftSyntaxMacros 2 | import SwiftSyntaxMacrosTestSupport 3 | import XCTest 4 | import Macros 5 | 6 | final class AddInitTests: XCTestCase { 7 | let testMacros: [String: Macro.Type] = [ 8 | "AddInit": AddInit.self, 9 | ] 10 | 11 | func testAddInitMacro() { 12 | assertMacroExpansion( 13 | """ 14 | @AddInit 15 | struct A { 16 | let a: Int? 17 | let b: Int 18 | } 19 | """, 20 | expandedSource: 21 | """ 22 | 23 | struct A { 24 | let a: Int? 25 | let b: Int 26 | 27 | init(a: Int? = nil, b: Int) { 28 | self.a = a 29 | self.b = b 30 | } 31 | } 32 | """, 33 | macros: testMacros 34 | ) 35 | } 36 | 37 | func testAddInitWithComputedPropertyMacro() { 38 | assertMacroExpansion( 39 | """ 40 | @AddInit 41 | struct A { 42 | let a: Int? 43 | var b: Int { 44 | 0 45 | } 46 | } 47 | """, 48 | expandedSource: 49 | """ 50 | 51 | struct A { 52 | let a: Int? 53 | var b: Int { 54 | 0 55 | } 56 | 57 | init(a: Int? = nil) { 58 | self.a = a 59 | } 60 | } 61 | """, 62 | macros: testMacros 63 | ) 64 | } 65 | 66 | func testAddPublicInitMacro() { 67 | assertMacroExpansion( 68 | """ 69 | @AddInit 70 | public struct A { 71 | let a: Int? 72 | let b: Int 73 | } 74 | """, 75 | expandedSource: 76 | """ 77 | 78 | public struct A { 79 | let a: Int? 80 | let b: Int 81 | 82 | public init(a: Int? = nil, b: Int) { 83 | self.a = a 84 | self.b = b 85 | } 86 | } 87 | """, 88 | macros: testMacros 89 | ) 90 | } 91 | 92 | func testAddPublicInitClassMacro() { 93 | assertMacroExpansion( 94 | """ 95 | @AddInit 96 | public class A { 97 | let a: Int? 98 | let b: Int 99 | } 100 | """, 101 | expandedSource: 102 | """ 103 | 104 | public class A { 105 | let a: Int? 106 | let b: Int 107 | 108 | public init(a: Int? = nil, b: Int) { 109 | self.a = a 110 | self.b = b 111 | } 112 | } 113 | """, 114 | macros: testMacros 115 | ) 116 | } 117 | 118 | func testAddPublicInitClassClosureMacro() { 119 | assertMacroExpansion( 120 | """ 121 | @AddInit 122 | public class A { 123 | let a: Int? 124 | let b: (Int) -> Void 125 | } 126 | """, 127 | expandedSource: 128 | """ 129 | 130 | public class A { 131 | let a: Int? 132 | let b: (Int) -> Void 133 | 134 | public init(a: Int? = nil, b: @escaping (Int) -> Void) { 135 | self.a = a 136 | self.b = b 137 | } 138 | } 139 | """, 140 | macros: testMacros 141 | ) 142 | } 143 | 144 | func testAddPublicInitOptionalClosureMacro() { 145 | assertMacroExpansion( 146 | """ 147 | @AddInit 148 | public class A { 149 | let a: Int? 150 | let b: ((Int) -> Void)? 151 | } 152 | """, 153 | expandedSource: 154 | """ 155 | 156 | public class A { 157 | let a: Int? 158 | let b: ((Int) -> Void)? 159 | 160 | public init(a: Int? = nil, b: ((Int) -> Void)? = nil) { 161 | self.a = a 162 | self.b = b 163 | } 164 | } 165 | """, 166 | macros: testMacros 167 | ) 168 | } 169 | 170 | func testAddInitToActorMacro() { 171 | assertMacroExpansion( 172 | """ 173 | @AddInit 174 | actor A { 175 | let a: Int 176 | let b: Int? 177 | } 178 | """, 179 | expandedSource: 180 | """ 181 | 182 | actor A { 183 | let a: Int 184 | let b: Int? 185 | 186 | init(a: Int, b: Int? = nil) { 187 | self.a = a 188 | self.b = b 189 | } 190 | } 191 | """, 192 | macros: testMacros 193 | ) 194 | } 195 | 196 | func testAddInitWithMockMacro() { 197 | assertMacroExpansion( 198 | """ 199 | @AddInit(withMock: true, randomMockValue: false) 200 | actor A { 201 | let a: Int 202 | let b: Int? 203 | let c: (Int) -> Int 204 | } 205 | """, 206 | expandedSource: 207 | """ 208 | 209 | actor A { 210 | let a: Int 211 | let b: Int? 212 | let c: (Int) -> Int 213 | 214 | init(a: Int, b: Int? = nil, c: @escaping (Int) -> Int) { 215 | self.a = a 216 | self.b = b 217 | self.c = c 218 | } 219 | 220 | #if DEBUG 221 | static let mock = A(a: 1, b: nil, c: { _ in 222 | return 1 223 | }) 224 | #endif 225 | } 226 | """, 227 | macros: testMacros 228 | ) 229 | } 230 | 231 | func testAddInitWithMockCollectionTypesMacro() { 232 | assertMacroExpansion( 233 | """ 234 | @AddInit(withMock: true, randomMockValue: false) 235 | actor A { 236 | let a: [Int] 237 | let b: Set 238 | let c: [Int: String] 239 | } 240 | """, 241 | expandedSource: 242 | """ 243 | 244 | actor A { 245 | let a: [Int] 246 | let b: Set 247 | let c: [Int: String] 248 | 249 | init(a: [Int], b: Set, c: [Int: String]) { 250 | self.a = a 251 | self.b = b 252 | self.c = c 253 | } 254 | 255 | #if DEBUG 256 | static let mock = A(a: [1], b: [1], c: [1: "abcd"]) 257 | #endif 258 | } 259 | """, 260 | macros: testMacros 261 | ) 262 | } 263 | 264 | func testAddInitWithCustomisedTypesMacro() { 265 | assertMacroExpansion( 266 | """ 267 | struct A { 268 | let a: Int 269 | } 270 | @AddInit(withMock: true, randomMockValue: false) 271 | struct B { 272 | let a: A 273 | } 274 | """, 275 | expandedSource: 276 | """ 277 | struct A { 278 | let a: Int 279 | } 280 | struct B { 281 | let a: A 282 | 283 | init(a: A) { 284 | self.a = a 285 | } 286 | 287 | #if DEBUG 288 | static let mock = B(a: A.mock) 289 | #endif 290 | } 291 | """, 292 | macros: testMacros 293 | ) 294 | } 295 | } 296 | -------------------------------------------------------------------------------- /Tests/MacroTests/AddPublisherTests.swift: -------------------------------------------------------------------------------- 1 | import SwiftSyntaxMacros 2 | import SwiftSyntaxMacrosTestSupport 3 | import XCTest 4 | import Macros 5 | 6 | final class AddPublisherTests: XCTestCase { 7 | let testMacros: [String: Macro.Type] = [ 8 | "AddPublisher": AddPublisher.self, 9 | ] 10 | 11 | func testAddPublisherPassthroughSubjectMacro() { 12 | assertMacroExpansion( 13 | """ 14 | @AddPublisher 15 | private let subject = PassthroughSubject() 16 | """, 17 | expandedSource: 18 | """ 19 | 20 | private let subject = PassthroughSubject() 21 | 22 | var subjectPublisher: AnyPublisher { 23 | subject.eraseToAnyPublisher() 24 | } 25 | """, 26 | macros: testMacros 27 | ) 28 | } 29 | 30 | func testAddPublisherOnCurrentValueSubjectMacro() { 31 | assertMacroExpansion( 32 | """ 33 | @AddPublisher 34 | private let subject = CurrentValueSubject(()) 35 | """, 36 | expandedSource: 37 | """ 38 | 39 | private let subject = CurrentValueSubject(()) 40 | 41 | var subjectPublisher: AnyPublisher { 42 | subject.eraseToAnyPublisher() 43 | } 44 | """, 45 | macros: testMacros 46 | ) 47 | } 48 | 49 | func testAddPublisherOnAlternativeSynatxMacro() { 50 | assertMacroExpansion( 51 | """ 52 | @AddPublisher 53 | private let subject: CurrentValueSubject = .init(()) 54 | """, 55 | expandedSource: 56 | """ 57 | 58 | private let subject: CurrentValueSubject = .init(()) 59 | 60 | var subjectPublisher: AnyPublisher { 61 | subject.eraseToAnyPublisher() 62 | } 63 | """, 64 | macros: testMacros 65 | ) 66 | } 67 | 68 | func testAddPublisherOnUnsupportedTypeMacro() { 69 | assertMacroExpansion( 70 | """ 71 | @AddPublisher 72 | private let subject = MyStruct() 73 | """, 74 | expandedSource: 75 | """ 76 | 77 | private let subject = MyStruct() 78 | """, 79 | diagnostics: [ 80 | DiagnosticSpec(message: "Can only be applied to a subject(PassthroughSubject/CurrentValueSubject) variable declaration", line: 1, column: 1) 81 | ], 82 | macros: testMacros 83 | ) 84 | } 85 | 86 | func testAddPublisherOnNotPrivateTypeMacro() { 87 | assertMacroExpansion( 88 | """ 89 | @AddPublisher 90 | let subject = PassthroughSubject() 91 | """, 92 | expandedSource: 93 | """ 94 | 95 | let subject = PassthroughSubject() 96 | """, 97 | diagnostics: [ 98 | DiagnosticSpec(message: "Please make the subject private and use the automated generated publisher variable outsite of this type", line: 1, column: 1) 99 | ], 100 | macros: testMacros 101 | ) 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /Tests/MacroTests/BuildDateTests.swift: -------------------------------------------------------------------------------- 1 | import SwiftSyntaxMacros 2 | import SwiftSyntaxMacrosTestSupport 3 | import XCTest 4 | import Macros 5 | 6 | final class BuildDateTests: XCTestCase { 7 | let testMacros: [String: Macro.Type] = [ 8 | "buildDate": BuildDate.self, 9 | ] 10 | 11 | func testBuildDateMacro() { 12 | assertMacroExpansion( 13 | """ 14 | let date = #buildDate(DateString("03/05/2003", dateFormat: "MM/dd/yyyy"), 15 | Date(), 16 | Month(10), 17 | Year(1909), 18 | YearForWeekOfYear(2025)) 19 | """, 20 | expandedSource: 21 | """ 22 | let date = buildDate { 23 | DateString("03/05/2003", dateFormat: "MM/dd/yyyy") 24 | Date() 25 | Month(10) 26 | Year(1909) 27 | YearForWeekOfYear(2025) 28 | } 29 | """, 30 | macros: testMacros 31 | ) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Tests/MacroTests/BuildURLRequestTests.swift: -------------------------------------------------------------------------------- 1 | import SwiftSyntaxMacros 2 | import SwiftSyntaxMacrosTestSupport 3 | import XCTest 4 | import Macros 5 | 6 | final class BuildURLRequestTests: XCTestCase { 7 | let testMacros: [String: Macro.Type] = [ 8 | "buildURLRequest": BuildURLRequest.self, 9 | ] 10 | 11 | func testBuildURLRequestMacro() { 12 | assertMacroExpansion( 13 | """ 14 | let url = #buildURLRequest(URL(string: "http://google.com")!, RequestTimeOutInterval(100)) 15 | """, 16 | expandedSource: 17 | """ 18 | let url = buildURLRequest { 19 | URL(string: "http://google.com")! 20 | RequestTimeOutInterval(100) 21 | } 22 | """, 23 | macros: testMacros 24 | ) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Tests/MacroTests/BuildURLTests.swift: -------------------------------------------------------------------------------- 1 | import SwiftSyntaxMacros 2 | import SwiftSyntaxMacrosTestSupport 3 | import XCTest 4 | import Macros 5 | 6 | final class BuildURLTests: XCTestCase { 7 | let testMacros: [String: Macro.Type] = [ 8 | "buildURL": BuildURL.self, 9 | ] 10 | 11 | func testBuildURLMacro() { 12 | assertMacroExpansion( 13 | """ 14 | let url = #buildURL("http://url.com", 15 | URLScheme(.https), 16 | URLFragment("fragment"), 17 | URLQueryItems([.init(name: "q1", value: "q1v"), .init(name: "q2", value: "q2v")])) 18 | """, 19 | expandedSource: 20 | """ 21 | let url = buildURL { 22 | "http://url.com" 23 | URLScheme(.https) 24 | URLFragment("fragment") 25 | URLQueryItems([.init(name: "q1", value: "q1v"), .init(name: "q2", value: "q2v")]) 26 | } 27 | """, 28 | macros: testMacros 29 | ) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Tests/MacroTests/ConformToEqutableTests.swift: -------------------------------------------------------------------------------- 1 | import SwiftSyntaxMacros 2 | import SwiftSyntaxMacrosTestSupport 3 | import XCTest 4 | import Macros 5 | 6 | final class ConformToEqutableTests: XCTestCase { 7 | let testMacros: [String: Macro.Type] = [ 8 | "ConformToEquatable": ConformToEquatable.self, 9 | ] 10 | 11 | func testConformToEquatableMacro() { 12 | assertMacroExpansion( 13 | """ 14 | @ConformToEquatable 15 | class AClass { 16 | let a: Int 17 | let b: Int 18 | init(a: Int, b: Int) { 19 | self.a = a 20 | self.b = b 21 | } 22 | } 23 | """, 24 | expandedSource: 25 | """ 26 | class AClass { 27 | let a: Int 28 | let b: Int 29 | init(a: Int, b: Int) { 30 | self.a = a 31 | self.b = b 32 | } 33 | } 34 | 35 | extension AClass: Equatable { 36 | static func == (lhs: AClass, rhs: AClass) -> Bool { 37 | lhs.a == rhs.a 38 | && lhs.b == rhs.b 39 | } 40 | } 41 | """, 42 | macros: testMacros 43 | ) 44 | } 45 | 46 | func testConformToEquatableIgnoreClosureTypeMacro() { 47 | assertMacroExpansion( 48 | """ 49 | @ConformToEquatable 50 | class AClass { 51 | let a: Int 52 | let b: (Int) -> Void 53 | init(a: Int, b: (Int) -> Void) { 54 | self.a = a 55 | self.b = b 56 | } 57 | } 58 | """, 59 | expandedSource: 60 | """ 61 | class AClass { 62 | let a: Int 63 | let b: (Int) -> Void 64 | init(a: Int, b: (Int) -> Void) { 65 | self.a = a 66 | self.b = b 67 | } 68 | } 69 | 70 | extension AClass: Equatable { 71 | static func == (lhs: AClass, rhs: AClass) -> Bool { 72 | lhs.a == rhs.a 73 | } 74 | } 75 | """, 76 | macros: testMacros 77 | ) 78 | } 79 | 80 | func testConformToEquatableIncludeOptionalTypeMacro() { 81 | assertMacroExpansion( 82 | """ 83 | @ConformToEquatable 84 | class AClass { 85 | let a: Int? 86 | init(a: Int?) { 87 | self.a = a 88 | } 89 | } 90 | """, 91 | expandedSource: 92 | """ 93 | class AClass { 94 | let a: Int? 95 | init(a: Int?) { 96 | self.a = a 97 | } 98 | } 99 | 100 | extension AClass: Equatable { 101 | static func == (lhs: AClass, rhs: AClass) -> Bool { 102 | lhs.a == rhs.a 103 | } 104 | } 105 | """, 106 | macros: testMacros 107 | ) 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /Tests/MacroTests/ConformToHashableTests.swift: -------------------------------------------------------------------------------- 1 | import SwiftSyntaxMacros 2 | import SwiftSyntaxMacrosTestSupport 3 | import XCTest 4 | import Macros 5 | 6 | final class ConformToHashableTests: XCTestCase { 7 | let testMacros: [String: Macro.Type] = [ 8 | "ConformToHashable": ConformToHashable.self, 9 | ] 10 | 11 | func testConformToEquatableMacro() { 12 | assertMacroExpansion( 13 | """ 14 | @ConformToHashable 15 | class AClass { 16 | let a: Int 17 | let b: Int 18 | init(a: Int, b: Int) { 19 | self.a = a 20 | self.b = b 21 | } 22 | } 23 | """, 24 | expandedSource: 25 | """ 26 | class AClass { 27 | let a: Int 28 | let b: Int 29 | init(a: Int, b: Int) { 30 | self.a = a 31 | self.b = b 32 | } 33 | } 34 | 35 | extension AClass: Hashable { 36 | func hash(into hasher: inout Hasher) { 37 | hasher.combine(a) 38 | hasher.combine(b) 39 | } 40 | } 41 | """, 42 | macros: testMacros 43 | ) 44 | } 45 | 46 | func testConformToEquatableIgnoreClosureTypeMacro() { 47 | assertMacroExpansion( 48 | """ 49 | @ConformToHashable 50 | class AClass { 51 | let a: Int 52 | let b: (Int) -> Void 53 | init(a: Int, b: (Int) -> Void) { 54 | self.a = a 55 | self.b = b 56 | } 57 | } 58 | """, 59 | expandedSource: 60 | """ 61 | class AClass { 62 | let a: Int 63 | let b: (Int) -> Void 64 | init(a: Int, b: (Int) -> Void) { 65 | self.a = a 66 | self.b = b 67 | } 68 | } 69 | 70 | extension AClass: Hashable { 71 | func hash(into hasher: inout Hasher) { 72 | hasher.combine(a) 73 | } 74 | } 75 | """, 76 | macros: testMacros 77 | ) 78 | } 79 | 80 | func testConformToEquatableIncludeOptionalTypeMacro() { 81 | assertMacroExpansion( 82 | """ 83 | @ConformToHashable 84 | class AClass { 85 | let a: Int? 86 | init(a: Int?) { 87 | self.a = a 88 | } 89 | } 90 | """, 91 | expandedSource: 92 | """ 93 | class AClass { 94 | let a: Int? 95 | init(a: Int?) { 96 | self.a = a 97 | } 98 | } 99 | 100 | extension AClass: Hashable { 101 | func hash(into hasher: inout Hasher) { 102 | hasher.combine(a) 103 | } 104 | } 105 | """, 106 | macros: testMacros 107 | ) 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /Tests/MacroTests/FormatDateComponentsTests.swift: -------------------------------------------------------------------------------- 1 | import SwiftSyntaxMacros 2 | import SwiftSyntaxMacrosTestSupport 3 | import XCTest 4 | import Macros 5 | 6 | final class FormatDateComponentsTests: XCTestCase { 7 | let testMacros: [String: Macro.Type] = [ 8 | "formatDateComponents": FormatDateComponents.self, 9 | ] 10 | 11 | func testFormatDateComponentsFromToMacro() { 12 | assertMacroExpansion( 13 | """ 14 | let date = #formatDateComponents(from: Date(), to: Date(), allowedUnits: [.day, .hour, .minute, .second]) 15 | """, 16 | expandedSource: 17 | """ 18 | let date = { 19 | let formatter = DateComponentsFormatter() 20 | formatter.allowedUnits = [.day, .hour, .minute, .second] 21 | return formatter.string(from: Date(), to: Date()) 22 | }() 23 | """, 24 | macros: testMacros 25 | ) 26 | } 27 | 28 | func testFormatDateComponentsFromTimeintervalMacro() { 29 | assertMacroExpansion( 30 | """ 31 | let date = #formatDateComponents(fromInterval: 100, allowedUnits: [.day, .hour, .minute, .second]) 32 | """, 33 | expandedSource: 34 | """ 35 | let date = { 36 | let formatter = DateComponentsFormatter() 37 | formatter.allowedUnits = [.day, .hour, .minute, .second] 38 | return formatter.string(from: 100) 39 | }() 40 | """, 41 | macros: testMacros 42 | ) 43 | } 44 | 45 | func testFormatDateComponentsFromComponentsMacro() { 46 | assertMacroExpansion( 47 | """ 48 | let date = #formatDateComponents(fromComponents: DateComponents(hour: 10), allowedUnits: [.day, .hour, .minute, .second]) 49 | """, 50 | expandedSource: 51 | """ 52 | let date = { 53 | let formatter = DateComponentsFormatter() 54 | formatter.allowedUnits = [.day, .hour, .minute, .second] 55 | return formatter.string(from: DateComponents(hour: 10)) 56 | }() 57 | """, 58 | macros: testMacros 59 | ) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Tests/MacroTests/FormatDateIntervalTests.swift: -------------------------------------------------------------------------------- 1 | import SwiftSyntaxMacros 2 | import SwiftSyntaxMacrosTestSupport 3 | import XCTest 4 | import Macros 5 | 6 | final class FormatDateIntervalTests: XCTestCase { 7 | let testMacros: [String: Macro.Type] = [ 8 | "formatDateInterval": FormatDateInterval.self, 9 | ] 10 | 11 | func testFormatDateIntervalMacro() { 12 | assertMacroExpansion( 13 | """ 14 | let date = #formatDateInterval(from: Date(), to: Date(), dateStyle: .short) 15 | """, 16 | expandedSource: 17 | """ 18 | let date = { 19 | let formatter = DateIntervalFormatter() 20 | formatter.dateStyle = .short 21 | return formatter.string(from: Date(), to: Date()) 22 | }() 23 | """, 24 | macros: testMacros 25 | ) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Tests/MacroTests/FormatDateTests.swift: -------------------------------------------------------------------------------- 1 | import SwiftSyntaxMacros 2 | import SwiftSyntaxMacrosTestSupport 3 | import XCTest 4 | import Macros 5 | 6 | final class FormatDateTests: XCTestCase { 7 | let testMacros: [String: Macro.Type] = [ 8 | "formatDate": FormatDate.self, 9 | ] 10 | 11 | func testFormateDateMacro() { 12 | assertMacroExpansion( 13 | """ 14 | let date = #formatDate(Date(), dateStyle: .full) 15 | """, 16 | expandedSource: 17 | """ 18 | let date = { 19 | let formatter = DateFormatter() 20 | formatter.dateStyle = .full 21 | return formatter.string(from: Date()) 22 | }() 23 | """, 24 | macros: testMacros 25 | ) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Tests/MacroTests/JSONEncoderDecoderTests.swift: -------------------------------------------------------------------------------- 1 | import SwiftSyntaxMacros 2 | import SwiftSyntaxMacrosTestSupport 3 | import XCTest 4 | import Macros 5 | 6 | final class JSONEncoderDecoderTests: XCTestCase { 7 | let testMacros: [String: Macro.Type] = [ 8 | "encode": Encode.self, 9 | "decode": Decode.self 10 | ] 11 | 12 | func testEncodeMacro() { 13 | assertMacroExpansion( 14 | """ 15 | let data = #encode(TestStruct()) 16 | """, 17 | expandedSource: 18 | """ 19 | let data = { 20 | let encoder = JSONEncoder() 21 | return try encoder.encode(TestStruct()) 22 | }() 23 | """, 24 | macros: testMacros 25 | ) 26 | } 27 | 28 | func testEncodeErrorMacro() { 29 | assertMacroExpansion( 30 | """ 31 | let data = #encode() 32 | """, 33 | expandedSource: 34 | """ 35 | let data = #encode() 36 | """, 37 | diagnostics: [ 38 | DiagnosticSpec(message: "Must specify the value to encode", line: 1, column: 12) 39 | ], 40 | macros: testMacros 41 | ) 42 | } 43 | 44 | func testEncodeWithDefaultValuesMacro() { 45 | assertMacroExpansion( 46 | """ 47 | let data = #encode(TestStruct(), outputFormatting: [.prettyPrinted, .sortedKeys], dateEncodingStrategy: .iso8601, dataEncodingStrategy: .base64, userInfo: [TestKey: 0]) 48 | """, 49 | expandedSource: 50 | """ 51 | let data = { 52 | let encoder = JSONEncoder() 53 | encoder.outputFormatting = [.prettyPrinted, .sortedKeys] 54 | encoder.dateEncodingStrategy = .iso8601 55 | encoder.dataEncodingStrategy = .base64 56 | encoder.userInfo = [TestKey: 0] 57 | return try encoder.encode(TestStruct()) 58 | }() 59 | """, 60 | macros: testMacros 61 | ) 62 | } 63 | 64 | func testDecodeMacro() { 65 | assertMacroExpansion( 66 | """ 67 | let value = #decode(TestStruct.self, from: data) 68 | """, 69 | expandedSource: 70 | """ 71 | let value = { 72 | let decoder = JSONDecoder() 73 | return try decoder.decode(TestStruct.self, from: data) 74 | }() 75 | """, 76 | macros: testMacros 77 | ) 78 | } 79 | 80 | func testDecodeErrorMacro() { 81 | assertMacroExpansion( 82 | """ 83 | let data = #decode(from: data) 84 | """, 85 | expandedSource: 86 | """ 87 | let data = #decode(from: data) 88 | """, 89 | diagnostics: [ 90 | DiagnosticSpec(message: "Must specify the type and the value to decode", line: 1, column: 12) 91 | ], 92 | macros: testMacros 93 | ) 94 | } 95 | 96 | func testDecodeWithDefaultValuesMacro() { 97 | assertMacroExpansion( 98 | """ 99 | let data = #decode(TestStruct.self, from: data, dateDecodingStrategy: .iso8601, dataDecodingStrategy: .base64, userInfo: [TestKey: 0], allowsJSON5: true, assumesTopLevelDictionary: false) 100 | """, 101 | expandedSource: 102 | """ 103 | let data = { 104 | let decoder = JSONDecoder() 105 | decoder.dateDecodingStrategy = .iso8601 106 | decoder.dataDecodingStrategy = .base64 107 | decoder.userInfo = [TestKey: 0] 108 | decoder.allowsJSON5 = true 109 | return try decoder.decode(TestStruct.self, from: data) 110 | }() 111 | """, 112 | macros: testMacros 113 | ) 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /Tests/MacroTests/MockTests.swift: -------------------------------------------------------------------------------- 1 | import SwiftSyntaxMacros 2 | import SwiftSyntaxMacrosTestSupport 3 | import XCTest 4 | import Macros 5 | 6 | final class MockTests: XCTestCase { 7 | let testMacros: [String: Macro.Type] = [ 8 | "Mock": Mock.self, 9 | ] 10 | 11 | func testMockMacro() { 12 | assertMacroExpansion( 13 | """ 14 | struct AStruct { 15 | let a: Int 16 | 17 | @Mock(type: AStruct.self, randomMockValue: false) 18 | init(a: Int) { 19 | self.a = a 20 | } 21 | } 22 | """, 23 | expandedSource: 24 | """ 25 | struct AStruct { 26 | let a: Int 27 | init(a: Int) { 28 | self.a = a 29 | } 30 | 31 | #if DEBUG 32 | static let mock = AStruct(a: 1) 33 | #endif 34 | } 35 | """, 36 | macros: testMacros 37 | ) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Tests/MacroTests/NotificationTests.swift: -------------------------------------------------------------------------------- 1 | import SwiftSyntaxMacros 2 | import SwiftSyntaxMacrosTestSupport 3 | import XCTest 4 | import Macros 5 | 6 | final class NotificationTests: XCTestCase { 7 | let testMacros: [String: Macro.Type] = [ 8 | "postNotification": PostNotification.self, 9 | ] 10 | 11 | func testPostNotificationWithNameMacro() { 12 | assertMacroExpansion( 13 | """ 14 | #postNotification(.NSCalendarDayChanged) 15 | """, 16 | expandedSource: 17 | """ 18 | NotificationCenter.default.post(name: .NSCalendarDayChanged, object: nil, userInfo: nil) 19 | """, 20 | macros: testMacros 21 | ) 22 | } 23 | 24 | func testPostNotificationWithNameAndCenterMacro() { 25 | assertMacroExpansion( 26 | """ 27 | #postNotification(.NSCalendarDayChanged, from: NotificationCenter()) 28 | """, 29 | expandedSource: 30 | """ 31 | NotificationCenter().post(name: .NSCalendarDayChanged, object: nil, userInfo: nil) 32 | """, 33 | macros: testMacros 34 | ) 35 | } 36 | 37 | func testPostNotificationWithNameWithUserInfoMacro() { 38 | assertMacroExpansion( 39 | """ 40 | #postNotification(.NSCalendarDayChanged, userInfo: ["value": 0]) 41 | """, 42 | expandedSource: 43 | """ 44 | NotificationCenter.default.post(name: .NSCalendarDayChanged, object: nil, userInfo: ["value": 0]) 45 | """, 46 | macros: testMacros 47 | ) 48 | } 49 | 50 | func testPostNotificationWithNameWithObjectMacro() { 51 | assertMacroExpansion( 52 | """ 53 | #postNotification(.NSCalendarDayChanged, object: NSObject()) 54 | """, 55 | expandedSource: 56 | """ 57 | NotificationCenter.default.post(name: .NSCalendarDayChanged, object: NSObject(), userInfo: nil) 58 | """, 59 | macros: testMacros 60 | ) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Tests/MacroTests/SingletonTests.swift: -------------------------------------------------------------------------------- 1 | import SwiftSyntaxMacros 2 | import SwiftSyntaxMacrosTestSupport 3 | import XCTest 4 | import Macros 5 | 6 | final class SingletonTests: XCTestCase { 7 | let testMacros: [String: Macro.Type] = [ 8 | "Singleton": Singleton.self, 9 | ] 10 | 11 | func testSingletonMacro() { 12 | assertMacroExpansion( 13 | """ 14 | @Singleton 15 | struct A { 16 | 17 | } 18 | """, 19 | expandedSource: 20 | """ 21 | 22 | struct A { 23 | 24 | private init() { 25 | } 26 | 27 | static let shared = A() 28 | 29 | } 30 | """, 31 | macros: testMacros 32 | ) 33 | } 34 | 35 | func testPublicSingletonMacro() { 36 | assertMacroExpansion( 37 | """ 38 | @Singleton 39 | public struct A { 40 | 41 | } 42 | """, 43 | expandedSource: 44 | """ 45 | 46 | public struct A { 47 | 48 | private init() { 49 | } 50 | 51 | public static let shared = A() 52 | 53 | } 54 | """, 55 | macros: testMacros 56 | ) 57 | } 58 | 59 | func testSingletonErrorMacro() { 60 | assertMacroExpansion( 61 | """ 62 | @Singleton 63 | enum A {} 64 | """, 65 | expandedSource: 66 | """ 67 | 68 | enum A {} 69 | """, 70 | diagnostics: [ 71 | DiagnosticSpec(message: "Can only be applied to a struct or class", line: 1, column: 1) 72 | ], 73 | macros: testMacros 74 | ) 75 | } 76 | 77 | func testSingletonNSObjectMacro() { 78 | assertMacroExpansion( 79 | """ 80 | @Singleton 81 | class A: NSObject { 82 | 83 | } 84 | """, 85 | expandedSource: 86 | """ 87 | 88 | class A: NSObject { 89 | 90 | private override init() { 91 | } 92 | 93 | static let shared = A() 94 | 95 | } 96 | """, 97 | macros: testMacros 98 | ) 99 | } 100 | } 101 | --------------------------------------------------------------------------------