├── .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 |
--------------------------------------------------------------------------------