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