├── .github └── workflows │ └── swift.yml ├── .gitignore ├── LICENSE ├── Package.resolved ├── Package.swift ├── README.md ├── Sources ├── Macros │ ├── AssociatedValues │ │ ├── AssociatedValuesMacro.swift │ │ └── AssociatedValuesMacroError.swift │ ├── Extensions │ │ └── TokenKind+keyword.swift │ ├── Plugins.swift │ ├── Singleton │ │ ├── SingletonMacro.swift │ │ └── SingletonMacroError.swift │ ├── Symbol │ │ ├── SymbolMacro.swift │ │ └── SymbolMacroError.swift │ └── URL │ │ ├── URLMacro.swift │ │ └── URLMacroError.swift └── SwiftMacros │ └── SwiftMacros.swift └── Tests └── MacroTests ├── AssociatedValuesMacroTests.swift ├── DefineMacros.swift ├── SingletonMacroTests.swift ├── SymbolMacroTests.swift └── URLMacroTests.swift /.github/workflows/swift.yml: -------------------------------------------------------------------------------- 1 | name: Build and Test 2 | on: 3 | push: 4 | branches: ["main"] 5 | pull_request: 6 | branches: ["main"] 7 | jobs: 8 | build: 9 | name: Build and test 10 | runs-on: macos-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | - name: Build 14 | run: swift build --build-tests -v 15 | - name: Run tests 16 | run: swift test -v 17 | 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | xcuserdata/ 4 | DerivedData/ 5 | .swiftpm -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Andrea Bernardini 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "pins" : [ 3 | { 4 | "identity" : "swift-syntax", 5 | "kind" : "remoteSourceControl", 6 | "location" : "https://github.com/swiftlang/swift-syntax.git", 7 | "state" : { 8 | "revision" : "0687f71944021d616d34d922343dcef086855920", 9 | "version" : "600.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: "SwiftMacros", 9 | platforms: [ 10 | .macOS(.v12) 11 | ], 12 | products: [ 13 | .library( 14 | name: "SwiftMacros", 15 | targets: [ 16 | "SwiftMacros" 17 | ] 18 | ) 19 | ], 20 | dependencies: [ 21 | .package(url: "https://github.com/swiftlang/swift-syntax.git", from: "600.0.0") 22 | ], 23 | targets: [ 24 | .macro( 25 | name: "Macros", 26 | dependencies: [ 27 | .product(name: "SwiftSyntaxMacros", package: "swift-syntax"), 28 | .product(name: "SwiftCompilerPlugin", package: "swift-syntax") 29 | ] 30 | ), 31 | .target( 32 | name: "SwiftMacros", 33 | dependencies: [ 34 | "Macros" 35 | ] 36 | ), 37 | .testTarget( 38 | name: "MacroTests", 39 | dependencies: [ 40 | "Macros", 41 | .product(name: "SwiftSyntaxMacrosTestSupport", package: "swift-syntax") 42 | ] 43 | ) 44 | ] 45 | ) 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Swift Macros 2 | Contains a collection of useful macros for my personal projects. 3 | 4 | ## Usage 5 | ### Symbol 6 | ```swift 7 | let symbol = #symbol("swift") // Macro expands to "swift" 8 | ``` 9 | > In case the provided value is not a valid SF Symbol, Xcode will show a compile error. 10 | 11 | ### URL 12 | ```swift 13 | let url = #URL("https://www.swift.org") // Macro expands to URL(string: "https://www.swift.org")! 14 | ``` 15 | > In case the provided value is not a valid URL, Xcode will show a compile error. 16 | 17 | ### AssociatedValues 18 | Add variables to retrieve the associated values. 19 | 20 | ```swift 21 | @AssociatedValues 22 | enum Barcode { 23 | case upc(Int, Int, Int, Int) 24 | case qrCode(String) 25 | } 26 | 27 | // Expands to 28 | enum Barcode { 29 | ... 30 | 31 | var upcValue: (Int, Int, Int, Int)? { 32 | if case let .upc(v0, v1, v2, v3) = self { 33 | return (v0, v1, v2, v3) 34 | } 35 | return nil 36 | } 37 | 38 | var qrCodeValue: (String)? { 39 | if case let .qrCode(v0) = self { 40 | return v0 41 | } 42 | return nil 43 | } 44 | } 45 | ``` 46 | 47 | ### Singleton 48 | Generate singleton code for struct and class 49 | 50 | ```swift 51 | @Singleton 52 | struct UserStore { 53 | } 54 | 55 | // Expands to 56 | struct UserStore { 57 | static let shared = UserStore() 58 | 59 | private init() { 60 | } 61 | } 62 | ``` 63 | -------------------------------------------------------------------------------- /Sources/Macros/AssociatedValues/AssociatedValuesMacro.swift: -------------------------------------------------------------------------------- 1 | import SwiftSyntax 2 | import SwiftSyntaxBuilder 3 | import SwiftSyntaxMacros 4 | 5 | struct AssociatedValuesMacro: MemberMacro { 6 | static func expansion( 7 | of node: AttributeSyntax, 8 | providingMembersOf declaration: some DeclGroupSyntax, 9 | in context: some MacroExpansionContext 10 | ) throws -> [DeclSyntax] { 11 | guard let enumDecl = declaration.as(EnumDeclSyntax.self) else { 12 | throw AssociatedValuesMacroError.notAEnum 13 | } 14 | 15 | return try enumDecl.memberBlock.members.compactMap { 16 | $0.decl.as(EnumCaseDeclSyntax.self)? 17 | .elements 18 | .compactMap { $0 } 19 | } 20 | .reduce([], +) 21 | .compactMap { element -> DeclSyntax? in 22 | guard let associatedValue = element.parameterClause else { 23 | return nil 24 | } 25 | 26 | let variableNames = associatedValue 27 | .parameters 28 | .enumerated() 29 | .map { index, _ in 30 | "v\(index)" 31 | } 32 | .joined(separator: ", ") 33 | 34 | return DeclSyntax( 35 | try VariableDeclSyntax("\(declaration.modifiers)var \(element.name)Value: \(raw: associatedValue)?") { 36 | try IfExprSyntax("if case let .\(element.name)(\(raw: variableNames)) = self") { 37 | if associatedValue.parameters.count == 1 { 38 | "return \(raw: variableNames)" 39 | } else { 40 | "return (\(raw: variableNames))" 41 | } 42 | } 43 | "return nil" 44 | } 45 | ) 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Sources/Macros/AssociatedValues/AssociatedValuesMacroError.swift: -------------------------------------------------------------------------------- 1 | enum AssociatedValuesMacroError: Error, CustomStringConvertible { 2 | case notAEnum 3 | 4 | var description: String { 5 | switch self { 6 | case .notAEnum: 7 | "Can only be applied to enum" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Sources/Macros/Extensions/TokenKind+keyword.swift: -------------------------------------------------------------------------------- 1 | import SwiftSyntax 2 | 3 | extension TokenKind { 4 | var keyword: Keyword? { 5 | switch self { 6 | case let .keyword(keyword): 7 | return keyword 8 | default: 9 | return nil 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Sources/Macros/Plugins.swift: -------------------------------------------------------------------------------- 1 | import SwiftCompilerPlugin 2 | import SwiftSyntaxMacros 3 | 4 | @main 5 | struct Plugins: CompilerPlugin { 6 | let providingMacros: [Macro.Type] = [ 7 | URLMacro.self, 8 | SymbolMacro.self, 9 | AssociatedValuesMacro.self, 10 | SingletonMacro.self 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /Sources/Macros/Singleton/SingletonMacro.swift: -------------------------------------------------------------------------------- 1 | import SwiftSyntax 2 | import SwiftSyntaxBuilder 3 | import SwiftSyntaxMacros 4 | 5 | struct SingletonMacro: MemberMacro { 6 | static func expansion( 7 | of node: AttributeSyntax, 8 | providingMembersOf declaration: some DeclGroupSyntax, 9 | in context: some MacroExpansionContext 10 | ) throws -> [DeclSyntax] { 11 | guard [SwiftSyntax.SyntaxKind.classDecl, .structDecl].contains(declaration.kind) else { 12 | throw SingletonMacroError.notAStructOrClass 13 | } 14 | 15 | let nameOfDecl = try name(providingMembersOf: declaration).text 16 | let isPublic = declaration.modifiers.map(\.name.tokenKind.keyword).contains(.public) 17 | let initializer = try InitializerDeclSyntax("private init()") {} 18 | 19 | let shared = "\(isPublic ? "public" : "") static let shared = \(nameOfDecl)()" 20 | 21 | return [ 22 | DeclSyntax(stringLiteral: shared), 23 | DeclSyntax(initializer), 24 | ] 25 | } 26 | } 27 | 28 | private extension SingletonMacro { 29 | static func name(providingMembersOf declaration: some DeclGroupSyntax) throws -> TokenSyntax { 30 | if let classDecl = declaration.as(ClassDeclSyntax.self) { 31 | classDecl.name 32 | } else if let structDecl = declaration.as(StructDeclSyntax.self) { 33 | structDecl.name 34 | } else { 35 | throw SingletonMacroError.notAStructOrClass 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Sources/Macros/Singleton/SingletonMacroError.swift: -------------------------------------------------------------------------------- 1 | enum SingletonMacroError: Error, CustomStringConvertible { 2 | case notAStructOrClass 3 | 4 | var description: String { 5 | switch self { 6 | case .notAStructOrClass: 7 | "Can only be applied to a struct or class" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Sources/Macros/Symbol/SymbolMacro.swift: -------------------------------------------------------------------------------- 1 | import SwiftSyntaxMacros 2 | import SwiftSyntax 3 | import SwiftUI 4 | 5 | struct SymbolMacro: ExpressionMacro { 6 | static func expansion( 7 | of node: some FreestandingMacroExpansionSyntax, 8 | in context: some MacroExpansionContext 9 | ) throws -> ExprSyntax { 10 | guard 11 | let argument = node.arguments.first?.expression, 12 | let segments = argument.as(StringLiteralExprSyntax.self)?.segments, 13 | segments.count == 1, 14 | case .stringSegment(let literalSegment)? = segments.first 15 | else { 16 | throw SymbolMacroError.parseName 17 | } 18 | 19 | try verifySymbol(name: literalSegment.content.text) 20 | return "\"\(raw: literalSegment.content.text)\"" 21 | } 22 | 23 | private static func verifySymbol(name: String) throws { 24 | #if canImport(UIKit) 25 | if let _ = UIImage(systemName: name) { return } 26 | #else 27 | if let _ = NSImage(systemSymbolName: name, accessibilityDescription: nil) { return } 28 | #endif 29 | throw SymbolMacroError.invalidSymbol(name: name) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Sources/Macros/Symbol/SymbolMacroError.swift: -------------------------------------------------------------------------------- 1 | enum SymbolMacroError: Error, CustomStringConvertible { 2 | case invalidSymbol(name: String) 3 | case parseName 4 | 5 | var description: String { 6 | switch self { 7 | case .parseName: 8 | "Cannot parse SF Symbol name" 9 | case .invalidSymbol(let name): 10 | "\"\(name)\" is not a valid symbol" 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Sources/Macros/URL/URLMacro.swift: -------------------------------------------------------------------------------- 1 | import SwiftSyntaxMacros 2 | import SwiftSyntax 3 | import Foundation 4 | 5 | struct URLMacro: ExpressionMacro { 6 | static func expansion( 7 | of node: some FreestandingMacroExpansionSyntax, 8 | in context: some MacroExpansionContext 9 | ) throws -> ExprSyntax { 10 | guard 11 | let argument = node.arguments.first?.expression, 12 | let segments = argument.as(StringLiteralExprSyntax.self)?.segments, 13 | segments.count == 1, 14 | case .stringSegment(let literalSegment)? = segments.first 15 | else { 16 | throw URLMacroError.requiresStaticStringLiteral 17 | } 18 | 19 | guard let _ = URL(string: literalSegment.content.text) else { 20 | throw URLMacroError.malformedURL(urlString: "\(argument)") 21 | } 22 | 23 | return "URL(string: \(argument))!" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Sources/Macros/URL/URLMacroError.swift: -------------------------------------------------------------------------------- 1 | enum URLMacroError: Error, CustomStringConvertible { 2 | case requiresStaticStringLiteral 3 | case malformedURL(urlString: String) 4 | 5 | var description: String { 6 | switch self { 7 | case .requiresStaticStringLiteral: 8 | "#URL requires a static string literal" 9 | case .malformedURL(let urlString): 10 | "The input URL is malformed: \(urlString)" 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Sources/SwiftMacros/SwiftMacros.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | @freestanding(expression) 4 | public macro URL(_ string: String) -> URL = #externalMacro(module: "Macros", type: "URLMacro") 5 | 6 | @freestanding(expression) 7 | public macro symbol(_ name: String) -> String = #externalMacro(module: "Macros", type: "SymbolMacro") 8 | 9 | @attached(member, names: arbitrary) 10 | public macro AssociatedValues() = #externalMacro(module: "Macros", type: "AssociatedValuesMacro") 11 | 12 | @attached(member, names: named(init), named(shared)) 13 | public macro Singleton() = #externalMacro(module: "Macros", type: "SingletonMacro") 14 | -------------------------------------------------------------------------------- /Tests/MacroTests/AssociatedValuesMacroTests.swift: -------------------------------------------------------------------------------- 1 | import SwiftSyntaxMacros 2 | import SwiftSyntaxMacrosTestSupport 3 | import XCTest 4 | 5 | final class AssociatedValuesMacroTests: XCTestCase { 6 | func testGenerateVarForAssociatedValues() throws { 7 | #if canImport(Macros) 8 | assertMacroExpansion( 9 | """ 10 | @AssociatedValues 11 | enum SomeEnum { 12 | case none 13 | case labeledValue(a: String, b: String) 14 | case optional(String?) 15 | case value(Int) 16 | case closure(() -> Void) 17 | indirect case someEnum(SomeEnum) 18 | } 19 | """, 20 | expandedSource: 21 | """ 22 | enum SomeEnum { 23 | case none 24 | case labeledValue(a: String, b: String) 25 | case optional(String?) 26 | case value(Int) 27 | case closure(() -> Void) 28 | indirect case someEnum(SomeEnum) 29 | 30 | var labeledValueValue: (a: String, b: String)? { 31 | if case let .labeledValue(v0, v1) = self { 32 | return (v0, v1) 33 | } 34 | return nil 35 | } 36 | 37 | var optionalValue: (String?)? { 38 | if case let .optional(v0) = self { 39 | return v0 40 | } 41 | return nil 42 | } 43 | 44 | var valueValue: (Int)? { 45 | if case let .value(v0) = self { 46 | return v0 47 | } 48 | return nil 49 | } 50 | 51 | var closureValue: (() -> Void)? { 52 | if case let .closure(v0) = self { 53 | return v0 54 | } 55 | return nil 56 | } 57 | 58 | var someEnumValue: (SomeEnum)? { 59 | if case let .someEnum(v0) = self { 60 | return v0 61 | } 62 | return nil 63 | } 64 | } 65 | """, 66 | macros: testMacros, 67 | indentationWidth: .spaces(2) 68 | ) 69 | #else 70 | throw XCTSkip("macros are only supported when running tests for the host platform") 71 | #endif 72 | } 73 | 74 | func testMacroIsOnlySupportEnum() throws { 75 | #if canImport(Macros) 76 | assertMacroExpansion( 77 | #""" 78 | @AssociatedValues 79 | struct SomeStructure { 80 | 81 | } 82 | """#, 83 | expandedSource: 84 | #""" 85 | struct SomeStructure { 86 | 87 | } 88 | """#, 89 | diagnostics: [ 90 | DiagnosticSpec(message: #"Can only be applied to enum"#, line: 1, column: 1) 91 | ], 92 | macros: testMacros 93 | ) 94 | #else 95 | throw XCTSkip("macros are only supported when running tests for the host platform") 96 | #endif 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Tests/MacroTests/DefineMacros.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import SwiftSyntaxMacros 3 | 4 | #if canImport(Macros) 5 | @testable import Macros 6 | 7 | let testMacros: [String: Macro.Type] = [ 8 | "symbol": SymbolMacro.self, 9 | "URL": URLMacro.self, 10 | "AssociatedValues": AssociatedValuesMacro.self, 11 | "Singleton": SingletonMacro.self 12 | ] 13 | #endif 14 | -------------------------------------------------------------------------------- /Tests/MacroTests/SingletonMacroTests.swift: -------------------------------------------------------------------------------- 1 | import SwiftSyntaxMacros 2 | import SwiftSyntaxMacrosTestSupport 3 | import XCTest 4 | 5 | final class SingletonMacroTests: XCTestCase { 6 | func testGenerateAPublicSignletonProperty() throws { 7 | #if canImport(Macros) 8 | assertMacroExpansion( 9 | """ 10 | @Singleton 11 | public struct UserStore { 12 | } 13 | """, 14 | expandedSource: 15 | """ 16 | public struct UserStore { 17 | 18 | public static let shared = UserStore() 19 | 20 | private init() { 21 | } 22 | } 23 | """, 24 | macros: testMacros, 25 | indentationWidth: .spaces(2) 26 | ) 27 | #else 28 | throw XCTSkip("macros are only supported when running tests for the host platform") 29 | #endif 30 | } 31 | 32 | func testGenerateASignletonProperty() throws { 33 | #if canImport(Macros) 34 | assertMacroExpansion( 35 | """ 36 | @Singleton 37 | class UserStore { 38 | } 39 | """, 40 | expandedSource: 41 | """ 42 | class UserStore { 43 | 44 | static let shared = UserStore() 45 | 46 | private init() { 47 | } 48 | } 49 | """, 50 | macros: testMacros, 51 | indentationWidth: .spaces(2) 52 | ) 53 | #else 54 | throw XCTSkip("macros are only supported when running tests for the host platform") 55 | #endif 56 | } 57 | 58 | func testMacroIsOnlySupportClassOrStruct() throws { 59 | #if canImport(Macros) 60 | assertMacroExpansion( 61 | """ 62 | @Singleton 63 | enum UserStore { 64 | } 65 | """, 66 | expandedSource: 67 | """ 68 | enum UserStore { 69 | } 70 | """, 71 | diagnostics: [ 72 | DiagnosticSpec(message: "Can only be applied to a struct or class", line: 1, 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/MacroTests/SymbolMacroTests.swift: -------------------------------------------------------------------------------- 1 | import SwiftSyntaxMacros 2 | import SwiftSyntaxMacrosTestSupport 3 | import XCTest 4 | 5 | final class SymbolMacroTests: XCTestCase { 6 | func testValidSymbol() throws { 7 | #if canImport(Macros) 8 | assertMacroExpansion( 9 | """ 10 | #symbol("swift") 11 | """, 12 | expandedSource: 13 | """ 14 | "swift" 15 | """, 16 | macros: testMacros 17 | ) 18 | #else 19 | throw XCTSkip("macros are only supported when running tests for the host platform") 20 | #endif 21 | } 22 | 23 | func testInvalidSymbol() throws { 24 | #if canImport(Macros) 25 | assertMacroExpansion( 26 | """ 27 | #symbol("test") 28 | """, 29 | expandedSource: 30 | """ 31 | #symbol("test") 32 | """, 33 | diagnostics: [ 34 | DiagnosticSpec(message: #""test" is not a valid symbol"#, line: 1, column: 1) 35 | ], 36 | macros: testMacros 37 | ) 38 | #else 39 | throw XCTSkip("macros are only supported when running tests for the host platform") 40 | #endif 41 | } 42 | 43 | func testParseNameError() throws { 44 | #if canImport(Macros) 45 | assertMacroExpansion( 46 | #""" 47 | #symbol("\("swift")") 48 | """#, 49 | expandedSource: 50 | #""" 51 | #symbol("\("swift")") 52 | """#, 53 | diagnostics: [ 54 | DiagnosticSpec(message: #"Cannot parse SF Symbol name"#, line: 1, column: 1) 55 | ], 56 | macros: testMacros 57 | ) 58 | #else 59 | throw XCTSkip("macros are only supported when running tests for the host platform") 60 | #endif 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Tests/MacroTests/URLMacroTests.swift: -------------------------------------------------------------------------------- 1 | import SwiftSyntaxMacros 2 | import SwiftSyntaxMacrosTestSupport 3 | import XCTest 4 | 5 | final class URLMacroTests: XCTestCase { 6 | func testValidURL() throws { 7 | #if canImport(Macros) 8 | assertMacroExpansion( 9 | """ 10 | #URL("https://www.apple.com") 11 | """, 12 | expandedSource: 13 | """ 14 | URL(string: "https://www.apple.com")! 15 | """, 16 | macros: testMacros 17 | ) 18 | #else 19 | throw XCTSkip("macros are only supported when running tests for the host platform") 20 | #endif 21 | } 22 | 23 | func testURLStringLiteralError() throws { 24 | #if canImport(Macros) 25 | assertMacroExpansion( 26 | #""" 27 | #URL("https://www.apple.com\(Int.random())") 28 | """#, 29 | expandedSource: 30 | #""" 31 | #URL("https://www.apple.com\(Int.random())") 32 | """#, 33 | diagnostics: [ 34 | DiagnosticSpec(message: "#URL requires a static string literal", line: 1, column: 1) 35 | ], 36 | macros: testMacros 37 | ) 38 | #else 39 | throw XCTSkip("macros are only supported when running tests for the host platform") 40 | #endif 41 | } 42 | 43 | func testMalformedURLError() throws { 44 | #if canImport(Macros) 45 | assertMacroExpansion( 46 | """ 47 | #URL("https://www. apple.com") 48 | """, 49 | expandedSource: 50 | """ 51 | #URL("https://www. apple.com") 52 | """, 53 | diagnostics: [ 54 | DiagnosticSpec(message: #"The input URL is malformed: "https://www. apple.com""#, line: 1, column: 1) 55 | ], 56 | macros: testMacros 57 | ) 58 | #else 59 | throw XCTSkip("macros are only supported when running tests for the host platform") 60 | #endif 61 | } 62 | } 63 | --------------------------------------------------------------------------------