├── .gitignore ├── LICENSE ├── Package.resolved ├── Package.swift ├── README.md ├── Sources ├── SwiftUIMacros │ └── SwiftUIMacros.swift └── SwiftUIMacrosImpl │ ├── EnvironmentStorage.swift │ ├── EnvironmentValue.swift │ ├── Feedback.swift │ ├── FocusedValue.swift │ └── SwiftUIMacros.swift └── Tests └── MyMacroTests └── MyMacroTests.swift /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | xcuserdata/ 5 | DerivedData/ 6 | .swiftpm/configuration/registries.json 7 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata 8 | .netrc 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Wouter Hennen 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/apple/swift-syntax", 7 | "state" : { 8 | "revision" : "fa8f95c2d536d6620cc2f504ebe8a6167c9fc2dd", 9 | "version" : "510.0.1" 10 | } 11 | } 12 | ], 13 | "version" : 2 14 | } 15 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 5.9 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | import CompilerPluginSupport 6 | 7 | let package = Package( 8 | name: "SwiftUIMacros", 9 | platforms: [.macOS(.v10_15), .iOS(.v13), .tvOS(.v13), .watchOS(.v6), .macCatalyst(.v13)], 10 | products: [ 11 | .library( 12 | name: "SwiftUIMacros", 13 | targets: ["SwiftUIMacros"] 14 | ), 15 | ], 16 | dependencies: [ 17 | .package(url: "https://github.com/apple/swift-syntax", from: "510.0.0") 18 | ], 19 | targets: [ 20 | .macro( 21 | name: "SwiftUIMacrosImpl", 22 | dependencies: [ 23 | .product(name: "SwiftSyntaxMacros", package: "swift-syntax"), 24 | .product(name: "SwiftCompilerPlugin", package: "swift-syntax") 25 | ] 26 | ), 27 | 28 | .target(name: "SwiftUIMacros", dependencies: ["SwiftUIMacrosImpl"]), 29 | 30 | .testTarget( 31 | name: "SwiftUIMacrosTests", 32 | dependencies: [ 33 | "SwiftUIMacrosImpl", 34 | .product(name: "SwiftSyntaxMacrosTestSupport", package: "swift-syntax"), 35 | ] 36 | ), 37 | ] 38 | ) 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SwiftUI-Macros 2 | 3 | A library of useful macros for SwiftUI. 4 | 5 | Swift 5.9 or later is required to use this package. 6 | 7 | ## Installation 8 | #### Swift Package Manager 9 | 10 | Add the dependency: 11 | ```swift 12 | dependencies: [ 13 | .package(url: "https://github.com/Wouter01/SwiftUI-Macros.git", from: "1.0.0") 14 | ] 15 | ``` 16 | 17 | Add the dependency to your target: 18 | ```swift 19 | .target( 20 | name: "MyTarget", 21 | dependencies: [.product(name: "SwiftUIMacros", package: "SwiftUI-Macros")] 22 | ) 23 | ``` 24 | 25 | 26 | ## Environment 27 | 28 | To allow easier creation of new environment keys, two macros are available: 29 | #### EnvironmentKey 30 | Apply this macro to a variable inside an `EnvironmentValues` extension to add it to the environment. 31 | The assigned value is the default value and is required, unless the type is Optional. 32 | The type can be inferred by its value, just like in normal Swift code. 33 | ```swift 34 | import SwiftUIMacros 35 | 36 | extension EnvironmentValues { 37 | @EnvironmentKey 38 | var alignment: Alignment = .center 39 | } 40 | 41 | // Expands to 42 | extension EnvironmentValues { 43 | var alignment: Alignment { 44 | get { 45 | self[EnvironmentKey_alignment.self] 46 | } 47 | set { 48 | self[EnvironmentKey_alignment.self] = newValue 49 | } 50 | } 51 | 52 | private struct EnvironmentKey_alignment: EnvironmentKey { 53 | static let defaultValue: Alignment = .center 54 | } 55 | } 56 | ``` 57 | 58 | #### EnvironmentValues 59 | Apply this to an `EnvironmentValues` extension to add the `EnviromentKey` macro to each variable inside the extension. 60 | 61 | ```swift 62 | import SwiftUIMacros 63 | 64 | @EnvironmentValues 65 | extension EnvironmentValues { 66 | var alignment: Alignment = .center 67 | 68 | var secondaryFont: Font? 69 | 70 | var gridLines = 0 71 | } 72 | 73 | // Expands to 74 | extension EnvironmentValues { 75 | @EnvironmentKey 76 | var alignment: Alignment = .center 77 | 78 | @EnvironmentKey 79 | var secondaryFont: Font? 80 | 81 | @EnvironmentKey 82 | var gridLines = 0 83 | } 84 | ``` 85 | 86 | ## Focus 87 | 88 | To allow easier creation of new focusedValue keys, two macros are available: 89 | 90 | #### FocusedValueKey 91 | Apply this macro to a variable inside a `FocusedValues` extension to add it to the focused values. 92 | ```swift 93 | import SwiftUIMacros 94 | 95 | extension FocusedValues { 96 | @FocusedValueKey 97 | var enabled: Bool? 98 | } 99 | 100 | // Expands to 101 | extension FocusedValues { 102 | @FocusedValueKey 103 | var enabled: Bool? { 104 | get { 105 | self [FocusedValueKey_enabled.self] 106 | } 107 | set { 108 | self [FocusedValueKey_enabled.self] = newValue 109 | } 110 | } 111 | 112 | private struct FocusedValueKey_enabled: FocusedValueKey { 113 | typealias Value = Bool 114 | } 115 | } 116 | ``` 117 | 118 | #### FocusedValues 119 | Apply this to an `FocusedValues` extension to add the `FocusedValueKey` macro to each variable inside the extension. 120 | 121 | ```swift 122 | import SwiftUIMacros 123 | 124 | @FocusedValues 125 | extension FocusedValues { 126 | var showCompletions: Binding? 127 | 128 | var value: Int? 129 | } 130 | 131 | // Expands to 132 | extension FocusedValues { 133 | @FocusedValueKey 134 | var showCompletions: Binding? 135 | 136 | @FocusedValueKey 137 | var value: Int? 138 | } 139 | ``` 140 | -------------------------------------------------------------------------------- /Sources/SwiftUIMacros/SwiftUIMacros.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftUIMacros.swift 3 | // SwiftUIMacros 4 | // 5 | // Created by Wouter Hennen on 12/06/2023. 6 | // 7 | 8 | /// Creates an unique EnvironmentKey for the variable and adds getters and setters. 9 | /// The initial value of the variable becomes the default value of the EnvironmentKey. 10 | @available(*, deprecated, renamed: "EnvironmentKey", message: "EnvironmentValue has been renamed to EnvironmentKey") 11 | @attached(peer, names: prefixed(EnvironmentKey_)) 12 | @attached(accessor, names: named(get), named(set)) 13 | public macro EnvironmentValue() = #externalMacro(module: "SwiftUIMacrosImpl", type: "AttachedMacroEnvironmentKey") 14 | 15 | /// Creates an unique EnvironmentKey for the variable and adds getters and setters. 16 | /// The initial value of the variable becomes the default value of the EnvironmentKey. 17 | @attached(peer, names: prefixed(EnvironmentKey_)) 18 | @attached(accessor, names: named(get), named(set)) 19 | public macro EnvironmentKey() = #externalMacro(module: "SwiftUIMacrosImpl", type: "AttachedMacroEnvironmentKey") 20 | 21 | /// Applies the @EnvironmentValue macro to each child in the scope. 22 | /// This should only be applied on an EnvironmentValues extension. 23 | @available(*, deprecated, renamed: "EnvironmentValues", message: "EnvironmentStorage has been renamed to EnvironmentValues") 24 | @attached(memberAttribute) 25 | public macro EnvironmentStorage() = #externalMacro(module: "SwiftUIMacrosImpl", type: "EnvironmentStorage") 26 | 27 | /// Applies the @EnvironmentValue macro to each child in the scope. 28 | /// This should only be applied on an EnvironmentValues extension. 29 | @attached(memberAttribute) 30 | public macro EnvironmentValues() = #externalMacro(module: "SwiftUIMacrosImpl", type: "EnvironmentStorage") 31 | 32 | /// Applies the @FocusedValue macro to each child in the scope. 33 | /// This should only be applied on an FocusedValues extension. 34 | @attached(memberAttribute) 35 | public macro FocusedValues() = #externalMacro(module: "SwiftUIMacrosImpl", type: "EnvironmentStorage") 36 | 37 | /// Creates an unique FocusedValueKey for the variable and adds getters and setters. 38 | @attached(peer, names: prefixed(FocusedValueKey_)) 39 | @attached(accessor, names: named(get), named(set)) 40 | public macro FocusedValueKey() = #externalMacro(module: "SwiftUIMacrosImpl", type: "AttachedMacroFocusedValueKey") 41 | -------------------------------------------------------------------------------- /Sources/SwiftUIMacrosImpl/EnvironmentStorage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EnvironmentStorage.swift 3 | // 4 | // 5 | // Created by Wouter Hennen on 14/06/2023. 6 | // 7 | 8 | import SwiftSyntax 9 | import SwiftSyntaxMacros 10 | import SwiftDiagnostics 11 | 12 | public struct EnvironmentStorage: MemberAttributeMacro { 13 | 14 | enum Kind: String { 15 | case environmentValues = "EnvironmentValues" 16 | case focusedValues = "FocusedValues" 17 | case environmentStorage = "EnvironmentStorage" 18 | 19 | init?(node: TokenKind) { 20 | if case .identifier(let name) = node { 21 | self.init(rawValue: name) 22 | } else { 23 | return nil 24 | } 25 | } 26 | 27 | var matchedType: String { 28 | if self == .environmentStorage { 29 | return Self.environmentValues.rawValue 30 | } 31 | 32 | return rawValue 33 | } 34 | 35 | var childMacro: String { 36 | switch self { 37 | case .environmentValues, .environmentStorage: 38 | "EnvironmentKey" 39 | case .focusedValues: 40 | "FocusedValueKey" 41 | } 42 | } 43 | } 44 | 45 | public static func expansion( 46 | of node: AttributeSyntax, 47 | attachedTo declaration: some DeclGroupSyntax, 48 | providingAttributesFor member: some DeclSyntaxProtocol, 49 | in context: some MacroExpansionContext 50 | ) throws -> [AttributeSyntax] { 51 | 52 | let macroName = node.attributeName.as(IdentifierTypeSyntax.self)?.name.tokenKind 53 | guard let macroName, let kind = Kind(node: macroName) else { return [] } 54 | 55 | let attachedName = declaration.as(ExtensionDeclSyntax.self)?.extendedType.as(IdentifierTypeSyntax.self)?.name.tokenKind 56 | 57 | // Check if macro is attached to extension type 58 | guard case .identifier(let attachedName) = attachedName else { 59 | let feedback = Feedback.notAttachedToExtension 60 | context.diagnose(Diagnostic(node: Syntax(node), message: feedback)) 61 | return [] 62 | } 63 | 64 | // Check if extension type is correct for the applied macro 65 | guard attachedName == kind.matchedType else { 66 | let feedback = Feedback.attachedToWrongType(macro: kind.rawValue, wrongType: attachedName, correctType: kind.matchedType) 67 | context.diagnose(Diagnostic(node: Syntax(node), message: feedback)) 68 | return [] 69 | } 70 | 71 | // Only attach macro if member is a variable. 72 | // Otherwise, it will also get attached to the structs generated by @EnvironmentValue 73 | guard member.is(VariableDeclSyntax.self) else { 74 | return [] 75 | } 76 | 77 | return [ 78 | AttributeSyntax( 79 | atSign: .atSignToken(), 80 | attributeName: IdentifierTypeSyntax(name: .identifier(kind.childMacro)) 81 | ) 82 | ] 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /Sources/SwiftUIMacrosImpl/EnvironmentValue.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EnvironmentValue.swift 3 | // 4 | // 5 | // Created by Wouter Hennen on 14/06/2023. 6 | // 7 | 8 | import SwiftSyntax 9 | import SwiftSyntaxMacros 10 | import SwiftDiagnostics 11 | 12 | public struct AttachedMacroEnvironmentKey: PeerMacro { 13 | public static func expansion( 14 | of node: AttributeSyntax, 15 | providingPeersOf declaration: some DeclSyntaxProtocol, 16 | in context: some MacroExpansionContext 17 | ) throws -> [DeclSyntax] { 18 | 19 | // Skip declarations other than variables 20 | guard let varDecl = declaration.as(VariableDeclSyntax.self) else { 21 | return [] 22 | } 23 | 24 | guard var binding = varDecl.bindings.first else { 25 | context.diagnose(Diagnostic(node: Syntax(node), message: Feedback.missingAnnotation)) 26 | return [] 27 | } 28 | 29 | guard let identifier = binding.pattern.as(IdentifierPatternSyntax.self)?.identifier else { 30 | context.diagnose(Diagnostic(node: Syntax(node), message: Feedback.notAnIdentifier)) 31 | return [] 32 | } 33 | 34 | binding.pattern = PatternSyntax(IdentifierPatternSyntax(identifier: .identifier("defaultValue"))) 35 | 36 | let isOptionalType = binding.typeAnnotation?.type.is(OptionalTypeSyntax.self) ?? false 37 | let hasDefaultValue = binding.initializer != nil 38 | 39 | guard isOptionalType || hasDefaultValue else { 40 | context.diagnose(Diagnostic(node: Syntax(node), message: Feedback.noDefaultArgument)) 41 | return [] 42 | } 43 | 44 | return [ 45 | """ 46 | private struct EnvironmentKey_\(identifier): EnvironmentKey { 47 | static let \(binding) \(raw: isOptionalType && !hasDefaultValue ? "= nil" : "") 48 | } 49 | """ 50 | ] 51 | } 52 | } 53 | 54 | extension AttachedMacroEnvironmentKey: AccessorMacro { 55 | public static func expansion( 56 | of node: AttributeSyntax, 57 | providingAccessorsOf declaration: some DeclSyntaxProtocol, 58 | in context: some MacroExpansionContext 59 | ) throws -> [AccessorDeclSyntax] { 60 | 61 | // Skip declarations other than variables 62 | guard let varDecl = declaration.as(VariableDeclSyntax.self) else { 63 | return [] 64 | } 65 | 66 | guard let binding = varDecl.bindings.first else { 67 | context.diagnose(Diagnostic(node: Syntax(node), message: Feedback.missingAnnotation)) 68 | return [] 69 | } 70 | 71 | guard let identifier = binding.pattern.as(IdentifierPatternSyntax.self)?.identifier else { 72 | context.diagnose(Diagnostic(node: Syntax(node), message: Feedback.notAnIdentifier)) 73 | return [] 74 | } 75 | 76 | return [ 77 | """ 78 | get { 79 | self[EnvironmentKey_\(identifier).self] 80 | } 81 | """, 82 | """ 83 | set { 84 | self[EnvironmentKey_\(identifier).self] = newValue 85 | } 86 | """ 87 | ] 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /Sources/SwiftUIMacrosImpl/Feedback.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Diagnostic.swift 3 | // 4 | // 5 | // Created by Wouter Hennen on 14/06/2023. 6 | // 7 | 8 | import SwiftSyntax 9 | import SwiftDiagnostics 10 | 11 | enum Feedback: Identifiable, DiagnosticMessage { 12 | case noDefaultArgument 13 | case missingAnnotation 14 | case notAnIdentifier 15 | case attachedToWrongType(macro: String, wrongType: String, correctType: String) 16 | case notAttachedToExtension 17 | case typeAnnotationRequired 18 | case optionalTypeRequired 19 | 20 | var severity: DiagnosticSeverity { return .error } 21 | 22 | var message: String { 23 | switch self { 24 | case .noDefaultArgument: 25 | "No default value provided." 26 | case .missingAnnotation: 27 | "No annotation provided." 28 | case .notAnIdentifier: 29 | "Identifier is not valid." 30 | case .attachedToWrongType(let macro, let wrongType, let correctType): 31 | "\(macro) is attached to \(wrongType), but must be attached to \(correctType)" 32 | case .notAttachedToExtension: 33 | "Macro can only be attached to an extension" 34 | case .typeAnnotationRequired: 35 | "Missing type declaration" 36 | case .optionalTypeRequired: 37 | "The type must be Optional." 38 | } 39 | } 40 | 41 | var id: String { 42 | switch self { 43 | case .noDefaultArgument: 44 | "noDefaultArgument" 45 | case .missingAnnotation: 46 | "missingAnnotation" 47 | case .notAnIdentifier: 48 | "notAnIdentifier" 49 | case .attachedToWrongType: 50 | "attachedToWrongType" 51 | case .notAttachedToExtension: 52 | "notAttachedToExtension" 53 | case .typeAnnotationRequired: 54 | "typeAnnotationRequired" 55 | case .optionalTypeRequired: 56 | "optionalTypeRequired" 57 | } 58 | } 59 | 60 | var diagnosticID: MessageID { 61 | MessageID(domain: "SwiftUIMacros", id: id) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Sources/SwiftUIMacrosImpl/FocusedValue.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FocusedValue.swift 3 | // 4 | // 5 | // Created by Wouter on 27/4/24. 6 | // 7 | 8 | import SwiftSyntax 9 | import SwiftSyntaxMacros 10 | import SwiftDiagnostics 11 | 12 | public struct AttachedMacroFocusedValueKey: PeerMacro { 13 | public static func expansion( 14 | of node: AttributeSyntax, 15 | providingPeersOf declaration: some DeclSyntaxProtocol, 16 | in context: some MacroExpansionContext 17 | ) throws -> [DeclSyntax] { 18 | 19 | // Skip declarations other than variables 20 | guard let varDecl = declaration.as(VariableDeclSyntax.self) else { 21 | return [] 22 | } 23 | 24 | guard let binding = varDecl.bindings.first else { 25 | context.diagnose(Diagnostic(node: Syntax(node), message: Feedback.missingAnnotation)) 26 | return [] 27 | } 28 | 29 | guard let identifier = binding.pattern.as(IdentifierPatternSyntax.self)?.identifier else { 30 | context.diagnose(Diagnostic(node: Syntax(node), message: Feedback.notAnIdentifier)) 31 | return [] 32 | } 33 | 34 | guard let annotation = binding.typeAnnotation else { 35 | context.diagnose(Diagnostic(node: Syntax(node), message: Feedback.typeAnnotationRequired)) 36 | return [] 37 | } 38 | 39 | guard let type = annotation.type.as(OptionalTypeSyntax.self) else { 40 | context.diagnose(Diagnostic(node: Syntax(node), message: Feedback.optionalTypeRequired)) 41 | return [] 42 | } 43 | 44 | return [ 45 | """ 46 | private struct FocusedValueKey_\(identifier): FocusedValueKey { 47 | typealias Value = \(type.wrappedType) 48 | } 49 | """ 50 | ] 51 | } 52 | } 53 | 54 | extension AttachedMacroFocusedValueKey: AccessorMacro { 55 | public static func expansion( 56 | of node: AttributeSyntax, 57 | providingAccessorsOf declaration: some DeclSyntaxProtocol, 58 | in context: some MacroExpansionContext 59 | ) throws -> [AccessorDeclSyntax] { 60 | 61 | // Skip declarations other than variables 62 | guard let varDecl = declaration.as(VariableDeclSyntax.self) else { 63 | return [] 64 | } 65 | 66 | guard let binding = varDecl.bindings.first else { 67 | context.diagnose(Diagnostic(node: Syntax(node), message: Feedback.missingAnnotation)) 68 | return [] 69 | } 70 | 71 | guard let identifier = binding.pattern.as(IdentifierPatternSyntax.self)?.identifier else { 72 | context.diagnose(Diagnostic(node: Syntax(node), message: Feedback.notAnIdentifier)) 73 | return [] 74 | } 75 | 76 | return [ 77 | """ 78 | get { 79 | self[FocusedValueKey_\(identifier).self] 80 | } 81 | """, 82 | """ 83 | set { 84 | self[FocusedValueKey_\(identifier).self] = newValue 85 | } 86 | """ 87 | ] 88 | } 89 | } 90 | 91 | -------------------------------------------------------------------------------- /Sources/SwiftUIMacrosImpl/SwiftUIMacros.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftUIMacros.swift 3 | // 4 | // 5 | // Created by Wouter Hennen on 14/06/2023. 6 | // 7 | 8 | import SwiftCompilerPlugin 9 | import SwiftSyntaxMacros 10 | 11 | @main 12 | struct SwiftUIMacros: CompilerPlugin { 13 | let providingMacros: [Macro.Type] = [ 14 | EnvironmentStorage.self, 15 | AttachedMacroEnvironmentKey.self, 16 | AttachedMacroFocusedValueKey.self 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /Tests/MyMacroTests/MyMacroTests.swift: -------------------------------------------------------------------------------- 1 | import SwiftSyntaxMacros 2 | import SwiftSyntaxMacrosTestSupport 3 | import XCTest 4 | import SwiftUIMacrosImpl 5 | 6 | let testMacros: [String: Macro.Type] = [ 7 | "EnvironmentStorage": EnvironmentStorage.self, 8 | "EnvironmentValue": AttachedMacroEnvironmentKey.self, 9 | "FocusedValue": AttachedMacroFocusedValueKey.self 10 | ] 11 | 12 | final class MyMacroTests: XCTestCase { 13 | func testMacroWithStringLiteral() { 14 | assertMacroExpansion( 15 | """ 16 | struct Hello { 17 | @EnvironmentValue 18 | var test: Bool = false 19 | } 20 | """, 21 | expandedSource: 22 | """ 23 | struct Hello { 24 | var test: Bool { 25 | get { 26 | self [EnvironmentKey_test.self] 27 | } 28 | set { 29 | self [EnvironmentKey_test.self] = newValue 30 | } 31 | } 32 | 33 | private struct EnvironmentKey_test: EnvironmentKey { 34 | static let defaultValue: Bool = false 35 | } 36 | } 37 | """, 38 | macros: testMacros 39 | ) 40 | } 41 | 42 | func testFocusedValueMacro() { 43 | assertMacroExpansion( 44 | """ 45 | struct Hello { 46 | @FocusedValue 47 | var test: Bool? 48 | } 49 | """, 50 | expandedSource: 51 | """ 52 | struct Hello { 53 | var test: Bool? { 54 | get { 55 | self [FocusedValueKey_test.self] 56 | } 57 | set { 58 | self [FocusedValueKey_test.self] = newValue 59 | } 60 | } 61 | 62 | private struct FocusedValueKey_test: FocusedValueKey { 63 | typealias Value = Bool 64 | } 65 | } 66 | """, 67 | macros: testMacros 68 | ) 69 | } 70 | 71 | func testEnvironmentStorage() { 72 | assertMacroExpansion( 73 | """ 74 | @EnvironmentStorage 75 | extension EnvironmentValues { 76 | var test: Bool = false 77 | } 78 | """, 79 | expandedSource: 80 | """ 81 | extension EnvironmentValues { 82 | @EnvironmentKey 83 | var test: Bool = false 84 | } 85 | """, 86 | macros: testMacros 87 | ) 88 | } 89 | } 90 | --------------------------------------------------------------------------------