├── ExampleApp ├── ExampleApp │ ├── Assets.xcassets │ │ ├── Contents.json │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Preview Content │ │ └── Preview Assets.xcassets │ │ │ └── Contents.json │ ├── AgeValidator.swift │ ├── ExampleAppApp.swift │ ├── ContentView.swift │ └── APIEndpoint.swift ├── ExampleAppTests │ ├── AgeVerificationTest.swift │ └── APIEndpointsTests.swift └── ExampleApp.xcodeproj │ └── project.pbxproj ├── Sources ├── XCTestParametrizedMacroMacros │ ├── String+Utils.swift │ ├── ParamValueTransformer.swift │ ├── ParametrizeMacroError.swift │ ├── XCTestParametrizedMacroMacro.swift │ ├── MacroDeclarationHelper.swift │ └── TestMethodsFactory.swift ├── XCTestParametrizedMacro │ └── XCTestParametrizedMacro.swift └── XCTestParametrizedMacroClient │ └── main.swift ├── Package.resolved ├── .github └── workflows │ └── swift.yml ├── Tests └── XCTestParametrizedMacroTests │ ├── ParamValueTransformerTests.swift │ ├── InputOutputParametersTests.swift │ ├── EffectSpecifiersTests.swift │ ├── LabelsParameterTests.swift │ ├── AttachmentTests.swift │ └── SimpleValuesTests.swift ├── LICENSE ├── Package.swift ├── README.md └── logo.svg /ExampleApp/ExampleApp/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /ExampleApp/ExampleApp/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /ExampleApp/ExampleApp/AgeValidator.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct AgeValidator { 4 | static func isAdult(age: Int) -> Bool { 5 | age >= 18 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /ExampleApp/ExampleApp/ExampleAppApp.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | @main 4 | struct ExampleAppApp: App { 5 | var body: some Scene { 6 | WindowGroup { 7 | ContentView() 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /ExampleApp/ExampleApp/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /ExampleApp/ExampleApp/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "platform" : "ios", 6 | "size" : "1024x1024" 7 | } 8 | ], 9 | "info" : { 10 | "author" : "xcode", 11 | "version" : 1 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Sources/XCTestParametrizedMacroMacros/String+Utils.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension String { 4 | 5 | /// Returns string with uppercased only first letter. 6 | var capitalizedFirst: String { 7 | let firstLetter = self.prefix(1).localizedCapitalized 8 | return firstLetter + String(self.dropFirst(1)) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /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" : "74203046135342e4a4a627476dd6caf8b28fe11b", 9 | "version" : "509.0.0" 10 | } 11 | } 12 | ], 13 | "version" : 2 14 | } 15 | -------------------------------------------------------------------------------- /ExampleApp/ExampleApp/ContentView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct ContentView: View { 4 | var body: some View { 5 | VStack { 6 | Image(systemName: "globe") 7 | .imageScale(.large) 8 | .foregroundStyle(.tint) 9 | Text("Hello, Swift Macros!") 10 | } 11 | .padding() 12 | } 13 | } 14 | 15 | #Preview { 16 | ContentView() 17 | } 18 | -------------------------------------------------------------------------------- /Sources/XCTestParametrizedMacroMacros/ParamValueTransformer.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct ParamValueTransformer { 4 | static func transform(value: String?) -> String { 5 | guard let value = value else { 6 | return "" 7 | } 8 | let val = value 9 | .replacingOccurrences(of: "\"", with: "") 10 | .map { ($0.isLetter || $0.isNumber) ? $0 : "_"} 11 | return String(val) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ExampleApp/ExampleAppTests/AgeVerificationTest.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import XCTestParametrizedMacro 3 | 4 | final class AgeVerificationTest: XCTestCase { 5 | 6 | @Parametrize(input: [18, 25, 45, 99]) 7 | func testAgeValidatorValidAge(input age: Int) throws { 8 | XCTAssertTrue(AgeValidator.isAdult(age: age)) 9 | } 10 | 11 | @Parametrize(input: [2, 5, 17]) 12 | func testAgeValidatorInvalidAge(input age: Int) throws { 13 | XCTAssertFalse(AgeValidator.isAdult(age: age)) 14 | } 15 | 16 | 17 | 18 | } 19 | -------------------------------------------------------------------------------- /ExampleApp/ExampleApp/APIEndpoint.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | enum APIEndpoint { 4 | case order(String) 5 | case transactions 6 | case profile 7 | 8 | var buildURL: URL? { 9 | switch self { 10 | case .order(let id): 11 | return URL(string: "https://example.com/api/order/\(id)") 12 | case .profile: 13 | return URL(string: "https://example.com/api/me") 14 | case .transactions: 15 | return URL(string: "https://example.com/api/transactions") 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /ExampleApp/ExampleAppTests/APIEndpointsTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import XCTestParametrizedMacro 3 | 4 | final class APIEndpointsTests: XCTestCase { 5 | 6 | @Parametrize( 7 | input: [APIEndpoint.profile, APIEndpoint.transactions, APIEndpoint.order("2345")], 8 | output: ["https://example.com/api/me", 9 | "https://example.com/api/transactions", 10 | "https://example.com/api/order/2345"], 11 | labels: ["profile", 12 | "transactions", 13 | "order"]) 14 | func testEndpointURL(input endpoint: APIEndpoint, output expectedUrl: String) throws { 15 | XCTAssertEqual(endpoint.buildURL?.absoluteString, expectedUrl) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Sources/XCTestParametrizedMacro/XCTestParametrizedMacro.swift: -------------------------------------------------------------------------------- 1 | @attached(peer, names: arbitrary) 2 | public macro Parametrize(input: [I]) = #externalMacro(module: "XCTestParametrizedMacroMacros", type: "ParametrizeMacro") 3 | 4 | @attached(peer, names: arbitrary) 5 | public macro Parametrize(input: [I], labels: [String]) = #externalMacro(module: "XCTestParametrizedMacroMacros", type: "ParametrizeMacro") 6 | 7 | @attached(peer, names: arbitrary) 8 | public macro Parametrize(input: [I], output: [O]) = #externalMacro(module: "XCTestParametrizedMacroMacros", type: "ParametrizeMacro") 9 | 10 | @attached(peer, names: arbitrary) 11 | public macro Parametrize(input: [I], output: [O], labels: [String]) = #externalMacro(module: "XCTestParametrizedMacroMacros", type: "ParametrizeMacro") 12 | -------------------------------------------------------------------------------- /Sources/XCTestParametrizedMacroClient/main.swift: -------------------------------------------------------------------------------- 1 | import XCTestParametrizedMacro 2 | import XCTest 3 | 4 | enum Foo: Int { 5 | case first = 1 6 | case second = 2 7 | case third = 3 8 | } 9 | 10 | func pow2(_ n: Int) -> Int { 11 | n*n 12 | } 13 | 14 | class Test { 15 | @Parametrize(input: [Foo.first, .second, .init(rawValue: 3)!]) 16 | func test_sample(input object: Foo) { 17 | print(object.rawValue) 18 | } 19 | 20 | @Parametrize(input: [1,2,3], output: [1,4,9]) 21 | func testPow2(input n: Int, output result: Int) { 22 | print("\(n) => \(result)") 23 | } 24 | 25 | @Parametrize(input: ["Swift","SwiftMacro"], output: [5, 10]) 26 | func testWordLength(input word: String, output length: Int) { 27 | XCTAssertEqual(word.count, length) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /.github/workflows/swift.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Swift project 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-swift 3 | 4 | name: Swift 5 | 6 | on: 7 | push: 8 | branches: [ "main" ] 9 | pull_request: 10 | branches: [ "main" ] 11 | 12 | jobs: 13 | build: 14 | name: Swift ${{ matrix.swift }} on ${{ matrix.os }} 15 | runs-on: ${{ matrix.os }} 16 | strategy: 17 | matrix: 18 | include: 19 | - os: macos-15 20 | swift_version: "6.1" 21 | xcode_version: "16.4" 22 | env: 23 | DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode_version }}.app/Contents/Developer 24 | steps: 25 | - uses: swift-actions/setup-swift@v2 26 | with: 27 | swift-version: ${{ matrix.swift_version }} 28 | - uses: actions/checkout@v3 29 | - name: Build 30 | run: swift build 31 | - name: Test 32 | run: swift test 33 | -------------------------------------------------------------------------------- /Tests/XCTestParametrizedMacroTests/ParamValueTransformerTests.swift: -------------------------------------------------------------------------------- 1 | import SwiftSyntaxMacros 2 | import XCTest 3 | 4 | @testable import XCTestParametrizedMacroMacros 5 | 6 | final class ParamValueTransformerTests: XCTestCase { 7 | 8 | func testTransform() throws { 9 | XCTAssertEqual(ParamValueTransformer.transform(value: "Simple"), "Simple") 10 | XCTAssertEqual(ParamValueTransformer.transform(value: "A/B Tests"), "A_B_Tests") 11 | XCTAssertEqual(ParamValueTransformer.transform(value: "3.14"), "3_14") 12 | } 13 | 14 | func testTransformInts() throws { 15 | XCTAssertEqual(ParamValueTransformer.transform(value: "999"), "999") 16 | XCTAssertEqual(ParamValueTransformer.transform(value: "123_000_000"), "123_000_000") 17 | } 18 | 19 | func testTransformCustomObjects() throws { 20 | XCTAssertEqual(ParamValueTransformer.transform(value: "Foo()"), "Foo__") 21 | XCTAssertEqual(ParamValueTransformer.transform(value: ".first"), "_first") 22 | XCTAssertEqual(ParamValueTransformer.transform(value: "Foo.init(rawValue: 1)!"), "Foo_init_rawValue__1__") 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Xebia Poland 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Sources/XCTestParametrizedMacroMacros/ParametrizeMacroError.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | enum ParametrizeMacroError: Error, CustomStringConvertible { 4 | case notAttachedToAFunction 5 | case functionInputParamSecondNameMissing 6 | case functionInputParamTypeMissing 7 | case functionBodyEmpty 8 | case macroAttributeNotAnArray 9 | case macroAttributeArraysMismatchSize 10 | 11 | var description: String { 12 | switch self { 13 | case .notAttachedToAFunction: 14 | return "Parametrize macro can be used only for functions." 15 | case .functionInputParamSecondNameMissing: 16 | return "Input parameter must have a second name. Something like `testMethod(input secondName: String)`." 17 | case .functionInputParamTypeMissing: 18 | return "Input parameter must have a type." 19 | case .functionBodyEmpty: 20 | return "Function must have a body." 21 | case .macroAttributeNotAnArray: 22 | return "Parametrize macro requires at least one attribute as array of input/output values." 23 | case .macroAttributeArraysMismatchSize: 24 | return "Arrays passed as an argument should be same size." 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Sources/XCTestParametrizedMacroMacros/XCTestParametrizedMacroMacro.swift: -------------------------------------------------------------------------------- 1 | import SwiftCompilerPlugin 2 | import SwiftSyntax 3 | import SwiftSyntaxBuilder 4 | import SwiftSyntaxMacros 5 | import SwiftDiagnostics 6 | 7 | public struct ParametrizeMacro: PeerMacro { 8 | public static func expansion( 9 | of node: SwiftSyntax.AttributeSyntax, 10 | providingPeersOf declaration: some SwiftSyntax.DeclSyntaxProtocol, 11 | in context: some SwiftSyntaxMacros.MacroExpansionContext 12 | ) throws -> [SwiftSyntax.DeclSyntax] { 13 | 14 | guard let declaration = declaration.as(FunctionDeclSyntax.self) else { 15 | throw ParametrizeMacroError.notAttachedToAFunction 16 | } 17 | 18 | let macroDeclarationHelper = MacroDeclarationHelper(declaration) 19 | 20 | return try TestMethodsFactory(macroDeclarationHelper: macroDeclarationHelper).create() 21 | } 22 | } 23 | 24 | extension ArrayElementSyntax { 25 | 26 | /// returns content as string representation that can be used in function name 27 | var asFunctionName: String { 28 | let value = self.expression.trimmed.description 29 | return ParamValueTransformer.transform(value: value) 30 | } 31 | } 32 | 33 | @main 34 | struct XCTestParametrizedMacroPlugin: CompilerPlugin { 35 | let providingMacros: [Macro.Type] = [ 36 | ParametrizeMacro.self, 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /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: "XCTestParametrizedMacro", 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: "XCTestParametrizedMacro", 14 | targets: ["XCTestParametrizedMacro"] 15 | ), 16 | .executable( 17 | name: "XCTestParametrizedMacroClient", 18 | targets: ["XCTestParametrizedMacroClient"] 19 | ), 20 | ], 21 | dependencies: [ 22 | // Depend on the latest Swift 5.9 prerelease of SwiftSyntax 23 | .package(url: "https://github.com/apple/swift-syntax.git", from: "509.0.0-swift-5.9-DEVELOPMENT-SNAPSHOT-2023-04-25-b"), 24 | ], 25 | targets: [ 26 | // Targets are the basic building blocks of a package, defining a module or a test suite. 27 | // Targets can depend on other targets in this package and products from dependencies. 28 | // Macro implementation that performs the source transformation of a macro. 29 | .macro( 30 | name: "XCTestParametrizedMacroMacros", 31 | dependencies: [ 32 | .product(name: "SwiftSyntaxMacros", package: "swift-syntax"), 33 | .product(name: "SwiftCompilerPlugin", package: "swift-syntax") 34 | ] 35 | ), 36 | 37 | // Library that exposes a macro as part of its API, which is used in client programs. 38 | .target(name: "XCTestParametrizedMacro", dependencies: ["XCTestParametrizedMacroMacros"]), 39 | 40 | // A client of the library, which is able to use the macro in its own code. 41 | .executableTarget(name: "XCTestParametrizedMacroClient", dependencies: ["XCTestParametrizedMacro"]), 42 | 43 | // A test target used to develop the macro implementation. 44 | .testTarget( 45 | name: "XCTestParametrizedMacroTests", 46 | dependencies: [ 47 | "XCTestParametrizedMacroMacros", 48 | .product(name: "SwiftSyntaxMacrosTestSupport", package: "swift-syntax"), 49 | ] 50 | ), 51 | ] 52 | ) 53 | -------------------------------------------------------------------------------- /Sources/XCTestParametrizedMacroMacros/MacroDeclarationHelper.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import SwiftSyntax 3 | 4 | struct MacroDeclarationHelper { 5 | let declaration: FunctionDeclSyntax 6 | 7 | init(_ declaration: FunctionDeclSyntax) { 8 | self.declaration = declaration 9 | } 10 | 11 | var funcName: TokenSyntax { 12 | declaration.name 13 | } 14 | 15 | var funcStatements: CodeBlockItemListSyntax? { 16 | declaration.body?.statements 17 | } 18 | 19 | /// Returns 'TokenSyntax' representing name of the input parameter. 20 | var inputParamName: TokenSyntax? { 21 | declaration.signature.parameterClause.parameters.first?.secondName 22 | } 23 | 24 | /// Returns 'TypeSyntax' representing type of the input object. 25 | var inputParamType: TypeSyntax? { 26 | declaration.signature.parameterClause.parameters.first?.type 27 | } 28 | 29 | /// Returns 'TokenSyntax' representing name of the output parameter. 30 | var outputParamName: TokenSyntax? { 31 | declaration.signature.parameterClause.parameters.last?.secondName 32 | } 33 | 34 | /// Returns 'TokenSyntax' representing type of the output object. 35 | var outputParamType: TypeSyntax? { 36 | declaration.signature.parameterClause.parameters.last?.type 37 | } 38 | 39 | /// Returns 'FunctionEffectSpecifiersSyntax' from method declaration. 40 | var effectSpecifiers: FunctionEffectSpecifiersSyntax? { 41 | declaration.signature.effectSpecifiers 42 | } 43 | 44 | var firstAttribute: AttributeSyntax? { 45 | return declaration.attributes.first?.as(AttributeSyntax.self) 46 | } 47 | 48 | var inputValues: ArrayElementListSyntax { 49 | get throws { 50 | guard let firstMacroArgument = firstAttribute?.arguments?.as(LabeledExprListSyntax.self) else { 51 | throw ParametrizeMacroError.macroAttributeNotAnArray 52 | } 53 | 54 | guard let arrayOfValues = firstMacroArgument.first?.as(LabeledExprSyntax.self)?.expression.as(ArrayExprSyntax.self)?.elements else { 55 | throw ParametrizeMacroError.macroAttributeNotAnArray 56 | } 57 | 58 | return arrayOfValues 59 | } 60 | } 61 | 62 | var outputValues: ArrayElementListSyntax? { 63 | get throws { 64 | guard let firstMacroArgument = firstAttribute?.arguments?.as(LabeledExprListSyntax.self) else { 65 | throw ParametrizeMacroError.macroAttributeNotAnArray 66 | } 67 | 68 | guard let outputArgument = firstMacroArgument.first(where: { $0.label?.text == "output" }) else { 69 | return nil 70 | } 71 | 72 | guard let arrayOfValues = outputArgument.as(LabeledExprSyntax.self)?.expression.as(ArrayExprSyntax.self)?.elements else { 73 | throw ParametrizeMacroError.macroAttributeNotAnArray 74 | } 75 | 76 | return arrayOfValues 77 | } 78 | } 79 | 80 | var labels: ArrayElementListSyntax? { 81 | get throws { 82 | guard let firstMacroArgument = firstAttribute?.arguments?.as(LabeledExprListSyntax.self) else { 83 | throw ParametrizeMacroError.macroAttributeNotAnArray 84 | } 85 | 86 | guard let labelsArgument = firstMacroArgument.first(where: { $0.label?.text == "labels" }) else { 87 | return nil 88 | } 89 | 90 | guard let arrayOfValues = labelsArgument.as(LabeledExprSyntax.self)?.expression.as(ArrayExprSyntax.self)?.elements else { 91 | throw ParametrizeMacroError.macroAttributeNotAnArray 92 | } 93 | 94 | return arrayOfValues 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /Tests/XCTestParametrizedMacroTests/InputOutputParametersTests.swift: -------------------------------------------------------------------------------- 1 | import SwiftSyntaxMacros 2 | import SwiftSyntaxMacrosTestSupport 3 | import XCTest 4 | 5 | import XCTestParametrizedMacroMacros 6 | 7 | final class InputOutputParametersTests: XCTestCase { 8 | 9 | let testMacros: [String: Macro.Type] = [ 10 | "Parametrize": ParametrizeMacro.self, 11 | ] 12 | 13 | func testParametrizeInputOutput_SingleInts() throws { 14 | assertMacroExpansion( 15 | """ 16 | struct TestStruct { 17 | @Parametrize(input: [3], output: [9]) 18 | func testPow2(input n: Int, output result: Int) { 19 | XCTAssertEqual(pow2(n),result) 20 | } 21 | } 22 | """, 23 | expandedSource: """ 24 | struct TestStruct { 25 | func testPow2(input n: Int, output result: Int) { 26 | XCTAssertEqual(pow2(n),result) 27 | } 28 | 29 | func testPow2_N_3_Result_9() { 30 | let n: Int = 3 31 | let result: Int = 9 32 | XCTAssertEqual(pow2(n), result) 33 | } 34 | } 35 | """, 36 | macros: testMacros 37 | ) 38 | } 39 | 40 | func testParametrizeInputOutput_TwoInts() throws { 41 | assertMacroExpansion( 42 | """ 43 | struct TestStruct { 44 | @Parametrize(input: [4, 5], output: [16, 25]) 45 | func testPow2(input n: Int, output result: Int) throws { 46 | XCTAssertEqual(pow2(n),result) 47 | } 48 | } 49 | """, 50 | expandedSource: """ 51 | struct TestStruct { 52 | func testPow2(input n: Int, output result: Int) throws { 53 | XCTAssertEqual(pow2(n),result) 54 | } 55 | 56 | func testPow2_N_4_Result_16() throws { 57 | let n: Int = 4 58 | let result: Int = 16 59 | XCTAssertEqual(pow2(n), result) 60 | } 61 | 62 | func testPow2_N_5_Result_25() throws { 63 | let n: Int = 5 64 | let result: Int = 25 65 | XCTAssertEqual(pow2(n), result) 66 | } 67 | } 68 | """, 69 | macros: testMacros 70 | ) 71 | } 72 | 73 | func testParametrizeInputOutput_TwoStringsTwoInts() throws { 74 | assertMacroExpansion( 75 | """ 76 | struct TestStruct { 77 | @Parametrize(input: ["Swift", "SwiftMacro"], output: [5, 10]) 78 | func testWordLength(input word: String, output length: Int) { 79 | XCTAssertEqual(word.count, length) 80 | } 81 | } 82 | """, 83 | expandedSource: """ 84 | struct TestStruct { 85 | func testWordLength(input word: String, output length: Int) { 86 | XCTAssertEqual(word.count, length) 87 | } 88 | 89 | func testWordLength_Word_Swift_Length_5() { 90 | let word: String = "Swift" 91 | let length: Int = 5 92 | XCTAssertEqual(word.count, length) 93 | } 94 | 95 | func testWordLength_Word_SwiftMacro_Length_10() { 96 | let word: String = "SwiftMacro" 97 | let length: Int = 10 98 | XCTAssertEqual(word.count, length) 99 | } 100 | } 101 | """, 102 | macros: testMacros 103 | ) 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /Sources/XCTestParametrizedMacroMacros/TestMethodsFactory.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import SwiftSyntax 3 | 4 | struct TestMethodsFactory { 5 | 6 | let macroDeclarationHelper: MacroDeclarationHelper 7 | 8 | var bodyFunc: String { 9 | get throws { 10 | guard let codeStatements = macroDeclarationHelper.funcStatements, codeStatements.count > 0 else { 11 | throw ParametrizeMacroError.functionBodyEmpty 12 | } 13 | return codeStatements.map { "\($0.trimmed)" }.joined(separator: "\n") 14 | } 15 | } 16 | 17 | func create() throws -> [DeclSyntax] { 18 | 19 | let funcName = macroDeclarationHelper.funcName 20 | 21 | guard let inputParamName = macroDeclarationHelper.inputParamName?.text else { 22 | throw ParametrizeMacroError.functionInputParamSecondNameMissing 23 | } 24 | 25 | guard let inputParamType = macroDeclarationHelper.inputParamType else { 26 | throw ParametrizeMacroError.functionInputParamTypeMissing 27 | } 28 | 29 | let outputParamName = macroDeclarationHelper.outputParamName?.text 30 | let outputParamType = macroDeclarationHelper.outputParamType 31 | 32 | let input = try macroDeclarationHelper.inputValues.map { $0 } 33 | let output: [ArrayElementListSyntax.Element?] = try macroDeclarationHelper.outputValues?.map { $0 } ?? .init(repeating: nil, count: input.count) 34 | let labels: [ArrayElementListSyntax.Element?] = try macroDeclarationHelper.labels?.map { $0 } ?? .init(repeating: nil, count: input.count) 35 | 36 | guard input.count == output.count, output.count == labels.count else { 37 | throw ParametrizeMacroError.macroAttributeArraysMismatchSize 38 | } 39 | return try zip(input, zip(output, labels)).map { (input, arg) in 40 | let (output, label) = arg 41 | return """ 42 | \(raw: buildTestMethodSignature(funcName: funcName, inputParamName: inputParamName, inputObject: input, outputParamName: outputParamName, outputObject: output, label: label, effectSpecifiers: macroDeclarationHelper.effectSpecifiers)) 43 | \(raw: buildLocalVariables(inputParamName: inputParamName, 44 | inputParamType: inputParamType, 45 | inputObject: input, 46 | outputParamName: outputParamName, 47 | outputParamType: outputParamType, 48 | outputObject: output)) 49 | \(raw: try bodyFunc) 50 | } 51 | """ 52 | } 53 | 54 | } 55 | 56 | func buildTestMethodSignature(funcName: TokenSyntax, 57 | inputParamName: String, 58 | inputObject: ArrayElementListSyntax.Element, 59 | outputParamName: String? = nil, 60 | outputObject: ArrayElementListSyntax.Element? = nil, 61 | label: ArrayElementListSyntax.Element? = nil, 62 | effectSpecifiers: FunctionEffectSpecifiersSyntax?) -> String { 63 | guard label == nil else { 64 | return "func \(funcName)_\(label!.asFunctionName)() \(effectSpecifiers?.description ?? ""){" 65 | } 66 | if let outputParamName = outputParamName, let outputObject = outputObject { 67 | return "func \(funcName)_\(inputParamName.capitalizedFirst)_\(inputObject.asFunctionName)_\(outputParamName.capitalizedFirst)_\(outputObject.asFunctionName)() \(effectSpecifiers?.description ?? ""){" 68 | } else { 69 | return "func \(funcName)_\(inputParamName.capitalizedFirst)_\(inputObject.asFunctionName)() \(effectSpecifiers?.description ?? ""){" 70 | } 71 | } 72 | 73 | func buildLocalVariables(inputParamName: String, 74 | inputParamType: TypeSyntax, 75 | inputObject: ArrayElementListSyntax.Element, 76 | outputParamName: String? = nil, 77 | outputParamType: TypeSyntax? = nil, 78 | outputObject: ArrayElementListSyntax.Element? = nil) -> String { 79 | var decl = "let \(inputParamName):\(inputParamType) = \(inputObject.expression)" 80 | if let outputParamName = outputParamName, 81 | let outputParamType = outputParamType, 82 | let outputObject = outputObject { 83 | decl.append("\n") 84 | decl.append("let \(outputParamName):\(outputParamType) = \(outputObject.expression)") 85 | } 86 | return decl 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /Tests/XCTestParametrizedMacroTests/EffectSpecifiersTests.swift: -------------------------------------------------------------------------------- 1 | import SwiftSyntaxMacros 2 | import SwiftSyntaxMacrosTestSupport 3 | import XCTest 4 | 5 | import XCTestParametrizedMacroMacros 6 | 7 | final class EffectSpecifiersTests: XCTestCase { 8 | 9 | let testMacros: [String: Macro.Type] = [ 10 | "Parametrize": ParametrizeMacro.self, 11 | ] 12 | 13 | func testWithInputNoSpecifiers() throws { 14 | assertMacroExpansion( 15 | """ 16 | struct TestStruct { 17 | @Parametrize(input: [1]) 18 | func testMethod(input n: Int) { 19 | XCTAssertTrue(n>0) 20 | } 21 | } 22 | """, 23 | expandedSource: """ 24 | struct TestStruct { 25 | func testMethod(input n: Int) { 26 | XCTAssertTrue(n>0) 27 | } 28 | 29 | func testMethod_N_1() { 30 | let n: Int = 1 31 | XCTAssertTrue(n > 0) 32 | } 33 | } 34 | """, 35 | macros: testMacros 36 | ) 37 | } 38 | 39 | func testWithInputAsyncAndThrows() throws { 40 | assertMacroExpansion( 41 | """ 42 | struct TestStruct { 43 | @Parametrize(input: [1]) 44 | func testMethod(input n: Int) async throws { 45 | XCTAssertTrue(n>0) 46 | } 47 | } 48 | """, 49 | expandedSource: """ 50 | struct TestStruct { 51 | func testMethod(input n: Int) async throws { 52 | XCTAssertTrue(n>0) 53 | } 54 | 55 | func testMethod_N_1() async throws { 56 | let n: Int = 1 57 | XCTAssertTrue(n > 0) 58 | } 59 | } 60 | """, 61 | macros: testMacros 62 | ) 63 | } 64 | 65 | func testWithInputAsync() throws { 66 | assertMacroExpansion( 67 | """ 68 | struct TestStruct { 69 | @Parametrize(input: [1]) 70 | func testMethod(input n: Int) async { 71 | XCTAssertTrue(n>0) 72 | } 73 | } 74 | """, 75 | expandedSource: """ 76 | struct TestStruct { 77 | func testMethod(input n: Int) async { 78 | XCTAssertTrue(n>0) 79 | } 80 | 81 | func testMethod_N_1() async { 82 | let n: Int = 1 83 | XCTAssertTrue(n > 0) 84 | } 85 | } 86 | """, 87 | macros: testMacros 88 | ) 89 | } 90 | 91 | func testWithInputOutputNoSpecifiers() throws { 92 | assertMacroExpansion( 93 | """ 94 | struct TestStruct { 95 | @Parametrize(input: [1], output: [1]) 96 | func testMethod(input n: Int, output r: Int) { 97 | XCTAssertEqual(n,r) 98 | } 99 | } 100 | """, 101 | expandedSource: """ 102 | struct TestStruct { 103 | func testMethod(input n: Int, output r: Int) { 104 | XCTAssertEqual(n,r) 105 | } 106 | 107 | func testMethod_N_1_R_1() { 108 | let n: Int = 1 109 | let r: Int = 1 110 | XCTAssertEqual(n, r) 111 | } 112 | } 113 | """, 114 | macros: testMacros 115 | ) 116 | } 117 | 118 | func testWithInputOutputAsync() throws { 119 | assertMacroExpansion( 120 | """ 121 | struct TestStruct { 122 | @Parametrize(input: [1], output: [1]) 123 | func testMethod(input n: Int, output r: Int) async { 124 | XCTAssertEqual(n,r) 125 | } 126 | } 127 | """, 128 | expandedSource: """ 129 | struct TestStruct { 130 | func testMethod(input n: Int, output r: Int) async { 131 | XCTAssertEqual(n,r) 132 | } 133 | 134 | func testMethod_N_1_R_1() async { 135 | let n: Int = 1 136 | let r: Int = 1 137 | XCTAssertEqual(n, r) 138 | } 139 | } 140 | """, 141 | macros: testMacros 142 | ) 143 | } 144 | 145 | func testWithInputOutputLabelsAsyncThrows() throws { 146 | assertMacroExpansion( 147 | """ 148 | struct TestStruct { 149 | @Parametrize(input: [1], output: [1], labels: ["One"]) 150 | func testMethod(input n: Int, output r: Int) async throws { 151 | XCTAssertEqual(n,r) 152 | } 153 | } 154 | """, 155 | expandedSource: """ 156 | struct TestStruct { 157 | func testMethod(input n: Int, output r: Int) async throws { 158 | XCTAssertEqual(n,r) 159 | } 160 | 161 | func testMethod_One() async throws { 162 | let n: Int = 1 163 | let r: Int = 1 164 | XCTAssertEqual(n, r) 165 | } 166 | } 167 | """, 168 | macros: testMacros 169 | ) 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /Tests/XCTestParametrizedMacroTests/LabelsParameterTests.swift: -------------------------------------------------------------------------------- 1 | import SwiftSyntaxMacros 2 | import SwiftSyntaxMacrosTestSupport 3 | import XCTest 4 | 5 | import XCTestParametrizedMacroMacros 6 | 7 | final class LabelsParameterTests: XCTestCase { 8 | 9 | let testMacros: [String: Macro.Type] = [ 10 | "Parametrize": ParametrizeMacro.self, 11 | ] 12 | 13 | func testParametrizeInputLabels_OneLabel() throws { 14 | assertMacroExpansion( 15 | """ 16 | struct TestStruct { 17 | @Parametrize(input: [3.1415], labels: ["Pi"]) 18 | func testValidateDouble(input n: Double) throws { 19 | XCTAssertNotNil(validate_number(n)) 20 | } 21 | } 22 | """, 23 | expandedSource: """ 24 | struct TestStruct { 25 | func testValidateDouble(input n: Double) throws { 26 | XCTAssertNotNil(validate_number(n)) 27 | } 28 | 29 | func testValidateDouble_Pi() throws { 30 | let n: Double = 3.1415 31 | XCTAssertNotNil(validate_number(n)) 32 | } 33 | } 34 | """, 35 | macros: testMacros 36 | ) 37 | } 38 | 39 | func testParametrizeInputOutputLabels_OneLabel() throws { 40 | assertMacroExpansion( 41 | """ 42 | struct TestStruct { 43 | @Parametrize(input: [3], output: [9], labels: ["ThreePowerOfTheTwo"]) 44 | func testPow2(input n: Int, output result: Int) { 45 | XCTAssertEqual(pow2(n),result) 46 | } 47 | } 48 | """, 49 | expandedSource: """ 50 | struct TestStruct { 51 | func testPow2(input n: Int, output result: Int) { 52 | XCTAssertEqual(pow2(n),result) 53 | } 54 | 55 | func testPow2_ThreePowerOfTheTwo() { 56 | let n: Int = 3 57 | let result: Int = 9 58 | XCTAssertEqual(pow2(n), result) 59 | } 60 | } 61 | """, 62 | macros: testMacros 63 | ) 64 | } 65 | 66 | func testParametrizeInputOutputLabels_TwoLabels() throws { 67 | assertMacroExpansion( 68 | """ 69 | struct TestStruct { 70 | @Parametrize(input: [3, 4], output: [9, 16], labels: ["ThreePowerOfTheTwo", "FourPowerOfTheTwo"]) 71 | func testPow2(input n: Int, output result: Int) { 72 | XCTAssertEqual(pow2(n),result) 73 | } 74 | } 75 | """, 76 | expandedSource: """ 77 | struct TestStruct { 78 | func testPow2(input n: Int, output result: Int) { 79 | XCTAssertEqual(pow2(n),result) 80 | } 81 | 82 | func testPow2_ThreePowerOfTheTwo() { 83 | let n: Int = 3 84 | let result: Int = 9 85 | XCTAssertEqual(pow2(n), result) 86 | } 87 | 88 | func testPow2_FourPowerOfTheTwo() { 89 | let n: Int = 4 90 | let result: Int = 16 91 | XCTAssertEqual(pow2(n), result) 92 | } 93 | } 94 | """, 95 | macros: testMacros 96 | ) 97 | } 98 | 99 | func testParametrizeInputOutputLabels_CustomObjects() throws { 100 | assertMacroExpansion( 101 | """ 102 | struct TestStruct { 103 | @Parametrize( 104 | input: [APIEndpoint.profile, APIEndpoint.transactions, APIEndpoint.order("2345")], 105 | output: ["https://example.com/api/me", 106 | "https://example.com/api/transactions", 107 | "https://example.com/api/order/2345"], 108 | labels: ["profile", 109 | "transactions", 110 | "order"]) 111 | func testEndpointURL(input endpoint: APIEndpoint, output expectedUrl: String) throws { 112 | XCTAssertEqual(endpoint.buildURL?.absoluteString, expectedUrl) 113 | } 114 | } 115 | """, 116 | expandedSource: """ 117 | struct TestStruct { 118 | func testEndpointURL(input endpoint: APIEndpoint, output expectedUrl: String) throws { 119 | XCTAssertEqual(endpoint.buildURL?.absoluteString, expectedUrl) 120 | } 121 | 122 | func testEndpointURL_profile() throws { 123 | let endpoint: APIEndpoint = APIEndpoint.profile 124 | let expectedUrl: String = "https://example.com/api/me" 125 | XCTAssertEqual(endpoint.buildURL?.absoluteString, expectedUrl) 126 | } 127 | 128 | func testEndpointURL_transactions() throws { 129 | let endpoint: APIEndpoint = APIEndpoint.transactions 130 | let expectedUrl: String = 131 | "https://example.com/api/transactions" 132 | XCTAssertEqual(endpoint.buildURL?.absoluteString, expectedUrl) 133 | } 134 | 135 | func testEndpointURL_order() throws { 136 | let endpoint: APIEndpoint = APIEndpoint.order("2345") 137 | let expectedUrl: String = 138 | "https://example.com/api/order/2345" 139 | XCTAssertEqual(endpoint.buildURL?.absoluteString, expectedUrl) 140 | } 141 | } 142 | """, 143 | macros: testMacros 144 | ) 145 | } 146 | } 147 | 148 | 149 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | logo 3 |

4 | 5 | # XCTestParametrizedMacro 6 | 7 | Swift Version 8 | License 9 | 10 | ## Summary 11 | 12 | A straightforward yet powerful Swift macro designed to simplify the unit test creation process. This tool allows you to easily parameterize your XCTest methods by automatically generating test methods based on specified arguments. Write your test methods once and effortlessly generate variations with different parameters. This approach not only simplifies test maintenance but also makes identifying failing tests more effective. Inspired by JUnit parametrized tests. 13 | 14 | ## Requirements 15 | 16 | Xcode 15 or above 17 | Swift 5.9 or later 18 | 19 | ## Installation 20 | 21 | **Xcode project** 22 | 23 | If you are using Xcode open `File -> Add Package Dependencies...` and enter url `https://github.com/pgssoft/XCTestParametrizedMacro` 24 | 25 | **Swift package manager** 26 | 27 | In `Package.swift` add: 28 | 29 | ``` swift 30 | dependencies: [ 31 | .package(url: "https://github.com/pgssoft/XCTestParametrizedMacro", branch: "main") 32 | ] 33 | ``` 34 | 35 | and then add the product to your test target. 36 | 37 | ```swift 38 | .product(name: "XCTestParametrizedMacro", package: "XCTestParametrizedMacro"), 39 | ``` 40 | 41 | and import it in your unit test file 42 | 43 | ```swift 44 | import XCTestParametrizedMacro 45 | ``` 46 | 47 | ## Examples 48 | 49 | Let's consider a simple example. You want to implement unit tests for the method validating the age of the user. 50 | ```swift 51 | struct AgeValidator { 52 | static func isAdult(age: Int) -> Bool { 53 | age >= 18 54 | } 55 | } 56 | ``` 57 | 58 | If you want to test it for a few values you can write two generic methods and use macro to create a separate test method for each value. 59 | ```swift 60 | @Parametrize(input: [18, 25, 99]) 61 | func testAgeValidatorValidAge(input age: Int) throws { 62 | XCTAssertTrue(AgeValidator.isAdult(age: age)) 63 | } 64 | 65 | @Parametrize(input: [2, 17]) 66 | func testAgeValidatorInvalidAge(input age: Int) throws { 67 | XCTAssertFalse(AgeValidator.isAdult(age: age)) 68 | } 69 | ``` 70 | 71 | Macro will generate a test method for every case. 72 | 73 | ```swift 74 | func testAgeValidatorValidAge_Age_18() throws { 75 | let age: Int = 18 76 | XCTAssertTrue(AgeValidator.isAdult(age: age)) 77 | } 78 | 79 | func testAgeValidatorValidAge_Age_25() throws { 80 | let age: Int = 25 81 | XCTAssertTrue(AgeValidator.isAdult(age: age)) 82 | } 83 | 84 | func testAgeValidatorValidAge_Age_99() throws { 85 | let age: Int = 99 86 | XCTAssertTrue(AgeValidator.isAdult(age: age)) 87 | } 88 | 89 | func testAgeValidatorInvalidAge_Age_2() throws { 90 | let age: Int = 2 91 | XCTAssertFalse(AgeValidator.isAdult(age: age)) 92 | } 93 | 94 | func testAgeValidatorInvalidAge_Age_17() throws { 95 | let age: Int = 17 96 | XCTAssertFalse(AgeValidator.isAdult(age: age)) 97 | } 98 | ``` 99 | 100 | But as an input parameter, you can use custom types as well. 101 | 102 | ```swift 103 | @Parametrize(input: [APIEndpoint.profile, APIEndpoint.transactions, APIEndpoint.order("2345")]) 104 | func testEndpointURL(input endpoint: APIEndpoint) throws { 105 | XCTAssertNotNil(endpoint.buildURL) 106 | } 107 | ``` 108 | 109 | The macro will generate the following test methods. 110 | 111 | ```swift 112 | func testEndpointURL_Endpoint_APIEndpoint_profile() throws { 113 | let endpoint: APIEndpoint = APIEndpoint.profile 114 | XCTAssertNotNil(endpoint.buildURL) 115 | } 116 | 117 | func testEndpointURL_Endpoint_APIEndpoint_transactions() throws { 118 | let endpoint: APIEndpoint = APIEndpoint.transactions 119 | XCTAssertNotNil(endpoint.buildURL) 120 | } 121 | 122 | func testEndpointURL_Endpoint_APIEndpoint_order_2345_() throws { 123 | let endpoint: APIEndpoint = APIEndpoint.order("2345") 124 | XCTAssertNotNil(endpoint.buildURL) 125 | } 126 | ``` 127 | 128 | You can also specify array of expected values and use it in your test. 129 | 130 | ```swift 131 | @Parametrize( 132 | input: [APIEndpoint.profile, 133 | APIEndpoint.transactions, 134 | APIEndpoint.order("2345")], 135 | output: ["https://example.com/api/me", 136 | "https://example.com/api/transactions", 137 | "https://example.com/api/order/2345"]) 138 | func testEndpointURL(input endpoint: APIEndpoint, output expectedUrl: String) throws { 139 | XCTAssertEqual(endpoint.buildURL?.absoluteString, expectedUrl) 140 | } 141 | ``` 142 | 143 | Because for example above names of test methods looks weird, You can use `labels` parameter to use custom naming. 144 | 145 | ```swift 146 | @Parametrize( 147 | input: [APIEndpoint.order("2345")], 148 | output: ["https://example.com/api/order/2345"]), 149 | labels: ["order"]) 150 | func testEndpointURL(input endpoint: APIEndpoint, output expectedUrl: String) throws { 151 | XCTAssertEqual(endpoint.buildURL?.absoluteString, expectedUrl) 152 | } 153 | ``` 154 | 155 | Macro will generate following test method. 156 | ```swift 157 | func testEndpointURL_order() throws { 158 | let endpoint: APIEndpoint = APIEndpoint.order("2345") 159 | let expectedUrl: String = 160 | "https://example.com/api/order/2345" 161 | XCTAssertEqual(endpoint.buildURL?.absoluteString, expectedUrl) 162 | } 163 | ``` 164 | 165 | ## Features 166 | 167 | - [x] Primitive types as input values (like Int, Double, String and Bool) 168 | - [x] Custom objects as input values (like structs, classes and enums) 169 | - [x] Diagnostics for error handling 170 | - [x] Expected output array of objects/values 171 | - [x] Labels for parameter values like: `@Parametrize(input: [3.14, 2.71], labels: ["pi", "e"])` 172 | - [ ] Array of tuple objects as macro argument 173 | 174 | ## License 175 | XCTestParametrizedMacro is released under an MIT license. See [LICENSE](LICENSE) 176 | -------------------------------------------------------------------------------- /Tests/XCTestParametrizedMacroTests/AttachmentTests.swift: -------------------------------------------------------------------------------- 1 | import SwiftSyntaxMacros 2 | import SwiftSyntaxMacrosTestSupport 3 | import XCTest 4 | 5 | import XCTestParametrizedMacroMacros 6 | 7 | final class AttachmentTests: XCTestCase { 8 | let testMacros: [String: Macro.Type] = [ 9 | "Parametrize": ParametrizeMacro.self, 10 | ] 11 | 12 | func testParametrizeInput_AttachToStruct_ShouldFail() throws { 13 | assertMacroExpansion( 14 | """ 15 | @Parametrize(input: ["Natasha"]) 16 | struct TestStruct { 17 | } 18 | """, 19 | expandedSource: """ 20 | struct TestStruct { 21 | } 22 | """, 23 | diagnostics: [ 24 | DiagnosticSpec(message: "Parametrize macro can be used only for functions.", line: 1, column: 1) 25 | ], 26 | macros: testMacros 27 | ) 28 | } 29 | 30 | func testParametrizeInput_AttachToEnum_ShouldFail() throws { 31 | assertMacroExpansion( 32 | """ 33 | @Parametrize(input: ["Natasha"]) 34 | enum TestEnum { 35 | } 36 | """, 37 | expandedSource: """ 38 | enum TestEnum { 39 | } 40 | """, 41 | diagnostics: [ 42 | DiagnosticSpec(message: "Parametrize macro can be used only for functions.", line: 1, column: 1) 43 | ], 44 | macros: testMacros 45 | ) 46 | } 47 | 48 | func testParametrizeInput_AttachToClass_ShouldFail() throws { 49 | assertMacroExpansion( 50 | """ 51 | @Parametrize(input: ["Natasha"]) 52 | class TestClass { 53 | } 54 | """, 55 | expandedSource: """ 56 | class TestClass { 57 | } 58 | """, 59 | diagnostics: [ 60 | DiagnosticSpec(message: "Parametrize macro can be used only for functions.", line: 1, column: 1) 61 | ], 62 | macros: testMacros 63 | ) 64 | } 65 | 66 | func testParametrizeInput_AttachToMethodWithoutInputSecondName_ShouldFail() throws { 67 | assertMacroExpansion( 68 | """ 69 | struct TestStruct { 70 | @Parametrize(input: ["Natasha"]) 71 | func testDataModel(input: String) throws { 72 | let model = DataModel(name: input) 73 | XCTAssertTrue(model.isFemale()) 74 | } 75 | } 76 | """, 77 | expandedSource: """ 78 | struct TestStruct { 79 | func testDataModel(input: String) throws { 80 | let model = DataModel(name: input) 81 | XCTAssertTrue(model.isFemale()) 82 | } 83 | } 84 | """, 85 | diagnostics: [ 86 | DiagnosticSpec(message: "Input parameter must have a second name. Something like `testMethod(input secondName: String)`.", line: 2, column: 5) 87 | ], 88 | macros: testMacros 89 | ) 90 | } 91 | 92 | func testParametrizeInput_AttachToMethodWithoutBody_ShouldFail() throws { 93 | assertMacroExpansion( 94 | """ 95 | struct TestStruct { 96 | @Parametrize(input: ["Natasha"]) 97 | func testDataModel(input name: String) throws { 98 | } 99 | } 100 | """, 101 | expandedSource: """ 102 | struct TestStruct { 103 | func testDataModel(input name: String) throws { 104 | } 105 | } 106 | """, 107 | diagnostics: [ 108 | DiagnosticSpec(message: "Function must have a body.", line: 2, column: 5) 109 | ], 110 | macros: testMacros 111 | ) 112 | } 113 | 114 | func testParametrizeInput_MacroWithoutAttributes_ShouldFail() throws { 115 | assertMacroExpansion( 116 | """ 117 | struct TestStruct { 118 | @Parametrize() 119 | func testDataModel(input name: String) throws { 120 | let model = DataModel(name: name) 121 | XCTAssertTrue(model.isFemale()) 122 | } 123 | } 124 | """, 125 | expandedSource: """ 126 | struct TestStruct { 127 | func testDataModel(input name: String) throws { 128 | let model = DataModel(name: name) 129 | XCTAssertTrue(model.isFemale()) 130 | } 131 | } 132 | """, 133 | diagnostics: [ 134 | DiagnosticSpec(message: "Parametrize macro requires at least one attribute as array of input/output values.", line: 2, column: 5) 135 | ], 136 | macros: testMacros 137 | ) 138 | } 139 | 140 | func testParametrizeInput_MacroWithoutArrayAttribute_ShouldFail() throws { 141 | assertMacroExpansion( 142 | """ 143 | struct TestStruct { 144 | @Parametrize(input: "Natasha") 145 | func testDataModel(input name: String) throws { 146 | let model = DataModel(name: name) 147 | XCTAssertTrue(model.isFemale()) 148 | } 149 | } 150 | """, 151 | expandedSource: """ 152 | struct TestStruct { 153 | func testDataModel(input name: String) throws { 154 | let model = DataModel(name: name) 155 | XCTAssertTrue(model.isFemale()) 156 | } 157 | } 158 | """, 159 | diagnostics: [ 160 | DiagnosticSpec(message: "Parametrize macro requires at least one attribute as array of input/output values.", line: 2, column: 5) 161 | ], 162 | macros: testMacros 163 | ) 164 | } 165 | 166 | func testParametrizeInputOutput_DifferentSizeOfArrays_ShouldFail() throws { 167 | assertMacroExpansion( 168 | """ 169 | struct TestStruct { 170 | @Parametrize(input: [1,2,3], output: [1,4]) 171 | func testPow2(input n: Int, output result: Int) { 172 | XCTAssertEqual(pow2(n), result) 173 | } 174 | } 175 | """, 176 | expandedSource: """ 177 | struct TestStruct { 178 | func testPow2(input n: Int, output result: Int) { 179 | XCTAssertEqual(pow2(n), result) 180 | } 181 | } 182 | """, 183 | diagnostics: [ 184 | DiagnosticSpec(message: "Arrays passed as an argument should be same size.", line: 2, column: 5) 185 | ], 186 | macros: testMacros 187 | ) 188 | } 189 | 190 | func testParametrizeInputOutputLabels_DifferentSizeOfArrays_ShouldFail() throws { 191 | assertMacroExpansion( 192 | """ 193 | struct TestStruct { 194 | @Parametrize(input: [1,2,3], output: [1,4,3], labels: ["first", "second"]) 195 | func testPow2(input n: Int, output result: Int) { 196 | XCTAssertEqual(pow2(n), result) 197 | } 198 | } 199 | """, 200 | expandedSource: """ 201 | struct TestStruct { 202 | func testPow2(input n: Int, output result: Int) { 203 | XCTAssertEqual(pow2(n), result) 204 | } 205 | } 206 | """, 207 | diagnostics: [ 208 | DiagnosticSpec(message: "Arrays passed as an argument should be same size.", line: 2, column: 5) 209 | ], 210 | macros: testMacros 211 | ) 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /Tests/XCTestParametrizedMacroTests/SimpleValuesTests.swift: -------------------------------------------------------------------------------- 1 | import SwiftSyntaxMacros 2 | import SwiftSyntaxMacrosTestSupport 3 | import XCTest 4 | 5 | import XCTestParametrizedMacroMacros 6 | 7 | final class SimpleValuesTests: XCTestCase { 8 | let testMacros: [String: Macro.Type] = [ 9 | "Parametrize": ParametrizeMacro.self, 10 | ] 11 | 12 | func testParametrizeInput_SingleStringValue() throws { 13 | assertMacroExpansion( 14 | """ 15 | struct TestStruct { 16 | @Parametrize(input: ["Natasha"]) 17 | func testDataModel(input name: String) throws { 18 | let model = DataModel(name: name) 19 | XCTAssertTrue(model.isFemale()) 20 | } 21 | } 22 | """, 23 | expandedSource: """ 24 | struct TestStruct { 25 | func testDataModel(input name: String) throws { 26 | let model = DataModel(name: name) 27 | XCTAssertTrue(model.isFemale()) 28 | } 29 | 30 | func testDataModel_Name_Natasha() throws { 31 | let name: String = "Natasha" 32 | let model = DataModel(name: name) 33 | XCTAssertTrue(model.isFemale()) 34 | } 35 | } 36 | """, 37 | macros: testMacros 38 | ) 39 | } 40 | 41 | func testParametrizeInput_TwoStringValues() throws { 42 | assertMacroExpansion( 43 | """ 44 | struct TestStruct { 45 | @Parametrize(input: ["Natasha", "Alexandra"]) 46 | func testDataModel(input name: String) throws { 47 | let model = DataModel(name: name) 48 | XCTAssertTrue(model.isFemale()) 49 | } 50 | } 51 | """, 52 | expandedSource: """ 53 | struct TestStruct { 54 | func testDataModel(input name: String) throws { 55 | let model = DataModel(name: name) 56 | XCTAssertTrue(model.isFemale()) 57 | } 58 | 59 | func testDataModel_Name_Natasha() throws { 60 | let name: String = "Natasha" 61 | let model = DataModel(name: name) 62 | XCTAssertTrue(model.isFemale()) 63 | } 64 | 65 | func testDataModel_Name_Alexandra() throws { 66 | let name: String = "Alexandra" 67 | let model = DataModel(name: name) 68 | XCTAssertTrue(model.isFemale()) 69 | } 70 | } 71 | """, 72 | macros: testMacros 73 | ) 74 | } 75 | 76 | func testParametrizeInput_SingleIntValue() throws { 77 | assertMacroExpansion( 78 | """ 79 | struct TestStruct { 80 | @Parametrize(input: [20]) 81 | func testDataModel(input age: Int) throws { 82 | let model = DataModel(age: age) 83 | XCTAssertTrue(model.isAdult()) 84 | } 85 | } 86 | """, 87 | expandedSource: """ 88 | struct TestStruct { 89 | func testDataModel(input age: Int) throws { 90 | let model = DataModel(age: age) 91 | XCTAssertTrue(model.isAdult()) 92 | } 93 | 94 | func testDataModel_Age_20() throws { 95 | let age: Int = 20 96 | let model = DataModel(age: age) 97 | XCTAssertTrue(model.isAdult()) 98 | } 99 | } 100 | """, 101 | macros: testMacros 102 | ) 103 | } 104 | 105 | func testParametrizeInput_TwoIntValues() throws { 106 | assertMacroExpansion( 107 | """ 108 | struct TestStruct { 109 | @Parametrize(input: [20, 30]) 110 | func testDataModel(input age: Int) throws { 111 | let model = DataModel(age: age) 112 | XCTAssertTrue(model.isAdult()) 113 | } 114 | } 115 | """, 116 | expandedSource: """ 117 | struct TestStruct { 118 | func testDataModel(input age: Int) throws { 119 | let model = DataModel(age: age) 120 | XCTAssertTrue(model.isAdult()) 121 | } 122 | 123 | func testDataModel_Age_20() throws { 124 | let age: Int = 20 125 | let model = DataModel(age: age) 126 | XCTAssertTrue(model.isAdult()) 127 | } 128 | 129 | func testDataModel_Age_30() throws { 130 | let age: Int = 30 131 | let model = DataModel(age: age) 132 | XCTAssertTrue(model.isAdult()) 133 | } 134 | } 135 | """, 136 | macros: testMacros 137 | ) 138 | } 139 | 140 | func testParametrizeInput_SingleBoolValue() throws { 141 | assertMacroExpansion( 142 | """ 143 | struct TestStruct { 144 | @Parametrize(input: [true]) 145 | func testDataModel(input isAuthorized: Bool) throws { 146 | let model = DataModel(isAuthorized: isAuthorized) 147 | XCTAssertTrue(model.isAuthorizationRequired()) 148 | } 149 | } 150 | """, 151 | expandedSource: """ 152 | struct TestStruct { 153 | func testDataModel(input isAuthorized: Bool) throws { 154 | let model = DataModel(isAuthorized: isAuthorized) 155 | XCTAssertTrue(model.isAuthorizationRequired()) 156 | } 157 | 158 | func testDataModel_IsAuthorized_true() throws { 159 | let isAuthorized: Bool = true 160 | let model = DataModel(isAuthorized: isAuthorized) 161 | XCTAssertTrue(model.isAuthorizationRequired()) 162 | } 163 | } 164 | """, 165 | macros: testMacros 166 | ) 167 | } 168 | 169 | func testParametrizeInput_TwoBoolValues() throws { 170 | assertMacroExpansion( 171 | """ 172 | struct TestStruct { 173 | @Parametrize(input: [true, false]) 174 | func testDataModel(input isAuthorized: Bool) throws { 175 | let model = DataModel(isAuthorized: isAuthorized) 176 | XCTAssertTrue(model.isAuthorizationRequired()) 177 | } 178 | } 179 | """, 180 | expandedSource: """ 181 | struct TestStruct { 182 | func testDataModel(input isAuthorized: Bool) throws { 183 | let model = DataModel(isAuthorized: isAuthorized) 184 | XCTAssertTrue(model.isAuthorizationRequired()) 185 | } 186 | 187 | func testDataModel_IsAuthorized_true() throws { 188 | let isAuthorized: Bool = true 189 | let model = DataModel(isAuthorized: isAuthorized) 190 | XCTAssertTrue(model.isAuthorizationRequired()) 191 | } 192 | 193 | func testDataModel_IsAuthorized_false() throws { 194 | let isAuthorized: Bool = false 195 | let model = DataModel(isAuthorized: isAuthorized) 196 | XCTAssertTrue(model.isAuthorizationRequired()) 197 | } 198 | } 199 | """, 200 | macros: testMacros 201 | ) 202 | } 203 | 204 | func testParametrizeInput_OneObject() throws { 205 | assertMacroExpansion( 206 | """ 207 | struct TestStruct { 208 | @Parametrize(input: [Foo()]) 209 | func testDataModel(input object: Foo) throws { 210 | let model = DataModel(object: object) 211 | XCTAssertTrue(model.isValid()) 212 | } 213 | } 214 | """, 215 | expandedSource: """ 216 | struct TestStruct { 217 | func testDataModel(input object: Foo) throws { 218 | let model = DataModel(object: object) 219 | XCTAssertTrue(model.isValid()) 220 | } 221 | 222 | func testDataModel_Object_Foo__() throws { 223 | let object: Foo = Foo() 224 | let model = DataModel(object: object) 225 | XCTAssertTrue(model.isValid()) 226 | } 227 | } 228 | """, 229 | macros: testMacros 230 | ) 231 | } 232 | 233 | func testParametrizeInput_FewObjects() throws { 234 | assertMacroExpansion( 235 | """ 236 | struct TestStruct { 237 | @Parametrize(input: [Foo.first, .second, .init(rawValue: 3)!]) 238 | func testDataModel(input object: Foo) throws { 239 | let dataModel = DataModel(object) 240 | XCTAssertTrue(dataModel.isValid) 241 | } 242 | } 243 | """, 244 | expandedSource: """ 245 | struct TestStruct { 246 | func testDataModel(input object: Foo) throws { 247 | let dataModel = DataModel(object) 248 | XCTAssertTrue(dataModel.isValid) 249 | } 250 | 251 | func testDataModel_Object_Foo_first() throws { 252 | let object: Foo = Foo.first 253 | let dataModel = DataModel(object) 254 | XCTAssertTrue(dataModel.isValid) 255 | } 256 | 257 | func testDataModel_Object__second() throws { 258 | let object: Foo = .second 259 | let dataModel = DataModel(object) 260 | XCTAssertTrue(dataModel.isValid) 261 | } 262 | 263 | func testDataModel_Object__init_rawValue__3__() throws { 264 | let object: Foo = .init(rawValue: 3)! 265 | let dataModel = DataModel(object) 266 | XCTAssertTrue(dataModel.isValid) 267 | } 268 | } 269 | """, 270 | macros: testMacros 271 | ) 272 | } 273 | 274 | } 275 | -------------------------------------------------------------------------------- /ExampleApp/ExampleApp.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 60; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | B4140C4E2AF4F6C60003C8D2 /* APIEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = B456A9852AEF987E003BCCCA /* APIEndpoint.swift */; }; 11 | B4140C4F2AF4F6C80003C8D2 /* APIEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = B456A9852AEF987E003BCCCA /* APIEndpoint.swift */; }; 12 | B4140C502AF4F6CA0003C8D2 /* AgeValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B49691FC2AE7F14E00227B30 /* AgeValidator.swift */; }; 13 | B456A9892AEF98A0003BCCCA /* APIEndpointsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B456A9872AEF9893003BCCCA /* APIEndpointsTests.swift */; }; 14 | B49691FB2AE7E3A600227B30 /* AgeVerificationTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = B49691FA2AE7E3A600227B30 /* AgeVerificationTest.swift */; }; 15 | B49691FD2AE7F14E00227B30 /* AgeValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B49691FC2AE7F14E00227B30 /* AgeValidator.swift */; }; 16 | C5C8B7892A77DD050020DE78 /* ExampleAppApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5C8B7882A77DD050020DE78 /* ExampleAppApp.swift */; }; 17 | C5C8B78B2A77DD050020DE78 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5C8B78A2A77DD050020DE78 /* ContentView.swift */; }; 18 | C5C8B78D2A77DD080020DE78 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C5C8B78C2A77DD080020DE78 /* Assets.xcassets */; }; 19 | C5C8B7902A77DD080020DE78 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C5C8B78F2A77DD080020DE78 /* Preview Assets.xcassets */; }; 20 | C5DDC21B2A7A7B4000E5DD15 /* XCTestParametrizedMacro in Frameworks */ = {isa = PBXBuildFile; productRef = C5DDC21A2A7A7B4000E5DD15 /* XCTestParametrizedMacro */; }; 21 | /* End PBXBuildFile section */ 22 | 23 | /* Begin PBXContainerItemProxy section */ 24 | C5C8B7962A77DD080020DE78 /* PBXContainerItemProxy */ = { 25 | isa = PBXContainerItemProxy; 26 | containerPortal = C5C8B77D2A77DD050020DE78 /* Project object */; 27 | proxyType = 1; 28 | remoteGlobalIDString = C5C8B7842A77DD050020DE78; 29 | remoteInfo = ExampleApp; 30 | }; 31 | /* End PBXContainerItemProxy section */ 32 | 33 | /* Begin PBXFileReference section */ 34 | B456A9852AEF987E003BCCCA /* APIEndpoint.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = APIEndpoint.swift; sourceTree = ""; }; 35 | B456A9872AEF9893003BCCCA /* APIEndpointsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = APIEndpointsTests.swift; sourceTree = ""; }; 36 | B49691FA2AE7E3A600227B30 /* AgeVerificationTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AgeVerificationTest.swift; sourceTree = ""; }; 37 | B49691FC2AE7F14E00227B30 /* AgeValidator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AgeValidator.swift; sourceTree = ""; }; 38 | C5C8B7852A77DD050020DE78 /* ExampleApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ExampleApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; 39 | C5C8B7882A77DD050020DE78 /* ExampleAppApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExampleAppApp.swift; sourceTree = ""; }; 40 | C5C8B78A2A77DD050020DE78 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 41 | C5C8B78C2A77DD080020DE78 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 42 | C5C8B78F2A77DD080020DE78 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 43 | C5C8B7952A77DD080020DE78 /* ExampleAppTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ExampleAppTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 44 | /* End PBXFileReference section */ 45 | 46 | /* Begin PBXFrameworksBuildPhase section */ 47 | C5C8B7822A77DD050020DE78 /* Frameworks */ = { 48 | isa = PBXFrameworksBuildPhase; 49 | buildActionMask = 2147483647; 50 | files = ( 51 | ); 52 | runOnlyForDeploymentPostprocessing = 0; 53 | }; 54 | C5C8B7922A77DD080020DE78 /* Frameworks */ = { 55 | isa = PBXFrameworksBuildPhase; 56 | buildActionMask = 2147483647; 57 | files = ( 58 | C5DDC21B2A7A7B4000E5DD15 /* XCTestParametrizedMacro in Frameworks */, 59 | ); 60 | runOnlyForDeploymentPostprocessing = 0; 61 | }; 62 | /* End PBXFrameworksBuildPhase section */ 63 | 64 | /* Begin PBXGroup section */ 65 | C5C8B77C2A77DD050020DE78 = { 66 | isa = PBXGroup; 67 | children = ( 68 | C5C8B7872A77DD050020DE78 /* ExampleApp */, 69 | C5C8B7982A77DD080020DE78 /* ExampleAppTests */, 70 | C5C8B7862A77DD050020DE78 /* Products */, 71 | C5DDC2162A7A7AE600E5DD15 /* Frameworks */, 72 | ); 73 | sourceTree = ""; 74 | }; 75 | C5C8B7862A77DD050020DE78 /* Products */ = { 76 | isa = PBXGroup; 77 | children = ( 78 | C5C8B7852A77DD050020DE78 /* ExampleApp.app */, 79 | C5C8B7952A77DD080020DE78 /* ExampleAppTests.xctest */, 80 | ); 81 | name = Products; 82 | sourceTree = ""; 83 | }; 84 | C5C8B7872A77DD050020DE78 /* ExampleApp */ = { 85 | isa = PBXGroup; 86 | children = ( 87 | C5C8B7882A77DD050020DE78 /* ExampleAppApp.swift */, 88 | B456A9852AEF987E003BCCCA /* APIEndpoint.swift */, 89 | C5C8B78A2A77DD050020DE78 /* ContentView.swift */, 90 | B49691FC2AE7F14E00227B30 /* AgeValidator.swift */, 91 | C5C8B78C2A77DD080020DE78 /* Assets.xcassets */, 92 | C5C8B78E2A77DD080020DE78 /* Preview Content */, 93 | ); 94 | path = ExampleApp; 95 | sourceTree = ""; 96 | }; 97 | C5C8B78E2A77DD080020DE78 /* Preview Content */ = { 98 | isa = PBXGroup; 99 | children = ( 100 | C5C8B78F2A77DD080020DE78 /* Preview Assets.xcassets */, 101 | ); 102 | path = "Preview Content"; 103 | sourceTree = ""; 104 | }; 105 | C5C8B7982A77DD080020DE78 /* ExampleAppTests */ = { 106 | isa = PBXGroup; 107 | children = ( 108 | B456A9872AEF9893003BCCCA /* APIEndpointsTests.swift */, 109 | B49691FA2AE7E3A600227B30 /* AgeVerificationTest.swift */, 110 | ); 111 | path = ExampleAppTests; 112 | sourceTree = ""; 113 | }; 114 | C5DDC2162A7A7AE600E5DD15 /* Frameworks */ = { 115 | isa = PBXGroup; 116 | children = ( 117 | ); 118 | name = Frameworks; 119 | sourceTree = ""; 120 | }; 121 | /* End PBXGroup section */ 122 | 123 | /* Begin PBXNativeTarget section */ 124 | C5C8B7842A77DD050020DE78 /* ExampleApp */ = { 125 | isa = PBXNativeTarget; 126 | buildConfigurationList = C5C8B7A92A77DD080020DE78 /* Build configuration list for PBXNativeTarget "ExampleApp" */; 127 | buildPhases = ( 128 | C5C8B7812A77DD050020DE78 /* Sources */, 129 | C5C8B7822A77DD050020DE78 /* Frameworks */, 130 | C5C8B7832A77DD050020DE78 /* Resources */, 131 | ); 132 | buildRules = ( 133 | ); 134 | dependencies = ( 135 | ); 136 | name = ExampleApp; 137 | productName = ExampleApp; 138 | productReference = C5C8B7852A77DD050020DE78 /* ExampleApp.app */; 139 | productType = "com.apple.product-type.application"; 140 | }; 141 | C5C8B7942A77DD080020DE78 /* ExampleAppTests */ = { 142 | isa = PBXNativeTarget; 143 | buildConfigurationList = C5C8B7AC2A77DD080020DE78 /* Build configuration list for PBXNativeTarget "ExampleAppTests" */; 144 | buildPhases = ( 145 | C5C8B7912A77DD080020DE78 /* Sources */, 146 | C5C8B7922A77DD080020DE78 /* Frameworks */, 147 | C5C8B7932A77DD080020DE78 /* Resources */, 148 | ); 149 | buildRules = ( 150 | ); 151 | dependencies = ( 152 | C5C8B7972A77DD080020DE78 /* PBXTargetDependency */, 153 | ); 154 | name = ExampleAppTests; 155 | packageProductDependencies = ( 156 | C5DDC21A2A7A7B4000E5DD15 /* XCTestParametrizedMacro */, 157 | ); 158 | productName = ExampleAppTests; 159 | productReference = C5C8B7952A77DD080020DE78 /* ExampleAppTests.xctest */; 160 | productType = "com.apple.product-type.bundle.unit-test"; 161 | }; 162 | /* End PBXNativeTarget section */ 163 | 164 | /* Begin PBXProject section */ 165 | C5C8B77D2A77DD050020DE78 /* Project object */ = { 166 | isa = PBXProject; 167 | attributes = { 168 | BuildIndependentTargetsInParallel = 1; 169 | LastSwiftUpdateCheck = 1500; 170 | LastUpgradeCheck = 1500; 171 | TargetAttributes = { 172 | C5C8B7842A77DD050020DE78 = { 173 | CreatedOnToolsVersion = 15.0; 174 | }; 175 | C5C8B7942A77DD080020DE78 = { 176 | CreatedOnToolsVersion = 15.0; 177 | }; 178 | }; 179 | }; 180 | buildConfigurationList = C5C8B7802A77DD050020DE78 /* Build configuration list for PBXProject "ExampleApp" */; 181 | compatibilityVersion = "Xcode 14.0"; 182 | developmentRegion = en; 183 | hasScannedForEncodings = 0; 184 | knownRegions = ( 185 | en, 186 | Base, 187 | ); 188 | mainGroup = C5C8B77C2A77DD050020DE78; 189 | packageReferences = ( 190 | C5DDC2192A7A7B4000E5DD15 /* XCLocalSwiftPackageReference ".." */, 191 | ); 192 | productRefGroup = C5C8B7862A77DD050020DE78 /* Products */; 193 | projectDirPath = ""; 194 | projectRoot = ""; 195 | targets = ( 196 | C5C8B7842A77DD050020DE78 /* ExampleApp */, 197 | C5C8B7942A77DD080020DE78 /* ExampleAppTests */, 198 | ); 199 | }; 200 | /* End PBXProject section */ 201 | 202 | /* Begin PBXResourcesBuildPhase section */ 203 | C5C8B7832A77DD050020DE78 /* Resources */ = { 204 | isa = PBXResourcesBuildPhase; 205 | buildActionMask = 2147483647; 206 | files = ( 207 | C5C8B7902A77DD080020DE78 /* Preview Assets.xcassets in Resources */, 208 | C5C8B78D2A77DD080020DE78 /* Assets.xcassets in Resources */, 209 | ); 210 | runOnlyForDeploymentPostprocessing = 0; 211 | }; 212 | C5C8B7932A77DD080020DE78 /* Resources */ = { 213 | isa = PBXResourcesBuildPhase; 214 | buildActionMask = 2147483647; 215 | files = ( 216 | ); 217 | runOnlyForDeploymentPostprocessing = 0; 218 | }; 219 | /* End PBXResourcesBuildPhase section */ 220 | 221 | /* Begin PBXSourcesBuildPhase section */ 222 | C5C8B7812A77DD050020DE78 /* Sources */ = { 223 | isa = PBXSourcesBuildPhase; 224 | buildActionMask = 2147483647; 225 | files = ( 226 | B49691FD2AE7F14E00227B30 /* AgeValidator.swift in Sources */, 227 | C5C8B78B2A77DD050020DE78 /* ContentView.swift in Sources */, 228 | C5C8B7892A77DD050020DE78 /* ExampleAppApp.swift in Sources */, 229 | B4140C4F2AF4F6C80003C8D2 /* APIEndpoint.swift in Sources */, 230 | ); 231 | runOnlyForDeploymentPostprocessing = 0; 232 | }; 233 | C5C8B7912A77DD080020DE78 /* Sources */ = { 234 | isa = PBXSourcesBuildPhase; 235 | buildActionMask = 2147483647; 236 | files = ( 237 | B4140C502AF4F6CA0003C8D2 /* AgeValidator.swift in Sources */, 238 | B49691FB2AE7E3A600227B30 /* AgeVerificationTest.swift in Sources */, 239 | B4140C4E2AF4F6C60003C8D2 /* APIEndpoint.swift in Sources */, 240 | B456A9892AEF98A0003BCCCA /* APIEndpointsTests.swift in Sources */, 241 | ); 242 | runOnlyForDeploymentPostprocessing = 0; 243 | }; 244 | /* End PBXSourcesBuildPhase section */ 245 | 246 | /* Begin PBXTargetDependency section */ 247 | C5C8B7972A77DD080020DE78 /* PBXTargetDependency */ = { 248 | isa = PBXTargetDependency; 249 | target = C5C8B7842A77DD050020DE78 /* ExampleApp */; 250 | targetProxy = C5C8B7962A77DD080020DE78 /* PBXContainerItemProxy */; 251 | }; 252 | /* End PBXTargetDependency section */ 253 | 254 | /* Begin XCBuildConfiguration section */ 255 | C5C8B7A72A77DD080020DE78 /* Debug */ = { 256 | isa = XCBuildConfiguration; 257 | buildSettings = { 258 | ALWAYS_SEARCH_USER_PATHS = NO; 259 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 260 | CLANG_ANALYZER_NONNULL = YES; 261 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 262 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 263 | CLANG_ENABLE_MODULES = YES; 264 | CLANG_ENABLE_OBJC_ARC = YES; 265 | CLANG_ENABLE_OBJC_WEAK = YES; 266 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 267 | CLANG_WARN_BOOL_CONVERSION = YES; 268 | CLANG_WARN_COMMA = YES; 269 | CLANG_WARN_CONSTANT_CONVERSION = YES; 270 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 271 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 272 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 273 | CLANG_WARN_EMPTY_BODY = YES; 274 | CLANG_WARN_ENUM_CONVERSION = YES; 275 | CLANG_WARN_INFINITE_RECURSION = YES; 276 | CLANG_WARN_INT_CONVERSION = YES; 277 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 278 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 279 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 280 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 281 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 282 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 283 | CLANG_WARN_STRICT_PROTOTYPES = YES; 284 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 285 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 286 | CLANG_WARN_UNREACHABLE_CODE = YES; 287 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 288 | COPY_PHASE_STRIP = NO; 289 | DEBUG_INFORMATION_FORMAT = dwarf; 290 | ENABLE_STRICT_OBJC_MSGSEND = YES; 291 | ENABLE_TESTABILITY = YES; 292 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 293 | GCC_C_LANGUAGE_STANDARD = gnu17; 294 | GCC_DYNAMIC_NO_PIC = NO; 295 | GCC_NO_COMMON_BLOCKS = YES; 296 | GCC_OPTIMIZATION_LEVEL = 0; 297 | GCC_PREPROCESSOR_DEFINITIONS = ( 298 | "DEBUG=1", 299 | "$(inherited)", 300 | ); 301 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 302 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 303 | GCC_WARN_UNDECLARED_SELECTOR = YES; 304 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 305 | GCC_WARN_UNUSED_FUNCTION = YES; 306 | GCC_WARN_UNUSED_VARIABLE = YES; 307 | IPHONEOS_DEPLOYMENT_TARGET = 17.0; 308 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 309 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 310 | MTL_FAST_MATH = YES; 311 | ONLY_ACTIVE_ARCH = YES; 312 | SDKROOT = iphoneos; 313 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; 314 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 315 | }; 316 | name = Debug; 317 | }; 318 | C5C8B7A82A77DD080020DE78 /* Release */ = { 319 | isa = XCBuildConfiguration; 320 | buildSettings = { 321 | ALWAYS_SEARCH_USER_PATHS = NO; 322 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 323 | CLANG_ANALYZER_NONNULL = YES; 324 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 325 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 326 | CLANG_ENABLE_MODULES = YES; 327 | CLANG_ENABLE_OBJC_ARC = YES; 328 | CLANG_ENABLE_OBJC_WEAK = YES; 329 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 330 | CLANG_WARN_BOOL_CONVERSION = YES; 331 | CLANG_WARN_COMMA = YES; 332 | CLANG_WARN_CONSTANT_CONVERSION = YES; 333 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 334 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 335 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 336 | CLANG_WARN_EMPTY_BODY = YES; 337 | CLANG_WARN_ENUM_CONVERSION = YES; 338 | CLANG_WARN_INFINITE_RECURSION = YES; 339 | CLANG_WARN_INT_CONVERSION = YES; 340 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 341 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 342 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 343 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 344 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 345 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 346 | CLANG_WARN_STRICT_PROTOTYPES = YES; 347 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 348 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 349 | CLANG_WARN_UNREACHABLE_CODE = YES; 350 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 351 | COPY_PHASE_STRIP = NO; 352 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 353 | ENABLE_NS_ASSERTIONS = NO; 354 | ENABLE_STRICT_OBJC_MSGSEND = YES; 355 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 356 | GCC_C_LANGUAGE_STANDARD = gnu17; 357 | GCC_NO_COMMON_BLOCKS = YES; 358 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 359 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 360 | GCC_WARN_UNDECLARED_SELECTOR = YES; 361 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 362 | GCC_WARN_UNUSED_FUNCTION = YES; 363 | GCC_WARN_UNUSED_VARIABLE = YES; 364 | IPHONEOS_DEPLOYMENT_TARGET = 17.0; 365 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 366 | MTL_ENABLE_DEBUG_INFO = NO; 367 | MTL_FAST_MATH = YES; 368 | SDKROOT = iphoneos; 369 | SWIFT_COMPILATION_MODE = wholemodule; 370 | VALIDATE_PRODUCT = YES; 371 | }; 372 | name = Release; 373 | }; 374 | C5C8B7AA2A77DD080020DE78 /* Debug */ = { 375 | isa = XCBuildConfiguration; 376 | buildSettings = { 377 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 378 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 379 | CODE_SIGN_STYLE = Automatic; 380 | CURRENT_PROJECT_VERSION = 1; 381 | DEVELOPMENT_ASSET_PATHS = "\"ExampleApp/Preview Content\""; 382 | ENABLE_PREVIEWS = YES; 383 | GENERATE_INFOPLIST_FILE = YES; 384 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; 385 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 386 | INFOPLIST_KEY_UILaunchScreen_Generation = YES; 387 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 388 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 389 | LD_RUNPATH_SEARCH_PATHS = ( 390 | "$(inherited)", 391 | "@executable_path/Frameworks", 392 | ); 393 | MARKETING_VERSION = 1.0; 394 | PRODUCT_BUNDLE_IDENTIFIER = com.xebia.SwiftPactify.ExampleApp; 395 | PRODUCT_NAME = "$(TARGET_NAME)"; 396 | SWIFT_EMIT_LOC_STRINGS = YES; 397 | SWIFT_VERSION = 5.0; 398 | TARGETED_DEVICE_FAMILY = "1,2"; 399 | }; 400 | name = Debug; 401 | }; 402 | C5C8B7AB2A77DD080020DE78 /* Release */ = { 403 | isa = XCBuildConfiguration; 404 | buildSettings = { 405 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 406 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 407 | CODE_SIGN_STYLE = Automatic; 408 | CURRENT_PROJECT_VERSION = 1; 409 | DEVELOPMENT_ASSET_PATHS = "\"ExampleApp/Preview Content\""; 410 | ENABLE_PREVIEWS = YES; 411 | GENERATE_INFOPLIST_FILE = YES; 412 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; 413 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 414 | INFOPLIST_KEY_UILaunchScreen_Generation = YES; 415 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 416 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 417 | LD_RUNPATH_SEARCH_PATHS = ( 418 | "$(inherited)", 419 | "@executable_path/Frameworks", 420 | ); 421 | MARKETING_VERSION = 1.0; 422 | PRODUCT_BUNDLE_IDENTIFIER = com.xebia.SwiftPactify.ExampleApp; 423 | PRODUCT_NAME = "$(TARGET_NAME)"; 424 | SWIFT_EMIT_LOC_STRINGS = YES; 425 | SWIFT_VERSION = 5.0; 426 | TARGETED_DEVICE_FAMILY = "1,2"; 427 | }; 428 | name = Release; 429 | }; 430 | C5C8B7AD2A77DD080020DE78 /* Debug */ = { 431 | isa = XCBuildConfiguration; 432 | buildSettings = { 433 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 434 | CODE_SIGN_STYLE = Automatic; 435 | CURRENT_PROJECT_VERSION = 1; 436 | GENERATE_INFOPLIST_FILE = YES; 437 | IPHONEOS_DEPLOYMENT_TARGET = 17.0; 438 | MARKETING_VERSION = 1.0; 439 | PRODUCT_BUNDLE_IDENTIFIER = com.xebia.SwiftPactify.ExampleAppTests; 440 | PRODUCT_NAME = "$(TARGET_NAME)"; 441 | SWIFT_EMIT_LOC_STRINGS = NO; 442 | SWIFT_VERSION = 5.0; 443 | TARGETED_DEVICE_FAMILY = "1,2"; 444 | }; 445 | name = Debug; 446 | }; 447 | C5C8B7AE2A77DD080020DE78 /* Release */ = { 448 | isa = XCBuildConfiguration; 449 | buildSettings = { 450 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 451 | CODE_SIGN_STYLE = Automatic; 452 | CURRENT_PROJECT_VERSION = 1; 453 | GENERATE_INFOPLIST_FILE = YES; 454 | IPHONEOS_DEPLOYMENT_TARGET = 17.0; 455 | MARKETING_VERSION = 1.0; 456 | PRODUCT_BUNDLE_IDENTIFIER = com.xebia.SwiftPactify.ExampleAppTests; 457 | PRODUCT_NAME = "$(TARGET_NAME)"; 458 | SWIFT_EMIT_LOC_STRINGS = NO; 459 | SWIFT_VERSION = 5.0; 460 | TARGETED_DEVICE_FAMILY = "1,2"; 461 | }; 462 | name = Release; 463 | }; 464 | /* End XCBuildConfiguration section */ 465 | 466 | /* Begin XCConfigurationList section */ 467 | C5C8B7802A77DD050020DE78 /* Build configuration list for PBXProject "ExampleApp" */ = { 468 | isa = XCConfigurationList; 469 | buildConfigurations = ( 470 | C5C8B7A72A77DD080020DE78 /* Debug */, 471 | C5C8B7A82A77DD080020DE78 /* Release */, 472 | ); 473 | defaultConfigurationIsVisible = 0; 474 | defaultConfigurationName = Release; 475 | }; 476 | C5C8B7A92A77DD080020DE78 /* Build configuration list for PBXNativeTarget "ExampleApp" */ = { 477 | isa = XCConfigurationList; 478 | buildConfigurations = ( 479 | C5C8B7AA2A77DD080020DE78 /* Debug */, 480 | C5C8B7AB2A77DD080020DE78 /* Release */, 481 | ); 482 | defaultConfigurationIsVisible = 0; 483 | defaultConfigurationName = Release; 484 | }; 485 | C5C8B7AC2A77DD080020DE78 /* Build configuration list for PBXNativeTarget "ExampleAppTests" */ = { 486 | isa = XCConfigurationList; 487 | buildConfigurations = ( 488 | C5C8B7AD2A77DD080020DE78 /* Debug */, 489 | C5C8B7AE2A77DD080020DE78 /* Release */, 490 | ); 491 | defaultConfigurationIsVisible = 0; 492 | defaultConfigurationName = Release; 493 | }; 494 | /* End XCConfigurationList section */ 495 | 496 | /* Begin XCLocalSwiftPackageReference section */ 497 | C5DDC2192A7A7B4000E5DD15 /* XCLocalSwiftPackageReference ".." */ = { 498 | isa = XCLocalSwiftPackageReference; 499 | relativePath = ..; 500 | }; 501 | /* End XCLocalSwiftPackageReference section */ 502 | 503 | /* Begin XCSwiftPackageProductDependency section */ 504 | C5DDC21A2A7A7B4000E5DD15 /* XCTestParametrizedMacro */ = { 505 | isa = XCSwiftPackageProductDependency; 506 | productName = XCTestParametrizedMacro; 507 | }; 508 | /* End XCSwiftPackageProductDependency section */ 509 | }; 510 | rootObject = C5C8B77D2A77DD050020DE78 /* Project object */; 511 | } 512 | -------------------------------------------------------------------------------- /logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | 536 | 537 | 538 | 539 | 540 | 541 | 542 | 543 | 544 | 545 | 546 | 547 | 548 | 549 | 550 | 551 | 552 | 553 | 554 | 555 | 556 | 557 | 558 | 559 | 560 | 561 | 562 | 563 | 564 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 | 634 | 635 | 636 | 637 | 638 | 639 | 640 | 641 | 642 | 643 | 644 | 645 | 646 | 647 | 648 | 649 | 650 | 651 | 652 | 653 | 654 | 655 | 656 | 657 | 658 | 659 | 660 | 661 | 662 | 663 | 664 | 665 | 666 | 667 | 668 | 669 | 670 | 671 | 672 | 673 | 674 | 675 | 676 | 677 | 678 | 679 | 680 | 681 | 682 | 683 | 684 | 685 | 686 | 687 | 688 | 689 | 690 | 691 | 692 | 693 | 694 | 695 | 696 | 697 | 698 | 699 | 700 | 701 | 702 | 703 | 704 | 705 | 706 | 707 | 708 | 709 | 710 | 711 | 712 | 713 | 714 | 715 | 716 | 717 | 718 | 719 | 720 | 721 | 722 | 723 | 724 | 725 | 726 | 727 | 728 | 729 | 730 | 731 | 732 | 733 | 734 | 735 | 736 | 737 | 738 | 739 | 740 | 741 | 742 | 743 | 744 | 745 | 746 | 747 | 748 | 749 | 750 | 751 | 752 | 753 | 754 | 755 | 756 | 757 | 758 | 759 | 760 | 761 | 762 | 763 | 764 | 765 | 766 | 767 | 768 | 769 | 770 | 771 | 772 | 773 | 774 | 775 | 776 | 777 | 778 | 779 | 780 | 781 | 782 | 783 | 784 | 785 | 786 | 787 | 788 | 789 | 790 | 791 | 792 | 793 | 794 | 795 | 796 | 797 | 798 | 799 | 800 | 801 | 802 | 803 | 804 | 805 | 806 | 807 | 808 | 809 | 810 | 811 | 812 | 813 | 814 | 815 | 816 | 817 | 818 | 819 | 820 | 821 | 822 | 823 | 824 | 825 | 826 | 827 | 828 | 829 | 830 | 831 | 832 | 833 | 834 | 835 | 836 | 837 | 838 | 839 | 840 | 841 | 842 | 843 | 844 | 845 | 846 | 847 | 848 | 849 | 850 | 851 | 852 | 853 | 854 | 855 | 856 | 857 | 858 | 859 | 860 | 861 | 862 | 863 | 864 | 865 | 866 | 867 | 868 | 869 | 870 | 871 | 872 | 873 | 874 | 875 | 876 | 877 | 878 | 879 | 880 | 881 | 882 | 883 | 884 | 885 | 886 | 887 | 888 | 889 | 890 | 891 | 892 | 893 | 894 | 895 | 896 | 897 | 898 | 899 | 900 | 901 | 902 | 903 | 904 | 905 | 906 | 907 | 908 | 909 | 910 | 911 | 912 | 913 | 914 | 915 | 916 | 917 | 918 | 919 | 920 | 921 | 922 | 923 | 924 | 925 | 926 | 927 | 928 | 929 | 930 | 931 | 932 | 933 | 934 | 935 | 936 | 937 | 938 | 939 | 940 | 941 | 942 | 943 | 944 | 945 | 946 | 947 | 948 | 949 | 950 | 951 | 952 | 953 | 954 | 955 | 956 | 957 | 958 | 959 | 960 | 961 | 962 | 963 | 964 | 965 | 966 | 967 | 968 | 969 | 970 | 971 | 972 | 973 | 974 | 975 | 976 | 977 | 978 | 979 | 980 | 981 | 982 | 983 | 984 | 985 | 986 | 987 | 988 | 989 | 990 | 991 | 992 | 993 | 994 | 995 | 996 | 997 | 998 | 999 | 1000 | 1001 | 1002 | 1003 | 1004 | 1005 | 1006 | 1007 | 1008 | 1009 | 1010 | 1011 | 1012 | 1013 | 1014 | 1015 | 1016 | 1017 | 1018 | 1019 | 1020 | 1021 | 1022 | 1023 | 1024 | 1025 | 1026 | 1027 | 1028 | 1029 | 1030 | 1031 | 1032 | 1033 | 1034 | 1035 | 1036 | 1037 | 1038 | 1039 | 1040 | 1041 | 1042 | 1043 | 1044 | 1045 | 1046 | 1047 | 1048 | 1049 | 1050 | 1051 | 1052 | 1053 | 1054 | 1055 | 1056 | 1057 | 1058 | 1059 | 1060 | 1061 | 1062 | 1063 | 1064 | 1065 | 1066 | 1067 | 1068 | 1069 | 1070 | 1071 | 1072 | 1073 | 1074 | 1075 | 1076 | 1077 | 1078 | 1079 | 1080 | 1081 | 1082 | 1083 | 1084 | 1085 | 1086 | 1087 | 1088 | 1089 | 1090 | 1091 | 1092 | 1093 | 1094 | 1095 | 1096 | 1097 | 1098 | 1099 | 1100 | 1101 | 1102 | 1103 | 1104 | 1105 | 1106 | 1107 | 1108 | 1109 | 1110 | 1111 | 1112 | 1113 | 1114 | 1115 | 1116 | 1117 | 1118 | 1119 | 1120 | 1121 | 1122 | 1123 | 1124 | 1125 | 1126 | 1127 | 1128 | 1129 | 1130 | 1131 | 1132 | 1133 | 1134 | 1135 | 1136 | 1137 | 1138 | 1139 | 1140 | 1141 | 1142 | 1143 | 1144 | 1145 | 1146 | 1147 | 1148 | 1149 | 1150 | 1151 | 1152 | 1153 | 1154 | 1155 | 1156 | 1157 | 1158 | 1159 | 1160 | 1161 | 1162 | 1163 | 1164 | 1165 | 1166 | 1167 | 1168 | 1169 | 1170 | 1171 | 1172 | 1173 | 1174 | 1175 | 1176 | 1177 | 1178 | 1179 | 1180 | 1181 | 1182 | 1183 | 1184 | 1185 | 1186 | 1187 | 1188 | 1189 | 1190 | 1191 | 1192 | 1193 | 1194 | 1195 | 1196 | 1197 | 1198 | 1199 | 1200 | 1201 | 1202 | 1203 | 1204 | 1205 | 1206 | 1207 | 1208 | 1209 | 1210 | 1211 | 1212 | 1213 | 1214 | 1215 | 1216 | 1217 | 1218 | 1219 | 1220 | 1221 | 1222 | 1223 | 1224 | 1225 | 1226 | 1227 | 1228 | 1229 | 1230 | 1231 | 1232 | 1233 | 1234 | 1235 | 1236 | 1237 | 1238 | 1239 | 1240 | 1241 | 1242 | 1243 | 1244 | 1245 | 1246 | 1247 | 1248 | 1249 | 1250 | 1251 | 1252 | 1253 | 1254 | 1255 | 1256 | 1257 | 1258 | 1259 | 1260 | 1261 | 1262 | 1263 | 1264 | 1265 | 1266 | 1267 | 1268 | 1269 | 1270 | 1271 | 1272 | 1273 | 1274 | 1275 | 1276 | 1277 | 1278 | 1279 | 1280 | 1281 | 1282 | 1283 | 1284 | 1285 | 1286 | 1287 | 1288 | 1289 | 1290 | 1291 | 1292 | 1293 | 1294 | 1295 | 1296 | 1297 | 1298 | 1299 | 1300 | 1301 | 1302 | 1303 | 1304 | 1305 | 1306 | 1307 | 1308 | 1309 | 1310 | 1311 | 1312 | 1313 | 1314 | 1315 | 1316 | 1317 | 1318 | 1319 | 1320 | 1321 | 1322 | --------------------------------------------------------------------------------