├── .gitignore
├── .swiftpm
└── xcode
│ ├── package.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
│ └── xcshareddata
│ └── xcschemes
│ ├── SwiftAndTipsMacros-Package.xcscheme
│ └── SwiftAndTipsMacros.xcscheme
├── LICENSE
├── Package.resolved
├── Package.swift
├── README.md
├── Sources
├── DataCategory
│ └── DataCategory.swift
├── DataGenerator
│ ├── DataGenerator.swift
│ ├── DataProvider
│ │ ├── DataProvider.swift
│ │ └── FakeDataProvider.swift
│ └── SampleBuilderItemCategory.swift
├── Macros
│ ├── BinaryString
│ │ └── BinaryStringMacro.swift
│ ├── Extensions
│ │ ├── EnumCaseDeclSyntax+Extension.swift
│ │ ├── TypeSyntax+Extension.swift
│ │ └── VariableDeclSyntax+Extension.swift
│ ├── Plugin
│ │ └── SwiftAndTipsMacrosPlugin.swift
│ ├── SampleBuilder
│ │ ├── Diagnostic
│ │ │ ├── SampleBuilderDiagnostic.swift
│ │ │ └── SampleBuilderFixIt.swift
│ │ ├── ParameterItem.swift
│ │ ├── SampleBuilderMacro+Enum.swift
│ │ ├── SampleBuilderMacro+ExprSyntax.swift
│ │ ├── SampleBuilderMacro+Struct.swift
│ │ ├── SampleBuilderMacro.swift
│ │ └── SupportedType.swift
│ └── SampleBuilderItem
│ │ ├── SampleBuilderItemDiagnostic.swift
│ │ └── SampleBuilderItemMacro.swift
├── SwiftAndTipsMacros
│ └── PublicMacros.swift
└── SwiftAndTipsMacrosClient
│ └── main.swift
└── Tests
└── SwiftAndTipsMacrosTests
├── BinaryStringTests.swift
├── SampleBuilderItemTests.swift
└── SampleBuilderTests.swift
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by https://www.toptal.com/developers/gitignore/api/xcode,macos,swift
2 | # Edit at https://www.toptal.com/developers/gitignore?templates=xcode,macos,swift
3 |
4 | ### macOS ###
5 | # General
6 | .DS_Store
7 | .AppleDouble
8 | .LSOverride
9 |
10 | # Icon must end with two \r
11 | Icon
12 |
13 |
14 | # Thumbnails
15 | ._*
16 |
17 | # Files that might appear in the root of a volume
18 | .DocumentRevisions-V100
19 | .fseventsd
20 | .Spotlight-V100
21 | .TemporaryItems
22 | .Trashes
23 | .VolumeIcon.icns
24 | .com.apple.timemachine.donotpresent
25 |
26 | # Directories potentially created on remote AFP share
27 | .AppleDB
28 | .AppleDesktop
29 | Network Trash Folder
30 | Temporary Items
31 | .apdisk
32 |
33 | ### macOS Patch ###
34 | # iCloud generated files
35 | *.icloud
36 |
37 | ### Swift ###
38 | # Xcode
39 | #
40 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
41 |
42 | ## User settings
43 | xcuserdata/
44 |
45 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
46 | *.xcscmblueprint
47 | *.xccheckout
48 |
49 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
50 | build/
51 | DerivedData/
52 | *.moved-aside
53 | *.pbxuser
54 | !default.pbxuser
55 | *.mode1v3
56 | !default.mode1v3
57 | *.mode2v3
58 | !default.mode2v3
59 | *.perspectivev3
60 | !default.perspectivev3
61 |
62 | ## Obj-C/Swift specific
63 | *.hmap
64 |
65 | ## App packaging
66 | *.ipa
67 | *.dSYM.zip
68 | *.dSYM
69 |
70 | ## Playgrounds
71 | timeline.xctimeline
72 | playground.xcworkspace
73 |
74 | # Swift Package Manager
75 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
76 | # Packages/
77 | # Package.pins
78 | # Package.resolved
79 | # *.xcodeproj
80 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
81 | # hence it is not needed unless you have added a package configuration file to your project
82 | # .swiftpm
83 |
84 | .build/
85 |
86 | # CocoaPods
87 | # We recommend against adding the Pods directory to your .gitignore. However
88 | # you should judge for yourself, the pros and cons are mentioned at:
89 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
90 | # Pods/
91 | # Add this line if you want to avoid checking in source code from the Xcode workspace
92 | # *.xcworkspace
93 |
94 | # Carthage
95 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
96 | # Carthage/Checkouts
97 |
98 | Carthage/Build/
99 |
100 | # Accio dependency management
101 | Dependencies/
102 | .accio/
103 |
104 | # fastlane
105 | # It is recommended to not store the screenshots in the git repo.
106 | # Instead, use fastlane to re-generate the screenshots whenever they are needed.
107 | # For more information about the recommended setup visit:
108 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
109 |
110 | fastlane/report.xml
111 | fastlane/Preview.html
112 | fastlane/screenshots/**/*.png
113 | fastlane/test_output
114 |
115 | # Code Injection
116 | # After new code Injection tools there's a generated folder /iOSInjectionProject
117 | # https://github.com/johnno1962/injectionforxcode
118 |
119 | iOSInjectionProject/
120 |
121 | ### Xcode ###
122 |
123 | ## Xcode 8 and earlier
124 |
125 | ### Xcode Patch ###
126 | *.xcodeproj/*
127 | !*.xcodeproj/project.pbxproj
128 | !*.xcodeproj/xcshareddata/
129 | !*.xcodeproj/project.xcworkspace/
130 | !*.xcworkspace/contents.xcworkspacedata
131 | /*.gcno
132 | **/xcshareddata/WorkspaceSettings.xcsettings
133 |
134 | # End of https://www.toptal.com/developers/gitignore/api/xcode,macos,swift
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/xcshareddata/xcschemes/SwiftAndTipsMacros-Package.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
29 |
35 |
36 |
37 |
43 |
49 |
50 |
51 |
52 |
53 |
59 |
60 |
62 |
68 |
69 |
70 |
71 |
72 |
82 |
84 |
90 |
91 |
92 |
93 |
99 |
101 |
107 |
108 |
109 |
110 |
112 |
113 |
116 |
117 |
118 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/xcshareddata/xcschemes/SwiftAndTipsMacros.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
31 |
32 |
34 |
40 |
41 |
42 |
43 |
44 |
54 |
55 |
61 |
62 |
68 |
69 |
70 |
71 |
73 |
74 |
77 |
78 |
79 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Pedro Rojas and project authors.
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.
--------------------------------------------------------------------------------
/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "pins" : [
3 | {
4 | "identity" : "swift-syntax",
5 | "kind" : "remoteSourceControl",
6 | "location" : "https://github.com/apple/swift-syntax.git",
7 | "state" : {
8 | "revision" : "fa8f95c2d536d6620cc2f504ebe8a6167c9fc2dd",
9 | "version" : "510.0.1"
10 | }
11 | }
12 | ],
13 | "version" : 2
14 | }
15 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version: 5.9
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 | import CompilerPluginSupport
6 |
7 | let package = Package(
8 | name: "SwiftAndTipsMacros",
9 | platforms: [.macOS(.v10_15), .iOS(.v13), .tvOS(.v13), .watchOS(.v6), .macCatalyst(.v13)],
10 | products: [
11 | // Products define the executables and libraries a package produces, making them visible to other packages.
12 | .library(
13 | name: "SwiftAndTipsMacros",
14 | targets: ["SwiftAndTipsMacros"]
15 | ),
16 | ],
17 | dependencies: [
18 | // Depend on the latest Swift 5.9 prerelease of SwiftSyntax
19 | .package(url: "https://github.com/apple/swift-syntax.git", from: "510.0.0"),
20 | ],
21 | targets: [
22 | // Targets are the basic building blocks of a package, defining a module or a test suite.
23 | // Targets can depend on other targets in this package and products from dependencies.
24 | // Macro implementation that performs the source transformation of a macro.
25 | .macro(
26 | name: "Macros",
27 | dependencies: [
28 | .product(name: "SwiftSyntaxMacros", package: "swift-syntax"),
29 | .product(name: "SwiftCompilerPlugin", package: "swift-syntax"),
30 | "DataGenerator"
31 | ]
32 | ),
33 |
34 | .target(name: "DataGenerator", dependencies: [
35 | "DataCategory"
36 | ]),
37 | .target(name: "DataCategory"),
38 |
39 | // Library that exposes a macro as part of its API, which is used in client programs.
40 | .target(name: "SwiftAndTipsMacros", dependencies: [
41 | "Macros",
42 | "DataGenerator"
43 | ]),
44 |
45 | // A client of the library, which is able to use the macro in its own code.
46 | .executableTarget(name: "SwiftAndTipsMacrosClient", dependencies: [
47 | "SwiftAndTipsMacros"
48 | ]),
49 |
50 | // A test target used to develop the macro implementation.
51 | .testTarget(
52 | name: "SwiftAndTipsMacrosTests",
53 | dependencies: [
54 | "Macros",
55 | .product(name: "SwiftSyntaxMacrosTestSupport", package: "swift-syntax"),
56 | "DataGenerator",
57 | "DataCategory",
58 | ]
59 | ),
60 | ]
61 | )
62 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SwiftAndTipsMacros
2 | This repository contains a list of Swift Macros to make your coding live on Apple ecosystem simpler and more productive.
3 |
4 |
5 |
6 |
7 |
8 | - [Requirements](#requirements)
9 | - [Macros](#macros)
10 | - [#binaryString](#binarystring)
11 | - [@SampleBuilder](#samplebuilder)
12 | - [How to use it?](#how-to-use-it)
13 | - [Supported Foundation Types](#supported-foundation-types)
14 | - [`.default` Generator Values](#default-generator-values)
15 | - [Custom Types](#custom-types)
16 | - [Enums](#enums)
17 | - [@SampleBuilderItem](#samplebuilderitem)
18 | - [Installation](#installation)
19 | - [Limitations](#limitations)
20 | - [@SampleBuilder](#samplebuilder-1)
21 | - [Future Work](#future-work)
22 | - [@SampleBuilder](#samplebuilder-2)
23 | - [Contributing](#contributing)
24 | - [Contact](#contact)
25 | - [License](#license)
26 |
27 |
28 |
29 | ## Requirements
30 | - Xcode 15 or above.
31 | - Swift 5.9 or above.
32 | - Platforms:
33 | - macOS 10.15 or above.
34 | - iOS 13.0 or above.
35 | - tvOS 13.0 or above.
36 | - watchOS 6.0 or above.
37 | - macCatalyst 13.0 or above.
38 |
39 | ## Macros
40 | ### #binaryString
41 | **\#binaryString** is a freestanding macro that will convert an Integer literal into a binary string representation:
42 | ```swift
43 | let x = #binaryString(10)
44 | /*
45 | expanded code:
46 | "1010"
47 | */
48 | print(x) // Output: "1010"
49 | ```
50 | > This macro was created as a tutorial to explain how macros work. It would be simpler to create a function to do this instead :). Learn more here: TBD
51 |
52 | ### @SampleBuilder
53 | The aim of @SampleBuilder is straightforward: Generate an array of sample data from your models for use in SwiftUI previews, unit tests, or any scenario that needs mock data—without the hassle of crafting it from scratch.
54 |
55 | > Interested in a demonstration? Check out this [video](https://youtu.be/qE1P26C2ZIQ)
56 |
57 | #### How to use it?
58 | 1. Import `SwiftAndTipsMacros` and `DataGenerator`.
59 | 2. Attach `@SampleBuilder` to an `struct` or `enum`.
60 | 3. Provide the number of items you want for your sample.
61 | 4. Provide the type of data generator you want to use:
62 | * `default` will generate a fixed value all the time (ideal for unit tests).
63 | * `random` will generate a random value for each property requested in the initialization.
64 |
65 | In this example, we are using the `default` generator to generate 10 items:
66 | ```swift
67 | // 1
68 | import SwiftAndTipsMacros
69 | import DataGenerator
70 | // 2
71 | @SampleBuilder(
72 | numberOfItems: 10, // 3
73 | dataGeneratorType: .default // 4
74 | )
75 | struct Example {
76 | let item1: String
77 | let item2: Int
78 | /*
79 | expanded code:
80 | #if DEBUG
81 | static var sample: [Self] {
82 | [
83 | .init(item1: DataGenerator.default.string(), item2: DataGenerator.default.int()),
84 | .init(item1: DataGenerator.default.string(), item2: DataGenerator.default.int()),
85 | .init(item1: DataGenerator.default.string(), item2: DataGenerator.default.int()),
86 | .init(item1: DataGenerator.default.string(), item2: DataGenerator.default.int()),
87 | .init(item1: DataGenerator.default.string(), item2: DataGenerator.default.int()),
88 | .init(item1: DataGenerator.default.string(), item2: DataGenerator.default.int()),
89 | .init(item1: DataGenerator.default.string(), item2: DataGenerator.default.int()),
90 | .init(item1: DataGenerator.default.string(), item2: DataGenerator.default.int()),
91 | .init(item1: DataGenerator.default.string(), item2: DataGenerator.default.int()),
92 | .init(item1: DataGenerator.default.string(), item2: DataGenerator.default.int()),
93 | ]
94 | }
95 | #endif
96 | */
97 | }
98 | ...
99 | for element in Example.sample {
100 | print(element.item1, element.item2)
101 | }
102 | /*
103 | Output:
104 | Hello World 0
105 | Hello World 0
106 | Hello World 0
107 | Hello World 0
108 | Hello World 0
109 | Hello World 0
110 | Hello World 0
111 | Hello World 0
112 | Hello World 0
113 | Hello World 0
114 | */
115 | ``````
116 | > To optimize your production code, the sample property is available only in DEBUG mode. Ensure you use the #if DEBUG condition or any other custom flag specific to debug mode before archiving your app.
117 |
118 | Now, if you need a more realistic data, you can use `random` generator type:
119 | ```swift
120 | @SampleBuilder(numberOfItems: 10, dataGeneratorType: .random)
121 | struct Example {
122 | let item1: String
123 | let item2: Int
124 | /*
125 | expanded code:
126 | #if DEBUG
127 | static var sample: [Self] {
128 | [
129 | .init(item1: DataGenerator.random().string(), item2: DataGenerator.random().int()),
130 | .init(item1: DataGenerator.random().string(), item2: DataGenerator.random().int()),
131 | .init(item1: DataGenerator.random().string(), item2: DataGenerator.random().int()),
132 | .init(item1: DataGenerator.random().string(), item2: DataGenerator.random().int()),
133 | .init(item1: DataGenerator.random().string(), item2: DataGenerator.random().int()),
134 | .init(item1: DataGenerator.random().string(), item2: DataGenerator.random().int()),
135 | .init(item1: DataGenerator.random().string(), item2: DataGenerator.random().int()),
136 | .init(item1: DataGenerator.random().string(), item2: DataGenerator.random().int()),
137 | .init(item1: DataGenerator.random().string(), item2: DataGenerator.random().int()),
138 | .init(item1: DataGenerator.random().string(), item2: DataGenerator.random().int()),
139 | ]
140 | }
141 | #endif
142 | */
143 | }
144 | ...
145 | for element in Example.sample {
146 | print(element.item1, element.item2)
147 | }
148 | /*
149 | Output:
150 | 1234-2121-1221-1211 738
151 | 6760 Nils Mall Suite 390, Kesslerstad, WV 53577-7421 192
152 | yazminzemlak1251 913
153 | lelahdaugherty 219
154 | Tony 228
155 | Jessie 826
156 | alanvonrueden6307@example.com 864
157 | Enola 858
158 | Fay 736
159 | myrtismcdermott@example.net 859
160 | */
161 | ``````
162 |
163 | #### Supported Foundation Types
164 | The current supported list includes:
165 | - `UUID`
166 | - `Array`*
167 | - `Dictionary`*
168 | - `Optional`*
169 | - `String`
170 | - `Int`
171 | - `Bool`
172 | - `Data`
173 | - `Date`
174 | - `Double`
175 | - `Float`
176 | - `Int8`
177 | - `Int16`
178 | - `Int32`
179 | - `Int64`
180 | - `UInt8`
181 | - `UInt16`
182 | - `UInt32`
183 | - `UInt64`
184 | - `URL`
185 | - `CGPoint`
186 | - `CGFloat`
187 | - `CGRect`
188 | - `CGSize`
189 | - `CGVector`
190 |
191 | > \* It includes nested types too!
192 |
193 | More types will be supported soon.
194 |
195 | #### `.default` Generator Values
196 | | Type | Value |
197 | | - | - |
198 | | `UUID` | 00000000-0000-0000-0000-000000000000 (auto increasing) |
199 | | `String` | "Hello World" |
200 | | `Int` | 0 |
201 | | `Bool` | `true` |
202 | | `Data` | Data() |
203 | | `Date` | Date(timeIntervalSinceReferenceDate: 0) |
204 | | `Double` | 0.0 |
205 | | `Float` | 0.0 |
206 | | `Int8` | 0 |
207 | | `Int16` | 0 |
208 | | `Int32` | 0 |
209 | | `Int64` | 0 |
210 | | `UInt8` | 0 |
211 | | `UInt16` | 0 |
212 | | `UInt32` | 0 |
213 | | `UInt64` | 0 |
214 | | `URL` | URL(string: "https://www.apple.com")! |
215 | | `CGPoint` | `CGPoint()` |
216 | | `CGFloat` | `CGFloat()` |
217 | | `CGRect` | `CGRect()` |
218 | | `CGSize` | `CGSize()` |
219 | | `CGVector` | `CGVector()` |
220 |
221 |
222 | #### Custom Types
223 | You can add `@SampleBuilder` to all your custom types to generate sample data from those types. Here's an example:
224 |
225 | ```swift
226 | @SampleBuilder(numberOfItems: 3, dataGeneratorType: .random)
227 | struct Review {
228 | let rating: Int
229 | let time: Date
230 | let product: Product
231 | /*
232 | expanded code:
233 | #if DEBUG
234 | static var sample: [Self] {
235 | [
236 | .init(rating: DataGenerator.random().int(), time: DataGenerator.random().date(), product: Product.sample.first!),
237 | .init(rating: DataGenerator.random().int(), time: DataGenerator.random().date(), product: Product.sample.first!),
238 | .init(rating: DataGenerator.random().int(), time: DataGenerator.random().date(), product: Product.sample.first!),
239 | ]
240 | }
241 | #endif
242 | */
243 | }
244 |
245 | @SampleBuilder(numberOfItems: 3, dataGeneratorType: .random)
246 | struct Product {
247 | var price: Int
248 | var description: String
249 | /*
250 | expanded code:
251 | #if DEBUG
252 | static var sample: [Self] {
253 | [
254 | .init(price: DataGenerator.random().int(), description: DataGenerator.random().string()),
255 | .init(price: DataGenerator.random().int(), description: DataGenerator.random().string()),
256 | .init(price: DataGenerator.random().int(), description: DataGenerator.random().string()),
257 | ]
258 | }
259 | #endif
260 | */
261 | }
262 | ```
263 | > To generate the sample property in structs, we always take the initialize with the longest number of parameters available. If there are no initializers available, we use the memberwise init.
264 |
265 | #### Enums
266 | Enums are also supported by `@SampleBuilder`.
267 | ```swift
268 | @SampleBuilder(numberOfItems: 6, dataGeneratorType: .random)
269 | enum MyEnum {
270 | indirect case case1(String, Int, String, [String])
271 | case case2
272 | case case3(Product)
273 | case case4([String: Product])
274 |
275 | /*
276 | expanded code:
277 | #if DEBUG
278 | static var sample: [Self] {
279 | [
280 | .case1(DataGenerator.random().string(), DataGenerator.random().int(), DataGenerator.random().string(), [DataGenerator.random().string()]),
281 | .case2,
282 | .case3(Product.sample.first!),
283 | .case4([DataGenerator.random().string(): Product.sample.first!]),
284 | .case1(DataGenerator.random().string(), DataGenerator.random().int(), DataGenerator.random().string(), [DataGenerator.random().string()]),
285 | .case2,
286 | ]
287 | }
288 | #endif
289 | */
290 | }
291 | ```
292 |
293 | To generate the sample for enums, we are adding each case to sample array one by one and starting over if `numberOfItems` is larger than the number of cases.
294 |
295 | ### @SampleBuilderItem
296 | If you want to customize your sample data even further for `.random` generator, you can use `@SampleBuilderItem` to specify the type of data you want to generate.
297 |
298 | The following list shows the supported categories:
299 | * String:
300 | - `firstName`
301 | - `lastName`
302 | - `fullName`
303 | - `email`
304 | - `address`
305 | - `appVersion`
306 | - `creditCardNumber`
307 | - `companyName`
308 | - `username`
309 | * Double:
310 | - `price`
311 | * URL:
312 | - `url` (generic web link)
313 | - `image` (image url)
314 |
315 | More category will be added soon.
316 |
317 | Here's an example:
318 | ```swift
319 | @SampleBuilder(numberOfItems: 3, dataGeneratorType: .random)
320 | struct Profile {
321 | @SampleBuilderItem(category: .firstName)
322 | let firstName: String
323 |
324 | @SampleBuilderItem(category: .lastName)
325 | let lastName: String
326 |
327 | @SampleBuilderItem(category: .image(width: 300, height: 300))
328 | let profileImage: URL
329 | /*
330 | expanded code:
331 | #if DEBUG
332 | static var sample: [Self] {
333 | [
334 | .init(firstName: DataGenerator.random(dataCategory: .init(rawValue: "firstName")).string(), lastName: DataGenerator.random(dataCategory: .init(rawValue: "lastName")).string(), profileImage: DataGenerator.random(dataCategory: .init(rawValue: "image(width:300,height:300)")).url()),
335 | .init(firstName: DataGenerator.random(dataCategory: .init(rawValue: "firstName")).string(), lastName: DataGenerator.random(dataCategory: .init(rawValue: "lastName")).string(), profileImage: DataGenerator.random(dataCategory: .init(rawValue: "image(width:300,height:300)")).url()),
336 | .init(firstName: DataGenerator.random(dataCategory: .init(rawValue: "firstName")).string(), lastName: DataGenerator.random(dataCategory: .init(rawValue: "lastName")).string(), profileImage: DataGenerator.random(dataCategory: .init(rawValue: "image(width:300,height:300)")).url()),
337 | ]
338 | }
339 | #endif
340 | */
341 | }
342 |
343 | /*
344 | Output:
345 | Sylvia Ullrich https://picsum.photos/300/300
346 | Precious Schneider https://picsum.photos/300/300
347 | Nyasia Tromp https://picsum.photos/300/300
348 | */
349 | ```
350 |
351 | > @SampleBuilderItem only works with `random` generator in structs. If you use this macro within `default` generator, a warning will appear indicating that macro is redundand.
352 |
353 |
354 | ## Installation
355 | ```swift
356 | import PackageDescription
357 |
358 | let package = Package(
359 | name: "",
360 | dependencies: [
361 | // ...
362 | .package(url: "https://github.com/pitt500/SwiftAndTipsMacros.git", branch: "main")
363 | // ...
364 | ]
365 | )
366 | ```
367 |
368 | ## Limitations
369 | ### @SampleBuilder
370 | * Conflict with `#Preview` and expanded `sample` property:
371 | For some reason, if you call `sample` property directly within a `#Preview` macro, the project will not compile.
372 | ```swift
373 | #Preview {
374 | ContentView(people: Person.sample)
375 | //Error: Type 'Person' has no member 'sample'
376 | }
377 | ```
378 | **Workaround**: Just create an instance that holds the view and use it inside `#Preview` instead of directly calling the View and `sample`:
379 | ```swift
380 | #Preview {
381 | contentView
382 | }
383 |
384 | let contentView = ContentView(people: Person.sample)
385 | ```
386 |
387 | * Both `SwiftAndTipsMacros` and `DataGenerator` are required to be imported in order to make `@SampleBuilder` work. I've explored another alternative using `@_exported` that will reimport `DataGenerator` directly from `SwiftAndTipsMacros`, allowing you to just requiring one import, however, using [underscored attributes](https://github.com/apple/swift/blob/main/docs/ReferenceGuides/UnderscoredAttributes.md) is not recommended because it may break your code after a new Swift release.
388 | > If you want more information about `@_exported`, watch this [video](https://youtu.be/xU6btygja5Y).
389 |
390 | ## Future Work
391 | * Create documentation to all functions, structs and enums needed and export it usind DocC.
392 |
393 | ### @SampleBuilder
394 | * Adding support to CGPoint and more types in random generator mode.
395 | * Remove the importing of DataGeneration once `@_exported` can be used publicly.
396 | * Adding more macros useful for your development.
397 |
398 | ## Contributing
399 | There are a lot of work to do, if you want to contribute adding a new macro or fixing an existing one, feel free to fork this project and follow these rules before creating a PR:
400 | 1. Include unit tests in your PR (unless is just to fix a typo).
401 | 2. Please add a description in your PR with the purpose of your change or new macro.
402 | 3. Add the following header to all your code files:
403 | ```swift
404 | /*
405 | This source file is part of SwiftAndTipsMacros
406 |
407 | Copyright (c) 2023 Pedro Rojas and project authors
408 | Licensed under MIT License
409 | */
410 | ```
411 |
412 | ## Contact
413 | If you have any feedback, I would love to hear from you. Please feel free to reach out to me through any of my social media channels:
414 |
415 | * [Youtube](https://youtube.com/@swiftandtips)
416 | * [Twitter](https://twitter.com/swiftandtips)
417 | * [LinkedIn](https://www.linkedin.com/in/pedrorojaslo/)
418 | * [Mastodon](https://iosdev.space/@swiftandtips)
419 |
420 | Thanks you, and have a great day! 😄
421 |
422 | ## License
423 | Licensed under MIT License, see [LICENSE](./LICENSE) for more information.
--------------------------------------------------------------------------------
/Sources/DataCategory/DataCategory.swift:
--------------------------------------------------------------------------------
1 | /*
2 | This source file is part of SwiftAndTipsMacros
3 |
4 | Copyright (c) 2023 Pedro Rojas and project authors
5 | Licensed under MIT License
6 | */
7 |
8 | //
9 | // DataCategory.swift
10 | //
11 | //
12 | // Created by Pedro Rojas on 29/08/23.
13 | //
14 |
15 | import Foundation
16 |
17 | public struct DataCategory: RawRepresentable {
18 |
19 | public enum BuiltInValue: String {
20 | // String
21 | case firstName
22 | case lastName
23 | case fullName
24 | case email
25 | case address
26 | case appVersion
27 | case creditCardNumber
28 | case companyName
29 | case username
30 |
31 | //Double
32 | case price
33 | case url
34 | }
35 |
36 | public enum Category {
37 | case builtInValue(BuiltInValue)
38 | case image(width: Int, height: Int)
39 | }
40 |
41 | public var category: Category
42 | public static let noCategory = DataCategory(rawValue: "")
43 |
44 | public init(_ value: BuiltInValue) {
45 | self.category = .builtInValue(value)
46 | }
47 |
48 | public init(imageWidth width: Int, height: Int) {
49 | category = .image(width: width, height: height)
50 | }
51 |
52 | public var rawValue: String {
53 | switch category {
54 | case .builtInValue(let value):
55 | return value.rawValue
56 | case .image(let width, let height):
57 | return "image(width:\(width),height:\(height))"
58 | }
59 | }
60 |
61 | public init?(rawValue: String) {
62 | let rawValue = rawValue.replacingOccurrences(of: " ", with: "")
63 | if let value = BuiltInValue(rawValue: rawValue) {
64 | category = .builtInValue(value)
65 | return
66 | }
67 |
68 | if rawValue.hasPrefix("image") {
69 | let (width, height) = DataCategory.getImageWidthAndHeight(from: rawValue)
70 | category = .image(width: width, height: height)
71 | return
72 | }
73 |
74 | return nil
75 | }
76 |
77 | public enum SupportedType: String {
78 | case string
79 | case url
80 | case double
81 |
82 | public var title: String {
83 | switch self {
84 | case .string:
85 | "String"
86 | case .url:
87 | "URL"
88 | case .double:
89 | "Double"
90 | }
91 | }
92 | }
93 |
94 | public func getSupportedType() -> SupportedType {
95 | switch self.category {
96 | case .builtInValue(let value):
97 | switch value {
98 | case .firstName, .lastName, .fullName, .email, .address, .appVersion, .creditCardNumber, .companyName, .username:
99 | return .string
100 | case .price:
101 | return .double
102 | case .url:
103 | return .url
104 | }
105 | case .image:
106 | return .url
107 | }
108 | }
109 |
110 | public func supports(type: String) -> Bool {
111 | guard let supportedType = SupportedType(rawValue: type.lowercased())
112 | else {
113 | return false
114 | }
115 |
116 | switch supportedType {
117 | case .string:
118 | let stringCategories: Set = [
119 | .firstName, .lastName, .fullName, .email,
120 | .address, .appVersion, .creditCardNumber, .companyName, .username
121 | ]
122 |
123 | guard case .builtInValue(let value) = category
124 | else {
125 | return false
126 | }
127 |
128 | return stringCategories.contains(value)
129 | case .url:
130 | if case .image(_,_) = category {
131 | return true
132 | }
133 |
134 | if case .builtInValue(let value) = category {
135 | return value == .url
136 | }
137 |
138 | return false
139 | case .double:
140 | guard case .builtInValue(let value) = category
141 | else {
142 | return false
143 | }
144 |
145 | return value == .price
146 | }
147 | }
148 |
149 | static private func getImageWidthAndHeight(from inputString: String) -> (Int, Int) {
150 |
151 | // 1. Create a regular expression pattern
152 | let pattern = "image\\(width:(\\d+),height:(\\d+)\\)"
153 |
154 | // 2. Match the string against the pattern
155 | guard let regex = try? NSRegularExpression(pattern: pattern, options: []),
156 | let match = regex.firstMatch(in: inputString, options: [], range: NSRange(location: 0, length: inputString.utf16.count)),
157 |
158 | // 3. Extract the values
159 | let widthRange = Range(match.range(at: 1), in: inputString),
160 | let heightRange = Range(match.range(at: 2), in: inputString),
161 | let width = Int(inputString[widthRange]),
162 | let height = Int(inputString[heightRange])
163 | else {
164 | print("No match found for '\(inputString)'. Please report this issue!")
165 | return (100, 100) // Default in case of any issue
166 | }
167 |
168 | return (width, height)
169 | }
170 | }
171 |
172 |
--------------------------------------------------------------------------------
/Sources/DataGenerator/DataGenerator.swift:
--------------------------------------------------------------------------------
1 | /*
2 | This source file is part of SwiftAndTipsMacros
3 |
4 | Copyright (c) 2023 Pedro Rojas and project authors
5 | Licensed under MIT License
6 | */
7 |
8 | //
9 | // DataGenerator.swift
10 | //
11 | //
12 | // Created by Pedro Rojas on 23/08/23.
13 | //
14 |
15 | import Foundation
16 | import DataCategory
17 |
18 | fileprivate typealias Provider = FakeDataProvider
19 |
20 | // Keep all method names in lowercase.
21 | public struct DataGenerator {
22 | public var int: () -> Int
23 | public var int8: () -> Int8
24 | public var int16: () -> Int16
25 | public var int32: () -> Int32
26 | public var int64: () -> Int64
27 | public var uint: () -> UInt
28 | public var uint8: () -> UInt8
29 | public var uint16: () -> UInt16
30 | public var uint32: () -> UInt32
31 | public var uint64: () -> UInt64
32 | public var float: () -> Float
33 | public var float32: () -> Float32
34 | public var float64: () -> Float64
35 | public var double: () -> Double
36 | public var string: () -> String
37 | public var bool: () -> Bool
38 | public var data: () -> Data
39 | public var date: () -> Date
40 | public var uuid: () -> UUID
41 | public var cgpoint: () -> CGPoint
42 | public var cgrect: () -> CGRect
43 | public var cgsize: () -> CGSize
44 | public var cgvector: () -> CGVector
45 | public var cgfloat: () -> CGFloat
46 | public var url: () -> URL
47 | }
48 |
49 | public extension DataGenerator {
50 | static let `default` = Self(
51 | int: { 0 },
52 | int8: { 0 },
53 | int16: { 0 },
54 | int32: { 0 },
55 | int64: { 0 },
56 | uint: { 0 },
57 | uint8: { 0 },
58 | uint16: { 0 },
59 | uint32: { 0 },
60 | uint64: { 0 },
61 | float: { 0 },
62 | float32: { 0 },
63 | float64: { 0 },
64 | double: { 0 },
65 | string: { "Hello World" },
66 | bool: { true },
67 | data: { Data() },
68 | date: { Date(timeIntervalSinceReferenceDate: 0) },
69 | uuid: { UUID.increasingUUID },
70 | cgpoint: { CGPoint() },
71 | cgrect: { CGRect() },
72 | cgsize: { CGSize() },
73 | cgvector: { CGVector() },
74 | cgfloat: { CGFloat() },
75 | url: { URL(string: "https://www.apple.com")! }
76 | )
77 | static func random(dataCategory: DataCategory? = nil) -> Self {
78 | Self(
79 | int: { Provider().randomInt(min: 0, max: 1000) },
80 | int8: { Int8(Provider().randomInt(min: 0, max: 1000)) },
81 | int16: { Int16(Provider().randomInt(min: 0, max: 1000)) },
82 | int32: { Int32(Provider().randomInt(min: 0, max: 1000)) },
83 | int64: { Int64(Provider().randomInt(min: 0, max: 1000)) },
84 | uint: { UInt(Provider().randomInt(min: 0, max: 1000) ) },
85 | uint8: { UInt8(Provider().randomInt(min: 0, max: 1000)) },
86 | uint16: { UInt16(Provider().randomInt(min: 0, max: 1000)) },
87 | uint32: { UInt32(Provider().randomInt(min: 0, max: 1000)) },
88 | uint64: { UInt64(Provider().randomInt(min: 0, max: 1000)) },
89 | float: { Provider().randomFloat(min: 0, max: 1000) },
90 | float32: { Float32(Provider().randomFloat(min: 0, max: 1000)) },
91 | float64: { Float64(Provider().randomFloat(min: 0, max: 1000)) },
92 | double: {
93 | guard case .builtInValue(let value) = dataCategory?.category
94 | else {
95 | return Provider().randomDouble(min: 0, max: 1000)
96 | }
97 |
98 | return if value == .price {
99 | Provider().price()
100 | } else {
101 | Provider().randomDouble(min: 0, max: 1000)
102 | }
103 | },
104 | string: {
105 | let stringCollection: [String] = [
106 | Provider().firstName(),
107 | Provider().lastName(),
108 | Provider().fullName(),
109 | Provider().email(),
110 | Provider().address(),
111 | Provider().appVersion(),
112 | Provider().creditCardNumber(),
113 | Provider().companyName(),
114 | Provider().username()
115 | ]
116 |
117 | guard case .builtInValue(let value) = dataCategory?.category
118 | else {
119 | return stringCollection.randomElement()!
120 | }
121 |
122 | return switch value {
123 | case .firstName:
124 | Provider().firstName()
125 | case .lastName:
126 | Provider().lastName()
127 | case .fullName:
128 | Provider().fullName()
129 | case .email:
130 | Provider().email()
131 | case .address:
132 | Provider().address()
133 | case .appVersion:
134 | Provider().appVersion()
135 | case .creditCardNumber:
136 | Provider().creditCardNumber()
137 | case .companyName:
138 | Provider().companyName()
139 | case .username:
140 | Provider().username()
141 | case .url, .price:
142 | stringCollection.randomElement()!
143 | }
144 | },
145 | bool: { Provider().randomBool() },
146 | data: { Data() },
147 | date: { Provider().date() },
148 | uuid: { UUID() },
149 | cgpoint: { CGPoint() },
150 | cgrect: { CGRect() },
151 | cgsize: { CGSize() },
152 | cgvector: { CGVector() },
153 | cgfloat: { CGFloat() },
154 | url: {
155 | guard case .image(let width, let height) = dataCategory?.category
156 | else {
157 | return Provider().url()
158 | }
159 |
160 | return Provider().image(width: width, height: height)
161 | }
162 | )
163 | }
164 | }
165 |
166 | public extension UUID {
167 | static var uuIdCounter: UInt = 0
168 |
169 | static var increasingUUID: UUID {
170 | defer {
171 | uuIdCounter += 1
172 | }
173 | return UUID(uuidString: "00000000-0000-0000-0000-\(String(format: "%012x", uuIdCounter))")!
174 | }
175 | }
176 |
177 | public enum DataGeneratorType: String {
178 | case `default`
179 | case random
180 | }
181 |
--------------------------------------------------------------------------------
/Sources/DataGenerator/DataProvider/DataProvider.swift:
--------------------------------------------------------------------------------
1 | /*
2 | This source file is part of SwiftAndTipsMacros
3 |
4 | Copyright (c) 2023 Pedro Rojas and project authors
5 | Licensed under MIT License
6 | */
7 |
8 | //
9 | // DataProvider.swift
10 | //
11 | //
12 | // Created by Pedro Rojas on 15/09/23.
13 | //
14 |
15 | import Foundation
16 |
17 | protocol StringDataProvider {
18 | func firstName() -> String
19 | func lastName() -> String
20 | func fullName() -> String
21 | func email() -> String
22 | func address() -> String
23 | func appVersion() -> String
24 | func username() -> String
25 | func creditCardNumber() -> String
26 | func companyName() -> String
27 | }
28 |
29 | protocol BooleanDataProvider {
30 | func randomBool() -> Bool
31 | }
32 |
33 | protocol DateDataProvider {
34 | func date() -> Date
35 | }
36 |
37 | protocol URLDataProvider {
38 | func url() -> URL
39 | func image(width: Int, height: Int) -> URL
40 | }
41 |
42 | protocol NumericDataProvider {
43 | func randomInt(min: Int, max: Int) -> Int
44 | func randomFloat(min: Float, max: Float) -> Float
45 | func randomDouble(min: Double, max: Double) -> Double
46 | func price() -> Double
47 | }
48 |
--------------------------------------------------------------------------------
/Sources/DataGenerator/DataProvider/FakeDataProvider.swift:
--------------------------------------------------------------------------------
1 | /*
2 | This source file is part of SwiftAndTipsMacros
3 |
4 | Copyright (c) 2023 Pedro Rojas and project authors
5 | Licensed under MIT License
6 | */
7 |
8 | //
9 | // FakeDataProvider.swift
10 | //
11 | //
12 | // Created by Pedro Rojas on 16/09/23.
13 | //
14 |
15 | import Foundation
16 |
17 | struct FakeDataProvider: StringDataProvider {
18 | func firstName() -> String {
19 | return [
20 | "Jeanie",
21 | "Roselyn",
22 | "Giles",
23 | "Montana",
24 | "Maximillia",
25 | "Etha",
26 | "Tony",
27 | "Lila",
28 | "Malcolm",
29 | "Clare",
30 | "Sonny",
31 | "Jensen",
32 | "Erik",
33 | "Alvera",
34 | "Alysson",
35 | "Queenie",
36 | "Kadin",
37 | "Nyasia",
38 | "Christina",
39 | "Otis",
40 | "Brook",
41 | "Demetrius",
42 | "Janiya",
43 | "Cloyd",
44 | "Estell",
45 | "Rafaela",
46 | "Wilfrid",
47 | "Fletcher",
48 | "Justus",
49 | "Gilda",
50 | "Orion",
51 | "Johnnie",
52 | "Burnice",
53 | "Mandy",
54 | "Precious",
55 | "Margarette",
56 | "Nadia",
57 | "Emanuel",
58 | "Alysha",
59 | "Abigale",
60 | "Ariane",
61 | "Alec",
62 | "Declan",
63 | "Celestine",
64 | "Shaniya",
65 | "Amanda",
66 | "Rebeka",
67 | "Milo",
68 | "Bettye",
69 | "Marlene",
70 | "Alisa",
71 | "Sylvia",
72 | "Xavier",
73 | "Shanelle",
74 | "Lorna",
75 | "Felicia",
76 | "Deven",
77 | "Jayda",
78 | "Jesse",
79 | "Karolann",
80 | "Devin",
81 | "Cayla",
82 | "Nyah",
83 | "Eve",
84 | "Vicente",
85 | "Lauryn",
86 | "Lauren",
87 | "Pinkie",
88 | "Augustus",
89 | "Valentina",
90 | "Eladio",
91 | "Sabryna",
92 | "Jessie",
93 | "Emilia",
94 | "Gus",
95 | "Marc",
96 | "Bernardo",
97 | "Gavin",
98 | "Odie",
99 | "Gage",
100 | "Hertha",
101 | "Kristopher",
102 | "Darien",
103 | "Mya",
104 | "Domenica",
105 | "Enola",
106 | "Antonetta",
107 | "Michele",
108 | "Elian",
109 | "Lizzie",
110 | "Grace",
111 | "Jaren",
112 | "Gust",
113 | "Lonzo",
114 | "Buford",
115 | "Mitchell",
116 | "Enid",
117 | "Courtney",
118 | "Sherman",
119 | "Pitt"
120 | ].randomElement()!
121 | }
122 |
123 | func lastName() -> String {
124 | [
125 | "Hudson",
126 | "Osinski",
127 | "Grant",
128 | "Fay",
129 | "Lockman",
130 | "Krajcik",
131 | "Cummerata",
132 | "Fritsch",
133 | "Hintz",
134 | "Pollich",
135 | "Thompson",
136 | "Donnelly",
137 | "Schiller",
138 | "Hilll",
139 | "Rath",
140 | "Terry",
141 | "Rempel",
142 | "Klocko",
143 | "Nienow",
144 | "Schamberger",
145 | "Hamill",
146 | "Beer",
147 | "Schmidt",
148 | "Flatley",
149 | "Kovacek",
150 | "Metz",
151 | "Hauck",
152 | "Ratke",
153 | "Watsica",
154 | "Tromp",
155 | "Wisoky",
156 | "Bode",
157 | "Ruecker",
158 | "Ward",
159 | "Parisian",
160 | "Mante",
161 | "Maggio",
162 | "Schoen",
163 | "Schmitt",
164 | "Kris",
165 | "Schneider",
166 | "Swaniawski",
167 | "Jenkins",
168 | "Stark",
169 | "Morar",
170 | "Erdman",
171 | "Haag",
172 | "Koch",
173 | "Altenwerth",
174 | "West",
175 | "Robel",
176 | "Windler",
177 | "Wilderman",
178 | "Konopelski",
179 | "Wilkinson",
180 | "Schuppe",
181 | "Connelly",
182 | "Upton",
183 | "Nolan",
184 | "Yost",
185 | "Turcotte",
186 | "Hirthe",
187 | "Kertzmann",
188 | "Weber",
189 | "Lynch",
190 | "Feeney",
191 | "Daugherty",
192 | "Streich",
193 | "Botsford",
194 | "Cole",
195 | "Veum",
196 | "Ledner",
197 | "Schulist",
198 | "McLaughlin",
199 | "Vandervort",
200 | "Lang",
201 | "Mann",
202 | "O'Keefe",
203 | "Kunze",
204 | "Kassulke",
205 | "Sauer",
206 | "Gulgowski",
207 | "Hoppe",
208 | "Crist",
209 | "O'Kon",
210 | "Beahan",
211 | "Predovic",
212 | "Bechtelar",
213 | "Labadie",
214 | "Kling",
215 | "Johnston",
216 | "Ullrich",
217 | "Stehr",
218 | "Schinner",
219 | "Lemke",
220 | "Leuschke",
221 | "O'Reilly",
222 | "Kerluke",
223 | "Padberg",
224 | "Conn",
225 | ].randomElement()!
226 | }
227 |
228 | func fullName() -> String {
229 | [
230 | "Ms. Mable Tremblay",
231 | "Jerald Yundt",
232 | "Marquis Aufderhar",
233 | "Jannie Cummings",
234 | "Adrien Barton",
235 | "Raymundo Boehm",
236 | "Talon Rice",
237 | "Idell Auer",
238 | "Lindsey Berge",
239 | "Lance Schamberger",
240 | "Sylvan Kuhn",
241 | "Thalia Hermann V",
242 | "Jayce Schowalter",
243 | "Tillman Effertz",
244 | "Robbie Jones",
245 | "Lia Koepp",
246 | "Estel Ward",
247 | "Adolphus Kunze DVM",
248 | "Lewis Schumm",
249 | "Ms. Ardith Goldner",
250 | "Stanley Funk Jr.",
251 | "Mr. Corine Abernathy",
252 | "Stanley Schoen DVM",
253 | "Noemy Auer",
254 | "Chadrick Abshire Jr.",
255 | "Berta Cronin",
256 | "Ned Russel",
257 | "Mr. Hadley Reichel",
258 | "Ms. Hugh Mohr",
259 | "Trudie Bergstrom",
260 | "Damien Watsica",
261 | "Mrs. Leilani Wyman",
262 | "Jaden McGlynn",
263 | "Eliza Cronin",
264 | "Esta Rogahn",
265 | "Dr. Rowan Nitzsche",
266 | "Grover Predovic Jr.",
267 | "Paolo Jenkins",
268 | "Claud Ledner",
269 | "Allene Padberg I",
270 | "Everette Senger",
271 | "Kyler Kling Sr.",
272 | "Stefanie Hauck I",
273 | "Jodie Witting",
274 | "Courtney Klein II",
275 | "Chanelle Marvin DDS",
276 | "Adriel Littel",
277 | "Bill Brekke",
278 | "Roger Nikolaus",
279 | "Aida Monahan V",
280 | "Mr. Wanda Kreiger",
281 | "Carlo Connelly",
282 | "Tess Fahey V",
283 | "Mr. Wilbert Abbott",
284 | "Kiera Beatty III",
285 | "Alverta Doyle",
286 | "Miss Litzy Schuppe",
287 | "Roxanne Hackett",
288 | "Clark Morar PhD",
289 | "Judd Hoppe",
290 | "Delphia Stoltenberg V",
291 | "Ms. Robyn Ankunding",
292 | "Laura Hessel I",
293 | "Mrs. Vickie Monahan",
294 | "Romaine Kunde",
295 | "Ms. Maurice Towne",
296 | "Yvonne Torp",
297 | "Lula O'Keefe",
298 | "Daija Hickle Sr.",
299 | "Mrs. Aurelie Halvorson",
300 | "Kattie Pagac MD",
301 | "Waino Sporer",
302 | "Jayne Price",
303 | "Peter Ruecker",
304 | "Bella Halvorson",
305 | "Ms. Sammy Kautzer",
306 | "Tyrique Kilback",
307 | "Lizeth Lockman",
308 | "Alfonzo Cole",
309 | "Emmett Weber Sr.",
310 | "Wilma Streich",
311 | "Krystina Lowe III",
312 | "Elton Harvey",
313 | "Bobby Muller",
314 | "Eddie Lesch PhD",
315 | "Drake Walter",
316 | "Roma Jacobs IV",
317 | "Ms. Drake Reynolds",
318 | "Chasity D'Amore DVM",
319 | "D'angelo Langworth",
320 | "Aletha Armstrong",
321 | "Maxine Schmidt",
322 | "Nicholas Zemlak",
323 | "Laurel Klocko",
324 | "Hadley Green",
325 | "Dr. Estevan Moore",
326 | "Ms. Brandy Mitchell",
327 | "Evert Roob",
328 | "Devin Schmidt",
329 | "Aliza Smith",
330 | ].randomElement()!
331 | }
332 |
333 | func email() -> String {
334 | [
335 | "arlofisher3550@example.org",
336 | "trekling@example.org",
337 | "jaunitabode3564@example.net",
338 | "toymueller@example.org",
339 | "adelinehammes@example.com",
340 | "jeffryparisian@example.org",
341 | "andersonlueilwitz@example.com",
342 | "stanbergnaum@example.com",
343 | "luigihilll2861@example.net",
344 | "flaviooreilly3590@example.com",
345 | "joanabecker3806@example.org",
346 | "adolfoerdman8750@example.net",
347 | "bennycrooks8272@example.org",
348 | "janaeweissnat@example.com",
349 | "nickschaden1805@example.net",
350 | "elsieparisian9900@example.net",
351 | "raymundokris7319@example.com",
352 | "davionreinger@example.org",
353 | "sheldondickinson@example.net",
354 | "ezequielgerlach@example.org",
355 | "braedenhayes9728@example.net",
356 | "justynokeefe6787@example.com",
357 | "jeremielebsack@example.net",
358 | "delphinemertz@example.com",
359 | "idawisozk@example.net",
360 | "courtneyswift@example.com",
361 | "myrtismcdermott@example.net",
362 | "margretpredovic2100@example.com",
363 | "andreannehomenick@example.net",
364 | "malcolmjones1807@example.org",
365 | "kiangibson@example.net",
366 | "assuntakuhn@example.org",
367 | "javonteschaefer895@example.org",
368 | "arliepacocha6522@example.net",
369 | "kayliharris6253@example.net",
370 | "cathrineklein9566@example.net",
371 | "llewellynswift7805@example.net",
372 | "pearlkunze4733@example.com",
373 | "yvonnemarquardt126@example.com",
374 | "brionnakilback1360@example.net",
375 | "lauryortiz1968@example.org",
376 | "kaileyturner@example.com",
377 | "giovannihoppe8589@example.org",
378 | "dorotheaskiles9400@example.net",
379 | "mandyborer@example.net",
380 | "lolitakoss@example.com",
381 | "harveyzboncak2755@example.org",
382 | "johnathandamore@example.com",
383 | "korbinrau@example.com",
384 | "josiahrunolfsson@example.org",
385 | "chaimcronin@example.net",
386 | "bartonkirlin@example.com",
387 | "kipbeer7161@example.net",
388 | "mortonfay641@example.net",
389 | "floydschumm@example.org",
390 | "garrettlind7328@example.net",
391 | "ronaldobernhard4818@example.com",
392 | "adelereynolds@example.net",
393 | "chandlerortiz@example.org",
394 | "ubaldocummings4532@example.com",
395 | "francescokunde@example.net",
396 | "othakilback@example.org",
397 | "tyrelokuneva1212@example.com",
398 | "jaquelineschoen@example.com",
399 | "amelienienow@example.org",
400 | "kaseydooley@example.com",
401 | "jaredcrooks8426@example.net",
402 | "abigailwhite@example.org",
403 | "laurelmonahan@example.com",
404 | "magnoliasimonis@example.com",
405 | "reymundohegmann6771@example.net",
406 | "jacklesch@example.org",
407 | "jamesonvonrueden5647@example.org",
408 | "noemygulgowski7044@example.com",
409 | "wilhelmwalsh7734@example.net",
410 | "velmasmitham@example.com",
411 | "broderickkoss@example.com",
412 | "jimmierice@example.net",
413 | "adanfahey1357@example.net",
414 | "ralphbode953@example.org",
415 | "talonchamplin5439@example.net",
416 | "francomayer1718@example.com",
417 | "alanvonrueden6307@example.com",
418 | "tamaramcglynn507@example.org",
419 | "federicoabshire@example.org",
420 | "michelleoreilly6005@example.com",
421 | "adrianfay387@example.com",
422 | "rosaleecrona2661@example.com",
423 | "ardithgrady1155@example.com",
424 | "todmoore2778@example.com",
425 | "broderickhauck6843@example.com",
426 | "norberthilll@example.org",
427 | "mikelheathcote@example.com",
428 | "allenlemke@example.net",
429 | "dalesmitham@example.net",
430 | "aliciakris@example.com",
431 | "keonpurdy9306@example.com",
432 | "virgilbailey4305@example.net",
433 | "cassidyschinner@example.net",
434 | "asaswift@example.com",
435 | ].randomElement()!
436 | }
437 |
438 | func address() -> String {
439 | [
440 | "177 Barton Park Suite 325, Port Desireefort, ND 63460-2938",
441 | "799 Brown Junction Suite 862, Lake Constance, NH 42691",
442 | "150 Hand Spring Apt. 212, Lake Elizabethburgh, KY 07372-7861",
443 | "8940 Brycen Mount Suite 549, North Kaelynmouth, AZ 21970",
444 | "7253 Geo Meadows Suite 004, North Randiview, NM 37030-4812",
445 | "835 Hilpert Square Suite 651, Johathanshire, AR 24800",
446 | "8301 Jacobson Drive Apt. 342, Port Ed, VA 60132",
447 | "7731 Tavares Fields Suite 211, Parisianview, MS 01372",
448 | "21283 Mann Mews Suite 732, Port Eliasborough, OK 53768-4104",
449 | "1331 Dooley Hill Apt. 415, Oswaldton, CA 75322",
450 | "4934 Paolo Causeway Suite 008, Lake Enriquestad, AL 01847",
451 | "66455 Jordi Course Apt. 550, South David, MT 68319",
452 | "31741 Koepp Pike Suite 386, Monahanbury, OH 47436-3352",
453 | "756 Kayli Ford Suite 643, Schummview, ME 54129-4394",
454 | "73059 Padberg Mountains Apt. 530, Charlenestad, NH 60840-9654",
455 | "56710 Orville Spur Suite 499, New Josiane, TN 64396",
456 | "4443 Nella Center Apt. 459, Buckridgefort, FL 13356-8528",
457 | "090 Hettinger Pass Suite 289, Joannieview, FL 51210-6637",
458 | "8090 Jerde Street Apt. 043, Port Dudley, IA 66665-2011",
459 | "22975 Mante Flats Apt. 304, Lockmanstad, AK 78861-4028",
460 | "6661 Feest Springs Suite 930, Schadenchester, AZ 86593",
461 | "144 Jazmyn Roads Suite 997, East Sierraville, TN 12466",
462 | "12754 Cummings Keys Apt. 194, Abbieport, VT 87222",
463 | "5374 Kurtis Shore Apt. 165, West Nigelton, DE 30015",
464 | "3889 Jocelyn Estate Suite 861, Port Freedaview, OK 38262-8125",
465 | "60807 Schimmel Flat Suite 437, Beattyhaven, PA 77437",
466 | "822 Stehr Cliff Apt. 749, Port Callie, MA 37497",
467 | "04991 Neil Rapid Suite 915, Lakinhaven, LA 64422",
468 | "8733 Frieda Ville Suite 476, Venaland, GA 30813",
469 | "8278 Clemens Garden Suite 149, New Augustusbury, MT 25632-9353",
470 | "7353 Wilderman Square Suite 200, North Joelletown, UT 57604",
471 | "49174 Gleichner Village Suite 784, Coleview, CA 45514",
472 | "221 Ernser Turnpike Suite 964, North Hallemouth, AL 33709-8164",
473 | "5211 Kertzmann Expressway Apt. 037, Kamrenhaven, ND 68425",
474 | "357 Elvera Harbor Apt. 641, Langworthview, LA 24085",
475 | "73639 Dietrich Shore Suite 108, East Elena, PA 72253-8763",
476 | "82166 Heathcote Grove Apt. 980, Port Hester, IA 35828",
477 | "842 Blick Loop Suite 242, Eichmannfort, ME 16750",
478 | "21025 Hessel Islands Apt. 618, Port Nellie, TX 86126",
479 | "79884 Windler Spurs Apt. 349, Imogenestad, VT 07028",
480 | "79825 Smitham Run Suite 829, Lake Vallie, IA 86441-5452",
481 | "0830 Estel Isle Suite 871, Boscomouth, UT 06972",
482 | "72547 Krajcik Plaza Apt. 112, Christbury, PA 78077-1242",
483 | "48703 Mallory Corners Apt. 211, Treutelland, CO 52572",
484 | "3123 Lehner Camp Apt. 101, Mooreland, ID 58905-3568",
485 | "4949 Runte Isle Suite 479, Easterport, TN 37137-4856",
486 | "86293 Swaniawski Expressway Suite 578, Lake Warrenbury, IN 40174",
487 | "8918 Dayna Views Apt. 036, Goldnerhaven, OR 73988-4256",
488 | "2727 Reichel Road Apt. 682, Daxhaven, GA 69351",
489 | "83909 Dedric Ways Apt. 192, South Rylan, OH 27334",
490 | "08045 Rutherford Square Suite 811, North Cynthia, VA 65335",
491 | "43100 Raynor Loaf Suite 869, Volkmanland, OR 57721-7001",
492 | "54294 Altenwerth Expressway Suite 794, South Easton, IL 76029-7695",
493 | "27690 Paucek Mountains Apt. 226, Lake Craig, TX 57731",
494 | "8255 Frami Plaza Suite 600, Yostberg, NE 85415-7929",
495 | "483 Abshire Common Apt. 791, Port August, WV 59609-0461",
496 | "8825 Olson Ramp Apt. 058, Jaylanmouth, NJ 29945-3355",
497 | "07838 Bahringer Expressway Suite 651, Ayanaside, WY 43205-3887",
498 | "31162 Arthur Vista Apt. 082, Colinport, KS 11783",
499 | "3062 Precious Creek Apt. 891, Lake Ozella, SC 70946-3911",
500 | "3368 Belle Plaza Apt. 266, Desmondfort, ME 43823",
501 | "8865 Hoyt Forges Suite 759, South Simchester, FL 15249",
502 | "61275 Shanelle Villages Apt. 581, Port Amirburgh, MN 62469",
503 | "499 Annabel Cove Suite 871, Pourosside, NC 77009-8290",
504 | "48987 Toby Pine Suite 324, Hirthebury, TX 00951-2528",
505 | "40218 Charlotte Locks Apt. 014, Lambertborough, NH 54003",
506 | "66776 Gaetano Coves Apt. 367, Elainahaven, MO 46698-5098",
507 | "8017 Francesca Extension Suite 097, Lake Drewfort, NJ 30136-7780",
508 | "40585 Gerda Flat Apt. 055, East Theresehaven, NV 82240",
509 | "10110 Amira Isle Apt. 128, Nicolettechester, RI 39686-8395",
510 | "439 Luther Wall Suite 483, Lake Alvaburgh, IA 28963-3861",
511 | "7417 Haley Forks Suite 483, West Moshe, MD 19133-8552",
512 | "03878 Streich Route Suite 328, Aftonshire, NJ 32088",
513 | "14091 Eula Branch Suite 117, Verdaton, NM 21829",
514 | "6760 Nils Mall Suite 390, Kesslerstad, WV 53577-7421",
515 | "1428 Emilio Hills Apt. 207, Lake Merle, MS 77552-7837",
516 | "2672 Adolf Pike Apt. 444, Keonshire, MS 38192-0949",
517 | "1414 Nathanael Corner Suite 722, Nolanshire, GA 02881-8398",
518 | "439 Denesik Lake Apt. 711, McGlynnstad, MA 32681",
519 | "831 Delta Key Suite 194, South Estevan, MO 39701",
520 | "157 Collins Ranch Apt. 400, North Montanaport, TN 22513",
521 | "69905 Ferry Groves Apt. 674, New Annamarieville, CA 83186",
522 | "663 Pouros Greens Suite 322, McCulloughmouth, LA 13810",
523 | "647 Stroman Meadow Apt. 788, Rethaborough, OK 09180-5044",
524 | "17655 Nya Green Suite 462, New Thorahaven, IN 37499",
525 | "40763 Bernhard Radial Apt. 553, Kimberlyfurt, UT 37180",
526 | "2714 Bogan Spur Suite 346, West Dahliafort, NV 28971-6746",
527 | "5083 Louvenia Meadows Suite 374, Geraldshire, MA 07497-3884",
528 | "4997 Kacie Trail Apt. 133, West Kiera, TN 35280-1589",
529 | "14218 Conrad Throughway Suite 710, Karastad, NY 74518",
530 | "322 Jacobi Mount Apt. 668, Murrayville, IA 14531-8860",
531 | "4374 Kirsten Spur Apt. 924, North Armand, IL 57879",
532 | "077 Missouri Forges Apt. 570, New Heidi, NM 36602",
533 | "7133 Cormier Station Apt. 410, Reichelburgh, ND 12390",
534 | "885 Lakin Meadow Suite 575, McGlynnport, OK 21059-6767",
535 | "66852 Luciano Plain Suite 371, Port Desmond, WA 02650-0511",
536 | "616 Alphonso Camp Apt. 402, New Jermaineton, ID 81255-9963",
537 | "33532 Hermann Mountain Apt. 955, Lake Reggie, WY 58507-1096",
538 | "33991 Rex Inlet Suite 071, Imaburgh, WV 01370-9523",
539 | "075 Lynch Manors Apt. 472, Blockton, CT 05388-1223",
540 | ].randomElement()!
541 | }
542 |
543 | func appVersion() -> String {
544 | [
545 | "0.4.3",
546 | "0.4.1",
547 | "7.00",
548 | "0.77",
549 | "2.50",
550 | "0.0.9",
551 | "2.43",
552 | "6.16",
553 | "0.50",
554 | "0.2.3",
555 | "0.3.1",
556 | "0.6.7",
557 | "1.4",
558 | "7.6.1",
559 | "7.27",
560 | "0.2.1",
561 | "0.69",
562 | "5.5",
563 | "0.9.1",
564 | "8.43",
565 | "6.03",
566 | "2.56",
567 | "2.68",
568 | "8.6",
569 | "3.38",
570 | "0.66",
571 | "6.6.1",
572 | "2.8.7",
573 | "0.4.8",
574 | "0.8.7",
575 | "4.8.8",
576 | "7.2",
577 | "0.7.4",
578 | "0.31",
579 | "0.7.2",
580 | "5.2.3",
581 | "1.23",
582 | "0.91",
583 | "6.33",
584 | "0.4.6",
585 | "0.4.7",
586 | "5.47",
587 | "0.59",
588 | "0.84",
589 | "2.1",
590 | "0.5.3",
591 | "0.49",
592 | "0.41",
593 | "0.58",
594 | "4.09",
595 | "7.5",
596 | "0.23",
597 | "3.7",
598 | "0.1.6",
599 | "3.1.9",
600 | "0.1.3",
601 | "0.8.3",
602 | "1.5",
603 | "5.72",
604 | "0.6.2",
605 | "6.2",
606 | "5.50",
607 | "0.3.6",
608 | "0.61",
609 | "0.5.4",
610 | "2.5",
611 | "3.8.4",
612 | "8.51",
613 | "6.34",
614 | "3.35",
615 | "0.8.9",
616 | "0.62",
617 | "6.8",
618 | "8.9",
619 | "2.2.6",
620 | "7.7.4",
621 | "8.5.5",
622 | "2.7.5",
623 | "8.0.8",
624 | "7.9.2",
625 | "0.3.7",
626 | "0.96",
627 | "0.0.1",
628 | "0.35",
629 | "8.1",
630 | "8.08",
631 | "0.94",
632 | "0.04",
633 | "0.0.4",
634 | "4.18",
635 | "0.52",
636 | "2.9",
637 | "6.3.8",
638 | "1.90",
639 | "0.67",
640 | "8.40",
641 | "6.48",
642 | "5.73",
643 | "0.2.0",
644 | "0.9",
645 | ].randomElement()!
646 | }
647 |
648 | func username() -> String {
649 | [
650 | "janieveum1600",
651 | "yazminzemlak1251",
652 | "stefaniewilliamson1015",
653 | "delilahdickinson8049",
654 | "eudorarosenbaum7658",
655 | "spencerbogisich7403",
656 | "ryleighbuckridge",
657 | "macieprosacco",
658 | "fanniekris8708",
659 | "haliesmitham6746",
660 | "bryonsipes5057",
661 | "vincentjohns223",
662 | "brigitteflatley6081",
663 | "patiencewaelchi1859",
664 | "kayleyhuel",
665 | "andreschaefer8206",
666 | "kimberlybergstrom",
667 | "mateojohnston",
668 | "lelahdaugherty",
669 | "kentonmarks7611",
670 | "aliarippin2563",
671 | "dorothywolf",
672 | "pascaledach8742",
673 | "johnsonpacocha3938",
674 | "ulisesstokes2664",
675 | "tadernser",
676 | "lourdesrobel330",
677 | "linniemacgyver4720",
678 | "samarahilll",
679 | "dellawiza",
680 | "cedrickgreenholt5510",
681 | "connordubuque4308",
682 | "altheagleichner9456",
683 | "maverickkuphal5033",
684 | "marleneschiller8703",
685 | "jeremywunsch",
686 | "audrasteuber",
687 | "brodyrutherford7780",
688 | "earnestgrady",
689 | "mayragerlach",
690 | "aracelidaniel605",
691 | "ariannawalker2848",
692 | "fabianspinka",
693 | "leonorking",
694 | "karlierobel5058",
695 | "edmunddooley",
696 | "koryturcotte",
697 | "pinkvandervort",
698 | "stephaniewilliamson",
699 | "dejondurgan8529",
700 | "felicianikolaus2818",
701 | "chaddpadberg",
702 | "kristophermacgyver2575",
703 | "emilianoruecker8314",
704 | "natashawalter",
705 | "rheaspencer67",
706 | "alfredolabadie",
707 | "penelopeconroy",
708 | "wilbertmorissette8557",
709 | "mariettamurray",
710 | "lennastehr4451",
711 | "louiecollins9022",
712 | "perryjaskolski1406",
713 | "lacyfeeney",
714 | "antoninapouros",
715 | "estellakiehn",
716 | "raulkreiger",
717 | "daisymurphy",
718 | "edwinaborer",
719 | "herminioharris",
720 | "zellabeer5557",
721 | "erikaokon",
722 | "ikejakubowski",
723 | "jaquelinmedhurst9455",
724 | "marisollarkin2129",
725 | "jamisonbartoletti5230",
726 | "onaconnelly7806",
727 | "lindajacobi",
728 | "cheyennewiza2766",
729 | "biankakshlerin",
730 | "aidanhomenick",
731 | "berryhammes9250",
732 | "damonrosenbaum7055",
733 | "declanbergnaum",
734 | "miguelrempel",
735 | "katelinwuckert1234",
736 | "sonyahilpert",
737 | "georgettebechtelar",
738 | "claudieschumm6139",
739 | "maudeherman8063",
740 | "paulaspencer3431",
741 | "olliebeer7703",
742 | "chadcummings9186",
743 | "rosannathiel",
744 | "madalynrau470",
745 | "florinerogahn",
746 | "enriquedickinson4162",
747 | "danielavon3687",
748 | "hattiehegmann666",
749 | "mozellreinger",
750 | ].randomElement()!
751 | }
752 |
753 | func creditCardNumber() -> String {
754 | [
755 | "1211-1221-1234-2201",
756 | "1211-1221-1234-2201",
757 | "1234-2121-1221-1211",
758 | "1228-1221-1221-1431",
759 | "1211-1221-1234-2201",
760 | "1212-1221-1121-1234",
761 | "1234-2121-1221-1211",
762 | "1228-1221-1221-1431",
763 | "1211-1221-1234-2201",
764 | "1211-1221-1234-2201",
765 | "1228-1221-1221-1431",
766 | "1234-2121-1221-1211",
767 | "1212-1221-1121-1234",
768 | "1234-2121-1221-1211",
769 | "1211-1221-1234-2201",
770 | "1234-2121-1221-1211",
771 | "1234-2121-1221-1211",
772 | "1212-1221-1121-1234",
773 | "1234-2121-1221-1211",
774 | "1234-2121-1221-1211",
775 | ].randomElement()!
776 | }
777 |
778 | func companyName() -> String {
779 | [
780 | "Ondricka and Sons",
781 | "Collier, Schmitt and Abbott",
782 | "Erdman, Heaney and Cartwright",
783 | "Greenholt, Howell and Gutmann",
784 | "Marks-Stoltenberg",
785 | "Ortiz and Sons",
786 | "Zboncak, Pouros and King",
787 | "Bogisich, Hoeger and White",
788 | "Herzog LLC",
789 | "Wehner, Auer and Swaniawski",
790 | "Hilll-Runte",
791 | "Bins-Tremblay",
792 | "Halvorson LLC",
793 | "Jerde and Sons",
794 | "Kovacek-Dach",
795 | "Gleichner LLC",
796 | "Ernser, King and Bayer",
797 | "Larson Inc",
798 | "Howell, Doyle and Strosin",
799 | "Herman, Lockman and Schiller",
800 | ].randomElement()!
801 | }
802 | }
803 |
804 | extension FakeDataProvider: BooleanDataProvider {
805 | func randomBool() -> Bool {
806 | Bool.random()
807 | }
808 | }
809 |
810 | extension FakeDataProvider: DateDataProvider {
811 | func date() -> Date {
812 | let years = 5
813 | let daysInYears = 365.25 // Consider average year including leap years
814 | let secondsInADay = 24.0 * 60.0 * 60.0
815 |
816 | // Current date as the start date
817 | let startDate = Date()
818 |
819 | // Maximum number of seconds in the specified number of years
820 | let maxSeconds = Int(daysInYears * Double(years) * secondsInADay)
821 |
822 | // Generate a random interval
823 | let randomInterval = TimeInterval(Int.random(in: 0.. URL {
832 | URL(string: "https://picsum.photos/\(width)/\(height)")!
833 | }
834 |
835 | func url() -> URL {
836 | [
837 | URL(string: "https://www.google.com")!,
838 | URL(string: "https://www.youtube.com")!,
839 | URL(string: "https://www.facebook.com")!,
840 | URL(string: "https://www.wikipedia.org")!,
841 | URL(string: "https://www.amazon.com")!,
842 | URL(string: "https://www.twitter.com")!,
843 | URL(string: "https://www.instagram.com")!,
844 | URL(string: "https://www.linkedin.com")!,
845 | URL(string: "https://www.netflix.com")!,
846 | URL(string: "https://www.microsoft.com")!,
847 | URL(string: "https://www.apple.com")!,
848 | URL(string: "https://www.whatsapp.com")!,
849 | URL(string: "https://www.tiktok.com")!,
850 | URL(string: "https://www.ebay.com")!,
851 | URL(string: "https://www.reddit.com")!,
852 | URL(string: "https://www.spotify.com")!,
853 | URL(string: "https://www.airbnb.com")!,
854 | URL(string: "https://www.zoom.us")!,
855 | URL(string: "https://www.adobe.com")!,
856 | URL(string: "https://www.paypal.com")!,
857 | ].randomElement()!
858 | }
859 | }
860 |
861 | extension FakeDataProvider: NumericDataProvider {
862 | func randomInt(min: Int, max: Int) -> Int {
863 | Int.random(in: min...max)
864 | }
865 |
866 | func randomFloat(min: Float, max: Float) -> Float {
867 | Float.random(in: min...max)
868 | }
869 |
870 | func randomDouble(min: Double, max: Double) -> Double {
871 | Double.random(in: min...max)
872 | }
873 |
874 | func price() -> Double {
875 | [
876 | 84.96,
877 | 72.04,
878 | 63.48,
879 | 80.62,
880 | 45.55,
881 | 46.4,
882 | 40.7,
883 | 21.23,
884 | 20.15,
885 | 28.01,
886 | 19.49,
887 | 86.12,
888 | 40.12,
889 | 19.98,
890 | 41.68,
891 | 46.8,
892 | 35.82,
893 | 58.51,
894 | 98.82,
895 | 71.84,
896 | 22.39,
897 | 45.62,
898 | 76.35,
899 | 26.21,
900 | 35.85,
901 | 91.68,
902 | 53.73,
903 | 24.21,
904 | 35.21,
905 | 36.59,
906 | 16.54,
907 | 4.75,
908 | 20.16,
909 | 18.45,
910 | 34.12,
911 | 52.56,
912 | 31.26,
913 | 93.89,
914 | 87.46,
915 | 89.04,
916 | 97.78,
917 | 3.41,
918 | 28.47,
919 | 17.54,
920 | 10.71,
921 | 82.31,
922 | 5.48,
923 | 4.87,
924 | 19.73,
925 | 60.29,
926 | 96.39,
927 | 81.48,
928 | 95.49,
929 | 44.81,
930 | 97.65,
931 | 55.3,
932 | 22.56,
933 | 30.64,
934 | 60.26,
935 | 44.66,
936 | 4.59,
937 | 86.96,
938 | 89.16,
939 | 85.95,
940 | 6.67,
941 | 26.79,
942 | 64.61,
943 | 35.11,
944 | 98.29,
945 | 60.99,
946 | 94.85,
947 | 87.54,
948 | 67.08,
949 | 34.62,
950 | 69.71,
951 | 54.9,
952 | 58.52,
953 | 95.12,
954 | 78.12,
955 | 0.88,
956 | 6.94,
957 | 98.21,
958 | 40.37,
959 | 60.3,
960 | 69.56,
961 | 16.81,
962 | 57.56,
963 | 29.51,
964 | 16.67,
965 | 11.87,
966 | 57.2,
967 | 86.24,
968 | 71.05,
969 | 12.04,
970 | 61.44,
971 | 61.72,
972 | 34.68,
973 | 57.51,
974 | 69.06,
975 | 3.45,
976 | ].randomElement()!
977 | }
978 | }
979 |
--------------------------------------------------------------------------------
/Sources/DataGenerator/SampleBuilderItemCategory.swift:
--------------------------------------------------------------------------------
1 | /*
2 | This source file is part of SwiftAndTipsMacros
3 |
4 | Copyright (c) 2023 Pedro Rojas and project authors
5 | Licensed under MIT License
6 | */
7 |
8 | //
9 | // SampleBuilderItemCategory.swift
10 | //
11 | //
12 | // Created by Pedro Rojas on 28/08/23.
13 | //
14 |
15 | import Foundation
16 |
17 | public enum SampleBuilderItemCategory {
18 | case firstName
19 | case lastName
20 | case fullName
21 | case email
22 | case address
23 | case appVersion
24 | case creditCardNumber
25 | case companyName
26 | case username
27 | case price
28 | case image(width: Int, height: Int)
29 | case url
30 | }
31 |
--------------------------------------------------------------------------------
/Sources/Macros/BinaryString/BinaryStringMacro.swift:
--------------------------------------------------------------------------------
1 | /*
2 | This source file is part of SwiftAndTipsMacros
3 |
4 | Copyright (c) 2023 Pedro Rojas and project authors
5 | Licensed under MIT License
6 | */
7 |
8 | //
9 | // BinaryStringMacro.swift
10 | //
11 | //
12 | // Created by Pedro Rojas on 05/08/23.
13 | //
14 |
15 | import SwiftCompilerPlugin
16 | import SwiftSyntax
17 | import SwiftSyntaxBuilder
18 | import SwiftSyntaxMacros
19 |
20 | /// Implementation of the `binaryString` macro, which takes an expression
21 | /// of type `Int` and produces a string value containing its binary representation . For example:
22 | ///
23 | /// #binary(111)
24 | ///
25 | /// will expand to:
26 | ///
27 | /// "1101111"
28 | public struct BinaryStringMacro: ExpressionMacro {
29 | public static func expansion(
30 | of node: some SwiftSyntax.FreestandingMacroExpansionSyntax,
31 | in context: some SwiftSyntaxMacros.MacroExpansionContext
32 | ) throws -> SwiftSyntax.ExprSyntax {
33 | guard let argument = node.arguments.first?.expression
34 | else {
35 | fatalError("compiler bug: the macro does not have any arguments")
36 | }
37 |
38 | guard let num = Int("\(argument)")
39 | else {
40 | throw BinaryStringError.notAnInt
41 | }
42 |
43 | let binaryString = String(num, radix: 2)
44 |
45 | return ExprSyntax(literal: binaryString)
46 | }
47 | }
48 |
49 | enum BinaryStringError: Error, CustomStringConvertible {
50 | case notAnInt
51 |
52 | var description: String {
53 | switch self {
54 | case .notAnInt:
55 | return "The argument is not an Int literal"
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/Sources/Macros/Extensions/EnumCaseDeclSyntax+Extension.swift:
--------------------------------------------------------------------------------
1 | /*
2 | This source file is part of SwiftAndTipsMacros
3 |
4 | Copyright (c) 2023 Pedro Rojas and project authors
5 | Licensed under MIT License
6 | */
7 |
8 | //
9 | // EnumCaseDeclSyntax+Extension.swift
10 | //
11 | //
12 | // Created by Pedro Rojas on 18/08/23.
13 | //
14 |
15 | import SwiftSyntax
16 |
17 | extension EnumCaseDeclSyntax {
18 | var hasAssociatedValues: Bool {
19 | self.elements.first?.parameterClause != nil
20 | }
21 |
22 | var name: String {
23 | guard let caseName = self.elements.first?.name.text
24 | else {
25 | fatalError("Compiler Bug: Case name not found")
26 | }
27 |
28 | return caseName
29 | }
30 |
31 | var parameters: [(TokenSyntax?, TypeSyntax)] {
32 | self.elements.first?.parameterClause?.parameters.map {
33 | ($0.firstName, $0.type)
34 | } ?? []
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/Sources/Macros/Extensions/TypeSyntax+Extension.swift:
--------------------------------------------------------------------------------
1 | /*
2 | This source file is part of SwiftAndTipsMacros
3 |
4 | Copyright (c) 2023 Pedro Rojas and project authors
5 | Licensed under MIT License
6 | */
7 |
8 | //
9 | // TypeSyntax+Extension.swift
10 | //
11 | //
12 | // Created by Pedro Rojas on 15/08/23.
13 | //
14 |
15 | import SwiftSyntax
16 |
17 | extension TypeSyntax {
18 | var isArray: Bool {
19 | self.as(ArrayTypeSyntax.self) != nil
20 | }
21 |
22 | var isSimpleType: Bool {
23 | self.as(IdentifierTypeSyntax.self) != nil
24 | }
25 |
26 | var isDictionary: Bool {
27 | self.as(DictionaryTypeSyntax.self) != nil
28 | }
29 |
30 | var isOptional: Bool {
31 | self.as(OptionalTypeSyntax.self) != nil
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Sources/Macros/Extensions/VariableDeclSyntax+Extension.swift:
--------------------------------------------------------------------------------
1 | /*
2 | This source file is part of SwiftAndTipsMacros
3 |
4 | Copyright (c) 2023 Pedro Rojas and project authors
5 | Licensed under MIT License
6 | */
7 |
8 | //
9 | // VariableDeclSyntax+Extension.swift
10 | //
11 | //
12 | // Created by Pedro Rojas on 15/08/23.
13 | //
14 |
15 | import SwiftSyntax
16 |
17 | extension VariableDeclSyntax {
18 | var isStoredProperty: Bool {
19 | // Stored properties cannot have more than 1 binding in its declaration.
20 | guard bindings.count == 1
21 | else {
22 | return false
23 | }
24 |
25 | guard let accesor = bindings.first?.accessorBlock
26 | else {
27 | // Nothing to review. It's a valid stored property
28 | return true
29 | }
30 |
31 | switch accesor.accessors {
32 | case .accessors(let accesorBlockSyntax):
33 | // Observers are valid accesors only
34 | let validAccesors = Set([
35 | .keyword(.willSet), .keyword(.didSet)
36 | ])
37 |
38 | let hasValidAccesors = accesorBlockSyntax.contains {
39 | // Other kind of accesors will make the variable a computed property
40 | validAccesors.contains($0.accessorSpecifier.tokenKind)
41 | }
42 | return hasValidAccesors
43 | case .getter:
44 | // A variable with only a getter is not valid for initialization.
45 | return false
46 | }
47 |
48 | }
49 |
50 | var isPublic: Bool {
51 | modifiers.contains {
52 | $0.name.tokenKind == .keyword(.public)
53 | }
54 | }
55 |
56 | var isPrivate: Bool {
57 | modifiers.contains {
58 | $0.name.tokenKind == .keyword(.private)
59 | }
60 | }
61 |
62 | var typeName: String {
63 | self.bindings.first?.typeAnnotation?.type.as(IdentifierTypeSyntax.self)?.name.text ?? ""
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/Sources/Macros/Plugin/SwiftAndTipsMacrosPlugin.swift:
--------------------------------------------------------------------------------
1 | /*
2 | This source file is part of SwiftAndTipsMacros
3 |
4 | Copyright (c) 2023 Pedro Rojas and project authors
5 | Licensed under MIT License
6 | */
7 |
8 | import SwiftCompilerPlugin
9 | import SwiftSyntax
10 | import SwiftSyntaxBuilder
11 | import SwiftSyntaxMacros
12 |
13 | @main
14 | struct SwiftAndTipsMacrosPlugin: CompilerPlugin {
15 | let providingMacros: [Macro.Type] = [
16 | BinaryStringMacro.self,
17 | SampleBuilderMacro.self,
18 | SampleBuilderItemMacro.self,
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/Sources/Macros/SampleBuilder/Diagnostic/SampleBuilderDiagnostic.swift:
--------------------------------------------------------------------------------
1 | /*
2 | This source file is part of SwiftAndTipsMacros
3 |
4 | Copyright (c) 2023 Pedro Rojas and project authors
5 | Licensed under MIT License
6 | */
7 |
8 | //
9 | // SampleBuilderError.swift
10 | //
11 | //
12 | // Created by Pedro Rojas on 15/08/23.
13 | //
14 |
15 | import SwiftSyntax
16 | import SwiftDiagnostics
17 | import SwiftSyntaxMacros
18 |
19 | enum SampleBuilderDiagnostic: String, DiagnosticMessage {
20 | case notAnStructOrEnum
21 | case argumentNotGreaterThanZero
22 | case enumWithEmptyCases
23 | case sampleBuilderItemRedundant
24 |
25 | var severity: DiagnosticSeverity {
26 | switch self {
27 | case .notAnStructOrEnum:
28 | return .error
29 | case .argumentNotGreaterThanZero:
30 | return .error
31 | case .enumWithEmptyCases:
32 | return .error
33 | case .sampleBuilderItemRedundant:
34 | return .warning
35 | }
36 | }
37 |
38 | var message: String {
39 | switch self {
40 | case .notAnStructOrEnum:
41 | return "@SampleBuilder can only be applied to Structs and Enums"
42 | case .argumentNotGreaterThanZero:
43 | return "'numberOfitems' argument must be greater than zero"
44 | case .enumWithEmptyCases:
45 | return "Enum must contain at least one case"
46 | case .sampleBuilderItemRedundant:
47 | return "@SampleBuilderItem attribute has no effect when generator type is 'default'"
48 | }
49 | }
50 |
51 | var diagnosticID: MessageID {
52 | return MessageID(domain: "SwiftAndTipsMacros", id: rawValue)
53 | }
54 |
55 | static func report(
56 | diagnostic: Self,
57 | node: Syntax,
58 | context: some SwiftSyntaxMacros.MacroExpansionContext
59 | ) {
60 |
61 | let fixIts = getFixIts(for: diagnostic, node: node)
62 | let diagnostic = Diagnostic(
63 | node: Syntax(node),
64 | message: diagnostic,
65 | fixIts: fixIts
66 | )
67 | context.diagnose(diagnostic)
68 | }
69 |
70 | static func getFixIts(
71 | for diagnostic: Self,
72 | node: Syntax
73 | ) -> [FixIt] {
74 | switch diagnostic {
75 | case .enumWithEmptyCases:
76 | return [
77 | .init(
78 | message: SampleBuilderFixIt.addNewEnumCase,
79 | changes: [
80 | .replace(
81 | oldNode: Syntax(
82 | fromProtocol: node.as(EnumDeclSyntax.self)?.memberBlock ?? node
83 | ),
84 | newNode: Syntax(
85 | MemberBlockSyntax(
86 | leftBrace: .leftBraceToken(),
87 | members: MemberBlockItemListSyntax {
88 | MemberBlockItemSyntax(
89 | decl: EnumCaseDeclSyntax(
90 | leadingTrivia: .newline,
91 | caseKeyword: .keyword(.case),
92 | elements: EnumCaseElementListSyntax {
93 | EnumCaseElementSyntax(
94 | leadingTrivia: .space,
95 | name: .identifier("<#your case#>")
96 | )
97 | },
98 | trailingTrivia: .newline
99 | )
100 | )
101 | },
102 | rightBrace: .rightBraceToken()
103 | )
104 | )
105 | )
106 | ]
107 | )
108 | ]
109 | case .sampleBuilderItemRedundant:
110 | return [
111 | .init(
112 | message: SampleBuilderFixIt.removeSampleBuilderItem,
113 | changes: [
114 | .replace(
115 | oldNode: Syntax(
116 | fromProtocol: node.as(AttributeSyntax.self) ?? node
117 | ),
118 | newNode: Syntax(AttributeSyntax(stringLiteral: ""))
119 | )
120 | ]
121 | )
122 | ]
123 | case .argumentNotGreaterThanZero, .notAnStructOrEnum:
124 | return [] // No suggestion to provide
125 | }
126 | }
127 |
128 | }
129 |
--------------------------------------------------------------------------------
/Sources/Macros/SampleBuilder/Diagnostic/SampleBuilderFixIt.swift:
--------------------------------------------------------------------------------
1 | /*
2 | This source file is part of SwiftAndTipsMacros
3 |
4 | Copyright (c) 2023 Pedro Rojas and project authors
5 | Licensed under MIT License
6 | */
7 |
8 | //
9 | // SampleBuilderFixIt.swift
10 | //
11 | //
12 | // Created by Pedro Rojas on 22/08/23.
13 | //
14 |
15 | import SwiftDiagnostics
16 |
17 | struct SampleBuilderFixIt: FixItMessage {
18 | public let message: String
19 | private let messageID: String
20 |
21 | /// This should only be called within a static var on FixItMessage, such
22 | /// as the examples below. This allows us to pick up the messageID from the
23 | /// var name.
24 | fileprivate init(_ message: String, messageID: String = #function) {
25 | self.message = message
26 | self.messageID = messageID
27 | }
28 |
29 | public var fixItID: MessageID {
30 | MessageID(
31 | domain: "SwiftAndTipsMacros",
32 | id: "\(type(of: self)).\(messageID)"
33 | )
34 | }
35 | }
36 |
37 |
38 | extension FixItMessage where Self == SampleBuilderFixIt {
39 | static var addNewEnumCase: Self {
40 | .init("add a new enum case")
41 | }
42 |
43 | static var removeSampleBuilderItem: Self {
44 | .init("remove @SampleBuilderItem")
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/Sources/Macros/SampleBuilder/ParameterItem.swift:
--------------------------------------------------------------------------------
1 | /*
2 | This source file is part of SwiftAndTipsMacros
3 |
4 | Copyright (c) 2023 Pedro Rojas and project authors
5 | Licensed under MIT License
6 | */
7 |
8 | //
9 | // InitParameterItem.swift
10 | //
11 | //
12 | // Created by Pedro Rojas on 16/08/23.
13 | //
14 |
15 | import SwiftSyntax
16 | import DataGenerator
17 | import DataCategory
18 |
19 | struct ParameterItem {
20 | let identifierName: String?
21 | let identifierType: TypeSyntax
22 | let category: DataCategory?
23 |
24 | var hasName: Bool {
25 | identifierName != nil
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/Sources/Macros/SampleBuilder/SampleBuilderMacro+Enum.swift:
--------------------------------------------------------------------------------
1 | /*
2 | This source file is part of SwiftAndTipsMacros
3 |
4 | Copyright (c) 2023 Pedro Rojas and project authors
5 | Licensed under MIT License
6 | */
7 |
8 | //
9 | // SampleBuilderMacro+Enum.swift
10 | //
11 | //
12 | // Created by Pedro Rojas on 19/08/23.
13 | //
14 |
15 | import SwiftSyntax
16 | import SwiftSyntaxMacros
17 | import DataGenerator
18 |
19 | // Enums
20 | extension SampleBuilderMacro {
21 | static func SampleBuilderMacroForEnum(
22 | enumDecl: EnumDeclSyntax,
23 | numberOfItems: Int,
24 | generatorType: DataGeneratorType,
25 | context: some SwiftSyntaxMacros.MacroExpansionContext
26 | ) -> [SwiftSyntax.DeclSyntax] {
27 |
28 | let cases = enumDecl.memberBlock.members.compactMap {
29 | $0.decl.as(EnumCaseDeclSyntax.self)
30 | }
31 |
32 | if cases.isEmpty {
33 | SampleBuilderDiagnostic.report(
34 | diagnostic: .enumWithEmptyCases,
35 | node: Syntax(enumDecl),
36 | context: context
37 | )
38 | return []
39 | }
40 |
41 | let sampleCode = generateSampleCodeSyntax(
42 | sampleData: generateSampleArrayCases(
43 | cases: cases,
44 | numberOfItems: numberOfItems,
45 | generatorType: generatorType
46 | )
47 | )
48 |
49 | return [DeclSyntax(sampleCode)]
50 | }
51 |
52 | static func generateSampleArrayCases(
53 | cases: [EnumCaseDeclSyntax],
54 | numberOfItems: Int,
55 | generatorType: DataGeneratorType
56 | ) -> ArrayElementListSyntax {
57 | var arrayElementListSyntax = ArrayElementListSyntax()
58 |
59 | let totalNumberOfCases = replicateCases(
60 | cases: cases,
61 | numberOfItems: numberOfItems
62 | )
63 |
64 | for caseItem in totalNumberOfCases {
65 | let parameters = caseItem.parameters.map {
66 | ParameterItem(
67 | identifierName: $0.0?.text,
68 | identifierType: $0.1,
69 | category: nil
70 | )
71 | }
72 |
73 | let caseExpression =
74 | if caseItem.hasAssociatedValues {
75 | ExprSyntax(
76 | FunctionCallExprSyntax(
77 | calledExpression: MemberAccessExprSyntax(
78 | period: .periodToken(),
79 | name: .identifier(caseItem.name)
80 | ),
81 | leftParen: .leftParenToken(),
82 | arguments: getParameterListForSampleElement(
83 | parameters: parameters,
84 | generatorType: generatorType
85 | ),
86 | rightParen: .rightParenToken()
87 | )
88 | )
89 | } else {
90 | ExprSyntax(
91 | MemberAccessExprSyntax(
92 | period: .periodToken(),
93 | name: .identifier(caseItem.name)
94 | )
95 | )
96 | }
97 |
98 | arrayElementListSyntax.append(
99 | ArrayElementSyntax(
100 | leadingTrivia: .newline,
101 | expression: caseExpression,
102 | trailingComma: .commaToken()
103 | )
104 | )
105 | }
106 |
107 | return arrayElementListSyntax
108 | }
109 |
110 | static func replicateCases(
111 | cases: [EnumCaseDeclSyntax],
112 | numberOfItems: Int
113 | ) -> [EnumCaseDeclSyntax] {
114 | var totalNumberOfCases: [EnumCaseDeclSyntax] = []
115 |
116 | /*
117 | we will extend the cases according to the number of items.
118 | for example, if cases are [case1, case2, case3]
119 | and numberOfItems = 2, the result should be [case1, case2].
120 |
121 | If cases are [case1, case2] and numberOfItems = 7, the result
122 | should be [case1, case2, case1, case2, case1, case2, case1]
123 | */
124 | for i in 0.. ExprSyntax {
25 | if type.isArray {
26 | getArrayExprSyntax(
27 | arrayType: type.as(ArrayTypeSyntax.self)!,
28 | generatorType: generatorType,
29 | category: category
30 | )
31 | } else if type.isDictionary {
32 | getDictionaryExprSyntax(
33 | dictionaryType: type.as(DictionaryTypeSyntax.self)!,
34 | generatorType: generatorType,
35 | category: category
36 | )
37 | } else if type.isOptional {
38 | getOptionalExprSyntax(
39 | optionalType: type.as(OptionalTypeSyntax.self)!,
40 | generatorType: generatorType,
41 | category: category
42 | )
43 | } else {
44 | getSimpleExprSyntax(
45 | simpleType: type.as(IdentifierTypeSyntax.self)!,
46 | generatorType: generatorType,
47 | category: category
48 | )
49 | }
50 | }
51 |
52 | static func getArrayExprSyntax(
53 | arrayType: ArrayTypeSyntax,
54 | generatorType: DataGeneratorType,
55 | category: DataCategory?
56 | ) -> ExprSyntax {
57 |
58 | if let simpleType = arrayType.element.as(IdentifierTypeSyntax.self),
59 | SupportedType(rawValue: simpleType.name.text) == nil {
60 | // Custom array type that attaches SampleBuilder in its declaration:
61 | return ExprSyntax(
62 | MemberAccessExprSyntax(
63 | base: DeclReferenceExprSyntax(
64 | baseName: simpleType.name
65 | ),
66 | period: .periodToken(),
67 | name: .identifier("sample")
68 | )
69 | )
70 | }
71 |
72 | return ExprSyntax(
73 | ArrayExprSyntax(
74 | leftSquare: .leftSquareToken(),
75 | elements: ArrayElementListSyntax {
76 | ArrayElementSyntax(
77 | expression: getExpressionSyntax(
78 | from: TypeSyntax(arrayType.element),
79 | generatorType: generatorType,
80 | category: category
81 | )
82 | )
83 | },
84 | rightSquare: .rightSquareToken()
85 | )
86 | )
87 | }
88 |
89 | static func getDictionaryExprSyntax(
90 | dictionaryType: DictionaryTypeSyntax,
91 | generatorType: DataGeneratorType,
92 | category: DataCategory?
93 | ) -> ExprSyntax {
94 | ExprSyntax(
95 | DictionaryExprSyntax {
96 | DictionaryElementListSyntax {
97 | DictionaryElementSyntax(
98 | key: getExpressionSyntax(
99 | from: dictionaryType.key,
100 | generatorType: generatorType,
101 | category: category
102 | ),
103 | value: getExpressionSyntax(
104 | from: dictionaryType.value,
105 | generatorType: generatorType,
106 | category: category
107 | )
108 | )
109 | }
110 | }
111 | )
112 | }
113 |
114 | static func getOptionalExprSyntax(
115 | optionalType: OptionalTypeSyntax,
116 | generatorType: DataGeneratorType,
117 | category: DataCategory?
118 | ) -> ExprSyntax {
119 | return getExpressionSyntax(
120 | from: optionalType.wrappedType,
121 | generatorType: generatorType,
122 | category: category
123 | )
124 | }
125 |
126 | static func getSimpleExprSyntax(
127 | simpleType: IdentifierTypeSyntax,
128 | generatorType: DataGeneratorType,
129 | category: DataCategory?
130 | ) -> ExprSyntax {
131 |
132 | if let supportedType = SupportedType(rawValue: simpleType.name.text) {
133 | return supportedType.exprSyntax(
134 | dataGeneratorType: generatorType,
135 | category: category
136 | )
137 | }
138 |
139 | // Custom type that attaches SampleBuilder in its declaration:
140 | return ExprSyntax(
141 | ForceUnwrapExprSyntax(
142 | expression: MemberAccessExprSyntax(
143 | base: MemberAccessExprSyntax(
144 | base: DeclReferenceExprSyntax(
145 | baseName: simpleType.name
146 | ),
147 | period: .periodToken(),
148 | name: .identifier("sample")
149 | ),
150 | period: .periodToken(),
151 | name: .identifier("first")
152 | ),
153 | exclamationMark: .exclamationMarkToken()
154 | )
155 | )
156 | }
157 | }
158 |
--------------------------------------------------------------------------------
/Sources/Macros/SampleBuilder/SampleBuilderMacro+Struct.swift:
--------------------------------------------------------------------------------
1 | /*
2 | This source file is part of SwiftAndTipsMacros
3 |
4 | Copyright (c) 2023 Pedro Rojas and project authors
5 | Licensed under MIT License
6 | */
7 |
8 | //
9 | // SampleBuilderMacro+Struct.swift
10 | //
11 | //
12 | // Created by Pedro Rojas on 19/08/23.
13 | //
14 |
15 | import SwiftSyntax
16 | import DataGenerator
17 | import DataCategory
18 | import SwiftSyntaxMacros
19 |
20 | extension SampleBuilderMacro {
21 | static func SampleBuilderMacroForStruct(
22 | structDecl: StructDeclSyntax,
23 | numberOfItems: Int,
24 | generatorType: DataGeneratorType,
25 | context: MacroExpansionContext
26 | ) -> [SwiftSyntax.DeclSyntax] {
27 | let validParameters = getValidParameterList(
28 | from: structDecl,
29 | generatorType: generatorType,
30 | context: context
31 | )
32 |
33 | let sampleCode = generateSampleCodeSyntax(
34 | sampleData: generateSampleData(
35 | parameters: validParameters,
36 | numberOfItems: numberOfItems,
37 | generatorType: generatorType
38 | )
39 | )
40 |
41 | return [DeclSyntax(sampleCode)]
42 | }
43 |
44 | static func getValidParameterList(
45 | from structDecl: StructDeclSyntax,
46 | generatorType: DataGeneratorType,
47 | context: MacroExpansionContext
48 | ) -> [ParameterItem] {
49 | let storedPropertyMembers = structDecl.memberBlock.members
50 | .compactMap {
51 | $0.decl.as(VariableDeclSyntax.self)
52 | }
53 | .filter {
54 | $0.isStoredProperty
55 | }
56 |
57 | let initMembers = structDecl.memberBlock.members
58 | .compactMap {
59 | $0.decl.as(InitializerDeclSyntax.self)
60 | }
61 |
62 | if initMembers.isEmpty {
63 | // No custom init around. We use the memberwise initializer's properties:
64 | return storedPropertyMembers.compactMap {
65 | if let identifier = $0.bindings.first?
66 | .pattern
67 | .as(IdentifierPatternSyntax.self)?
68 | .identifier.text,
69 | let type = $0.bindings.first?
70 | .typeAnnotation?
71 | .type {
72 |
73 | return (identifier, type, getDataCategory(from: $0, generatorType: generatorType, context: context))
74 | }
75 |
76 | return nil
77 | }.map {
78 | ParameterItem(
79 | identifierName: $0.0,
80 | identifierType: $0.1,
81 | category: $0.2
82 | )
83 | }
84 | }
85 |
86 | let largestParameterList = initMembers
87 | .map {
88 | getParametersFromInit(initSyntax: $0)
89 | }.max {
90 | $0.count < $1.count
91 | } ?? []
92 |
93 | return largestParameterList
94 | }
95 |
96 | static func getDataCategory(
97 | from variableDecl: VariableDeclSyntax,
98 | generatorType: DataGeneratorType,
99 | context: MacroExpansionContext
100 | ) -> DataCategory? {
101 |
102 | // Check if SampleBuilderItem is used.
103 | guard let attribute = variableDecl.attributes
104 | .first(where: {
105 | $0.as(AttributeSyntax.self)?
106 | .attributeName
107 | .as(IdentifierTypeSyntax.self)?
108 | .name.text == "SampleBuilderItem"
109 | })?.as(AttributeSyntax.self)
110 |
111 | else {
112 | return DataCategory.noCategory
113 | }
114 |
115 | if generatorType == .default {
116 | // Since default will always the return the same value,
117 | // Using SampleBuilderItem is useless and will throw a warning.
118 | SampleBuilderDiagnostic.report(
119 | diagnostic: .sampleBuilderItemRedundant,
120 | node: Syntax(attribute),
121 | context: context
122 | )
123 | }
124 |
125 | if let simpleCategoryString = attribute // All categories except image
126 | .arguments?
127 | .as(LabeledExprListSyntax.self)?
128 | .first?.expression
129 | .as(MemberAccessExprSyntax.self)?
130 | .declName.baseName.text {
131 |
132 | return DataCategory(rawValue: simpleCategoryString)
133 | }
134 |
135 | if let imageCategoryExpression = attribute
136 | .arguments?
137 | .as(LabeledExprListSyntax.self)?.first?
138 | .expression.as(FunctionCallExprSyntax.self),
139 |
140 | imageCategoryExpression
141 | .calledExpression.as(MemberAccessExprSyntax.self)?
142 | .declName.baseName.text == "image" {
143 |
144 | // Image's width and height
145 | let argumentsValues = imageCategoryExpression.arguments.compactMap {
146 | Int($0.expression.as(IntegerLiteralExprSyntax.self)?.literal.text ?? "")
147 | }
148 |
149 | guard argumentsValues.count == 2
150 | else {
151 | return DataCategory.noCategory
152 | }
153 |
154 | return DataCategory(imageWidth: argumentsValues[0], height: argumentsValues[1])
155 | }
156 |
157 | return DataCategory.noCategory
158 | }
159 |
160 | static func getParametersFromInit(
161 | initSyntax: InitializerDeclSyntax
162 | ) -> [ParameterItem] {
163 | let parameters = initSyntax.signature.parameterClause.parameters
164 |
165 | return parameters.map {
166 | ParameterItem(
167 | identifierName: $0.firstName.text,
168 | identifierType: $0.type,
169 | category: nil
170 | )
171 | }
172 | }
173 |
174 | static func generateSampleData(
175 | parameters: [ParameterItem],
176 | numberOfItems: Int,
177 | generatorType: DataGeneratorType
178 | ) -> ArrayElementListSyntax {
179 | let parameterList = getParameterListForSampleElement(
180 | parameters: parameters,
181 | generatorType: generatorType
182 | )
183 |
184 | var arrayElementListSyntax = ArrayElementListSyntax()
185 |
186 | for _ in 1...numberOfItems {
187 | arrayElementListSyntax
188 | .append(
189 | ArrayElementSyntax(
190 | leadingTrivia: .newline,
191 | expression: FunctionCallExprSyntax(
192 | calledExpression: MemberAccessExprSyntax(
193 | period: .periodToken(),
194 | name: .keyword(.`init`)
195 | ),
196 | leftParen: .leftParenToken(),
197 | arguments: parameterList,
198 | rightParen: .rightParenToken()
199 | ),
200 | trailingComma: .commaToken()
201 | )
202 | )
203 | }
204 |
205 | return arrayElementListSyntax
206 | }
207 | }
208 |
--------------------------------------------------------------------------------
/Sources/Macros/SampleBuilder/SampleBuilderMacro.swift:
--------------------------------------------------------------------------------
1 | /*
2 | This source file is part of SwiftAndTipsMacros
3 |
4 | Copyright (c) 2023 Pedro Rojas and project authors
5 | Licensed under MIT License
6 | */
7 |
8 | //
9 | // SampleBuilderMacro.swift
10 | //
11 | //
12 | // Created by Pedro Rojas on 05/08/23.
13 | //
14 |
15 | import SwiftCompilerPlugin
16 | import SwiftSyntax
17 | import SwiftSyntaxBuilder
18 | import SwiftSyntaxMacros
19 | import SwiftDiagnostics
20 | import DataGenerator
21 |
22 | public struct SampleBuilderMacro: MemberMacro {
23 | public static func expansion(
24 | of node: SwiftSyntax.AttributeSyntax,
25 | providingMembersOf declaration: some SwiftSyntax.DeclGroupSyntax,
26 | in context: some SwiftSyntaxMacros.MacroExpansionContext
27 | ) throws -> [SwiftSyntax.DeclSyntax] {
28 |
29 | let numberOfItems = try getNumberOfItems(from: node)
30 |
31 | if numberOfItems <= 0 {
32 | SampleBuilderDiagnostic.report(
33 | diagnostic: .argumentNotGreaterThanZero,
34 | node: Syntax(declaration),
35 | context: context
36 | )
37 | return []
38 | }
39 |
40 | let generatorType = getDataGeneratorType(from: node)
41 |
42 | if let enumDecl = declaration.as(EnumDeclSyntax.self) {
43 | return SampleBuilderMacroForEnum(
44 | enumDecl: enumDecl,
45 | numberOfItems: numberOfItems,
46 | generatorType: generatorType,
47 | context: context
48 | )
49 | }
50 |
51 | if let structDecl = declaration.as(StructDeclSyntax.self) {
52 | return SampleBuilderMacroForStruct(
53 | structDecl: structDecl,
54 | numberOfItems: numberOfItems,
55 | generatorType: generatorType,
56 | context: context
57 | )
58 | }
59 |
60 | SampleBuilderDiagnostic.report(
61 | diagnostic: .notAnStructOrEnum,
62 | node: Syntax(declaration),
63 | context: context
64 | )
65 | return []
66 | }
67 | }
68 |
69 | extension SampleBuilderMacro {
70 |
71 | static func generateSampleCodeSyntax(
72 | sampleData: ArrayElementListSyntax
73 | ) -> IfConfigDeclSyntax {
74 | let returnType = ArrayTypeSyntax(
75 | leftSquare: .leftSquareToken(),
76 | element: IdentifierTypeSyntax(
77 | name: .keyword(.Self)
78 | ),
79 | rightSquare: .rightSquareToken()
80 | )
81 |
82 | let sampleCode = VariableDeclSyntax(
83 | modifiers: DeclModifierListSyntax {
84 | DeclModifierSyntax(name: .keyword(.static))
85 | },
86 | bindingSpecifier: .keyword(.var),
87 | bindings: PatternBindingListSyntax {
88 | PatternBindingSyntax(
89 | pattern: IdentifierPatternSyntax(identifier: .identifier("sample")
90 | ),
91 | typeAnnotation: TypeAnnotationSyntax(
92 | colon: .colonToken(),
93 | type: returnType
94 | ),
95 | accessorBlock: AccessorBlockSyntax(
96 | leftBrace: .leftBraceToken(),
97 | accessors: .getter(
98 | CodeBlockItemListSyntax {
99 | CodeBlockItemSyntax(
100 | item: .expr(
101 | ExprSyntax(
102 | ArrayExprSyntax(
103 | leftSquare: .leftSquareToken(),
104 | elements: sampleData,
105 | rightSquare: .rightSquareToken(leadingTrivia: .newline)
106 | )
107 | )
108 | )
109 | )
110 |
111 | }
112 | ),
113 | rightBrace: .rightBraceToken()
114 | )
115 | )
116 | }
117 | )
118 |
119 | // This will make the code available only on DEBUG mode
120 | return IfConfigDeclSyntax(
121 | clauses: IfConfigClauseListSyntax {
122 | IfConfigClauseSyntax(
123 | poundKeyword: .poundIfToken(),
124 | condition: DeclReferenceExprSyntax(
125 | baseName: .identifier("DEBUG")
126 | ),
127 | elements: .decls(
128 | MemberBlockItemListSyntax {
129 | MemberBlockItemSyntax(decl: sampleCode)
130 | }
131 | )
132 | )
133 | }
134 | )
135 | }
136 |
137 | static func getDataGeneratorType(
138 | from node: SwiftSyntax.AttributeSyntax
139 | ) -> DataGeneratorType {
140 | guard let argumentTuple = node.arguments?.as(LabeledExprListSyntax.self)
141 | else {
142 | fatalError("Compiler bug: Argument must exist")
143 | }
144 |
145 | guard let generatorArgument = argumentTuple.first(
146 | where: { $0.label?.text == "dataGeneratorType" }
147 | ),
148 | let argumentValue = generatorArgument.expression.as(MemberAccessExprSyntax.self)?.declName.baseName,
149 | let generatorType = DataGeneratorType(rawValue: argumentValue.text)
150 | else {
151 | // return default generator type
152 | return .random
153 | }
154 |
155 | return generatorType
156 | }
157 |
158 | static func getNumberOfItems(
159 | from node: SwiftSyntax.AttributeSyntax
160 | ) throws -> Int {
161 | guard let argumentTuple = node.arguments?.as(LabeledExprListSyntax.self)?.first
162 | else {
163 | fatalError("Compiler bug: Argument must exist")
164 | }
165 |
166 | if let prefixExpression = argumentTuple
167 | .expression
168 | .as(PrefixOperatorExprSyntax.self) {
169 |
170 | return negativeNumberOfItems(expression: prefixExpression)
171 | } else if let integerExpression = argumentTuple
172 | .expression
173 | .as(IntegerLiteralExprSyntax.self),
174 | let numberOfItems = Int(integerExpression.literal.text) {
175 | return numberOfItems
176 | }
177 |
178 | return 0 // Will throw .argumentNotGreaterThanZero in Xcode
179 | }
180 |
181 | static func negativeNumberOfItems(
182 | expression: PrefixOperatorExprSyntax
183 | ) -> Int {
184 | let operatorToken = expression
185 | .operator
186 | .text
187 |
188 | guard
189 | let integerExpression = expression
190 | .expression
191 | .as(IntegerLiteralExprSyntax.self),
192 | let numberOfItems = Int(operatorToken + integerExpression.literal.text)
193 | else {
194 | return 0 // Will throw .argumentNotGreaterThanZero in Xcode
195 | }
196 |
197 | return numberOfItems
198 | }
199 |
200 | //Used for both Structs and Enums
201 | static func getParameterListForSampleElement(
202 | parameters: [ParameterItem],
203 | generatorType: DataGeneratorType
204 | ) -> LabeledExprListSyntax {
205 |
206 | var parameterList = LabeledExprListSyntax()
207 |
208 | for parameter in parameters {
209 |
210 | let expressionSyntax = getExpressionSyntax(
211 | from: parameter.identifierType,
212 | generatorType: generatorType,
213 | category: parameter.category
214 | )
215 |
216 | let isNotLast = parameter.identifierType != parameters.last?.identifierType
217 | let parameterElement = LabeledExprSyntax(
218 | label: parameter.hasName ? .identifier(parameter.identifierName!) : nil,
219 | colon: parameter.hasName ? .colonToken() : nil,
220 | expression: expressionSyntax,
221 | trailingComma: isNotLast ? .commaToken() : nil
222 | )
223 |
224 | parameterList.append(parameterElement)
225 | }
226 |
227 | return parameterList
228 | }
229 | }
230 |
--------------------------------------------------------------------------------
/Sources/Macros/SampleBuilder/SupportedType.swift:
--------------------------------------------------------------------------------
1 | /*
2 | This source file is part of SwiftAndTipsMacros
3 |
4 | Copyright (c) 2023 Pedro Rojas and project authors
5 | Licensed under MIT License
6 | */
7 |
8 | //
9 | // SupportedType.swift
10 | //
11 | //
12 | // Created by Pedro Rojas on 15/08/23.
13 | //
14 |
15 | import SwiftSyntax
16 | import DataGenerator
17 | import DataCategory
18 |
19 |
20 | enum SupportedType: String {
21 | case int = "Int"
22 | case int8 = "Int8"
23 | case int16 = "Int16"
24 | case int32 = "Int32"
25 | case int64 = "Int64"
26 | case uInt8 = "UInt8"
27 | case uInt16 = "UInt16"
28 | case uInt32 = "UInt32"
29 | case uInt64 = "UInt64"
30 | case float = "Float"
31 | case double = "Double"
32 | case string = "String"
33 | case bool = "Bool"
34 | case data = "Data"
35 | case date = "Date"
36 | case uuid = "UUID"
37 | case cgPoint = "CGPoint"
38 | case cgRect = "CGRect"
39 | case cgSize = "CGSize"
40 | case cgVector = "CGVector"
41 | case cgFloat = "CGFloat"
42 | case url = "URL"
43 |
44 | func exprSyntax(
45 | dataGeneratorType: DataGeneratorType,
46 | category: DataCategory?
47 | ) -> ExprSyntax {
48 | switch dataGeneratorType {
49 | case .default:
50 | return ExprSyntax(stringLiteral: "DataGenerator.\(dataGeneratorType).\(self.rawValue.lowercased())()")
51 | case .random:
52 | let categoryParameter = if let category = category {
53 | "dataCategory: .init(rawValue: \"\(category.rawValue)\")"
54 | } else { "" }
55 |
56 | return ExprSyntax(stringLiteral: "DataGenerator.\(dataGeneratorType)(\(categoryParameter)).\(self.rawValue.lowercased())()")
57 | }
58 | }
59 | }
60 |
61 |
--------------------------------------------------------------------------------
/Sources/Macros/SampleBuilderItem/SampleBuilderItemDiagnostic.swift:
--------------------------------------------------------------------------------
1 | /*
2 | This source file is part of SwiftAndTipsMacros
3 |
4 | Copyright (c) 2023 Pedro Rojas and project authors
5 | Licensed under MIT License
6 | */
7 |
8 | //
9 | // SampleBuilderItemDiagnostic.swift
10 | //
11 | //
12 | // Created by Pedro Rojas on 29/08/23.
13 | //
14 |
15 | import SwiftSyntax
16 | import SwiftDiagnostics
17 | import SwiftSyntaxMacros
18 | import DataCategory
19 |
20 | enum SampleBuilderItemDiagnostic: DiagnosticMessage {
21 | case notAStoredProperty
22 | case categoryNotSupported(category: DataCategory, typeName: String)
23 |
24 | var severity: DiagnosticSeverity {
25 | switch self {
26 | case .notAStoredProperty:
27 | return .error
28 | case .categoryNotSupported:
29 | return .error
30 | }
31 | }
32 |
33 | var message: String {
34 | switch self {
35 | case .notAStoredProperty:
36 | return "@SampleBuilderItem can only be applied to stored properties in structs"
37 | case .categoryNotSupported(category: let category, typeName: let typeName):
38 | return "'\(category.rawValue)' category is not compatible with '\(typeName)' type. Use '\(category.getSupportedType().title)' type instead."
39 | }
40 | }
41 |
42 | var id: String {
43 | switch self {
44 | case .notAStoredProperty:
45 | return "notAStoredProperty"
46 | case .categoryNotSupported:
47 | return "categoryNotSupported"
48 | }
49 | }
50 |
51 | var diagnosticID: MessageID {
52 | return MessageID(domain: "SwiftAndTipsMacros", id: id)
53 | }
54 |
55 | static func report(
56 | diagnostic: Self,
57 | node: AttributeSyntax,
58 | context: some SwiftSyntaxMacros.MacroExpansionContext
59 | ) {
60 | let diagnostic = Diagnostic(
61 | node: Syntax(node),
62 | message: diagnostic
63 | )
64 | context.diagnose(diagnostic)
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/Sources/Macros/SampleBuilderItem/SampleBuilderItemMacro.swift:
--------------------------------------------------------------------------------
1 | /*
2 | This source file is part of SwiftAndTipsMacros
3 |
4 | Copyright (c) 2023 Pedro Rojas and project authors
5 | Licensed under MIT License
6 | */
7 |
8 | //
9 | // SampleBuilderItemMacro.swift
10 | //
11 | //
12 | // Created by Pedro Rojas on 23/08/23.
13 | //
14 |
15 | import SwiftCompilerPlugin
16 | import SwiftSyntax
17 | import SwiftSyntaxBuilder
18 | import SwiftSyntaxMacros
19 | import SwiftDiagnostics
20 | import DataCategory
21 |
22 | public struct SampleBuilderItemMacro: PeerMacro {
23 | /*
24 | Why is this a peer macro? TL;DR: Temporary issue in Xcode beta 6 and 7
25 |
26 | Read this post for more context:
27 | https://forums.swift.org/t/member-macro-cannot-be-attached-to-property/66670/2
28 | */
29 |
30 | public static func expansion(
31 | of node: SwiftSyntax.AttributeSyntax,
32 | providingPeersOf declaration: some SwiftSyntax.DeclSyntaxProtocol,
33 | in context: some SwiftSyntaxMacros.MacroExpansionContext
34 | ) throws -> [SwiftSyntax.DeclSyntax] {
35 |
36 | let dataCategory = getDataCategory(from: node)
37 |
38 | guard let variableDecl = declaration
39 | .as(VariableDeclSyntax.self),
40 | variableDecl.isStoredProperty
41 | else {
42 | SampleBuilderItemDiagnostic.report(
43 | diagnostic: .notAStoredProperty,
44 | node: node,
45 | context: context
46 | )
47 | return []
48 | }
49 |
50 | guard dataCategory.supports(type: variableDecl.typeName)
51 | else {
52 | SampleBuilderItemDiagnostic.report(
53 | diagnostic: .categoryNotSupported(category: dataCategory, typeName: variableDecl.typeName),
54 | node: node,
55 | context: context
56 | )
57 | return []
58 | }
59 |
60 | // It does nothing, but will help SampleBuilder to support categories
61 | return []
62 | }
63 |
64 | static func getDataCategory(from node: AttributeSyntax) -> DataCategory {
65 | guard let argumentTuple = node.arguments?.as(LabeledExprListSyntax.self)?.first
66 | else {
67 | fatalError("Compiler bug: Argument must exist")
68 | }
69 |
70 | if let simpleCategoryString = argumentTuple
71 | .expression
72 | .as(MemberAccessExprSyntax.self)?
73 | .declName.baseName.text,
74 | let dataCategory = DataCategory(rawValue: simpleCategoryString) {
75 |
76 | return dataCategory
77 | } else if let imageCategoryString = argumentTuple
78 | .expression
79 | .as(FunctionCallExprSyntax.self)?
80 | .description
81 | .dropFirst(), // it removes the dot from .image(...)
82 | let dataCategory = DataCategory(rawValue: String(imageCategoryString)) {
83 |
84 | return dataCategory
85 | }
86 |
87 | fatalError("Compiler bug: Argument must exist")
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/Sources/SwiftAndTipsMacros/PublicMacros.swift:
--------------------------------------------------------------------------------
1 | /*
2 | This source file is part of SwiftAndTipsMacros
3 |
4 | Copyright (c) 2023 Pedro Rojas and project authors
5 | Licensed under MIT License
6 | */
7 |
8 | import DataGenerator
9 |
10 | /// A macro that transform an Int into a binary representation and returned
11 | /// as string. For example:
12 | ///
13 | /// #binaryString(10)
14 | ///
15 | /// produces a string `"1010"`.
16 | @freestanding(expression)
17 | public macro binaryString(_ value: Int) -> String = #externalMacro(module: "Macros", type: "BinaryStringMacro")
18 |
19 | @attached(member, names: named(sample))
20 | public macro SampleBuilder(
21 | numberOfItems: Int,
22 | dataGeneratorType: DataGeneratorType
23 | ) = #externalMacro(module: "Macros", type: "SampleBuilderMacro")
24 |
25 | @attached(peer)
26 | public macro SampleBuilderItem(
27 | category: SampleBuilderItemCategory
28 | ) = #externalMacro(module: "Macros", type: "SampleBuilderItemMacro")
29 |
--------------------------------------------------------------------------------
/Sources/SwiftAndTipsMacrosClient/main.swift:
--------------------------------------------------------------------------------
1 | /*
2 | This source file is part of SwiftAndTipsMacros
3 |
4 | Copyright (c) 2023 Pedro Rojas and project authors
5 | Licensed under MIT License
6 | */
7 |
8 | import SwiftAndTipsMacros
9 | import Foundation
10 | import DataGenerator
11 |
12 | let x = #binaryString(10)
13 |
14 | print(x)
15 |
16 | @SampleBuilder(numberOfItems: 10, dataGeneratorType: .default)
17 | struct Example {
18 | let item1: UUID
19 | let item2: Int
20 | }
21 |
22 | for element in Example.sample {
23 | print(element.item1, element.item2)
24 | }
25 |
26 | @SampleBuilder(numberOfItems: 3, dataGeneratorType: .random)
27 | struct Review {
28 | let rating: Int
29 | let time: Date
30 | let product: Product
31 | }
32 |
33 | @SampleBuilder(numberOfItems: 3, dataGeneratorType: .random)
34 | struct Product {
35 | var price: Int
36 | var description: String
37 | }
38 |
39 | @SampleBuilder(numberOfItems: 3, dataGeneratorType: .random)
40 | struct Profile {
41 | @SampleBuilderItem(category: .firstName)
42 | let firstName: String
43 |
44 | @SampleBuilderItem(category: .lastName)
45 | let lastName: String
46 |
47 | @SampleBuilderItem(category: .image(width: 300, height: 300))
48 | let profileImage: URL
49 | }
50 |
51 | for profile in Profile.sample {
52 | print(profile.firstName, profile.lastName, profile.profileImage)
53 | }
54 |
55 | //@SampleBuilder(numberOfItems: 5)
56 | //struct Person {
57 | // let id: UUID
58 | // let item1: String
59 | // let item2: Int
60 | // let item3: Bool
61 | // let item4: Data
62 | // let item5: Date
63 | // let item6: Double
64 | // let item7: Float
65 | // let item8: Int8
66 | // let item9: Int16
67 | // let item10: Int32
68 | // let item11: Int64
69 | // let item12: UInt8
70 | // let item13: UInt16
71 | // let item14: UInt32
72 | // let item15: UInt64
73 | // let item16: URL
74 | // let item17: CGPoint
75 | // let item18: CGFloat
76 | // let item19: CGRect
77 | // let item20: CGSize
78 | // let item21: CGVector
79 | //}
80 |
81 |
82 | //
83 | //@SampleBuilder(numberOfItems: 10)
84 | //struct Review {
85 | // let rating: Int
86 | // let time: Date
87 | // let product: Product
88 | //}
89 | //
90 | //@SampleBuilder(numberOfItems: 3)
91 | //struct Product {
92 | // var price: Int
93 | // var description: String
94 | //
95 | //// init(price: Int) {
96 | //// self.price = 0
97 | //// self.description = ""
98 | //// }
99 | //}
100 |
101 | //@SampleBuilder(numberOfItems: 3)
102 | //struct Order {
103 | // var productList: [Product]
104 | // var address: String
105 | // var time: Date
106 | //}
107 |
108 | //@SampleBuilder(numberOfItems: 3)
109 | //struct Example {
110 | // let x: Int
111 | // var y: String
112 | //
113 | // static var notAValidStoredProperty: Self {
114 | // .init(x: 0, y: "Hello World")
115 | // }
116 | //
117 | // var aComputedProperty: String {
118 | // "Hello"
119 | // }
120 | //}
121 |
122 |
123 | //@SampleBuilder(numberOfItems: 3)
124 | //struct Example {
125 | // let x: Int
126 | // private var y: String {
127 | // didSet {
128 | // print("didSet called")
129 | // }
130 | // willSet {
131 | // print("willSet called")
132 | // }
133 | // }
134 | // static var asd: Self {
135 | // .init(x: 0, y: "Hello World")
136 | // }
137 | // var z: String {
138 | // get { y }
139 | // }
140 | // var w: String {
141 | // get {
142 | // y
143 | // }
144 | // set {
145 | // y = newValue
146 | // }
147 | // }
148 | //}
149 |
150 | //@SampleBuilder(numberOfItems: 3)
151 | //struct Product {
152 | // var price: Int
153 | // var description: String
154 | // var dict1: [String: Int]
155 | // var dict2: [String: [Int]]
156 | // var dict3: [String: [String: Example]]
157 | //}
158 | //
159 | //@SampleBuilder(numberOfItems: 3)
160 | //struct Example {
161 | // let x: Int
162 | // let y: String
163 | // let myEnum: MyEnum
164 | //}
165 | //
166 | //@SampleBuilder(numberOfItems: 20)
167 | //struct Product {
168 | // var item1: Int
169 | // var item2: String
170 | //}
171 | //
172 | //@SampleBuilder(numberOfItems: 6)
173 | //enum MyEnum {
174 | // indirect case case1(String, Int, String, [String])
175 | // case case2
176 | // case case3(String, [Product])
177 | //}
178 |
179 |
180 | @SampleBuilder(numberOfItems: 6, dataGeneratorType: .random)
181 | enum MyEnum {
182 | indirect case case1(String, Int, String, [String])
183 | case case2
184 | case case3(Product)
185 | case case4([String: Product])
186 | }
187 | //
188 | //@SampleBuilder(numberOfItems: 6)
189 | //struct Test {
190 | // var item1: [[Int]: [[String: [String: [Int: [Int: MyEnum]]]]]]
191 | //}
192 |
193 |
194 |
195 |
196 |
197 |
198 | //@SampleBuilder(numberOfItems: 3)
199 | //enum Example {
200 | // case response(time: Date, name: String, Data)
201 | //}
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
--------------------------------------------------------------------------------
/Tests/SwiftAndTipsMacrosTests/BinaryStringTests.swift:
--------------------------------------------------------------------------------
1 | /*
2 | This source file is part of SwiftAndTipsMacros
3 |
4 | Copyright (c) 2023 Pedro Rojas and project authors
5 | Licensed under MIT License
6 | */
7 |
8 | import SwiftSyntaxMacros
9 | import SwiftSyntaxMacrosTestSupport
10 | import XCTest
11 |
12 | // Macro implementations build for the host, so the corresponding module is not available when cross-compiling. Cross-compiled tests may still make use of the macro itself in end-to-end tests.
13 | #if canImport(Macros)
14 | import Macros
15 |
16 | fileprivate let testMacros: [String: Macro.Type] = [
17 | "binaryString": BinaryStringMacro.self
18 | ]
19 | #else
20 | #error("Macros library is not running")
21 | #endif
22 |
23 | final class BinaryStringTests: XCTestCase {
24 |
25 | func testBinaryStringMacro_WithIntLiteral() throws {
26 | #if canImport(Macros)
27 | assertMacroExpansion(
28 | """
29 | #binaryString(111)
30 | """,
31 | expandedSource: """
32 | "1101111"
33 | """,
34 | macros: testMacros
35 | )
36 | #else
37 | throw XCTSkip("macros are only supported when running tests for the host platform")
38 | #endif
39 | }
40 |
41 | func testBinaryStringMacro_WithStringLiteral() throws {
42 | #if canImport(Macros)
43 | assertMacroExpansion(
44 | #"""
45 | #binaryString("Hello")
46 | """#,
47 | expandedSource: """
48 | #binaryString("Hello")
49 | """,
50 | diagnostics: [
51 | DiagnosticSpec(message: "The argument is not an Int literal", line: 1, column: 1)
52 | ],
53 | macros: testMacros
54 | )
55 | #else
56 | throw XCTSkip("macros are only supported when running tests for the host platform")
57 | #endif
58 | }
59 |
60 | func testBinaryStringMacro_WithVariableOfTypeInt() throws {
61 | #if canImport(Macros)
62 | assertMacroExpansion(
63 | """
64 | let input = 7
65 | #binaryString(input)
66 | """,
67 | expandedSource: #"""
68 | let input = 7
69 | #binaryString(input)
70 | """#,
71 | diagnostics: [
72 | DiagnosticSpec(message: "The argument is not an Int literal", line: 2, column: 1)
73 | ],
74 | macros: testMacros
75 | )
76 | #else
77 | throw XCTSkip("macros are only supported when running tests for the host platform")
78 | #endif
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/Tests/SwiftAndTipsMacrosTests/SampleBuilderItemTests.swift:
--------------------------------------------------------------------------------
1 | /*
2 | This source file is part of SwiftAndTipsMacros
3 |
4 | Copyright (c) 2023 Pedro Rojas and project authors
5 | Licensed under MIT License
6 | */
7 |
8 | //
9 | // SampleBuilderItemTests.swift
10 | //
11 | //
12 | // Created by Pedro Rojas on 28/08/23.
13 | //
14 |
15 | import SwiftSyntaxMacros
16 | import SwiftSyntaxMacrosTestSupport
17 | import XCTest
18 |
19 | // Macro implementations build for the host, so the corresponding module is not available when cross-compiling. Cross-compiled tests may still make use of the macro itself in end-to-end tests.
20 | #if canImport(Macros)
21 | import Macros
22 |
23 | fileprivate let testMacros: [String: Macro.Type] = [
24 | "SampleBuilderItem": SampleBuilderItemMacro.self
25 | ]
26 | #else
27 | #error("Macros library is not running")
28 | #endif
29 |
30 | final class SampleBuilderItemTests: XCTestCase {
31 |
32 | func testSampleBuilderItemMacro_email_category_and_string_property() throws {
33 | #if canImport(Macros)
34 | assertMacroExpansion(
35 | """
36 | struct Example {
37 | let item1: Int
38 |
39 | @SampleBuilderItem(category: .email)
40 | let item2: String
41 | }
42 | """,
43 | expandedSource: """
44 | struct Example {
45 | let item1: Int
46 | let item2: String
47 | }
48 | """,
49 | macros: testMacros
50 | )
51 | #else
52 | throw XCTSkip("macros are only supported when running tests for the host platform")
53 | #endif
54 | }
55 | func testSampleBuilderItemMacro_firstName_category_and_string_property() throws {
56 | #if canImport(Macros)
57 | assertMacroExpansion(
58 | """
59 | struct Example {
60 | let item1: Int
61 |
62 | @SampleBuilderItem(category: .firstName)
63 | let item2: String
64 | }
65 | """,
66 | expandedSource: """
67 | struct Example {
68 | let item1: Int
69 | let item2: String
70 | }
71 | """,
72 | macros: testMacros
73 | )
74 | #else
75 | throw XCTSkip("macros are only supported when running tests for the host platform")
76 | #endif
77 | }
78 | func testSampleBuilderItemMacro_lastName_category_and_string_property() throws {
79 | #if canImport(Macros)
80 | assertMacroExpansion(
81 | """
82 | struct Example {
83 | let item1: Int
84 |
85 | @SampleBuilderItem(category: .lastName)
86 | let item2: String
87 | }
88 | """,
89 | expandedSource: """
90 | struct Example {
91 | let item1: Int
92 | let item2: String
93 | }
94 | """,
95 | macros: testMacros
96 | )
97 | #else
98 | throw XCTSkip("macros are only supported when running tests for the host platform")
99 | #endif
100 | }
101 | func testSampleBuilderItemMacro_fullName_category_and_string_property() throws {
102 | #if canImport(Macros)
103 | assertMacroExpansion(
104 | """
105 | struct Example {
106 | let item1: Int
107 |
108 | @SampleBuilderItem(category: .fullName)
109 | let item2: String
110 | }
111 | """,
112 | expandedSource: """
113 | struct Example {
114 | let item1: Int
115 | let item2: String
116 | }
117 | """,
118 | macros: testMacros
119 | )
120 | #else
121 | throw XCTSkip("macros are only supported when running tests for the host platform")
122 | #endif
123 | }
124 | func testSampleBuilderItemMacro_address_category_and_string_property() throws {
125 | #if canImport(Macros)
126 | assertMacroExpansion(
127 | """
128 | struct Example {
129 | let item1: Int
130 |
131 | @SampleBuilderItem(category: .address)
132 | let item2: String
133 | }
134 | """,
135 | expandedSource: """
136 | struct Example {
137 | let item1: Int
138 | let item2: String
139 | }
140 | """,
141 | macros: testMacros
142 | )
143 | #else
144 | throw XCTSkip("macros are only supported when running tests for the host platform")
145 | #endif
146 | }
147 | func testSampleBuilderItemMacro_appVersion_category_and_string_property() throws {
148 | #if canImport(Macros)
149 | assertMacroExpansion(
150 | """
151 | struct Example {
152 | let item1: Int
153 |
154 | @SampleBuilderItem(category: .appVersion)
155 | let item2: String
156 | }
157 | """,
158 | expandedSource: """
159 | struct Example {
160 | let item1: Int
161 | let item2: String
162 | }
163 | """,
164 | macros: testMacros
165 | )
166 | #else
167 | throw XCTSkip("macros are only supported when running tests for the host platform")
168 | #endif
169 | }
170 | func testSampleBuilderItemMacro_creditCardNumber_category_and_string_property() throws {
171 | #if canImport(Macros)
172 | assertMacroExpansion(
173 | """
174 | struct Example {
175 | let item1: Int
176 |
177 | @SampleBuilderItem(category: .creditCardNumber)
178 | let item2: String
179 | }
180 | """,
181 | expandedSource: """
182 | struct Example {
183 | let item1: Int
184 | let item2: String
185 | }
186 | """,
187 | macros: testMacros
188 | )
189 | #else
190 | throw XCTSkip("macros are only supported when running tests for the host platform")
191 | #endif
192 | }
193 | func testSampleBuilderItemMacro_companyName_category_and_string_property() throws {
194 | #if canImport(Macros)
195 | assertMacroExpansion(
196 | """
197 | struct Example {
198 | let item1: Int
199 |
200 | @SampleBuilderItem(category: .companyName)
201 | let item2: String
202 | }
203 | """,
204 | expandedSource: """
205 | struct Example {
206 | let item1: Int
207 | let item2: String
208 | }
209 | """,
210 | macros: testMacros
211 | )
212 | #else
213 | throw XCTSkip("macros are only supported when running tests for the host platform")
214 | #endif
215 | }
216 | func testSampleBuilderItemMacro_username_category_and_string_property() throws {
217 | #if canImport(Macros)
218 | assertMacroExpansion(
219 | """
220 | struct Example {
221 | let item1: Int
222 |
223 | @SampleBuilderItem(category: .username)
224 | let item2: String
225 | }
226 | """,
227 | expandedSource: """
228 | struct Example {
229 | let item1: Int
230 | let item2: String
231 | }
232 | """,
233 | macros: testMacros
234 | )
235 | #else
236 | throw XCTSkip("macros are only supported when running tests for the host platform")
237 | #endif
238 | }
239 | func testSampleBuilderItemMacro_url_category_and_string_property() throws {
240 | #if canImport(Macros)
241 | assertMacroExpansion(
242 | """
243 | struct Example {
244 | let item1: Int
245 |
246 | @SampleBuilderItem(category: .url)
247 | let item2: String
248 | }
249 | """,
250 | expandedSource: """
251 | struct Example {
252 | let item1: Int
253 | let item2: String
254 | }
255 | """,
256 | diagnostics: [
257 | DiagnosticSpec(message: "'url' category is not compatible with 'String' type. Use 'URL' type instead.", line: 4, column: 5)
258 | ],
259 | macros: testMacros
260 | )
261 | #else
262 | throw XCTSkip("macros are only supported when running tests for the host platform")
263 | #endif
264 | }
265 | func testSampleBuilderItemMacro_image_category_and_string_property() throws {
266 | #if canImport(Macros)
267 | assertMacroExpansion(
268 | """
269 | struct Example {
270 | let item1: Int
271 |
272 | @SampleBuilderItem(category: .image(width: 200, height: 300))
273 | let item2: String
274 | }
275 | """,
276 | expandedSource: """
277 | struct Example {
278 | let item1: Int
279 | let item2: String
280 | }
281 | """,
282 | diagnostics: [
283 | DiagnosticSpec(message: "'image(width:200,height:300)' category is not compatible with 'String' type. Use 'URL' type instead.", line: 4, column: 5)
284 | ],
285 | macros: testMacros
286 | )
287 | #else
288 | throw XCTSkip("macros are only supported when running tests for the host platform")
289 | #endif
290 | }
291 | func testSampleBuilderItemMacro_price_category_and_string_property() throws {
292 | #if canImport(Macros)
293 | assertMacroExpansion(
294 | """
295 | struct Example {
296 | let item1: Int
297 |
298 | @SampleBuilderItem(category: .price)
299 | let item2: String
300 | }
301 | """,
302 | expandedSource: """
303 | struct Example {
304 | let item1: Int
305 | let item2: String
306 | }
307 | """,
308 | diagnostics: [
309 | DiagnosticSpec(message: "'price' category is not compatible with 'String' type. Use 'Double' type instead.", line: 4, column: 5)
310 | ],
311 | macros: testMacros
312 | )
313 | #else
314 | throw XCTSkip("macros are only supported when running tests for the host platform")
315 | #endif
316 | }
317 | func testSampleBuilderItemMacro_email_category_and_URL_property() throws {
318 | #if canImport(Macros)
319 | assertMacroExpansion(
320 | """
321 | struct Example {
322 | let item1: Int
323 |
324 | @SampleBuilderItem(category: .email)
325 | let item2: URL
326 | }
327 | """,
328 | expandedSource: """
329 | struct Example {
330 | let item1: Int
331 | let item2: URL
332 | }
333 | """,
334 | diagnostics: [
335 | DiagnosticSpec(message: "'email' category is not compatible with 'URL' type. Use 'String' type instead.", line: 4, column: 5)
336 | ],
337 | macros: testMacros
338 | )
339 | #else
340 | throw XCTSkip("macros are only supported when running tests for the host platform")
341 | #endif
342 | }
343 | func testSampleBuilderItemMacro_firstName_category_and_URL_property() throws {
344 | #if canImport(Macros)
345 | assertMacroExpansion(
346 | """
347 | struct Example {
348 | let item1: Int
349 |
350 | @SampleBuilderItem(category: .firstName)
351 | let item2: URL
352 | }
353 | """,
354 | expandedSource: """
355 | struct Example {
356 | let item1: Int
357 | let item2: URL
358 | }
359 | """,
360 | diagnostics: [
361 | DiagnosticSpec(message: "'firstName' category is not compatible with 'URL' type. Use 'String' type instead.", line: 4, column: 5)
362 | ],
363 | macros: testMacros
364 | )
365 | #else
366 | throw XCTSkip("macros are only supported when running tests for the host platform")
367 | #endif
368 | }
369 | func testSampleBuilderItemMacro_lastName_category_and_URL_property() throws {
370 | #if canImport(Macros)
371 | assertMacroExpansion(
372 | """
373 | struct Example {
374 | let item1: Int
375 |
376 | @SampleBuilderItem(category: .lastName)
377 | let item2: URL
378 | }
379 | """,
380 | expandedSource: """
381 | struct Example {
382 | let item1: Int
383 | let item2: URL
384 | }
385 | """,
386 | diagnostics: [
387 | DiagnosticSpec(message: "'lastName' category is not compatible with 'URL' type. Use 'String' type instead.", line: 4, column: 5)
388 | ],
389 | macros: testMacros
390 | )
391 | #else
392 | throw XCTSkip("macros are only supported when running tests for the host platform")
393 | #endif
394 | }
395 | func testSampleBuilderItemMacro_fullName_category_and_URL_property() throws {
396 | #if canImport(Macros)
397 | assertMacroExpansion(
398 | """
399 | struct Example {
400 | let item1: Int
401 |
402 | @SampleBuilderItem(category: .fullName)
403 | let item2: URL
404 | }
405 | """,
406 | expandedSource: """
407 | struct Example {
408 | let item1: Int
409 | let item2: URL
410 | }
411 | """,
412 | diagnostics: [
413 | DiagnosticSpec(message: "'fullName' category is not compatible with 'URL' type. Use 'String' type instead.", line: 4, column: 5)
414 | ],
415 | macros: testMacros
416 | )
417 | #else
418 | throw XCTSkip("macros are only supported when running tests for the host platform")
419 | #endif
420 | }
421 | func testSampleBuilderItemMacro_address_category_and_URL_property() throws {
422 | #if canImport(Macros)
423 | assertMacroExpansion(
424 | """
425 | struct Example {
426 | let item1: Int
427 |
428 | @SampleBuilderItem(category: .address)
429 | let item2: URL
430 | }
431 | """,
432 | expandedSource: """
433 | struct Example {
434 | let item1: Int
435 | let item2: URL
436 | }
437 | """,
438 | diagnostics: [
439 | DiagnosticSpec(message: "'address' category is not compatible with 'URL' type. Use 'String' type instead.", line: 4, column: 5)
440 | ],
441 | macros: testMacros
442 | )
443 | #else
444 | throw XCTSkip("macros are only supported when running tests for the host platform")
445 | #endif
446 | }
447 | func testSampleBuilderItemMacro_appVersion_category_and_URL_property() throws {
448 | #if canImport(Macros)
449 | assertMacroExpansion(
450 | """
451 | struct Example {
452 | let item1: Int
453 |
454 | @SampleBuilderItem(category: .appVersion)
455 | let item2: URL
456 | }
457 | """,
458 | expandedSource: """
459 | struct Example {
460 | let item1: Int
461 | let item2: URL
462 | }
463 | """,
464 | diagnostics: [
465 | DiagnosticSpec(message: "'appVersion' category is not compatible with 'URL' type. Use 'String' type instead.", line: 4, column: 5)
466 | ],
467 | macros: testMacros
468 | )
469 | #else
470 | throw XCTSkip("macros are only supported when running tests for the host platform")
471 | #endif
472 | }
473 | func testSampleBuilderItemMacro_creditCardNumber_category_and_URL_property() throws {
474 | #if canImport(Macros)
475 | assertMacroExpansion(
476 | """
477 | struct Example {
478 | let item1: Int
479 |
480 | @SampleBuilderItem(category: .creditCardNumber)
481 | let item2: URL
482 | }
483 | """,
484 | expandedSource: """
485 | struct Example {
486 | let item1: Int
487 | let item2: URL
488 | }
489 | """,
490 | diagnostics: [
491 | DiagnosticSpec(message: "'creditCardNumber' category is not compatible with 'URL' type. Use 'String' type instead.", line: 4, column: 5)
492 | ],
493 | macros: testMacros
494 | )
495 | #else
496 | throw XCTSkip("macros are only supported when running tests for the host platform")
497 | #endif
498 | }
499 | func testSampleBuilderItemMacro_companyName_category_and_URL_property() throws {
500 | #if canImport(Macros)
501 | assertMacroExpansion(
502 | """
503 | struct Example {
504 | let item1: Int
505 |
506 | @SampleBuilderItem(category: .companyName)
507 | let item2: URL
508 | }
509 | """,
510 | expandedSource: """
511 | struct Example {
512 | let item1: Int
513 | let item2: URL
514 | }
515 | """,
516 | diagnostics: [
517 | DiagnosticSpec(message: "'companyName' category is not compatible with 'URL' type. Use 'String' type instead.", line: 4, column: 5)
518 | ],
519 | macros: testMacros
520 | )
521 | #else
522 | throw XCTSkip("macros are only supported when running tests for the host platform")
523 | #endif
524 | }
525 | func testSampleBuilderItemMacro_username_category_and_URL_property() throws {
526 | #if canImport(Macros)
527 | assertMacroExpansion(
528 | """
529 | struct Example {
530 | let item1: Int
531 |
532 | @SampleBuilderItem(category: .username)
533 | let item2: URL
534 | }
535 | """,
536 | expandedSource: """
537 | struct Example {
538 | let item1: Int
539 | let item2: URL
540 | }
541 | """,
542 | diagnostics: [
543 | DiagnosticSpec(message: "'username' category is not compatible with 'URL' type. Use 'String' type instead.", line: 4, column: 5)
544 | ],
545 | macros: testMacros
546 | )
547 | #else
548 | throw XCTSkip("macros are only supported when running tests for the host platform")
549 | #endif
550 | }
551 | func testSampleBuilderItemMacro_url_category_and_URL_property() throws {
552 | #if canImport(Macros)
553 | assertMacroExpansion(
554 | """
555 | struct Example {
556 | let item1: Int
557 |
558 | @SampleBuilderItem(category: .url)
559 | let item2: URL
560 | }
561 | """,
562 | expandedSource: """
563 | struct Example {
564 | let item1: Int
565 | let item2: URL
566 | }
567 | """,
568 | macros: testMacros
569 | )
570 | #else
571 | throw XCTSkip("macros are only supported when running tests for the host platform")
572 | #endif
573 | }
574 | func testSampleBuilderItemMacro_image_category_and_URL_property() throws {
575 | #if canImport(Macros)
576 | assertMacroExpansion(
577 | """
578 | struct Example {
579 | let item1: Int
580 |
581 | @SampleBuilderItem(category: .image(width: 200, height: 300))
582 | let item2: URL
583 | }
584 | """,
585 | expandedSource: """
586 | struct Example {
587 | let item1: Int
588 | let item2: URL
589 | }
590 | """,
591 | macros: testMacros
592 | )
593 | #else
594 | throw XCTSkip("macros are only supported when running tests for the host platform")
595 | #endif
596 | }
597 | func testSampleBuilderItemMacro_price_category_and_URL_property() throws {
598 | #if canImport(Macros)
599 | assertMacroExpansion(
600 | """
601 | struct Example {
602 | let item1: Int
603 |
604 | @SampleBuilderItem(category: .price)
605 | let item2: URL
606 | }
607 | """,
608 | expandedSource: """
609 | struct Example {
610 | let item1: Int
611 | let item2: URL
612 | }
613 | """,
614 | diagnostics: [
615 | DiagnosticSpec(message: "'price' category is not compatible with 'URL' type. Use 'Double' type instead.", line: 4, column: 5)
616 | ],
617 | macros: testMacros
618 | )
619 | #else
620 | throw XCTSkip("macros are only supported when running tests for the host platform")
621 | #endif
622 | }
623 | func testSampleBuilderItemMacro_email_category_and_Double_property() throws {
624 | #if canImport(Macros)
625 | assertMacroExpansion(
626 | """
627 | struct Example {
628 | let item1: Int
629 |
630 | @SampleBuilderItem(category: .email)
631 | let item2: Double
632 | }
633 | """,
634 | expandedSource: """
635 | struct Example {
636 | let item1: Int
637 | let item2: Double
638 | }
639 | """,
640 | diagnostics: [
641 | DiagnosticSpec(message: "'email' category is not compatible with 'Double' type. Use 'String' type instead.", line: 4, column: 5)
642 | ],
643 | macros: testMacros
644 | )
645 | #else
646 | throw XCTSkip("macros are only supported when running tests for the host platform")
647 | #endif
648 | }
649 | func testSampleBuilderItemMacro_firstName_category_and_Double_property() throws {
650 | #if canImport(Macros)
651 | assertMacroExpansion(
652 | """
653 | struct Example {
654 | let item1: Int
655 |
656 | @SampleBuilderItem(category: .firstName)
657 | let item2: Double
658 | }
659 | """,
660 | expandedSource: """
661 | struct Example {
662 | let item1: Int
663 | let item2: Double
664 | }
665 | """,
666 | diagnostics: [
667 | DiagnosticSpec(message: "'firstName' category is not compatible with 'Double' type. Use 'String' type instead.", line: 4, column: 5)
668 | ],
669 | macros: testMacros
670 | )
671 | #else
672 | throw XCTSkip("macros are only supported when running tests for the host platform")
673 | #endif
674 | }
675 | func testSampleBuilderItemMacro_lastName_category_and_Double_property() throws {
676 | #if canImport(Macros)
677 | assertMacroExpansion(
678 | """
679 | struct Example {
680 | let item1: Int
681 |
682 | @SampleBuilderItem(category: .lastName)
683 | let item2: Double
684 | }
685 | """,
686 | expandedSource: """
687 | struct Example {
688 | let item1: Int
689 | let item2: Double
690 | }
691 | """,
692 | diagnostics: [
693 | DiagnosticSpec(message: "'lastName' category is not compatible with 'Double' type. Use 'String' type instead.", line: 4, column: 5)
694 | ],
695 | macros: testMacros
696 | )
697 | #else
698 | throw XCTSkip("macros are only supported when running tests for the host platform")
699 | #endif
700 | }
701 | func testSampleBuilderItemMacro_fullName_category_and_Double_property() throws {
702 | #if canImport(Macros)
703 | assertMacroExpansion(
704 | """
705 | struct Example {
706 | let item1: Int
707 |
708 | @SampleBuilderItem(category: .fullName)
709 | let item2: Double
710 | }
711 | """,
712 | expandedSource: """
713 | struct Example {
714 | let item1: Int
715 | let item2: Double
716 | }
717 | """,
718 | diagnostics: [
719 | DiagnosticSpec(message: "'fullName' category is not compatible with 'Double' type. Use 'String' type instead.", line: 4, column: 5)
720 | ],
721 | macros: testMacros
722 | )
723 | #else
724 | throw XCTSkip("macros are only supported when running tests for the host platform")
725 | #endif
726 | }
727 | func testSampleBuilderItemMacro_address_category_and_Double_property() throws {
728 | #if canImport(Macros)
729 | assertMacroExpansion(
730 | """
731 | struct Example {
732 | let item1: Int
733 |
734 | @SampleBuilderItem(category: .address)
735 | let item2: Double
736 | }
737 | """,
738 | expandedSource: """
739 | struct Example {
740 | let item1: Int
741 | let item2: Double
742 | }
743 | """,
744 | diagnostics: [
745 | DiagnosticSpec(message: "'address' category is not compatible with 'Double' type. Use 'String' type instead.", line: 4, column: 5)
746 | ],
747 | macros: testMacros
748 | )
749 | #else
750 | throw XCTSkip("macros are only supported when running tests for the host platform")
751 | #endif
752 | }
753 | func testSampleBuilderItemMacro_appVersion_category_and_Double_property() throws {
754 | #if canImport(Macros)
755 | assertMacroExpansion(
756 | """
757 | struct Example {
758 | let item1: Int
759 |
760 | @SampleBuilderItem(category: .appVersion)
761 | let item2: Double
762 | }
763 | """,
764 | expandedSource: """
765 | struct Example {
766 | let item1: Int
767 | let item2: Double
768 | }
769 | """,
770 | diagnostics: [
771 | DiagnosticSpec(message: "'appVersion' category is not compatible with 'Double' type. Use 'String' type instead.", line: 4, column: 5)
772 | ],
773 | macros: testMacros
774 | )
775 | #else
776 | throw XCTSkip("macros are only supported when running tests for the host platform")
777 | #endif
778 | }
779 | func testSampleBuilderItemMacro_creditCardNumber_category_and_Double_property() throws {
780 | #if canImport(Macros)
781 | assertMacroExpansion(
782 | """
783 | struct Example {
784 | let item1: Int
785 |
786 | @SampleBuilderItem(category: .creditCardNumber)
787 | let item2: Double
788 | }
789 | """,
790 | expandedSource: """
791 | struct Example {
792 | let item1: Int
793 | let item2: Double
794 | }
795 | """,
796 | diagnostics: [
797 | DiagnosticSpec(message: "'creditCardNumber' category is not compatible with 'Double' type. Use 'String' type instead.", line: 4, column: 5)
798 | ],
799 | macros: testMacros
800 | )
801 | #else
802 | throw XCTSkip("macros are only supported when running tests for the host platform")
803 | #endif
804 | }
805 | func testSampleBuilderItemMacro_companyName_category_and_Double_property() throws {
806 | #if canImport(Macros)
807 | assertMacroExpansion(
808 | """
809 | struct Example {
810 | let item1: Int
811 |
812 | @SampleBuilderItem(category: .companyName)
813 | let item2: Double
814 | }
815 | """,
816 | expandedSource: """
817 | struct Example {
818 | let item1: Int
819 | let item2: Double
820 | }
821 | """,
822 | diagnostics: [
823 | DiagnosticSpec(message: "'companyName' category is not compatible with 'Double' type. Use 'String' type instead.", line: 4, column: 5)
824 | ],
825 | macros: testMacros
826 | )
827 | #else
828 | throw XCTSkip("macros are only supported when running tests for the host platform")
829 | #endif
830 | }
831 | func testSampleBuilderItemMacro_username_category_and_Double_property() throws {
832 | #if canImport(Macros)
833 | assertMacroExpansion(
834 | """
835 | struct Example {
836 | let item1: Int
837 |
838 | @SampleBuilderItem(category: .username)
839 | let item2: Double
840 | }
841 | """,
842 | expandedSource: """
843 | struct Example {
844 | let item1: Int
845 | let item2: Double
846 | }
847 | """,
848 | diagnostics: [
849 | DiagnosticSpec(message: "'username' category is not compatible with 'Double' type. Use 'String' type instead.", line: 4, column: 5)
850 | ],
851 | macros: testMacros
852 | )
853 | #else
854 | throw XCTSkip("macros are only supported when running tests for the host platform")
855 | #endif
856 | }
857 | func testSampleBuilderItemMacro_Double_category_and_Double_property() throws {
858 | #if canImport(Macros)
859 | assertMacroExpansion(
860 | """
861 | struct Example {
862 | let item1: Int
863 |
864 | @SampleBuilderItem(category: .url)
865 | let item2: Double
866 | }
867 | """,
868 | expandedSource: """
869 | struct Example {
870 | let item1: Int
871 | let item2: Double
872 | }
873 | """,
874 | diagnostics: [
875 | DiagnosticSpec(message: "'url' category is not compatible with 'Double' type. Use 'URL' type instead.", line: 4, column: 5)
876 | ],
877 | macros: testMacros
878 | )
879 | #else
880 | throw XCTSkip("macros are only supported when running tests for the host platform")
881 | #endif
882 | }
883 | func testSampleBuilderItemMacro_image_category_and_Double_property() throws {
884 | #if canImport(Macros)
885 | assertMacroExpansion(
886 | """
887 | struct Example {
888 | let item1: Int
889 |
890 | @SampleBuilderItem(category: .image(width: 200, height: 300))
891 | let item2: Double
892 | }
893 | """,
894 | expandedSource: """
895 | struct Example {
896 | let item1: Int
897 | let item2: Double
898 | }
899 | """,
900 | diagnostics: [
901 | DiagnosticSpec(message: "'image(width:200,height:300)' category is not compatible with 'Double' type. Use 'URL' type instead.", line: 4, column: 5)
902 | ],
903 | macros: testMacros
904 | )
905 | #else
906 | throw XCTSkip("macros are only supported when running tests for the host platform")
907 | #endif
908 | }
909 | func testSampleBuilderItemMacro_price_category_and_Double_property() throws {
910 | #if canImport(Macros)
911 | assertMacroExpansion(
912 | """
913 | struct Example {
914 | let item1: Int
915 |
916 | @SampleBuilderItem(category: .price)
917 | let item2: Double
918 | }
919 | """,
920 | expandedSource: """
921 | struct Example {
922 | let item1: Int
923 | let item2: Double
924 | }
925 | """,
926 | macros: testMacros
927 | )
928 | #else
929 | throw XCTSkip("macros are only supported when running tests for the host platform")
930 | #endif
931 | }
932 | }
933 |
934 |
--------------------------------------------------------------------------------