From 0d9af5d4325ca77311729ddbd90bbb802c0d17f8 Mon Sep 17 00:00:00 2001 From: Kamil Strzelecki Date: Fri, 21 Nov 2025 17:56:02 +0100 Subject: [PATCH 01/49] - --- .../Builders/EnumCaseCallExprBuilderTests.swift | 2 +- .../PrincipleMacrosTests/Builders/SwitchExprBuilderTests.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/PrincipleMacrosTests/Builders/EnumCaseCallExprBuilderTests.swift b/Tests/PrincipleMacrosTests/Builders/EnumCaseCallExprBuilderTests.swift index da06d9b..a345f88 100644 --- a/Tests/PrincipleMacrosTests/Builders/EnumCaseCallExprBuilderTests.swift +++ b/Tests/PrincipleMacrosTests/Builders/EnumCaseCallExprBuilderTests.swift @@ -11,7 +11,7 @@ import Testing internal struct EnumCaseCallExprBuilderTests { - func makeEnumCase(from decl: DeclSyntax) throws -> EnumCase { + private func makeEnumCase(from decl: DeclSyntax) throws -> EnumCase { let enumCaseDecl = try #require(EnumCaseDeclSyntax(decl)) let enumElement = try #require(enumCaseDecl.elements.first) return EnumCase(declaration: enumCaseDecl, element: enumElement) diff --git a/Tests/PrincipleMacrosTests/Builders/SwitchExprBuilderTests.swift b/Tests/PrincipleMacrosTests/Builders/SwitchExprBuilderTests.swift index a627982..011c462 100644 --- a/Tests/PrincipleMacrosTests/Builders/SwitchExprBuilderTests.swift +++ b/Tests/PrincipleMacrosTests/Builders/SwitchExprBuilderTests.swift @@ -11,7 +11,7 @@ import Testing internal struct SwitchExprBuilderTests { - func makeEnumCase(from decl: DeclSyntax) throws -> EnumCase { + private func makeEnumCase(from decl: DeclSyntax) throws -> EnumCase { let enumCaseDecl = try #require(EnumCaseDeclSyntax(decl)) let enumElement = try #require(enumCaseDecl.elements.first) return EnumCase(declaration: enumCaseDecl, element: enumElement) From d1012f1d54c038a9dd3b9eca33fad8eee5c12b77 Mon Sep 17 00:00:00 2001 From: Kamil Strzelecki Date: Fri, 21 Nov 2025 17:57:01 +0100 Subject: [PATCH 02/49] - --- .../Syntax/Extensions/ClassDeclSyntax.swift | 56 +++++++++++ .../Syntax/ClassDeclSyntaxTests.swift | 99 +++++++++++++++++++ 2 files changed, 155 insertions(+) create mode 100644 Sources/PrincipleMacros/Syntax/Extensions/ClassDeclSyntax.swift create mode 100644 Tests/PrincipleMacrosTests/Syntax/ClassDeclSyntaxTests.swift diff --git a/Sources/PrincipleMacros/Syntax/Extensions/ClassDeclSyntax.swift b/Sources/PrincipleMacros/Syntax/Extensions/ClassDeclSyntax.swift new file mode 100644 index 0000000..bb8f5e3 --- /dev/null +++ b/Sources/PrincipleMacros/Syntax/Extensions/ClassDeclSyntax.swift @@ -0,0 +1,56 @@ +// +// ClassDeclSyntax.swift +// PrincipleMacros +// +// Created by Kamil Strzelecki on 21/11/2025. +// Copyright © 2025 Kamil Strzelecki. All rights reserved. +// + +import SwiftSyntaxMacros + +extension ClassDeclSyntax { + + public func inferredSuperclass() -> TypeSyntax? { + let superclassFinder = SuperclassFinder(for: self) + return superclassFinder.find()?.trimmed + } +} + +extension ClassDeclSyntax { + + private final class SuperclassFinder: SyntaxVisitor { + + private let classDecl: ClassDeclSyntax + private var didVerify = false + + init(for classDecl: ClassDeclSyntax) { + self.classDecl = classDecl + super.init(viewMode: .sourceAccurate) + } + + func find() -> TypeSyntax? { + guard let inheritanceClause = classDecl.inheritanceClause, + let firstInheritedType = inheritanceClause.inheritedTypes.first?.type + else { + return nil + } + + walk(classDecl) + return didVerify ? firstInheritedType : nil + } + + override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind { + node == classDecl ? .visitChildren : .skipChildren + } + + override func visit(_ node: DeclModifierSyntax) -> SyntaxVisitorContinueKind { + didVerify = didVerify || node.overrideSpecifier != nil + return .visitChildren + } + + override func visit(_ node: SuperExprSyntax) -> SyntaxVisitorContinueKind { + didVerify = true + return .visitChildren + } + } +} diff --git a/Tests/PrincipleMacrosTests/Syntax/ClassDeclSyntaxTests.swift b/Tests/PrincipleMacrosTests/Syntax/ClassDeclSyntaxTests.swift new file mode 100644 index 0000000..4913831 --- /dev/null +++ b/Tests/PrincipleMacrosTests/Syntax/ClassDeclSyntaxTests.swift @@ -0,0 +1,99 @@ +// +// ClassDeclSyntaxTests.swift +// PrincipleMacros +// +// Created by Kamil Strzelecki on 21/11/2025. +// Copyright © 2025 Kamil Strzelecki. All rights reserved. +// + +@testable import PrincipleMacros +import Testing + +internal enum ClassDeclSyntaxTests { + + struct NoInheritanceClause { + + private func makeDecl() throws -> ClassDeclSyntax { + let decl: DeclSyntax = "class MyClass {}" + return try #require(decl.as(ClassDeclSyntax.self)) + } + + @Test + func inferredSuperclass() throws { + let result = try makeDecl().inferredSuperclass() + #expect(result == nil) + } + } + + struct InheritanceClause { + + private func makeDecl() throws -> ClassDeclSyntax { + let decl: DeclSyntax = "class MyClass: Equatable, Hashable {}" + return try #require(decl.as(ClassDeclSyntax.self)) + } + + @Test + func inferredSuperclass() throws { + let result = try makeDecl().inferredSuperclass() + #expect(result == nil) + } + } + + struct OverrideModifier { + + private func makeDecl() throws -> ClassDeclSyntax { + let decl: DeclSyntax = """ + class MyClass: BaseClass, Hashable { + override func test() {} + } + """ + return try #require(decl.as(ClassDeclSyntax.self)) + } + + @Test + func inferredSuperclass() throws { + let result = try makeDecl().inferredSuperclass() + #expect(result?.description == "BaseClass") + } + } + + struct SuperExpr { + + private func makeDecl() throws -> ClassDeclSyntax { + let decl: DeclSyntax = """ + class MyClass: BaseClass, Hashable { + init(value: Int) { + super.init() + } + } + """ + return try #require(decl.as(ClassDeclSyntax.self)) + } + + @Test + func inferredSuperclass() throws { + let result = try makeDecl().inferredSuperclass() + #expect(result?.description == "BaseClass") + } + } + + struct NestedClassDecl { + + private func makeDecl() throws -> ClassDeclSyntax { + let decl: DeclSyntax = """ + class MyClass: Equatable, Hashable { + class NestedClass: BaseClass { + override func test() {} + } + } + """ + return try #require(decl.as(ClassDeclSyntax.self)) + } + + @Test + func inferredSuperclass() throws { + let result = try makeDecl().inferredSuperclass() + #expect(result == nil) + } + } +} From c0cdfaff7ecf29c8a19d9872371015b3b30dcc48 Mon Sep 17 00:00:00 2001 From: Kamil Strzelecki Date: Fri, 21 Nov 2025 17:57:01 +0100 Subject: [PATCH 03/49] [SwiftFormat] Applied formatting --- Sources/PrincipleMacros/Syntax/Extensions/ClassDeclSyntax.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/PrincipleMacros/Syntax/Extensions/ClassDeclSyntax.swift b/Sources/PrincipleMacros/Syntax/Extensions/ClassDeclSyntax.swift index bb8f5e3..251649b 100644 --- a/Sources/PrincipleMacros/Syntax/Extensions/ClassDeclSyntax.swift +++ b/Sources/PrincipleMacros/Syntax/Extensions/ClassDeclSyntax.swift @@ -48,7 +48,7 @@ extension ClassDeclSyntax { return .visitChildren } - override func visit(_ node: SuperExprSyntax) -> SyntaxVisitorContinueKind { + override func visit(_: SuperExprSyntax) -> SyntaxVisitorContinueKind { didVerify = true return .visitChildren } From 5f93e9bcd9299490dd3c97feb901efc3bd1a54d9 Mon Sep 17 00:00:00 2001 From: Kamil Strzelecki Date: Fri, 21 Nov 2025 17:58:20 +0100 Subject: [PATCH 04/49] - --- .../Diagnostics/DiagnosticsError.swift | 21 ++++++ .../MacroExpansionContext.swift | 0 .../Concepts/GlobalActorIsolation.swift | 6 +- .../Extensions/WithModifiersSyntax.swift | 72 ++++++++++++++----- 4 files changed, 78 insertions(+), 21 deletions(-) create mode 100644 Sources/PrincipleMacros/Diagnostics/DiagnosticsError.swift rename Sources/PrincipleMacros/{ExpansionContext => Diagnostics}/MacroExpansionContext.swift (100%) diff --git a/Sources/PrincipleMacros/Diagnostics/DiagnosticsError.swift b/Sources/PrincipleMacros/Diagnostics/DiagnosticsError.swift new file mode 100644 index 0000000..ba71be6 --- /dev/null +++ b/Sources/PrincipleMacros/Diagnostics/DiagnosticsError.swift @@ -0,0 +1,21 @@ +// +// DiagnosticsError.swift +// PrincipleMacros +// +// Created by Kamil Strzelecki on 21/11/2025. +// Copyright © 2025 Kamil Strzelecki. All rights reserved. +// + +import SwiftSyntaxMacros + +extension DiagnosticsError { + + public init( + node: some SyntaxProtocol, + message: String + ) { + let message = MacroExpansionErrorMessage(message) + let diagnostic = Diagnostic(node: node, message: message) + self.init(diagnostics: [diagnostic]) + } +} diff --git a/Sources/PrincipleMacros/ExpansionContext/MacroExpansionContext.swift b/Sources/PrincipleMacros/Diagnostics/MacroExpansionContext.swift similarity index 100% rename from Sources/PrincipleMacros/ExpansionContext/MacroExpansionContext.swift rename to Sources/PrincipleMacros/Diagnostics/MacroExpansionContext.swift diff --git a/Sources/PrincipleMacros/Syntax/Concepts/GlobalActorIsolation.swift b/Sources/PrincipleMacros/Syntax/Concepts/GlobalActorIsolation.swift index ed5a70b..bd62dd0 100644 --- a/Sources/PrincipleMacros/Syntax/Concepts/GlobalActorIsolation.swift +++ b/Sources/PrincipleMacros/Syntax/Concepts/GlobalActorIsolation.swift @@ -32,10 +32,10 @@ public enum GlobalActorIsolation { } public var standardizedIsolationAttribute: AttributeSyntax? { - guard let standardizedIsolationType else { - return nil + if let standardizedIsolationType { + return AttributeSyntax(attributeName: standardizedIsolationType) } - return AttributeSyntax(attributeName: standardizedIsolationType) + return nil } } diff --git a/Sources/PrincipleMacros/Syntax/Extensions/WithModifiersSyntax.swift b/Sources/PrincipleMacros/Syntax/Extensions/WithModifiersSyntax.swift index 5c81264..f904a84 100644 --- a/Sources/PrincipleMacros/Syntax/Extensions/WithModifiersSyntax.swift +++ b/Sources/PrincipleMacros/Syntax/Extensions/WithModifiersSyntax.swift @@ -11,13 +11,9 @@ import SwiftSyntaxMacros extension WithModifiersSyntax { public var globalActorIsolation: GlobalActorIsolation? { - let modifier = modifiers.first { modifier in - modifier.name.tokenKind == .keyword(.nonisolated) - } - if let modifier { - return .nonisolated(trimmedModifer: modifier.trimmed) - } - return nil + modifiers.lazy + .compactMap(\.globalActorIsolation) + .first } } @@ -34,28 +30,68 @@ extension WithModifiersSyntax { private func accessControlLevel(detail: TokenKind?) -> AccessControlLevel? { modifiers.lazy - .filter { $0.detail?.detail.tokenKind == detail } - .compactMap { AccessControlLevel(tokenSyntax: $0.name) } + .compactMap { $0.accessControlLevel(detail: detail) } .first } } extension WithModifiersSyntax { + public var overrideSpecifier: TokenSyntax? { + modifiers.lazy + .compactMap(\.overrideSpecifier) + .first + } + public var finalSpecifier: TokenSyntax? { - modifiers.lazy.map(\.name).first { name in - name.tokenKind == .keyword(.final) + modifiers.lazy + .compactMap(\.finalSpecifier) + .first + } + + public var typeScopeSpecifier: TokenSyntax? { + modifiers.lazy + .compactMap(\.typeScopeSpecifier) + .first + } +} + +extension DeclModifierSyntax { + + public var globalActorIsolation: GlobalActorIsolation? { + if name.tokenKind == .keyword(.nonisolated) { + return .nonisolated(trimmedModifer: trimmed) + } + return nil + } +} + +extension DeclModifierSyntax { + + public func accessControlLevel(detail: TokenKind?) -> AccessControlLevel? { + if self.detail?.detail.tokenKind == detail { + return AccessControlLevel(tokenSyntax: name) } + return nil + } +} + +extension DeclModifierSyntax { + + public var overrideSpecifier: TokenSyntax? { + name.tokenKind == .keyword(.override) ? name : nil + } + + public var finalSpecifier: TokenSyntax? { + name.tokenKind == .keyword(.final) ? name : nil } public var typeScopeSpecifier: TokenSyntax? { - modifiers.lazy.map(\.name).first { name in - switch name.tokenKind { - case .keyword(.class), .keyword(.static): - true - default: - false - } + switch name.tokenKind { + case .keyword(.class), .keyword(.static): + name + default: + nil } } } From 77acc752560f51f25f036948b59cc3bae5a57c1f Mon Sep 17 00:00:00 2001 From: Kamil Strzelecki Date: Fri, 21 Nov 2025 18:23:07 +0100 Subject: [PATCH 05/49] - --- ...thModifiersSyntax+AccessControlLevel.swift | 43 ++++++++ ...ModifiersSyntax+GlobalActorIsolation.swift | 28 ++++++ .../WithModifiersSyntax+Specifiers.swift | 50 ++++++++++ .../Extensions/WithModifiersSyntax.swift | 97 ------------------- 4 files changed, 121 insertions(+), 97 deletions(-) create mode 100644 Sources/PrincipleMacros/Syntax/Extensions/WithModifiersSyntax+AccessControlLevel.swift create mode 100644 Sources/PrincipleMacros/Syntax/Extensions/WithModifiersSyntax+GlobalActorIsolation.swift create mode 100644 Sources/PrincipleMacros/Syntax/Extensions/WithModifiersSyntax+Specifiers.swift delete mode 100644 Sources/PrincipleMacros/Syntax/Extensions/WithModifiersSyntax.swift diff --git a/Sources/PrincipleMacros/Syntax/Extensions/WithModifiersSyntax+AccessControlLevel.swift b/Sources/PrincipleMacros/Syntax/Extensions/WithModifiersSyntax+AccessControlLevel.swift new file mode 100644 index 0000000..535876d --- /dev/null +++ b/Sources/PrincipleMacros/Syntax/Extensions/WithModifiersSyntax+AccessControlLevel.swift @@ -0,0 +1,43 @@ +// +// WithModifiersSyntax+AccessControlLevel.swift +// PrincipleMacros +// +// Created by Kamil Strzelecki on 12/01/2025. +// Copyright © 2025 Kamil Strzelecki. All rights reserved. +// + +import SwiftSyntaxMacros + +extension WithModifiersSyntax { + + public var accessControlLevel: AccessControlLevel? { + modifiers.lazy + .compactMap(\.accessControlLevel) + .first + } + + public var setterAccessControlLevel: AccessControlLevel? { + modifiers.lazy + .compactMap(\.setterAccessControlLevel) + .first + } +} + +extension DeclModifierSyntax { + + public var accessControlLevel: AccessControlLevel? { + accessControlLevel(detail: nil) + } + + public var setterAccessControlLevel: AccessControlLevel? { + accessControlLevel(detail: .identifier("set")) + ?? accessControlLevel + } + + private func accessControlLevel(detail: TokenKind?) -> AccessControlLevel? { + if self.detail?.detail.tokenKind == detail { + return AccessControlLevel(tokenSyntax: name) + } + return nil + } +} diff --git a/Sources/PrincipleMacros/Syntax/Extensions/WithModifiersSyntax+GlobalActorIsolation.swift b/Sources/PrincipleMacros/Syntax/Extensions/WithModifiersSyntax+GlobalActorIsolation.swift new file mode 100644 index 0000000..1582ab9 --- /dev/null +++ b/Sources/PrincipleMacros/Syntax/Extensions/WithModifiersSyntax+GlobalActorIsolation.swift @@ -0,0 +1,28 @@ +// +// WithModifiersSyntax+GlobalActorIsolation.swift +// PrincipleMacros +// +// Created by Kamil Strzelecki on 12/01/2025. +// Copyright © 2025 Kamil Strzelecki. All rights reserved. +// + +import SwiftSyntaxMacros + +extension WithModifiersSyntax { + + public var globalActorIsolation: GlobalActorIsolation? { + modifiers.lazy + .compactMap(\.globalActorIsolation) + .first + } +} + +extension DeclModifierSyntax { + + public var globalActorIsolation: GlobalActorIsolation? { + if name.tokenKind == .keyword(.nonisolated) { + return .nonisolated(trimmedModifer: trimmed) + } + return nil + } +} diff --git a/Sources/PrincipleMacros/Syntax/Extensions/WithModifiersSyntax+Specifiers.swift b/Sources/PrincipleMacros/Syntax/Extensions/WithModifiersSyntax+Specifiers.swift new file mode 100644 index 0000000..e3c1cea --- /dev/null +++ b/Sources/PrincipleMacros/Syntax/Extensions/WithModifiersSyntax+Specifiers.swift @@ -0,0 +1,50 @@ +// +// WithModifiersSyntax+Specifiers.swift +// PrincipleMacros +// +// Created by Kamil Strzelecki on 12/01/2025. +// Copyright © 2025 Kamil Strzelecki. All rights reserved. +// + +import SwiftSyntaxMacros + +extension WithModifiersSyntax { + + public var overrideSpecifier: TokenSyntax? { + modifiers.lazy + .compactMap(\.overrideSpecifier) + .first + } + + public var finalSpecifier: TokenSyntax? { + modifiers.lazy + .compactMap(\.finalSpecifier) + .first + } + + public var typeScopeSpecifier: TokenSyntax? { + modifiers.lazy + .compactMap(\.typeScopeSpecifier) + .first + } +} + +extension DeclModifierSyntax { + + public var overrideSpecifier: TokenSyntax? { + name.tokenKind == .keyword(.override) ? name : nil + } + + public var finalSpecifier: TokenSyntax? { + name.tokenKind == .keyword(.final) ? name : nil + } + + public var typeScopeSpecifier: TokenSyntax? { + switch name.tokenKind { + case .keyword(.class), .keyword(.static): + name + default: + nil + } + } +} diff --git a/Sources/PrincipleMacros/Syntax/Extensions/WithModifiersSyntax.swift b/Sources/PrincipleMacros/Syntax/Extensions/WithModifiersSyntax.swift deleted file mode 100644 index f904a84..0000000 --- a/Sources/PrincipleMacros/Syntax/Extensions/WithModifiersSyntax.swift +++ /dev/null @@ -1,97 +0,0 @@ -// -// WithModifiersSyntax.swift -// PrincipleMacros -// -// Created by Kamil Strzelecki on 12/01/2025. -// Copyright © 2025 Kamil Strzelecki. All rights reserved. -// - -import SwiftSyntaxMacros - -extension WithModifiersSyntax { - - public var globalActorIsolation: GlobalActorIsolation? { - modifiers.lazy - .compactMap(\.globalActorIsolation) - .first - } -} - -extension WithModifiersSyntax { - - public var accessControlLevel: AccessControlLevel? { - accessControlLevel(detail: nil) - } - - public var setterAccessControlLevel: AccessControlLevel? { - accessControlLevel(detail: .identifier("set")) - ?? accessControlLevel - } - - private func accessControlLevel(detail: TokenKind?) -> AccessControlLevel? { - modifiers.lazy - .compactMap { $0.accessControlLevel(detail: detail) } - .first - } -} - -extension WithModifiersSyntax { - - public var overrideSpecifier: TokenSyntax? { - modifiers.lazy - .compactMap(\.overrideSpecifier) - .first - } - - public var finalSpecifier: TokenSyntax? { - modifiers.lazy - .compactMap(\.finalSpecifier) - .first - } - - public var typeScopeSpecifier: TokenSyntax? { - modifiers.lazy - .compactMap(\.typeScopeSpecifier) - .first - } -} - -extension DeclModifierSyntax { - - public var globalActorIsolation: GlobalActorIsolation? { - if name.tokenKind == .keyword(.nonisolated) { - return .nonisolated(trimmedModifer: trimmed) - } - return nil - } -} - -extension DeclModifierSyntax { - - public func accessControlLevel(detail: TokenKind?) -> AccessControlLevel? { - if self.detail?.detail.tokenKind == detail { - return AccessControlLevel(tokenSyntax: name) - } - return nil - } -} - -extension DeclModifierSyntax { - - public var overrideSpecifier: TokenSyntax? { - name.tokenKind == .keyword(.override) ? name : nil - } - - public var finalSpecifier: TokenSyntax? { - name.tokenKind == .keyword(.final) ? name : nil - } - - public var typeScopeSpecifier: TokenSyntax? { - switch name.tokenKind { - case .keyword(.class), .keyword(.static): - name - default: - nil - } - } -} From 0495316c6641a5a0efe6e5fd5365c236087224fc Mon Sep 17 00:00:00 2001 From: Kamil Strzelecki Date: Fri, 21 Nov 2025 18:57:15 +0100 Subject: [PATCH 06/49] - --- .../Extensions/AttributeListSyntax.swift | 37 -------------- .../Syntax/Extensions/AttributeSyntax.swift | 16 ------ .../Syntax/Extensions/ExprSyntax.swift | 8 ++- .../Extensions/IfConfigDeclSyntax.swift | 48 ++++++++++++++++++ .../WithAttributesSyntax+Availability.swift | 49 +++++++++++++++++++ ...ttributesSyntax+GlobalActorIsolation.swift | 41 ++++++++++++++++ .../Extensions/WithAttributesSyntax.swift | 23 --------- 7 files changed, 144 insertions(+), 78 deletions(-) delete mode 100644 Sources/PrincipleMacros/Syntax/Extensions/AttributeListSyntax.swift delete mode 100644 Sources/PrincipleMacros/Syntax/Extensions/AttributeSyntax.swift create mode 100644 Sources/PrincipleMacros/Syntax/Extensions/IfConfigDeclSyntax.swift create mode 100644 Sources/PrincipleMacros/Syntax/Extensions/WithAttributesSyntax+Availability.swift create mode 100644 Sources/PrincipleMacros/Syntax/Extensions/WithAttributesSyntax+GlobalActorIsolation.swift delete mode 100644 Sources/PrincipleMacros/Syntax/Extensions/WithAttributesSyntax.swift diff --git a/Sources/PrincipleMacros/Syntax/Extensions/AttributeListSyntax.swift b/Sources/PrincipleMacros/Syntax/Extensions/AttributeListSyntax.swift deleted file mode 100644 index b24ed65..0000000 --- a/Sources/PrincipleMacros/Syntax/Extensions/AttributeListSyntax.swift +++ /dev/null @@ -1,37 +0,0 @@ -// -// AttributeListSyntax.swift -// PrincipleMacros -// -// Created by Kamil Strzelecki on 14/11/2025. -// Copyright © 2025 Kamil Strzelecki. All rights reserved. -// - -import SwiftSyntaxMacros - -extension AttributeListSyntax { - - public var attributeElements: some Collection { - lazy.compactMap { element in - switch element { - case let .attribute(attribute): - attribute - default: - nil - } - } - } - - public func first(like other: AttributeSyntax) -> AttributeSyntax? { - attributeElements.first { $0.isLike(other) } - } - - public func contains(like other: AttributeSyntax) -> Bool { - first(like: other) != nil - } - - public func contains(likeOneOf other: AttributeSyntax...) -> Bool { - attributeElements.contains { attribute in - other.contains { attribute.isLike($0) } - } - } -} diff --git a/Sources/PrincipleMacros/Syntax/Extensions/AttributeSyntax.swift b/Sources/PrincipleMacros/Syntax/Extensions/AttributeSyntax.swift deleted file mode 100644 index abddf7d..0000000 --- a/Sources/PrincipleMacros/Syntax/Extensions/AttributeSyntax.swift +++ /dev/null @@ -1,16 +0,0 @@ -// -// AttributeSyntax.swift -// PrincipleMacros -// -// Created by Kamil Strzelecki on 15/11/2025. -// Copyright © 2025 Kamil Strzelecki. All rights reserved. -// - -import SwiftSyntaxMacros - -extension AttributeSyntax { - - public func isLike(_ other: AttributeSyntax) -> Bool { - attributeName.isLike(other.attributeName) - } -} diff --git a/Sources/PrincipleMacros/Syntax/Extensions/ExprSyntax.swift b/Sources/PrincipleMacros/Syntax/Extensions/ExprSyntax.swift index 0153a3d..ee43cef 100644 --- a/Sources/PrincipleMacros/Syntax/Extensions/ExprSyntax.swift +++ b/Sources/PrincipleMacros/Syntax/Extensions/ExprSyntax.swift @@ -66,10 +66,14 @@ extension StringLiteralExprSyntax { extension OptionalChainingExprSyntax { public var inferredType: TypeSyntax? { - guard let inferredType = expression.inferredType else { + guard let inferredWrappedType else { return nil } - return "Optional<\(inferredType)>" + return "Optional<\(inferredWrappedType)>" + } + + public var inferredWrappedType: TypeSyntax? { + expression.inferredType } } diff --git a/Sources/PrincipleMacros/Syntax/Extensions/IfConfigDeclSyntax.swift b/Sources/PrincipleMacros/Syntax/Extensions/IfConfigDeclSyntax.swift new file mode 100644 index 0000000..938b6d6 --- /dev/null +++ b/Sources/PrincipleMacros/Syntax/Extensions/IfConfigDeclSyntax.swift @@ -0,0 +1,48 @@ +// +// IfConfigDeclSyntax.swift +// PrincipleMacros +// +// Created by Kamil Strzelecki on 21/11/2025. +// Copyright © 2025 Kamil Strzelecki. All rights reserved. +// + +import SwiftSyntaxMacros + +extension IfConfigDeclSyntax { + + var availability: Self? { + var elements = clauses.compactMap(\.availability) + guard let first = elements.first else { + return nil + } + + elements[0] = first.detached.with(\.poundKeyword, .poundIfToken()) + return with(\.clauses, IfConfigClauseListSyntax(elements)) + } +} + +extension IfConfigClauseSyntax { + + var availability: Self? { + if let availability = elements?.availability { + return with(\.elements, availability) + } else { + return nil + } + } +} + +extension IfConfigClauseSyntax.Elements { + + var availability: Self? { + switch self { + case .attributes(let attributes): + if let availability = attributes.availability { + return .attributes(availability) + } + default: + break + } + return nil + } +} diff --git a/Sources/PrincipleMacros/Syntax/Extensions/WithAttributesSyntax+Availability.swift b/Sources/PrincipleMacros/Syntax/Extensions/WithAttributesSyntax+Availability.swift new file mode 100644 index 0000000..49e9e2b --- /dev/null +++ b/Sources/PrincipleMacros/Syntax/Extensions/WithAttributesSyntax+Availability.swift @@ -0,0 +1,49 @@ +// +// WithAttributesSyntax.swift +// PrincipleMacros +// +// Created by Kamil Strzelecki on 17/01/2025. +// Copyright © 2025 Kamil Strzelecki. All rights reserved. +// + +import SwiftSyntaxMacros + +extension WithAttributesSyntax { + + var availability: AttributeListSyntax? { + let availability = attributes.compactMap(\.availability) + return availability.isEmpty ? nil : AttributeListSyntax(availability) + } +} + +extension AttributeListSyntax { + + var availability: Self? { + let elements = compactMap(\.availability) + return elements.isEmpty ? nil : AttributeListSyntax(elements) + } +} + +extension AttributeListSyntax.Element { + + var availability: Self? { + switch self { + case .attribute(let attribute): + if let availability = attribute.availability { + return .attribute(availability) + } + case .ifConfigDecl(let ifConfig): + if let availability = ifConfig.availability { + return .ifConfigDecl(availability) + } + } + return nil + } +} + +extension AttributeSyntax { + + public var availability: Self? { + arguments?.is(AvailabilityArgumentListSyntax.self) == true ? self : nil + } +} diff --git a/Sources/PrincipleMacros/Syntax/Extensions/WithAttributesSyntax+GlobalActorIsolation.swift b/Sources/PrincipleMacros/Syntax/Extensions/WithAttributesSyntax+GlobalActorIsolation.swift new file mode 100644 index 0000000..0992387 --- /dev/null +++ b/Sources/PrincipleMacros/Syntax/Extensions/WithAttributesSyntax+GlobalActorIsolation.swift @@ -0,0 +1,41 @@ +// +// WithAttributesSyntax.swift +// PrincipleMacros +// +// Created by Kamil Strzelecki on 17/01/2025. +// Copyright © 2025 Kamil Strzelecki. All rights reserved. +// + +import SwiftSyntaxMacros + +extension WithAttributesSyntax { + + public var globalActorIsolation: GlobalActorIsolation? { + attributes.lazy + .compactMap(\.attribute?.globalActorIsolation) + .first + } +} + +extension AttributeSyntax { + + public var globalActorIsolation: GlobalActorIsolation? { + if attributeName.trimmedDescription.hasSuffix("Actor") { + let standardizedType = attributeName.standardized + return .isolated(standardizedType: standardizedType) + } + return nil + } +} + +extension AttributeListSyntax.Element { + + public var attribute: AttributeSyntax? { + switch self { + case let .attribute(attribute): + attribute + case .ifConfigDecl: + nil + } + } +} diff --git a/Sources/PrincipleMacros/Syntax/Extensions/WithAttributesSyntax.swift b/Sources/PrincipleMacros/Syntax/Extensions/WithAttributesSyntax.swift deleted file mode 100644 index e148e03..0000000 --- a/Sources/PrincipleMacros/Syntax/Extensions/WithAttributesSyntax.swift +++ /dev/null @@ -1,23 +0,0 @@ -// -// WithAttributesSyntax.swift -// PrincipleMacros -// -// Created by Kamil Strzelecki on 17/01/2025. -// Copyright © 2025 Kamil Strzelecki. All rights reserved. -// - -import SwiftSyntaxMacros - -extension WithAttributesSyntax { - - public var globalActorIsolation: GlobalActorIsolation? { - let attribute = attributes.attributeElements.first { attribute in - attribute.attributeName.trimmedDescription.hasSuffix("Actor") - } - if let attribute { - let standardizedType = attribute.attributeName.standardized - return .isolated(standardizedType: standardizedType) - } - return nil - } -} From b218f685d8bb652269e197e76140dd1070adc634 Mon Sep 17 00:00:00 2001 From: Kamil Strzelecki Date: Fri, 21 Nov 2025 18:57:15 +0100 Subject: [PATCH 07/49] [SwiftFormat] Applied formatting --- .../Syntax/Extensions/IfConfigDeclSyntax.swift | 6 +++--- .../Extensions/WithAttributesSyntax+Availability.swift | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Sources/PrincipleMacros/Syntax/Extensions/IfConfigDeclSyntax.swift b/Sources/PrincipleMacros/Syntax/Extensions/IfConfigDeclSyntax.swift index 938b6d6..cad7cfa 100644 --- a/Sources/PrincipleMacros/Syntax/Extensions/IfConfigDeclSyntax.swift +++ b/Sources/PrincipleMacros/Syntax/Extensions/IfConfigDeclSyntax.swift @@ -25,9 +25,9 @@ extension IfConfigClauseSyntax { var availability: Self? { if let availability = elements?.availability { - return with(\.elements, availability) + with(\.elements, availability) } else { - return nil + nil } } } @@ -36,7 +36,7 @@ extension IfConfigClauseSyntax.Elements { var availability: Self? { switch self { - case .attributes(let attributes): + case let .attributes(attributes): if let availability = attributes.availability { return .attributes(availability) } diff --git a/Sources/PrincipleMacros/Syntax/Extensions/WithAttributesSyntax+Availability.swift b/Sources/PrincipleMacros/Syntax/Extensions/WithAttributesSyntax+Availability.swift index 49e9e2b..6aa84a3 100644 --- a/Sources/PrincipleMacros/Syntax/Extensions/WithAttributesSyntax+Availability.swift +++ b/Sources/PrincipleMacros/Syntax/Extensions/WithAttributesSyntax+Availability.swift @@ -28,11 +28,11 @@ extension AttributeListSyntax.Element { var availability: Self? { switch self { - case .attribute(let attribute): + case let .attribute(attribute): if let availability = attribute.availability { return .attribute(availability) } - case .ifConfigDecl(let ifConfig): + case let .ifConfigDecl(ifConfig): if let availability = ifConfig.availability { return .ifConfigDecl(availability) } From 3823d0a96d3a563f32ed1a1ccc8042ea00aa6e70 Mon Sep 17 00:00:00 2001 From: Kamil Strzelecki Date: Fri, 21 Nov 2025 19:09:21 +0100 Subject: [PATCH 08/49] - --- .../Parsers/Common/Parser.swift | 14 ++++++++++ .../Common/ParserResultsCollection.swift | 6 +++++ .../Parsers/Common/_Parser.swift | 26 ------------------- .../Common/_ParserResultsCollection.swift | 19 -------------- .../Parsers/EnumCases/EnumCasesList.swift | 4 +-- .../Parsers/EnumCases/EnumCasesParser.swift | 2 +- .../Parsers/Properties/PropertiesList.swift | 4 +-- .../Parsers/Properties/PropertiesParser.swift | 2 +- ...thModifiersSyntax+AccessControlLevel.swift | 4 +-- 9 files changed, 28 insertions(+), 53 deletions(-) delete mode 100644 Sources/PrincipleMacros/Parsers/Common/_Parser.swift delete mode 100644 Sources/PrincipleMacros/Parsers/Common/_ParserResultsCollection.swift diff --git a/Sources/PrincipleMacros/Parsers/Common/Parser.swift b/Sources/PrincipleMacros/Parsers/Common/Parser.swift index 5e450e5..9574088 100644 --- a/Sources/PrincipleMacros/Parsers/Common/Parser.swift +++ b/Sources/PrincipleMacros/Parsers/Common/Parser.swift @@ -22,3 +22,17 @@ public protocol Parser { in context: some MacroExpansionContext ) -> ResultsCollection } + +extension Parser { + + public static func parse( + memberBlock: MemberBlockSyntax, + in context: some MacroExpansionContext + ) -> ResultsCollection { + ResultsCollection( + memberBlock.members.flatMap { member in + parse(declaration: member.decl, in: context) + } + ) + } +} diff --git a/Sources/PrincipleMacros/Parsers/Common/ParserResultsCollection.swift b/Sources/PrincipleMacros/Parsers/Common/ParserResultsCollection.swift index 9b39016..d01aa89 100644 --- a/Sources/PrincipleMacros/Parsers/Common/ParserResultsCollection.swift +++ b/Sources/PrincipleMacros/Parsers/Common/ParserResultsCollection.swift @@ -11,11 +11,17 @@ where Element: ParserResult { associatedtype Element + init(_ all: [Element]) + var all: [Element] { get } } extension ParserResultsCollection { + public init() { + self.init([]) + } + public var startIndex: Int { all.startIndex } diff --git a/Sources/PrincipleMacros/Parsers/Common/_Parser.swift b/Sources/PrincipleMacros/Parsers/Common/_Parser.swift deleted file mode 100644 index 4b16f81..0000000 --- a/Sources/PrincipleMacros/Parsers/Common/_Parser.swift +++ /dev/null @@ -1,26 +0,0 @@ -// -// _Parser.swift -// PrincipleMacros -// -// Created by Kamil Strzelecki on 22/01/2025. -// Copyright © 2025 Kamil Strzelecki. All rights reserved. -// - -import SwiftSyntaxMacros - -internal protocol _Parser: Parser -where ResultsCollection: _ParserResultsCollection {} - -extension _Parser { - - public static func parse( - memberBlock: MemberBlockSyntax, - in context: some MacroExpansionContext - ) -> ResultsCollection { - ResultsCollection( - memberBlock.members.flatMap { member in - parse(declaration: member.decl, in: context) - } - ) - } -} diff --git a/Sources/PrincipleMacros/Parsers/Common/_ParserResultsCollection.swift b/Sources/PrincipleMacros/Parsers/Common/_ParserResultsCollection.swift deleted file mode 100644 index 571d1d6..0000000 --- a/Sources/PrincipleMacros/Parsers/Common/_ParserResultsCollection.swift +++ /dev/null @@ -1,19 +0,0 @@ -// -// _ParserResultsCollection.swift -// PrincipleMacros -// -// Created by Kamil Strzelecki on 22/01/2025. -// Copyright © 2025 Kamil Strzelecki. All rights reserved. -// - -internal protocol _ParserResultsCollection: ParserResultsCollection { - - init(_ all: [Element]) -} - -extension _ParserResultsCollection { - - init() { - self.init([]) - } -} diff --git a/Sources/PrincipleMacros/Parsers/EnumCases/EnumCasesList.swift b/Sources/PrincipleMacros/Parsers/EnumCases/EnumCasesList.swift index cb84d39..064e32f 100644 --- a/Sources/PrincipleMacros/Parsers/EnumCases/EnumCasesList.swift +++ b/Sources/PrincipleMacros/Parsers/EnumCases/EnumCasesList.swift @@ -8,11 +8,11 @@ import SwiftSyntaxMacros -public struct EnumCasesList: _ParserResultsCollection { +public struct EnumCasesList: ParserResultsCollection { public let all: [EnumCase] - init(_ all: [EnumCase]) { + public init(_ all: [EnumCase]) { self.all = all } } diff --git a/Sources/PrincipleMacros/Parsers/EnumCases/EnumCasesParser.swift b/Sources/PrincipleMacros/Parsers/EnumCases/EnumCasesParser.swift index b9cd9d3..b5ee110 100644 --- a/Sources/PrincipleMacros/Parsers/EnumCases/EnumCasesParser.swift +++ b/Sources/PrincipleMacros/Parsers/EnumCases/EnumCasesParser.swift @@ -8,7 +8,7 @@ import SwiftSyntaxMacros -public enum EnumCasesParser: _Parser { +public enum EnumCasesParser: Parser { public static func parse( declaration: some DeclSyntaxProtocol, diff --git a/Sources/PrincipleMacros/Parsers/Properties/PropertiesList.swift b/Sources/PrincipleMacros/Parsers/Properties/PropertiesList.swift index 65d1a7b..25b713b 100644 --- a/Sources/PrincipleMacros/Parsers/Properties/PropertiesList.swift +++ b/Sources/PrincipleMacros/Parsers/Properties/PropertiesList.swift @@ -8,11 +8,11 @@ import SwiftSyntaxMacros -public struct PropertiesList: _ParserResultsCollection { +public struct PropertiesList: ParserResultsCollection { public let all: [Property] - init(_ all: [Property]) { + public init(_ all: [Property]) { self.all = all } } diff --git a/Sources/PrincipleMacros/Parsers/Properties/PropertiesParser.swift b/Sources/PrincipleMacros/Parsers/Properties/PropertiesParser.swift index 797ac3a..a2eaa32 100644 --- a/Sources/PrincipleMacros/Parsers/Properties/PropertiesParser.swift +++ b/Sources/PrincipleMacros/Parsers/Properties/PropertiesParser.swift @@ -8,7 +8,7 @@ import SwiftSyntaxMacros -public enum PropertiesParser: _Parser { +public enum PropertiesParser: Parser { public static func parse( declaration: some DeclSyntaxProtocol, diff --git a/Sources/PrincipleMacros/Syntax/Extensions/WithModifiersSyntax+AccessControlLevel.swift b/Sources/PrincipleMacros/Syntax/Extensions/WithModifiersSyntax+AccessControlLevel.swift index 535876d..756a784 100644 --- a/Sources/PrincipleMacros/Syntax/Extensions/WithModifiersSyntax+AccessControlLevel.swift +++ b/Sources/PrincipleMacros/Syntax/Extensions/WithModifiersSyntax+AccessControlLevel.swift @@ -34,8 +34,8 @@ extension DeclModifierSyntax { ?? accessControlLevel } - private func accessControlLevel(detail: TokenKind?) -> AccessControlLevel? { - if self.detail?.detail.tokenKind == detail { + private func accessControlLevel(detail detailTokenKind: TokenKind?) -> AccessControlLevel? { + if detail?.detail.tokenKind == detailTokenKind { return AccessControlLevel(tokenSyntax: name) } return nil From a9d5ff620a56b4677e26770d6dae8bc6ec0af213 Mon Sep 17 00:00:00 2001 From: Kamil Strzelecki Date: Fri, 21 Nov 2025 19:09:21 +0100 Subject: [PATCH 09/49] [SwiftFormat] Applied formatting --- .../Parsers/Common/ParserResultsCollection.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/PrincipleMacros/Parsers/Common/ParserResultsCollection.swift b/Sources/PrincipleMacros/Parsers/Common/ParserResultsCollection.swift index d01aa89..201e70b 100644 --- a/Sources/PrincipleMacros/Parsers/Common/ParserResultsCollection.swift +++ b/Sources/PrincipleMacros/Parsers/Common/ParserResultsCollection.swift @@ -12,7 +12,7 @@ where Element: ParserResult { associatedtype Element init(_ all: [Element]) - + var all: [Element] { get } } From 4d47af160e87cc9992ce93b462e6b53f3e0dea32 Mon Sep 17 00:00:00 2001 From: Kamil Strzelecki Date: Fri, 21 Nov 2025 19:22:01 +0100 Subject: [PATCH 10/49] - --- .../Parsers/Common/ParserResultsCollection.swift | 12 ++++++------ .../WithAttributesSyntax+Availability.swift | 2 +- .../WithAttributesSyntax+GlobalActorIsolation.swift | 2 +- .../Syntax/ClassDeclSyntaxTests.swift | 4 ++++ 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/Sources/PrincipleMacros/Parsers/Common/ParserResultsCollection.swift b/Sources/PrincipleMacros/Parsers/Common/ParserResultsCollection.swift index 201e70b..c319954 100644 --- a/Sources/PrincipleMacros/Parsers/Common/ParserResultsCollection.swift +++ b/Sources/PrincipleMacros/Parsers/Common/ParserResultsCollection.swift @@ -11,17 +11,13 @@ where Element: ParserResult { associatedtype Element - init(_ all: [Element]) - var all: [Element] { get } + + init(_ all: [Element]) } extension ParserResultsCollection { - public init() { - self.init([]) - } - public var startIndex: Int { all.startIndex } @@ -30,6 +26,10 @@ extension ParserResultsCollection { all.endIndex } + public init() { + self.init([]) + } + public subscript(position: Int) -> Element { all[position] } diff --git a/Sources/PrincipleMacros/Syntax/Extensions/WithAttributesSyntax+Availability.swift b/Sources/PrincipleMacros/Syntax/Extensions/WithAttributesSyntax+Availability.swift index 6aa84a3..e14267f 100644 --- a/Sources/PrincipleMacros/Syntax/Extensions/WithAttributesSyntax+Availability.swift +++ b/Sources/PrincipleMacros/Syntax/Extensions/WithAttributesSyntax+Availability.swift @@ -1,5 +1,5 @@ // -// WithAttributesSyntax.swift +// WithAttributesSyntax+Availability.swift // PrincipleMacros // // Created by Kamil Strzelecki on 17/01/2025. diff --git a/Sources/PrincipleMacros/Syntax/Extensions/WithAttributesSyntax+GlobalActorIsolation.swift b/Sources/PrincipleMacros/Syntax/Extensions/WithAttributesSyntax+GlobalActorIsolation.swift index 0992387..2c6d22a 100644 --- a/Sources/PrincipleMacros/Syntax/Extensions/WithAttributesSyntax+GlobalActorIsolation.swift +++ b/Sources/PrincipleMacros/Syntax/Extensions/WithAttributesSyntax+GlobalActorIsolation.swift @@ -1,5 +1,5 @@ // -// WithAttributesSyntax.swift +// WithAttributesSyntax+GlobalActorIsolation.swift // PrincipleMacros // // Created by Kamil Strzelecki on 17/01/2025. diff --git a/Tests/PrincipleMacrosTests/Syntax/ClassDeclSyntaxTests.swift b/Tests/PrincipleMacrosTests/Syntax/ClassDeclSyntaxTests.swift index 4913831..bc6e119 100644 --- a/Tests/PrincipleMacrosTests/Syntax/ClassDeclSyntaxTests.swift +++ b/Tests/PrincipleMacrosTests/Syntax/ClassDeclSyntaxTests.swift @@ -9,6 +9,8 @@ @testable import PrincipleMacros import Testing +// swiftlint:disable empty_line_after_type_declaration + internal enum ClassDeclSyntaxTests { struct NoInheritanceClause { @@ -97,3 +99,5 @@ internal enum ClassDeclSyntaxTests { } } } + +// swiftlint:enable empty_line_after_type_declaration From 6f59de2b7078138c23d45a299adbb3fc1da52e9e Mon Sep 17 00:00:00 2001 From: Kamil Strzelecki Date: Fri, 21 Nov 2025 19:23:33 +0100 Subject: [PATCH 11/49] - --- .../Extensions/WithModifiersSyntax+AccessControlLevel.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Sources/PrincipleMacros/Syntax/Extensions/WithModifiersSyntax+AccessControlLevel.swift b/Sources/PrincipleMacros/Syntax/Extensions/WithModifiersSyntax+AccessControlLevel.swift index 756a784..f9614f5 100644 --- a/Sources/PrincipleMacros/Syntax/Extensions/WithModifiersSyntax+AccessControlLevel.swift +++ b/Sources/PrincipleMacros/Syntax/Extensions/WithModifiersSyntax+AccessControlLevel.swift @@ -19,7 +19,7 @@ extension WithModifiersSyntax { public var setterAccessControlLevel: AccessControlLevel? { modifiers.lazy .compactMap(\.setterAccessControlLevel) - .first + .first ?? accessControlLevel } } @@ -31,7 +31,6 @@ extension DeclModifierSyntax { public var setterAccessControlLevel: AccessControlLevel? { accessControlLevel(detail: .identifier("set")) - ?? accessControlLevel } private func accessControlLevel(detail detailTokenKind: TokenKind?) -> AccessControlLevel? { From 87bf3bd05e88d9c675e2e3b565b6dba15fdbefe3 Mon Sep 17 00:00:00 2001 From: Kamil Strzelecki Date: Fri, 21 Nov 2025 21:05:54 +0100 Subject: [PATCH 12/49] - --- .../Syntax/Extensions/ExprSyntax.swift | 8 +- .../Extensions/IfConfigDeclSyntax.swift | 2 +- .../WithAttributesSyntax+Availability.swift | 3 +- .../EnumCaseCallExprBuilderTests.swift | 6 +- .../Parsers/EnumCasesParserTests.swift | 8 +- .../Syntax/ClassDeclSyntaxTests.swift | 113 ++++------- .../Syntax/ClosureExprSyntaxTests.swift | 165 +++++++-------- .../Syntax/ClosureTypeSyntaxTests.swift | 8 +- .../Syntax/ExprSyntaxTests.swift | 192 +++++++++--------- .../Syntax/GlobalActorIsolationTests.swift | 23 +-- .../Syntax/TypeSyntaxTests.swift | 126 ++++++------ 11 files changed, 298 insertions(+), 356 deletions(-) diff --git a/Sources/PrincipleMacros/Syntax/Extensions/ExprSyntax.swift b/Sources/PrincipleMacros/Syntax/Extensions/ExprSyntax.swift index ee43cef..b8d385d 100644 --- a/Sources/PrincipleMacros/Syntax/Extensions/ExprSyntax.swift +++ b/Sources/PrincipleMacros/Syntax/Extensions/ExprSyntax.swift @@ -138,10 +138,6 @@ extension GenericSpecializationExprSyntax { extension MemberAccessExprSyntax { - public var referencesBaseType: Bool { - declName.baseName.tokenKind == .keyword(.self) - } - public var inferredType: TypeSyntax? { guard let first = base?.inferredType else { return nil @@ -151,6 +147,10 @@ extension MemberAccessExprSyntax { } return first } + + public var referencesBaseType: Bool { + declName.baseName.tokenKind == .keyword(.self) + } } extension DeclReferenceExprSyntax { diff --git a/Sources/PrincipleMacros/Syntax/Extensions/IfConfigDeclSyntax.swift b/Sources/PrincipleMacros/Syntax/Extensions/IfConfigDeclSyntax.swift index cad7cfa..c8a9897 100644 --- a/Sources/PrincipleMacros/Syntax/Extensions/IfConfigDeclSyntax.swift +++ b/Sources/PrincipleMacros/Syntax/Extensions/IfConfigDeclSyntax.swift @@ -16,7 +16,7 @@ extension IfConfigDeclSyntax { return nil } - elements[0] = first.detached.with(\.poundKeyword, .poundIfToken()) + elements[0] = first.with(\.poundKeyword, .poundIfToken()) return with(\.clauses, IfConfigClauseListSyntax(elements)) } } diff --git a/Sources/PrincipleMacros/Syntax/Extensions/WithAttributesSyntax+Availability.swift b/Sources/PrincipleMacros/Syntax/Extensions/WithAttributesSyntax+Availability.swift index e14267f..04acd78 100644 --- a/Sources/PrincipleMacros/Syntax/Extensions/WithAttributesSyntax+Availability.swift +++ b/Sources/PrincipleMacros/Syntax/Extensions/WithAttributesSyntax+Availability.swift @@ -11,8 +11,7 @@ import SwiftSyntaxMacros extension WithAttributesSyntax { var availability: AttributeListSyntax? { - let availability = attributes.compactMap(\.availability) - return availability.isEmpty ? nil : AttributeListSyntax(availability) + attributes.availability } } diff --git a/Tests/PrincipleMacrosTests/Builders/EnumCaseCallExprBuilderTests.swift b/Tests/PrincipleMacrosTests/Builders/EnumCaseCallExprBuilderTests.swift index a345f88..b0ba636 100644 --- a/Tests/PrincipleMacrosTests/Builders/EnumCaseCallExprBuilderTests.swift +++ b/Tests/PrincipleMacrosTests/Builders/EnumCaseCallExprBuilderTests.swift @@ -18,7 +18,7 @@ internal struct EnumCaseCallExprBuilderTests { } @Test - func callWithoutAssociatedValues() throws { + func withoutAssociatedValues() throws { let enumCase = try makeEnumCase(from: "case first") let builder = EnumCaseCallExprBuilder(for: enumCase) { _ in Issue.record() @@ -28,7 +28,7 @@ internal struct EnumCaseCallExprBuilderTests { } @Test - func callWithUnnamedAssociatedValue() throws { + func withUnnamedAssociatedValue() throws { let enumCase = try makeEnumCase(from: "case second(Int)") let builder = EnumCaseCallExprBuilder(for: enumCase) { _ in "123" as ExprSyntax @@ -37,7 +37,7 @@ internal struct EnumCaseCallExprBuilderTests { } @Test - func callWithMultipleAssociatedValues() throws { + func withMultipleAssociatedValues() throws { let enumCase = try makeEnumCase(from: "case third(arg: String, Int)") let builder = EnumCaseCallExprBuilder(for: enumCase) { associatedValue in if associatedValue.standardizedName.description == "arg" { diff --git a/Tests/PrincipleMacrosTests/Parsers/EnumCasesParserTests.swift b/Tests/PrincipleMacrosTests/Parsers/EnumCasesParserTests.swift index 98a46a1..65ea7f9 100644 --- a/Tests/PrincipleMacrosTests/Parsers/EnumCasesParserTests.swift +++ b/Tests/PrincipleMacrosTests/Parsers/EnumCasesParserTests.swift @@ -15,7 +15,7 @@ internal struct EnumCasesParserTests { private let context = BasicMacroExpansionContext() @Test - func enumCase() throws { + func withoutAssociatedValue() throws { let decl: DeclSyntax = """ case myCase """ @@ -25,7 +25,7 @@ internal struct EnumCasesParserTests { } @Test - func enumCaseWithUnnamedAssociatedValue() throws { + func withUnnamedAssociatedValue() throws { let decl: DeclSyntax = """ case myCase(Int?) """ @@ -38,7 +38,7 @@ internal struct EnumCasesParserTests { } @Test - func enumCaseWithNamedAssociatedValue() throws { + func withNamedAssociatedValue() throws { let decl: DeclSyntax = """ case myCase(values: [String]) """ @@ -51,7 +51,7 @@ internal struct EnumCasesParserTests { } @Test - func enumCaseWithManyAssociatedValues() throws { + func withMultipleAssociatedValues() throws { let decl: DeclSyntax = """ case myCase(value: Int?, [String]) """ diff --git a/Tests/PrincipleMacrosTests/Syntax/ClassDeclSyntaxTests.swift b/Tests/PrincipleMacrosTests/Syntax/ClassDeclSyntaxTests.swift index bc6e119..239927d 100644 --- a/Tests/PrincipleMacrosTests/Syntax/ClassDeclSyntaxTests.swift +++ b/Tests/PrincipleMacrosTests/Syntax/ClassDeclSyntaxTests.swift @@ -11,92 +11,65 @@ import Testing // swiftlint:disable empty_line_after_type_declaration -internal enum ClassDeclSyntaxTests { - - struct NoInheritanceClause { - - private func makeDecl() throws -> ClassDeclSyntax { - let decl: DeclSyntax = "class MyClass {}" - return try #require(decl.as(ClassDeclSyntax.self)) - } - - @Test - func inferredSuperclass() throws { - let result = try makeDecl().inferredSuperclass() - #expect(result == nil) - } +internal struct ClassDeclSyntaxTests { + + @Test + func withoutInheritenceClause() throws { + let decl: DeclSyntax = "class MyClass {}" + let classDecl = try #require(decl.as(ClassDeclSyntax.self)) + let result = classDecl.inferredSuperclass() + #expect(result == nil) } - struct InheritanceClause { - - private func makeDecl() throws -> ClassDeclSyntax { - let decl: DeclSyntax = "class MyClass: Equatable, Hashable {}" - return try #require(decl.as(ClassDeclSyntax.self)) - } - - @Test - func inferredSuperclass() throws { - let result = try makeDecl().inferredSuperclass() - #expect(result == nil) - } + @Test + func withProtocolConformance() throws { + let decl: DeclSyntax = "class MyClass: Equatable, Hashable {}" + let classDecl = try #require(decl.as(ClassDeclSyntax.self)) + let result = classDecl.inferredSuperclass() + #expect(result == nil) } - struct OverrideModifier { - - private func makeDecl() throws -> ClassDeclSyntax { - let decl: DeclSyntax = """ - class MyClass: BaseClass, Hashable { - override func test() {} - } - """ - return try #require(decl.as(ClassDeclSyntax.self)) + @Test + func withOverrideModifier() throws { + let decl: DeclSyntax = """ + class MyClass: BaseClass, Hashable { + override func test() {} } + """ - @Test - func inferredSuperclass() throws { - let result = try makeDecl().inferredSuperclass() - #expect(result?.description == "BaseClass") - } + let classDecl = try #require(decl.as(ClassDeclSyntax.self)) + let result = classDecl.inferredSuperclass() + #expect(result?.description == "BaseClass") } - struct SuperExpr { - - private func makeDecl() throws -> ClassDeclSyntax { - let decl: DeclSyntax = """ - class MyClass: BaseClass, Hashable { - init(value: Int) { - super.init() - } + @Test + func withSuperExpression() throws { + let decl: DeclSyntax = """ + class MyClass: BaseClass, Hashable { + init(value: Int) { + super.init() } - """ - return try #require(decl.as(ClassDeclSyntax.self)) } + """ - @Test - func inferredSuperclass() throws { - let result = try makeDecl().inferredSuperclass() - #expect(result?.description == "BaseClass") - } + let classDecl = try #require(decl.as(ClassDeclSyntax.self)) + let result = classDecl.inferredSuperclass() + #expect(result?.description == "BaseClass") } - struct NestedClassDecl { - - private func makeDecl() throws -> ClassDeclSyntax { - let decl: DeclSyntax = """ - class MyClass: Equatable, Hashable { - class NestedClass: BaseClass { - override func test() {} - } + @Test + func withNestedClass() throws { + let decl: DeclSyntax = """ + class MyClass: Equatable, Hashable { + class NestedClass: BaseClass { + override func test() {} } - """ - return try #require(decl.as(ClassDeclSyntax.self)) } + """ - @Test - func inferredSuperclass() throws { - let result = try makeDecl().inferredSuperclass() - #expect(result == nil) - } + let classDecl = try #require(decl.as(ClassDeclSyntax.self)) + let result = classDecl.inferredSuperclass() + #expect(result == nil) } } diff --git a/Tests/PrincipleMacrosTests/Syntax/ClosureExprSyntaxTests.swift b/Tests/PrincipleMacrosTests/Syntax/ClosureExprSyntaxTests.swift index 14d84ff..cc7e64e 100644 --- a/Tests/PrincipleMacrosTests/Syntax/ClosureExprSyntaxTests.swift +++ b/Tests/PrincipleMacrosTests/Syntax/ClosureExprSyntaxTests.swift @@ -9,80 +9,69 @@ @testable import PrincipleMacros import Testing -internal enum ClosureExprSyntaxTests { +internal struct ClosureExprSyntaxTests { - enum SingleLine { - - struct WithoutSignature { - - private let expr: ExprSyntax = """ - { Date.now } - """ - - @Test - func expansion() { - let closure = expr.expanded(nestingLevel: 2) - let interpolation: ExprSyntax = """ - .init( - parameter: .init( - closure: \(closure), - value: "Foo" - ) - ) - """ + @Test + func withoutSignature() { + let expr: ExprSyntax = """ + { Date.now } + """ - let expectation = """ - .init( - parameter: .init( - closure: { - Date.now - }, - value: "Foo" - ) - ) - """ + let interpolation: ExprSyntax = """ + .init( + parameter: .init( + closure: \(expr.expanded(nestingLevel: 2)), + value: "Foo" + ) + ) + """ - #expect(interpolation.description == expectation) - } - } + let expectation = """ + .init( + parameter: .init( + closure: { + Date.now + }, + value: "Foo" + ) + ) + """ - struct WithSignature { + #expect(interpolation.description == expectation) + } - private let expr: ExprSyntax = """ - { [weak self] arg0, _ -> String in arg0 } - """ + @Test + func withSignature() { + let expr: ExprSyntax = """ + { [weak self] arg0, _ -> String in arg0 } + """ - @Test - func expansion() { - let closure = expr.expanded(nestingLevel: 2) - let interpolation: ExprSyntax = """ - .init( - parameter: .init( - closure: \(closure), - value: "Foo" - ) - ) - """ + let interpolation: ExprSyntax = """ + .init( + parameter: .init( + closure: \(expr.expanded(nestingLevel: 2)), + value: "Foo" + ) + ) + """ - let expectation = """ - .init( - parameter: .init( - closure: { [weak self] arg0, _ -> String in - arg0 - }, - value: "Foo" - ) - ) - """ + let expectation = """ + .init( + parameter: .init( + closure: { [weak self] arg0, _ -> String in + arg0 + }, + value: "Foo" + ) + ) + """ - #expect(interpolation.description == expectation) - } - } + #expect(interpolation.description == expectation) } - struct MultiLine { - - private let expr: ExprSyntax = """ + @Test + func multiline() { + let expr: ExprSyntax = """ { [weak self] arg0, arg1 -> String in if arg0 > 0 { return String(arg0) @@ -93,35 +82,31 @@ internal enum ClosureExprSyntaxTests { } """ - @Test - func expansion() { - let closure = expr.expanded(nestingLevel: 2) - let interpolation: ExprSyntax = """ - .init( - parameter: .init( - closure: \(closure), - value: "Foo" - ) + let interpolation: ExprSyntax = """ + .init( + parameter: .init( + closure: \(expr.expanded(nestingLevel: 2)), + value: "Foo" ) - """ + ) + """ - let expectation = """ - .init( - parameter: .init( - closure: { [weak self] arg0, arg1 -> String in - if arg0 > 0 { - return String(arg0) - } else if arg1 { - return "Test" - } - return "" - }, - value: "Foo" - ) + let expectation = """ + .init( + parameter: .init( + closure: { [weak self] arg0, arg1 -> String in + if arg0 > 0 { + return String(arg0) + } else if arg1 { + return "Test" + } + return "" + }, + value: "Foo" ) - """ + ) + """ - #expect(interpolation.description == expectation) - } + #expect(interpolation.description == expectation) } } diff --git a/Tests/PrincipleMacrosTests/Syntax/ClosureTypeSyntaxTests.swift b/Tests/PrincipleMacrosTests/Syntax/ClosureTypeSyntaxTests.swift index a36dac7..0ed3231 100644 --- a/Tests/PrincipleMacrosTests/Syntax/ClosureTypeSyntaxTests.swift +++ b/Tests/PrincipleMacrosTests/Syntax/ClosureTypeSyntaxTests.swift @@ -12,7 +12,7 @@ import Testing internal struct ClosureTypeSyntaxTests { @Test - func unnamedClosure() throws { + func unnamed() throws { let type: TypeSyntax = "(Int?, [Bool]) async throws -> ()" let closure = try #require(ClosureTypeSyntax(type)) @@ -30,7 +30,7 @@ internal struct ClosureTypeSyntaxTests { } @Test - func namedClosure() throws { + func named() throws { let type: TypeSyntax = "(_ value: Int!, _ argument: (first: String, [Int])) throws(SomeError) -> String?" let closure = try #require(ClosureTypeSyntax(type)) @@ -48,7 +48,7 @@ internal struct ClosureTypeSyntaxTests { } @Test - func mixedClosure() throws { + func mixed() throws { let type: TypeSyntax = "(_ value: Bool, String.Key) -> Void" let closure = try #require(ClosureTypeSyntax(type)) @@ -66,7 +66,7 @@ internal struct ClosureTypeSyntaxTests { } @Test - func attributedClosure() throws { + func attributed() throws { let type: TypeSyntax = "@Sendable () -> Void" let closure = try #require(ClosureTypeSyntax(type)) let attribute = try #require(closure.attributes.first) diff --git a/Tests/PrincipleMacrosTests/Syntax/ExprSyntaxTests.swift b/Tests/PrincipleMacrosTests/Syntax/ExprSyntaxTests.swift index f26a851..b6f9ebf 100644 --- a/Tests/PrincipleMacrosTests/Syntax/ExprSyntaxTests.swift +++ b/Tests/PrincipleMacrosTests/Syntax/ExprSyntaxTests.swift @@ -9,106 +9,100 @@ @testable import PrincipleMacros import Testing -internal enum ExprSyntaxTests { - - struct Basic { - - @Test - func optionalLiteral() { - let expr: ExprSyntax = "Int?" - #expect(expr.inferredType?.description == "Optional") - } - - @Test - func integerLiteral() { - let expr: ExprSyntax = "123" - #expect(expr.inferredType?.description == "Int") - } - - @Test - func floatLiteral() { - let expr: ExprSyntax = "1.23" - #expect(expr.inferredType?.description == "Double") - } - - @Test - func boolLiteral() { - let expr: ExprSyntax = "false" - #expect(expr.inferredType?.description == "Bool") - } - - @Test - func stringLiteral() { - let expr: ExprSyntax = "\"Hello\"" - #expect(expr.inferredType?.description == "String") - } - - @Test - func arrayLiteral() { - let expr: ExprSyntax = "[String]" - #expect(expr.inferredType?.description == "Array") - } - - @Test - func dictionaryLiteral() { - let expr: ExprSyntax = "[String: Int]" - #expect(expr.inferredType?.description == "Dictionary") - } - - @Test - func initializer() { - let expr: ExprSyntax = "UIView()" - #expect(expr.inferredType?.description == "UIView") - } - - @Test - func genericInitializer() { - let expr: ExprSyntax = "Dictionary()" - #expect(expr.inferredType?.description == "Dictionary") - } - - @Test - func memberAccess() { - let expr: ExprSyntax = "Options.first" - #expect(expr.inferredType?.description == "Options") - } - - @Test - func functionCall() { - let expr: ExprSyntax = "Model.create(arg: true)" - #expect(expr.inferredType?.description == "Model") - } - - @Test - func nestedFunctionCall() { - let expr: ExprSyntax = "Model.Default.create()" - #expect(expr.inferredType?.description == "Model.Default") - } - - @Test - func typeReference() { - let expr: ExprSyntax = "Model.self" - #expect(expr.inferredType?.description == "Model.Type") - } +internal struct ExprSyntaxTests { + + @Test + func optionalLiteral() { + let expr: ExprSyntax = "Int?" + #expect(expr.inferredType?.description == "Optional") + } + + @Test + func integerLiteral() { + let expr: ExprSyntax = "123" + #expect(expr.inferredType?.description == "Int") + } + + @Test + func floatLiteral() { + let expr: ExprSyntax = "1.23" + #expect(expr.inferredType?.description == "Double") + } + + @Test + func boolLiteral() { + let expr: ExprSyntax = "false" + #expect(expr.inferredType?.description == "Bool") + } + + @Test + func stringLiteral() { + let expr: ExprSyntax = "\"Hello\"" + #expect(expr.inferredType?.description == "String") + } + + @Test + func arrayLiteral() { + let expr: ExprSyntax = "[String]" + #expect(expr.inferredType?.description == "Array") + } + + @Test + func dictionaryLiteral() { + let expr: ExprSyntax = "[String: Int]" + #expect(expr.inferredType?.description == "Dictionary") + } + + @Test + func initializer() { + let expr: ExprSyntax = "UIView()" + #expect(expr.inferredType?.description == "UIView") + } + + @Test + func genericInitializer() { + let expr: ExprSyntax = "Dictionary()" + #expect(expr.inferredType?.description == "Dictionary") + } + + @Test + func memberAccess() { + let expr: ExprSyntax = "Options.first" + #expect(expr.inferredType?.description == "Options") + } + + @Test + func functionCall() { + let expr: ExprSyntax = "Model.create(arg: true)" + #expect(expr.inferredType?.description == "Model") + } + + @Test + func nestedFunctionCall() { + let expr: ExprSyntax = "Model.Default.create()" + #expect(expr.inferredType?.description == "Model.Default") + } + + @Test + func typeReference() { + let expr: ExprSyntax = "Model.self" + #expect(expr.inferredType?.description == "Model.Type") } - struct Complex { - - @Test( - arguments: [ - ( - "[String.Key: Int]()", - "Dictionary" - ), - ( - "Outer.Inner?", - "Optional>.Inner>" - ) - ] - ) - func composition(expr: String, expectation: String) { - let expr: ExprSyntax = "\(raw: expr)" - #expect(expr.inferredType?.description == expectation) - } + @Test( + arguments: [ + ( + "[String.Key: Int]()", + "Dictionary" + ), + ( + "Outer.Inner?", + "Optional>.Inner>" + ) + ] + ) + func composition(expr: String, expectation: String) { + let expr: ExprSyntax = "\(raw: expr)" + #expect(expr.inferredType?.description == expectation) } } diff --git a/Tests/PrincipleMacrosTests/Syntax/GlobalActorIsolationTests.swift b/Tests/PrincipleMacrosTests/Syntax/GlobalActorIsolationTests.swift index f0feeb6..45963e5 100644 --- a/Tests/PrincipleMacrosTests/Syntax/GlobalActorIsolationTests.swift +++ b/Tests/PrincipleMacrosTests/Syntax/GlobalActorIsolationTests.swift @@ -11,18 +11,7 @@ import Testing internal enum GlobalActorIsolationTests { - internal struct Function { - - @Test - func withNonisolatedNonsendingModifier() throws { - let decl: DeclSyntax = "nonisolated(nonsending) func test() {}" - let functionDecl = try #require(decl.as(FunctionDeclSyntax.self)) - let isolation = GlobalActorIsolation.resolved(for: functionDecl, in: []) - #expect(isolation?.trimmedNonisolatedModifier?.trimmedDescription == "nonisolated(nonsending)") - } - } - - internal struct Property { + internal struct MemberDeclaration { @Test func withGlobalActor() throws { @@ -48,6 +37,14 @@ internal enum GlobalActorIsolationTests { #expect(isolation?.trimmedNonisolatedModifier?.trimmedDescription == "nonisolated") } + @Test + func withNonisolatedNonsendingModifier() throws { + let decl: DeclSyntax = "nonisolated(nonsending) func test() {}" + let functionDecl = try #require(decl.as(FunctionDeclSyntax.self)) + let isolation = GlobalActorIsolation.resolved(for: functionDecl, in: []) + #expect(isolation?.trimmedNonisolatedModifier?.trimmedDescription == "nonisolated(nonsending)") + } + @Test func withoutGlobalActorInStructWithGlobalActor() throws { let decl: DeclSyntax = "var test = 123" @@ -101,7 +98,7 @@ internal enum GlobalActorIsolationTests { } } - internal struct Class { + internal struct TypeDeclaration { @Test func withGlobalActor() throws { diff --git a/Tests/PrincipleMacrosTests/Syntax/TypeSyntaxTests.swift b/Tests/PrincipleMacrosTests/Syntax/TypeSyntaxTests.swift index a2ab13c..32c20ea 100644 --- a/Tests/PrincipleMacrosTests/Syntax/TypeSyntaxTests.swift +++ b/Tests/PrincipleMacrosTests/Syntax/TypeSyntaxTests.swift @@ -9,82 +9,76 @@ @testable import PrincipleMacros import Testing -internal enum TypeSyntaxTests { +internal struct TypeSyntaxTests { - struct Basic { - - @Test - func optionalLiteral() { - let type: TypeSyntax = "Int?" - #expect(type.standardized.description == "Optional") - } - - @Test - func implicitlyUnwrappedOptionalLiteral() { - let type: TypeSyntax = "String!" - #expect(type.standardized.description == "Optional") - } + @Test + func optionalLiteral() { + let type: TypeSyntax = "Int?" + #expect(type.standardized.description == "Optional") + } - @Test - func arrayLiteral() { - let type: TypeSyntax = "[String]" - #expect(type.standardized.description == "Array") - } + @Test + func implicitlyUnwrappedOptionalLiteral() { + let type: TypeSyntax = "String!" + #expect(type.standardized.description == "Optional") + } - @Test - func dictionaryLiteral() { - let type: TypeSyntax = "[String: Int]" - #expect(type.standardized.description == "Dictionary") - } + @Test + func arrayLiteral() { + let type: TypeSyntax = "[String]" + #expect(type.standardized.description == "Array") + } - @Test - func basicType() { - let type: TypeSyntax = "UIView" - #expect(type.standardized.description == "UIView") - } + @Test + func dictionaryLiteral() { + let type: TypeSyntax = "[String: Int]" + #expect(type.standardized.description == "Dictionary") + } - @Test - func memberType() { - let type: TypeSyntax = "UIView.Constraints" - #expect(type.standardized.description == "UIView.Constraints") - } + @Test + func basicType() { + let type: TypeSyntax = "UIView" + #expect(type.standardized.description == "UIView") + } - @Test - func genericType() { - let type: TypeSyntax = "Cache" - #expect(type.standardized.description == "Cache") - } + @Test + func memberType() { + let type: TypeSyntax = "UIView.Constraints" + #expect(type.standardized.description == "UIView.Constraints") + } - @Test - func voidType() { - let type: TypeSyntax = "()" - #expect(type.standardized.description == "Void") - } + @Test + func genericType() { + let type: TypeSyntax = "Cache" + #expect(type.standardized.description == "Cache") + } - @Test - func tupleType() { - let type: TypeSyntax = "(_ first: String, second: Int, Bool)" - #expect(type.standardized.description == "(_ first: String, second: Int, Bool)") - } + @Test + func voidType() { + let type: TypeSyntax = "()" + #expect(type.standardized.description == "Void") } - struct Complex { + @Test + func tupleType() { + let type: TypeSyntax = "(_ first: String, second: Int, Bool)" + #expect(type.standardized.description == "(_ first: String, second: Int, Bool)") + } - @Test( - arguments: [ - ( - "[String.Key: Cache]", - "Dictionary, Int>>" - ), - ( - "(_ first: String?, second secondArg: [Int: Value.Nested])", - "(_ first: Optional, second secondArg: Dictionary)" - ) - ] - ) - func composition(type: String, expectation: String) { - let type: TypeSyntax = "\(raw: type)" - #expect(type.standardized.description == expectation) - } + @Test( + arguments: [ + ( + "[String.Key: Cache]", + "Dictionary, Int>>" + ), + ( + "(_ first: String?, second secondArg: [Int: Value.Nested])", + "(_ first: Optional, second secondArg: Dictionary)" + ) + ] + ) + func composition(type: String, expectation: String) { + let type: TypeSyntax = "\(raw: type)" + #expect(type.standardized.description == expectation) } } From b5193127b589c160a76126b5e372ec4bfafd022e Mon Sep 17 00:00:00 2001 From: Kamil Strzelecki Date: Fri, 21 Nov 2025 21:49:01 +0100 Subject: [PATCH 13/49] - --- .../Parsers/Common/Parser.swift | 38 +++++++--- ... => IfConfigDeclSyntax+Availability.swift} | 2 +- ...IfConfigDeclSyntax+EnclosingIfConfig.swift | 69 +++++++++++++++++++ .../Syntax/ClassDeclSyntaxTests.swift | 8 +-- 4 files changed, 104 insertions(+), 13 deletions(-) rename Sources/PrincipleMacros/Syntax/Extensions/{IfConfigDeclSyntax.swift => IfConfigDeclSyntax+Availability.swift} (96%) create mode 100644 Sources/PrincipleMacros/Syntax/Extensions/IfConfigDeclSyntax+EnclosingIfConfig.swift diff --git a/Sources/PrincipleMacros/Parsers/Common/Parser.swift b/Sources/PrincipleMacros/Parsers/Common/Parser.swift index 9574088..f4073f4 100644 --- a/Sources/PrincipleMacros/Parsers/Common/Parser.swift +++ b/Sources/PrincipleMacros/Parsers/Common/Parser.swift @@ -16,23 +16,45 @@ public protocol Parser { declaration: some DeclSyntaxProtocol, in context: some MacroExpansionContext ) -> ResultsCollection - - static func parse( - memberBlock: MemberBlockSyntax, - in context: some MacroExpansionContext - ) -> ResultsCollection } extension Parser { public static func parse( - memberBlock: MemberBlockSyntax, + ifConfig: IfConfigDeclSyntax, + in context: some MacroExpansionContext + ) -> ResultsCollection { + ResultsCollection( + ifConfig.clauses.flatMap { clause in + switch clause.elements { + case let .decls(members): + parse(members: members, in: context) + default: + ResultsCollection() + } + } + ) + } + + public static func parse( + members: MemberBlockItemListSyntax, in context: some MacroExpansionContext ) -> ResultsCollection { ResultsCollection( - memberBlock.members.flatMap { member in - parse(declaration: member.decl, in: context) + members.flatMap { member in + if let ifConfig = member.decl.as(IfConfigDeclSyntax.self) { + parse(ifConfig: ifConfig, in: context) + } else { + parse(declaration: member.decl, in: context) + } } ) } + + public static func parse( + memberBlock: MemberBlockSyntax, + in context: some MacroExpansionContext + ) -> ResultsCollection { + parse(members: memberBlock.members, in: context) + } } diff --git a/Sources/PrincipleMacros/Syntax/Extensions/IfConfigDeclSyntax.swift b/Sources/PrincipleMacros/Syntax/Extensions/IfConfigDeclSyntax+Availability.swift similarity index 96% rename from Sources/PrincipleMacros/Syntax/Extensions/IfConfigDeclSyntax.swift rename to Sources/PrincipleMacros/Syntax/Extensions/IfConfigDeclSyntax+Availability.swift index c8a9897..37050f0 100644 --- a/Sources/PrincipleMacros/Syntax/Extensions/IfConfigDeclSyntax.swift +++ b/Sources/PrincipleMacros/Syntax/Extensions/IfConfigDeclSyntax+Availability.swift @@ -1,5 +1,5 @@ // -// IfConfigDeclSyntax.swift +// IfConfigDeclSyntax+Availability.swift // PrincipleMacros // // Created by Kamil Strzelecki on 21/11/2025. diff --git a/Sources/PrincipleMacros/Syntax/Extensions/IfConfigDeclSyntax+EnclosingIfConfig.swift b/Sources/PrincipleMacros/Syntax/Extensions/IfConfigDeclSyntax+EnclosingIfConfig.swift new file mode 100644 index 0000000..b9dade1 --- /dev/null +++ b/Sources/PrincipleMacros/Syntax/Extensions/IfConfigDeclSyntax+EnclosingIfConfig.swift @@ -0,0 +1,69 @@ +// +// IfConfigDeclSyntax+EnclosingIfConfig.swift +// PrincipleMacros +// +// Created by Kamil Strzelecki on 21/11/2025. +// Copyright © 2025 Kamil Strzelecki. All rights reserved. +// + +import SwiftSyntax + +extension IfConfigDeclSyntax { + + var enclosingIfConfig: IfConfigDeclSyntax? { + if let parent = parent?.as(MemberBlockItemSyntax.self) { + return parent.enclosingIfConfig + } + return nil + } +} + +extension IfConfigClauseListSyntax { + + var enclosingIfConfig: IfConfigDeclSyntax? { + if let parent = parent?.as(IfConfigDeclSyntax.self) { + return parent.enclosingIfConfig ?? parent + } + return nil + } +} + +extension IfConfigClauseSyntax { + + var enclosingIfConfig: IfConfigDeclSyntax? { + if let parent = parent?.as(IfConfigClauseListSyntax.self) { + return parent.enclosingIfConfig + } + return nil + } +} + +extension MemberBlockItemListSyntax { + + var enclosingIfConfig: IfConfigDeclSyntax? { + if let parent = parent?.as(IfConfigClauseSyntax.self) { + return parent.enclosingIfConfig + } + return nil + } +} + +extension MemberBlockItemSyntax { + + var enclosingIfConfig: IfConfigDeclSyntax? { + if let parent = parent?.as(MemberBlockItemListSyntax.self) { + return parent.enclosingIfConfig + } + return nil + } +} + +extension DeclSyntaxProtocol { + + var enclosingIfConfig: IfConfigDeclSyntax? { + if let parent = parent?.as(MemberBlockItemSyntax.self) { + return parent.enclosingIfConfig + } + return nil + } +} diff --git a/Tests/PrincipleMacrosTests/Syntax/ClassDeclSyntaxTests.swift b/Tests/PrincipleMacrosTests/Syntax/ClassDeclSyntaxTests.swift index 239927d..614264e 100644 --- a/Tests/PrincipleMacrosTests/Syntax/ClassDeclSyntaxTests.swift +++ b/Tests/PrincipleMacrosTests/Syntax/ClassDeclSyntaxTests.swift @@ -9,8 +9,6 @@ @testable import PrincipleMacros import Testing -// swiftlint:disable empty_line_after_type_declaration - internal struct ClassDeclSyntaxTests { @Test @@ -29,6 +27,8 @@ internal struct ClassDeclSyntaxTests { #expect(result == nil) } + // swiftlint:disable empty_line_after_type_declaration + @Test func withOverrideModifier() throws { let decl: DeclSyntax = """ @@ -71,6 +71,6 @@ internal struct ClassDeclSyntaxTests { let result = classDecl.inferredSuperclass() #expect(result == nil) } -} -// swiftlint:enable empty_line_after_type_declaration + // swiftlint:enable empty_line_after_type_declaration +} From ed34e2b27377da563f61e03fb0bc678472ff45c0 Mon Sep 17 00:00:00 2001 From: Kamil Strzelecki Date: Sat, 22 Nov 2025 11:58:41 +0100 Subject: [PATCH 14/49] - --- .../Parsers/Common/Parser.swift | 32 ++++++++----------- .../Parsers/EnumCases/EnumCasesParser.swift | 3 +- .../Parsers/Properties/PropertiesParser.swift | 17 ++++------ .../ClosureTypeSyntax.swift | 0 .../Syntax/Extensions/ClassDeclSyntax.swift | 8 ++--- 5 files changed, 26 insertions(+), 34 deletions(-) rename Sources/PrincipleMacros/Syntax/{Custom => Concepts}/ClosureTypeSyntax.swift (100%) diff --git a/Sources/PrincipleMacros/Parsers/Common/Parser.swift b/Sources/PrincipleMacros/Parsers/Common/Parser.swift index f4073f4..d523dcc 100644 --- a/Sources/PrincipleMacros/Parsers/Common/Parser.swift +++ b/Sources/PrincipleMacros/Parsers/Common/Parser.swift @@ -13,22 +13,20 @@ public protocol Parser { associatedtype ResultsCollection: ParserResultsCollection static func parse( - declaration: some DeclSyntaxProtocol, - in context: some MacroExpansionContext - ) -> ResultsCollection + declaration: some DeclSyntaxProtocol + ) throws -> ResultsCollection } extension Parser { public static func parse( - ifConfig: IfConfigDeclSyntax, - in context: some MacroExpansionContext - ) -> ResultsCollection { - ResultsCollection( + ifConfig: IfConfigDeclSyntax + ) throws -> ResultsCollection { + try ResultsCollection( ifConfig.clauses.flatMap { clause in switch clause.elements { case let .decls(members): - parse(members: members, in: context) + try parse(members: members) default: ResultsCollection() } @@ -37,24 +35,22 @@ extension Parser { } public static func parse( - members: MemberBlockItemListSyntax, - in context: some MacroExpansionContext - ) -> ResultsCollection { - ResultsCollection( + members: MemberBlockItemListSyntax + ) throws -> ResultsCollection { + try ResultsCollection( members.flatMap { member in if let ifConfig = member.decl.as(IfConfigDeclSyntax.self) { - parse(ifConfig: ifConfig, in: context) + try parse(ifConfig: ifConfig) } else { - parse(declaration: member.decl, in: context) + try parse(declaration: member.decl) } } ) } public static func parse( - memberBlock: MemberBlockSyntax, - in context: some MacroExpansionContext - ) -> ResultsCollection { - parse(members: memberBlock.members, in: context) + memberBlock: MemberBlockSyntax + ) throws -> ResultsCollection { + try parse(members: memberBlock.members) } } diff --git a/Sources/PrincipleMacros/Parsers/EnumCases/EnumCasesParser.swift b/Sources/PrincipleMacros/Parsers/EnumCases/EnumCasesParser.swift index b5ee110..09d896a 100644 --- a/Sources/PrincipleMacros/Parsers/EnumCases/EnumCasesParser.swift +++ b/Sources/PrincipleMacros/Parsers/EnumCases/EnumCasesParser.swift @@ -11,8 +11,7 @@ import SwiftSyntaxMacros public enum EnumCasesParser: Parser { public static func parse( - declaration: some DeclSyntaxProtocol, - in _: some MacroExpansionContext + declaration: some DeclSyntaxProtocol ) -> EnumCasesList { guard let declaration = EnumCaseDeclSyntax(declaration) else { return .init() diff --git a/Sources/PrincipleMacros/Parsers/Properties/PropertiesParser.swift b/Sources/PrincipleMacros/Parsers/Properties/PropertiesParser.swift index a2eaa32..106cc98 100644 --- a/Sources/PrincipleMacros/Parsers/Properties/PropertiesParser.swift +++ b/Sources/PrincipleMacros/Parsers/Properties/PropertiesParser.swift @@ -11,29 +11,26 @@ import SwiftSyntaxMacros public enum PropertiesParser: Parser { public static func parse( - declaration: some DeclSyntaxProtocol, - in context: some MacroExpansionContext - ) -> PropertiesList { + declaration: some DeclSyntaxProtocol + ) throws -> PropertiesList { guard let declaration = VariableDeclSyntax(declaration) else { return .init() } - return PropertiesList( + return try PropertiesList( declaration.bindings.compactMap { binding -> Property? in guard let name = binding.name else { - context.diagnose( + throw DiagnosticsError( node: declaration, - errorMessage: "Property cannot be parsed" + message: "Property cannot be parsed" ) - return nil } guard let inferredType = binding.inferredType else { - context.diagnose( + throw DiagnosticsError( node: declaration, - errorMessage: "Type of property cannot be inferred - provide it explicitly" + message: "Type of property cannot be inferred - provide it explicitly" ) - return nil } return Property( diff --git a/Sources/PrincipleMacros/Syntax/Custom/ClosureTypeSyntax.swift b/Sources/PrincipleMacros/Syntax/Concepts/ClosureTypeSyntax.swift similarity index 100% rename from Sources/PrincipleMacros/Syntax/Custom/ClosureTypeSyntax.swift rename to Sources/PrincipleMacros/Syntax/Concepts/ClosureTypeSyntax.swift diff --git a/Sources/PrincipleMacros/Syntax/Extensions/ClassDeclSyntax.swift b/Sources/PrincipleMacros/Syntax/Extensions/ClassDeclSyntax.swift index 251649b..94831da 100644 --- a/Sources/PrincipleMacros/Syntax/Extensions/ClassDeclSyntax.swift +++ b/Sources/PrincipleMacros/Syntax/Extensions/ClassDeclSyntax.swift @@ -21,7 +21,7 @@ extension ClassDeclSyntax { private final class SuperclassFinder: SyntaxVisitor { private let classDecl: ClassDeclSyntax - private var didVerify = false + private var didFind = false init(for classDecl: ClassDeclSyntax) { self.classDecl = classDecl @@ -36,7 +36,7 @@ extension ClassDeclSyntax { } walk(classDecl) - return didVerify ? firstInheritedType : nil + return didFind ? firstInheritedType : nil } override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind { @@ -44,12 +44,12 @@ extension ClassDeclSyntax { } override func visit(_ node: DeclModifierSyntax) -> SyntaxVisitorContinueKind { - didVerify = didVerify || node.overrideSpecifier != nil + didFind = didFind || node.overrideSpecifier != nil return .visitChildren } override func visit(_: SuperExprSyntax) -> SyntaxVisitorContinueKind { - didVerify = true + didFind = true return .visitChildren } } From 40f666c09d764a4c534b2cb94e74e99964c30ae4 Mon Sep 17 00:00:00 2001 From: Kamil Strzelecki Date: Sat, 22 Nov 2025 11:59:14 +0100 Subject: [PATCH 15/49] - --- .../IfConfigDeclSyntax+Availability.swift | 12 ++-- ...IfConfigDeclSyntax+EnclosingIfConfig.swift | 68 +++++++++++++++---- .../WithAttributesSyntax+Availability.swift | 6 +- .../BasicDeclSyntax.swift | 0 .../StatefulDeclSyntax.swift | 0 .../TypeDeclSyntax.swift | 0 6 files changed, 63 insertions(+), 23 deletions(-) rename Sources/PrincipleMacros/Syntax/{Custom => Protocols}/BasicDeclSyntax.swift (100%) rename Sources/PrincipleMacros/Syntax/{Custom => Protocols}/StatefulDeclSyntax.swift (100%) rename Sources/PrincipleMacros/Syntax/{Custom => Protocols}/TypeDeclSyntax.swift (100%) diff --git a/Sources/PrincipleMacros/Syntax/Extensions/IfConfigDeclSyntax+Availability.swift b/Sources/PrincipleMacros/Syntax/Extensions/IfConfigDeclSyntax+Availability.swift index 37050f0..e2281d1 100644 --- a/Sources/PrincipleMacros/Syntax/Extensions/IfConfigDeclSyntax+Availability.swift +++ b/Sources/PrincipleMacros/Syntax/Extensions/IfConfigDeclSyntax+Availability.swift @@ -10,13 +10,13 @@ import SwiftSyntaxMacros extension IfConfigDeclSyntax { - var availability: Self? { - var elements = clauses.compactMap(\.availability) - guard let first = elements.first else { + public var availability: Self? { + guard clauses.contains(where: { $0.availability != nil }) else { return nil } - - elements[0] = first.with(\.poundKeyword, .poundIfToken()) + let elements = clauses.compactMap { clause in + clause.availability ?? clause.with(\.elements, .attributes([])) + } return with(\.clauses, IfConfigClauseListSyntax(elements)) } } @@ -34,7 +34,7 @@ extension IfConfigClauseSyntax { extension IfConfigClauseSyntax.Elements { - var availability: Self? { + public var availability: Self? { switch self { case let .attributes(attributes): if let availability = attributes.availability { diff --git a/Sources/PrincipleMacros/Syntax/Extensions/IfConfigDeclSyntax+EnclosingIfConfig.swift b/Sources/PrincipleMacros/Syntax/Extensions/IfConfigDeclSyntax+EnclosingIfConfig.swift index b9dade1..bdd8ab5 100644 --- a/Sources/PrincipleMacros/Syntax/Extensions/IfConfigDeclSyntax+EnclosingIfConfig.swift +++ b/Sources/PrincipleMacros/Syntax/Extensions/IfConfigDeclSyntax+EnclosingIfConfig.swift @@ -10,8 +10,12 @@ import SwiftSyntax extension IfConfigDeclSyntax { - var enclosingIfConfig: IfConfigDeclSyntax? { - if let parent = parent?.as(MemberBlockItemSyntax.self) { + fileprivate var aligned: Self { + with(\.poundEndif, .poundEndifToken(leadingTrivia: .newline)) + } + + public var enclosingIfConfig: IfConfigDeclSyntax? { + if let parent = aligned.parent?.as(MemberBlockItemSyntax.self) { return parent.enclosingIfConfig } return nil @@ -20,9 +24,9 @@ extension IfConfigDeclSyntax { extension IfConfigClauseListSyntax { - var enclosingIfConfig: IfConfigDeclSyntax? { + public var enclosingIfConfig: IfConfigDeclSyntax? { if let parent = parent?.as(IfConfigDeclSyntax.self) { - return parent.enclosingIfConfig ?? parent + return parent.enclosingIfConfig ?? parent.aligned } return nil } @@ -30,17 +34,32 @@ extension IfConfigClauseListSyntax { extension IfConfigClauseSyntax { - var enclosingIfConfig: IfConfigDeclSyntax? { - if let parent = parent?.as(IfConfigClauseListSyntax.self) { - return parent.enclosingIfConfig + private var aligned: Self { + with(\.poundKeyword, poundKeyword.trimmed.withTrailingSpace) + } + + public var enclosingIfConfig: IfConfigDeclSyntax? { + guard var parent = parent?.as(IfConfigClauseListSyntax.self) else { + return nil } - return nil + + for (index, clause) in zip(parent.indices, parent) { + if clause == self { + parent[index] = aligned + } else { + parent[index] = clause.aligned + .with(\.elements, .decls([])) + .withTrailingNewline + } + } + + return parent.enclosingIfConfig } } extension MemberBlockItemListSyntax { - var enclosingIfConfig: IfConfigDeclSyntax? { + public var enclosingIfConfig: IfConfigDeclSyntax? { if let parent = parent?.as(IfConfigClauseSyntax.self) { return parent.enclosingIfConfig } @@ -50,20 +69,41 @@ extension MemberBlockItemListSyntax { extension MemberBlockItemSyntax { - var enclosingIfConfig: IfConfigDeclSyntax? { - if let parent = parent?.as(MemberBlockItemListSyntax.self) { - return parent.enclosingIfConfig + public var enclosingIfConfig: IfConfigDeclSyntax? { + guard var parent = parent?.as(MemberBlockItemListSyntax.self) else { + return nil } - return nil + + parent.replaceSubrange( + parent.startIndex ..< parent.endIndex, + with: CollectionOfOne(trimmed.withLeadingNewline) + ) + + return parent.enclosingIfConfig } } extension DeclSyntaxProtocol { - var enclosingIfConfig: IfConfigDeclSyntax? { + public var enclosingIfConfig: IfConfigDeclSyntax? { if let parent = parent?.as(MemberBlockItemSyntax.self) { return parent.enclosingIfConfig } return nil } + + public func applyingEnclosingIfConfig( + to members: MemberBlockItemListSyntax + ) -> IfConfigDeclSyntax? { + guard var parent = parent?.parent?.as(MemberBlockItemListSyntax.self) else { + return nil + } + + parent.replaceSubrange( + parent.startIndex ..< parent.endIndex, + with: members + ) + + return parent.withLeadingNewline.enclosingIfConfig + } } diff --git a/Sources/PrincipleMacros/Syntax/Extensions/WithAttributesSyntax+Availability.swift b/Sources/PrincipleMacros/Syntax/Extensions/WithAttributesSyntax+Availability.swift index 04acd78..91faea1 100644 --- a/Sources/PrincipleMacros/Syntax/Extensions/WithAttributesSyntax+Availability.swift +++ b/Sources/PrincipleMacros/Syntax/Extensions/WithAttributesSyntax+Availability.swift @@ -10,14 +10,14 @@ import SwiftSyntaxMacros extension WithAttributesSyntax { - var availability: AttributeListSyntax? { + public var availability: AttributeListSyntax? { attributes.availability } } extension AttributeListSyntax { - var availability: Self? { + public var availability: Self? { let elements = compactMap(\.availability) return elements.isEmpty ? nil : AttributeListSyntax(elements) } @@ -25,7 +25,7 @@ extension AttributeListSyntax { extension AttributeListSyntax.Element { - var availability: Self? { + public var availability: Self? { switch self { case let .attribute(attribute): if let availability = attribute.availability { diff --git a/Sources/PrincipleMacros/Syntax/Custom/BasicDeclSyntax.swift b/Sources/PrincipleMacros/Syntax/Protocols/BasicDeclSyntax.swift similarity index 100% rename from Sources/PrincipleMacros/Syntax/Custom/BasicDeclSyntax.swift rename to Sources/PrincipleMacros/Syntax/Protocols/BasicDeclSyntax.swift diff --git a/Sources/PrincipleMacros/Syntax/Custom/StatefulDeclSyntax.swift b/Sources/PrincipleMacros/Syntax/Protocols/StatefulDeclSyntax.swift similarity index 100% rename from Sources/PrincipleMacros/Syntax/Custom/StatefulDeclSyntax.swift rename to Sources/PrincipleMacros/Syntax/Protocols/StatefulDeclSyntax.swift diff --git a/Sources/PrincipleMacros/Syntax/Custom/TypeDeclSyntax.swift b/Sources/PrincipleMacros/Syntax/Protocols/TypeDeclSyntax.swift similarity index 100% rename from Sources/PrincipleMacros/Syntax/Custom/TypeDeclSyntax.swift rename to Sources/PrincipleMacros/Syntax/Protocols/TypeDeclSyntax.swift From 600d256cb1a6ea812bda45141f9885824d7f93e8 Mon Sep 17 00:00:00 2001 From: Kamil Strzelecki Date: Sat, 22 Nov 2025 11:59:14 +0100 Subject: [PATCH 16/49] [SwiftFormat] Applied formatting --- .../Extensions/IfConfigDeclSyntax+EnclosingIfConfig.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/PrincipleMacros/Syntax/Extensions/IfConfigDeclSyntax+EnclosingIfConfig.swift b/Sources/PrincipleMacros/Syntax/Extensions/IfConfigDeclSyntax+EnclosingIfConfig.swift index bdd8ab5..186fb52 100644 --- a/Sources/PrincipleMacros/Syntax/Extensions/IfConfigDeclSyntax+EnclosingIfConfig.swift +++ b/Sources/PrincipleMacros/Syntax/Extensions/IfConfigDeclSyntax+EnclosingIfConfig.swift @@ -44,10 +44,10 @@ extension IfConfigClauseSyntax { } for (index, clause) in zip(parent.indices, parent) { - if clause == self { - parent[index] = aligned + parent[index] = if clause == self { + aligned } else { - parent[index] = clause.aligned + clause.aligned .with(\.elements, .decls([])) .withTrailingNewline } From 2dc4d9bba58352c8aa5ab538c52abc4b9e32fa7a Mon Sep 17 00:00:00 2001 From: Kamil Strzelecki Date: Sat, 22 Nov 2025 12:02:34 +0100 Subject: [PATCH 17/49] - --- .../Extensions/IfConfigDeclSyntax+EnclosingIfConfig.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/PrincipleMacros/Syntax/Extensions/IfConfigDeclSyntax+EnclosingIfConfig.swift b/Sources/PrincipleMacros/Syntax/Extensions/IfConfigDeclSyntax+EnclosingIfConfig.swift index 186fb52..d50b045 100644 --- a/Sources/PrincipleMacros/Syntax/Extensions/IfConfigDeclSyntax+EnclosingIfConfig.swift +++ b/Sources/PrincipleMacros/Syntax/Extensions/IfConfigDeclSyntax+EnclosingIfConfig.swift @@ -45,7 +45,7 @@ extension IfConfigClauseSyntax { for (index, clause) in zip(parent.indices, parent) { parent[index] = if clause == self { - aligned + clause.aligned } else { clause.aligned .with(\.elements, .decls([])) From d252434b1f109a54816fd8a45688a9d32f68d3ff Mon Sep 17 00:00:00 2001 From: Kamil Strzelecki Date: Sat, 22 Nov 2025 12:02:38 +0100 Subject: [PATCH 18/49] - --- .../Parameters/ParameterExtractorTests.swift | 7 - .../Parsers/EnumCasesParserTests.swift | 10 +- .../Parsers/PropertiesListTests.swift | 6 +- .../Parsers/PropertiesParserTests.swift | 13 +- .../CamelCaseNotationTests.swift | 0 .../ClosureTypeSyntaxTests.swift | 0 .../GlobalActorIsolationTests.swift | 10 ++ .../ClassDeclSyntaxTests.swift | 20 +-- .../ClosureExprSyntaxTests.swift | 0 .../{ => Extensions}/ExprSyntaxTests.swift | 0 .../Extensions/IfConfigDeclSyntaxTests.swift | 165 ++++++++++++++++++ .../{ => Extensions}/TypeSyntaxTests.swift | 0 12 files changed, 196 insertions(+), 35 deletions(-) rename Tests/PrincipleMacrosTests/Syntax/{ => Concepts}/CamelCaseNotationTests.swift (100%) rename Tests/PrincipleMacrosTests/Syntax/{ => Concepts}/ClosureTypeSyntaxTests.swift (100%) rename Tests/PrincipleMacrosTests/Syntax/{ => Concepts}/GlobalActorIsolationTests.swift (91%) rename Tests/PrincipleMacrosTests/Syntax/{ => Extensions}/ClassDeclSyntaxTests.swift (73%) rename Tests/PrincipleMacrosTests/Syntax/{ => Extensions}/ClosureExprSyntaxTests.swift (100%) rename Tests/PrincipleMacrosTests/Syntax/{ => Extensions}/ExprSyntaxTests.swift (100%) create mode 100644 Tests/PrincipleMacrosTests/Syntax/Extensions/IfConfigDeclSyntaxTests.swift rename Tests/PrincipleMacrosTests/Syntax/{ => Extensions}/TypeSyntaxTests.swift (100%) diff --git a/Tests/PrincipleMacrosTests/Parameters/ParameterExtractorTests.swift b/Tests/PrincipleMacrosTests/Parameters/ParameterExtractorTests.swift index b7cda1b..9cf2af5 100644 --- a/Tests/PrincipleMacrosTests/Parameters/ParameterExtractorTests.swift +++ b/Tests/PrincipleMacrosTests/Parameters/ParameterExtractorTests.swift @@ -128,13 +128,6 @@ extension ParameterExtractorTests { #expect(extracted?.standardizedIsolationType?.trimmedDescription == isolation) } - @Test - func missingGlobalActorExtraction() throws { - let extractor = try makeExtractor(from: "#MyMacro()") - let extracted = try extractor.globalActorIsolation(withLabel: "isolation") - #expect(extracted == nil) - } - @Test func explicitNilGlobalActorExtraction() throws { let extractor = try makeExtractor(from: "#MyMacro(isolation: nil)") diff --git a/Tests/PrincipleMacrosTests/Parsers/EnumCasesParserTests.swift b/Tests/PrincipleMacrosTests/Parsers/EnumCasesParserTests.swift index 65ea7f9..c19d039 100644 --- a/Tests/PrincipleMacrosTests/Parsers/EnumCasesParserTests.swift +++ b/Tests/PrincipleMacrosTests/Parsers/EnumCasesParserTests.swift @@ -12,14 +12,12 @@ import Testing internal struct EnumCasesParserTests { - private let context = BasicMacroExpansionContext() - @Test func withoutAssociatedValue() throws { let decl: DeclSyntax = """ case myCase """ - let enumCase = try #require(EnumCasesParser.parse(declaration: decl, in: context).first) + let enumCase = try #require(EnumCasesParser.parse(declaration: decl).first) #expect(enumCase.trimmedName.description == "myCase") #expect(enumCase.associatedValues.isEmpty) } @@ -29,7 +27,7 @@ internal struct EnumCasesParserTests { let decl: DeclSyntax = """ case myCase(Int?) """ - let enumCase = try #require(EnumCasesParser.parse(declaration: decl, in: context).first) + let enumCase = try #require(EnumCasesParser.parse(declaration: decl).first) #expect(enumCase.trimmedName.description == "myCase") let associatedValue0 = try #require(enumCase.associatedValues.first) @@ -42,7 +40,7 @@ internal struct EnumCasesParserTests { let decl: DeclSyntax = """ case myCase(values: [String]) """ - let enumCase = try #require(EnumCasesParser.parse(declaration: decl, in: context).first) + let enumCase = try #require(EnumCasesParser.parse(declaration: decl).first) #expect(enumCase.trimmedName.description == "myCase") let associatedValue0 = try #require(enumCase.associatedValues.first) @@ -55,7 +53,7 @@ internal struct EnumCasesParserTests { let decl: DeclSyntax = """ case myCase(value: Int?, [String]) """ - let enumCase = try #require(EnumCasesParser.parse(declaration: decl, in: context).first) + let enumCase = try #require(EnumCasesParser.parse(declaration: decl).first) #expect(enumCase.trimmedName.description == "myCase") let associatedValue0 = try #require(enumCase.associatedValues.first) diff --git a/Tests/PrincipleMacrosTests/Parsers/PropertiesListTests.swift b/Tests/PrincipleMacrosTests/Parsers/PropertiesListTests.swift index a30ad93..4214982 100644 --- a/Tests/PrincipleMacrosTests/Parsers/PropertiesListTests.swift +++ b/Tests/PrincipleMacrosTests/Parsers/PropertiesListTests.swift @@ -7,7 +7,6 @@ // @testable import PrincipleMacros -import SwiftSyntaxMacroExpansion import Testing internal struct PropertiesListTests { @@ -34,9 +33,8 @@ internal struct PropertiesListTests { } """ - let classDecl = try #require(ClassDeclSyntax(decl)) - let context = BasicMacroExpansionContext() - self.list = PropertiesParser.parse(memberBlock: classDecl.memberBlock, in: context) + let classDecl = try #require(decl.as(ClassDeclSyntax.self)) + self.list = try PropertiesParser.parse(memberBlock: classDecl.memberBlock) } @Test diff --git a/Tests/PrincipleMacrosTests/Parsers/PropertiesParserTests.swift b/Tests/PrincipleMacrosTests/Parsers/PropertiesParserTests.swift index b5e0b85..95f2e2a 100644 --- a/Tests/PrincipleMacrosTests/Parsers/PropertiesParserTests.swift +++ b/Tests/PrincipleMacrosTests/Parsers/PropertiesParserTests.swift @@ -7,19 +7,16 @@ // @testable import PrincipleMacros -import SwiftSyntaxMacroExpansion import Testing internal struct PropertiesParserTests { - private let context = BasicMacroExpansionContext() - @Test func storedLet() throws { let decl: DeclSyntax = """ public internal(set) static let myLet: Int? """ - let property = try #require(PropertiesParser.parse(declaration: decl, in: context).first) + let property = try #require(PropertiesParser.parse(declaration: decl).first) #expect(property.kind == .stored) #expect(property.mutability == .immutable) #expect(property.accessControlLevel == .public) @@ -36,7 +33,7 @@ internal struct PropertiesParserTests { let decl: DeclSyntax = """ private(set) var myVar = "Hello, world!" """ - let property = try #require(PropertiesParser.parse(declaration: decl, in: context).first) + let property = try #require(PropertiesParser.parse(declaration: decl).first) #expect(property.kind == .stored) #expect(property.mutability == .mutable) #expect(property.accessControlLevel == nil) @@ -56,7 +53,7 @@ internal struct PropertiesParserTests { didSet { print("didSet", oldValue) } } """ - let property = try #require(PropertiesParser.parse(declaration: decl, in: context).first) + let property = try #require(PropertiesParser.parse(declaration: decl).first) #expect(property.kind == .stored) #expect(property.mutability == .mutable) #expect(property.accessControlLevel == nil) @@ -76,7 +73,7 @@ internal struct PropertiesParserTests { [1, 2, 3] } """ - let property = try #require(PropertiesParser.parse(declaration: decl, in: context).first) + let property = try #require(PropertiesParser.parse(declaration: decl).first) #expect(property.kind == .computed) #expect(property.mutability == .immutable) #expect(property.accessControlLevel == .fileprivate) @@ -96,7 +93,7 @@ internal struct PropertiesParserTests { set { _storage = newValue } } """ - let property = try #require(PropertiesParser.parse(declaration: decl, in: context).first) + let property = try #require(PropertiesParser.parse(declaration: decl).first) #expect(property.kind == .computed) #expect(property.mutability == .mutable) #expect(property.accessControlLevel == nil) diff --git a/Tests/PrincipleMacrosTests/Syntax/CamelCaseNotationTests.swift b/Tests/PrincipleMacrosTests/Syntax/Concepts/CamelCaseNotationTests.swift similarity index 100% rename from Tests/PrincipleMacrosTests/Syntax/CamelCaseNotationTests.swift rename to Tests/PrincipleMacrosTests/Syntax/Concepts/CamelCaseNotationTests.swift diff --git a/Tests/PrincipleMacrosTests/Syntax/ClosureTypeSyntaxTests.swift b/Tests/PrincipleMacrosTests/Syntax/Concepts/ClosureTypeSyntaxTests.swift similarity index 100% rename from Tests/PrincipleMacrosTests/Syntax/ClosureTypeSyntaxTests.swift rename to Tests/PrincipleMacrosTests/Syntax/Concepts/ClosureTypeSyntaxTests.swift diff --git a/Tests/PrincipleMacrosTests/Syntax/GlobalActorIsolationTests.swift b/Tests/PrincipleMacrosTests/Syntax/Concepts/GlobalActorIsolationTests.swift similarity index 91% rename from Tests/PrincipleMacrosTests/Syntax/GlobalActorIsolationTests.swift rename to Tests/PrincipleMacrosTests/Syntax/Concepts/GlobalActorIsolationTests.swift index 45963e5..7ef8485 100644 --- a/Tests/PrincipleMacrosTests/Syntax/GlobalActorIsolationTests.swift +++ b/Tests/PrincipleMacrosTests/Syntax/Concepts/GlobalActorIsolationTests.swift @@ -75,6 +75,16 @@ internal enum GlobalActorIsolationTests { #expect(isolation?.trimmedNonisolatedModifier?.trimmedDescription == "nonisolated") } + @Test + func withNonisolatedNonsendingModifierInStructWithGlobalActor() throws { + let decl: DeclSyntax = "nonisolated(nonsending) func test() {}" + let functionDecl = try #require(decl.as(FunctionDeclSyntax.self)) + let structDecl: DeclSyntax = "@MyActor struct MyStruct {}" + let lexicalContext = [Syntax(structDecl)] + let isolation = GlobalActorIsolation.resolved(for: functionDecl, in: lexicalContext) + #expect(isolation?.trimmedNonisolatedModifier?.trimmedDescription == "nonisolated(nonsending)") + } + @Test func inNestedStructWithGlobalActor() throws { let decl: DeclSyntax = "var test = 123" diff --git a/Tests/PrincipleMacrosTests/Syntax/ClassDeclSyntaxTests.swift b/Tests/PrincipleMacrosTests/Syntax/Extensions/ClassDeclSyntaxTests.swift similarity index 73% rename from Tests/PrincipleMacrosTests/Syntax/ClassDeclSyntaxTests.swift rename to Tests/PrincipleMacrosTests/Syntax/Extensions/ClassDeclSyntaxTests.swift index 614264e..af3ba91 100644 --- a/Tests/PrincipleMacrosTests/Syntax/ClassDeclSyntaxTests.swift +++ b/Tests/PrincipleMacrosTests/Syntax/Extensions/ClassDeclSyntaxTests.swift @@ -15,16 +15,16 @@ internal struct ClassDeclSyntaxTests { func withoutInheritenceClause() throws { let decl: DeclSyntax = "class MyClass {}" let classDecl = try #require(decl.as(ClassDeclSyntax.self)) - let result = classDecl.inferredSuperclass() - #expect(result == nil) + let inferredSuperclass = classDecl.inferredSuperclass() + #expect(inferredSuperclass == nil) } @Test func withProtocolConformance() throws { let decl: DeclSyntax = "class MyClass: Equatable, Hashable {}" let classDecl = try #require(decl.as(ClassDeclSyntax.self)) - let result = classDecl.inferredSuperclass() - #expect(result == nil) + let inferredSuperclass = classDecl.inferredSuperclass() + #expect(inferredSuperclass == nil) } // swiftlint:disable empty_line_after_type_declaration @@ -38,8 +38,8 @@ internal struct ClassDeclSyntaxTests { """ let classDecl = try #require(decl.as(ClassDeclSyntax.self)) - let result = classDecl.inferredSuperclass() - #expect(result?.description == "BaseClass") + let inferredSuperclass = classDecl.inferredSuperclass() + #expect(inferredSuperclass?.description == "BaseClass") } @Test @@ -53,8 +53,8 @@ internal struct ClassDeclSyntaxTests { """ let classDecl = try #require(decl.as(ClassDeclSyntax.self)) - let result = classDecl.inferredSuperclass() - #expect(result?.description == "BaseClass") + let inferredSuperclass = classDecl.inferredSuperclass() + #expect(inferredSuperclass?.description == "BaseClass") } @Test @@ -68,8 +68,8 @@ internal struct ClassDeclSyntaxTests { """ let classDecl = try #require(decl.as(ClassDeclSyntax.self)) - let result = classDecl.inferredSuperclass() - #expect(result == nil) + let inferredSuperclass = classDecl.inferredSuperclass() + #expect(inferredSuperclass == nil) } // swiftlint:enable empty_line_after_type_declaration diff --git a/Tests/PrincipleMacrosTests/Syntax/ClosureExprSyntaxTests.swift b/Tests/PrincipleMacrosTests/Syntax/Extensions/ClosureExprSyntaxTests.swift similarity index 100% rename from Tests/PrincipleMacrosTests/Syntax/ClosureExprSyntaxTests.swift rename to Tests/PrincipleMacrosTests/Syntax/Extensions/ClosureExprSyntaxTests.swift diff --git a/Tests/PrincipleMacrosTests/Syntax/ExprSyntaxTests.swift b/Tests/PrincipleMacrosTests/Syntax/Extensions/ExprSyntaxTests.swift similarity index 100% rename from Tests/PrincipleMacrosTests/Syntax/ExprSyntaxTests.swift rename to Tests/PrincipleMacrosTests/Syntax/Extensions/ExprSyntaxTests.swift diff --git a/Tests/PrincipleMacrosTests/Syntax/Extensions/IfConfigDeclSyntaxTests.swift b/Tests/PrincipleMacrosTests/Syntax/Extensions/IfConfigDeclSyntaxTests.swift new file mode 100644 index 0000000..ca19017 --- /dev/null +++ b/Tests/PrincipleMacrosTests/Syntax/Extensions/IfConfigDeclSyntaxTests.swift @@ -0,0 +1,165 @@ +// +// IfConfigDeclSyntaxTests.swift +// PrincipleMacros +// +// Created by Kamil Strzelecki on 21/11/2025. +// Copyright © 2025 Kamil Strzelecki. All rights reserved. +// + +@testable import PrincipleMacros +import Testing + +internal struct IfConfigDeclSyntaxTests { + + private func parseLastProperty(in decl: DeclSyntax) throws -> Property { + let classDecl = try #require(decl.as(ClassDeclSyntax.self)) + let properties = try PropertiesParser.parse(memberBlock: classDecl.memberBlock) + return try #require(properties.last) + } + + // swiftlint:disable empty_line_after_type_declaration + + @Test + func withoutIfConfig() throws { + let decl: DeclSyntax = """ + class MyClass { + var test = 123 + } + """ + + let ifConfig = try parseLastProperty(in: decl).enclosingIfConfig + #expect(ifConfig == nil) + } + + @Test + func withIfConfig() throws { + let decl: DeclSyntax = """ + class MyClass { + #if os(macOS) + var other = "hello" + var test = 123 + #endif + } + """ + + let expectation = """ + #if os(macOS) + var test = 123 + #endif + """ + + let ifConfig = try parseLastProperty(in: decl).enclosingIfConfig + #expect(ifConfig?.description == expectation) + } + + @Test + func withElseIfConfig() throws { + let decl: DeclSyntax = """ + class MyClass { + #if os(iOS) + var other = "hello" + #elseif os(macOS) + var test = 123 + #endif + } + """ + + let expectation = """ + #if os(iOS) + #elseif os(macOS) + var test = 123 + #endif + """ + + let ifConfig = try parseLastProperty(in: decl).enclosingIfConfig + #expect(ifConfig?.description == expectation) + } + + @Test + func withNestedIfConfig() throws { + let decl: DeclSyntax = """ + class MyClass { + #if DEBUG + var other = "hello" + #if os(macOS) + var test = 123 + #endif + #endif + } + """ + + let expectation = """ + #if DEBUG + #if os(macOS) + var test = 123 + #endif + #endif + """ + + let ifConfig = try parseLastProperty(in: decl).enclosingIfConfig + #expect(ifConfig?.description == expectation) + } + + @Test + func withNestedElseConfig() throws { + let decl: DeclSyntax = """ + class MyClass { + #if DEBUG + var other = "hello" + #else + #if os(macOS) + var test = 123 + #endif + #endif + } + """ + + let expectation = """ + #if DEBUG + #else + #if os(macOS) + var test = 123 + #endif + #endif + """ + + let ifConfig = try parseLastProperty(in: decl).enclosingIfConfig + #expect(ifConfig?.description == expectation) + } + + @Test + func applyToNewMembers() throws{ + let decl: DeclSyntax = """ + class MyClass { + #if DEBUG + var other = "hello" + #else + #if os(macOS) + var test = 123 + #endif + #endif + } + """ + + let newMembers: MemberBlockItemListSyntax = """ + var replacement = "Hello" + func test() {} + """ + + let expectation = """ + #if DEBUG + #else + #if os(macOS) + var replacement = "Hello" + func test() {} + #endif + #endif + """ + + let property = try parseLastProperty(in: decl) + let ifConfig = property.underlying.applyingEnclosingIfConfig(to: newMembers) + #expect(ifConfig?.description == expectation) + } + + // swiftlint:enable empty_line_after_type_declaration +} diff --git a/Tests/PrincipleMacrosTests/Syntax/TypeSyntaxTests.swift b/Tests/PrincipleMacrosTests/Syntax/Extensions/TypeSyntaxTests.swift similarity index 100% rename from Tests/PrincipleMacrosTests/Syntax/TypeSyntaxTests.swift rename to Tests/PrincipleMacrosTests/Syntax/Extensions/TypeSyntaxTests.swift From a37ce11c08e8677acd26dc69d3211f77b8414c78 Mon Sep 17 00:00:00 2001 From: Kamil Strzelecki Date: Sat, 22 Nov 2025 12:02:38 +0100 Subject: [PATCH 19/49] [SwiftFormat] Applied formatting --- .../Syntax/Extensions/IfConfigDeclSyntaxTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/PrincipleMacrosTests/Syntax/Extensions/IfConfigDeclSyntaxTests.swift b/Tests/PrincipleMacrosTests/Syntax/Extensions/IfConfigDeclSyntaxTests.swift index ca19017..214ae46 100644 --- a/Tests/PrincipleMacrosTests/Syntax/Extensions/IfConfigDeclSyntaxTests.swift +++ b/Tests/PrincipleMacrosTests/Syntax/Extensions/IfConfigDeclSyntaxTests.swift @@ -128,7 +128,7 @@ internal struct IfConfigDeclSyntaxTests { } @Test - func applyToNewMembers() throws{ + func applyToNewMembers() throws { let decl: DeclSyntax = """ class MyClass { #if DEBUG From 39bedd12548450da1adfa5320cf3466430684f0d Mon Sep 17 00:00:00 2001 From: Kamil Strzelecki Date: Sat, 22 Nov 2025 12:27:08 +0100 Subject: [PATCH 20/49] - --- .../IfConfigDeclSyntax+Availability.swift | 21 +++++-- .../Syntax/Extensions/AvailabilityTests.swift | 59 +++++++++++++++++++ 2 files changed, 75 insertions(+), 5 deletions(-) create mode 100644 Tests/PrincipleMacrosTests/Syntax/Extensions/AvailabilityTests.swift diff --git a/Sources/PrincipleMacros/Syntax/Extensions/IfConfigDeclSyntax+Availability.swift b/Sources/PrincipleMacros/Syntax/Extensions/IfConfigDeclSyntax+Availability.swift index e2281d1..468ee40 100644 --- a/Sources/PrincipleMacros/Syntax/Extensions/IfConfigDeclSyntax+Availability.swift +++ b/Sources/PrincipleMacros/Syntax/Extensions/IfConfigDeclSyntax+Availability.swift @@ -11,13 +11,24 @@ import SwiftSyntaxMacros extension IfConfigDeclSyntax { public var availability: Self? { - guard clauses.contains(where: { $0.availability != nil }) else { - return nil + var elements = [IfConfigClauseSyntax]() + var isEmpty = true + + for clause in clauses { + if let availability = clause.availability { + elements.append(availability) + isEmpty = false + } else { + elements.append(clause.with(\.elements, .attributes([]))) + } } - let elements = clauses.compactMap { clause in - clause.availability ?? clause.with(\.elements, .attributes([])) + + guard !isEmpty else { + return nil } - return with(\.clauses, IfConfigClauseListSyntax(elements)) + + let clauses = IfConfigClauseListSyntax(elements) + return with(\.clauses, clauses) } } diff --git a/Tests/PrincipleMacrosTests/Syntax/Extensions/AvailabilityTests.swift b/Tests/PrincipleMacrosTests/Syntax/Extensions/AvailabilityTests.swift new file mode 100644 index 0000000..91613dc --- /dev/null +++ b/Tests/PrincipleMacrosTests/Syntax/Extensions/AvailabilityTests.swift @@ -0,0 +1,59 @@ +// +// AvailabilityTests.swift +// PrincipleMacros +// +// Created by Kamil Strzelecki on 22/11/2025. +// Copyright © 2025 Kamil Strzelecki. All rights reserved. +// + +@testable import PrincipleMacros +import Testing + +internal struct AvailabilityTests { + + @Test + func withoutAvailability() throws { + let decl: DeclSyntax = """ + @MainActor @Observable + class MyClass {} + """ + + let classDecl = try #require(decl.as(ClassDeclSyntax.self)) + #expect(classDecl.availability == nil) + } + + @Test + func withAvailability() throws { + let decl: DeclSyntax = """ + @MainActor @Observable + @available(iOS 26, *) + class MyClass {} + """ + + let classDecl = try #require(decl.as(ClassDeclSyntax.self)) + #expect(classDecl.availability?.trimmedDescription == "@available(iOS 26, *)") + } + + @Test + func withIfConfig() throws { + let decl: DeclSyntax = """ + #if os(macOS) + @MainActor + #else + @Observable + @available(iOS 26, *) + #endif + class MyClass {} + """ + + let expectation = """ + #if os(macOS) + #else + @available(iOS 26, *) + #endif + """ + + let classDecl = try #require(decl.as(ClassDeclSyntax.self)) + #expect(classDecl.availability?.trimmedDescription == expectation) + } +} From 58d9aa73dbb5e5b91644c04221d18edaee2c9412 Mon Sep 17 00:00:00 2001 From: Kamil Strzelecki Date: Sat, 22 Nov 2025 13:57:31 +0100 Subject: [PATCH 21/49] - --- .../Parameters/ParameterExtractor.swift | 40 ++++++++++++++++--- .../Syntax/Extensions/ExprSyntax.swift | 31 +++++++------- .../IfConfigDeclSyntax+Availability.swift | 2 +- .../Parameters/ParameterExtractorTests.swift | 23 +++++++++++ .../Extensions/ClassDeclSyntaxTests.swift | 2 +- 5 files changed, 77 insertions(+), 21 deletions(-) diff --git a/Sources/PrincipleMacros/Parameters/ParameterExtractor.swift b/Sources/PrincipleMacros/Parameters/ParameterExtractor.swift index 2b16262..bc0176a 100644 --- a/Sources/PrincipleMacros/Parameters/ParameterExtractor.swift +++ b/Sources/PrincipleMacros/Parameters/ParameterExtractor.swift @@ -165,13 +165,15 @@ extension ParameterExtractor { return .nonisolated(trimmedModifer: isolation) } - if let memberAccessExpression = MemberAccessExprSyntax(expression), - let explicitType = memberAccessExpression.base?.inferredType, - memberAccessExpression.referencesBaseType { - return .isolated(standardizedType: explicitType.standardized) + guard let memberAccessExpression = MemberAccessExprSyntax(expression), + let globalActorType = memberAccessExpression.baseTypeReference + else { + throw ParameterExtractionError.unexpectedSyntaxType } - throw ParameterExtractionError.unexpectedSyntaxType + return .isolated( + standardizedType: globalActorType.standardized + ) } public func requiredGlobalActorIsolation( @@ -183,3 +185,31 @@ extension ParameterExtractor { return isolation } } + +extension ParameterExtractor { + + public func type( + withLabel label: TokenSyntax? + ) throws -> TypeSyntax? { + guard let expression = expression(withLabel: label) else { + return nil + } + + guard let memberAccessExpression = MemberAccessExprSyntax(expression), + let type = memberAccessExpression.baseTypeReference + else { + throw ParameterExtractionError.unexpectedSyntaxType + } + + return type + } + + public func requiredType( + withLabel label: TokenSyntax? + ) throws -> TypeSyntax { + guard let type = try type(withLabel: label) else { + throw ParameterExtractionError.missingRequirement + } + return type + } +} diff --git a/Sources/PrincipleMacros/Syntax/Extensions/ExprSyntax.swift b/Sources/PrincipleMacros/Syntax/Extensions/ExprSyntax.swift index b8d385d..c6b0190 100644 --- a/Sources/PrincipleMacros/Syntax/Extensions/ExprSyntax.swift +++ b/Sources/PrincipleMacros/Syntax/Extensions/ExprSyntax.swift @@ -66,10 +66,10 @@ extension StringLiteralExprSyntax { extension OptionalChainingExprSyntax { public var inferredType: TypeSyntax? { - guard let inferredWrappedType else { - return nil + if let inferredWrappedType { + return "Optional<\(inferredWrappedType)>" } - return "Optional<\(inferredWrappedType)>" + return nil } public var inferredWrappedType: TypeSyntax? { @@ -80,10 +80,10 @@ extension OptionalChainingExprSyntax { extension ArrayExprSyntax { public var inferredType: TypeSyntax? { - guard let inferredElementType else { - return nil + if let inferredElementType { + return "Array<\(inferredElementType)>" } - return "Array<\(inferredElementType)>" + return nil } public var inferredElementType: TypeSyntax? { @@ -94,10 +94,10 @@ extension ArrayExprSyntax { extension DictionaryExprSyntax { public var inferredType: TypeSyntax? { - guard let inferredKeyType, let inferredValueType else { - return nil + if let inferredKeyType, let inferredValueType { + return "Dictionary<\(inferredKeyType), \(inferredValueType)>" } - return "Dictionary<\(inferredKeyType), \(inferredValueType)>" + return nil } public var inferredKeyType: TypeSyntax? { @@ -129,10 +129,10 @@ extension FunctionCallExprSyntax { extension GenericSpecializationExprSyntax { public var inferredType: TypeSyntax? { - guard let inferredType = expression.inferredType else { - return nil + if let inferredType = expression.inferredType { + return "\(inferredType)\(genericArgumentClause.standardized)" } - return "\(inferredType)\(genericArgumentClause.standardized)" + return nil } } @@ -148,8 +148,11 @@ extension MemberAccessExprSyntax { return first } - public var referencesBaseType: Bool { - declName.baseName.tokenKind == .keyword(.self) + public var baseTypeReference: TypeSyntax? { + if let base, declName.baseName.tokenKind == .keyword(.self) { + return "\(base.trimmed)" + } + return nil } } diff --git a/Sources/PrincipleMacros/Syntax/Extensions/IfConfigDeclSyntax+Availability.swift b/Sources/PrincipleMacros/Syntax/Extensions/IfConfigDeclSyntax+Availability.swift index 468ee40..a071284 100644 --- a/Sources/PrincipleMacros/Syntax/Extensions/IfConfigDeclSyntax+Availability.swift +++ b/Sources/PrincipleMacros/Syntax/Extensions/IfConfigDeclSyntax+Availability.swift @@ -34,7 +34,7 @@ extension IfConfigDeclSyntax { extension IfConfigClauseSyntax { - var availability: Self? { + public var availability: Self? { if let availability = elements?.availability { with(\.elements, availability) } else { diff --git a/Tests/PrincipleMacrosTests/Parameters/ParameterExtractorTests.swift b/Tests/PrincipleMacrosTests/Parameters/ParameterExtractorTests.swift index 9cf2af5..ff9adb9 100644 --- a/Tests/PrincipleMacrosTests/Parameters/ParameterExtractorTests.swift +++ b/Tests/PrincipleMacrosTests/Parameters/ParameterExtractorTests.swift @@ -143,3 +143,26 @@ extension ParameterExtractorTests { } } } + +extension ParameterExtractorTests { + + @Test( + arguments: [ + "MyType", + "SomeType.MyType" + ] + ) + func typeExtraction(_ type: String) throws { + let extractor = try makeExtractor(from: "#MyMacro(type: \(raw: type).self)") + let extracted = try extractor.type(withLabel: "type") + #expect(extracted?.trimmedDescription == type) + } + + @Test + func unexpectedSyntaxWhenPerformingTypeExtraction() throws { + let extractor = try makeExtractor(from: #"#MyMacro(type: MainActor.Type)"#) + #expect(throws: ParameterExtractionError.unexpectedSyntaxType) { + try extractor.type(withLabel: "type") + } + } +} diff --git a/Tests/PrincipleMacrosTests/Syntax/Extensions/ClassDeclSyntaxTests.swift b/Tests/PrincipleMacrosTests/Syntax/Extensions/ClassDeclSyntaxTests.swift index af3ba91..6d11ecc 100644 --- a/Tests/PrincipleMacrosTests/Syntax/Extensions/ClassDeclSyntaxTests.swift +++ b/Tests/PrincipleMacrosTests/Syntax/Extensions/ClassDeclSyntaxTests.swift @@ -12,7 +12,7 @@ import Testing internal struct ClassDeclSyntaxTests { @Test - func withoutInheritenceClause() throws { + func withoutInheritanceClause() throws { let decl: DeclSyntax = "class MyClass {}" let classDecl = try #require(decl.as(ClassDeclSyntax.self)) let inferredSuperclass = classDecl.inferredSuperclass() From 9d7a4835024b9e310daf5d1964a5ab22ff59900a Mon Sep 17 00:00:00 2001 From: Kamil Strzelecki Date: Sat, 22 Nov 2025 14:13:53 +0100 Subject: [PATCH 22/49] - --- .../Syntax/Extensions/TypeSyntax.swift | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/Sources/PrincipleMacros/Syntax/Extensions/TypeSyntax.swift b/Sources/PrincipleMacros/Syntax/Extensions/TypeSyntax.swift index cfdcc3e..52c2930 100644 --- a/Sources/PrincipleMacros/Syntax/Extensions/TypeSyntax.swift +++ b/Sources/PrincipleMacros/Syntax/Extensions/TypeSyntax.swift @@ -15,6 +15,26 @@ extension TypeSyntax { } } +extension AttributeSyntax { + + public func isLike(_ other: AttributeSyntax) -> Bool { + attributeName.isLike(other.attributeName) + } +} + +extension AttributeListSyntax { + + public func contains(like attribute: AttributeSyntax) -> Bool { + first(like: attribute) != nil + } + + public func first(like attribute: AttributeSyntax) -> AttributeSyntax? { + lazy.compactMap(\.attribute).first { element in + element.isLike(attribute) + } + } +} + extension TypeSyntax { public var standardized: TypeSyntax { From a7567a0932074c2924e3ddc682b075ccddab81a8 Mon Sep 17 00:00:00 2001 From: Kamil Strzelecki Date: Sun, 23 Nov 2025 11:40:04 +0100 Subject: [PATCH 23/49] - --- .../Parameters/ParameterExtractor.swift | 26 +++++++++++++++++++ .../Syntax/Extensions/ClassDeclSyntax.swift | 8 +++--- .../Parameters/ParameterExtractorTests.swift | 23 ++++++++++++++++ 3 files changed, 54 insertions(+), 3 deletions(-) diff --git a/Sources/PrincipleMacros/Parameters/ParameterExtractor.swift b/Sources/PrincipleMacros/Parameters/ParameterExtractor.swift index bc0176a..af8e50c 100644 --- a/Sources/PrincipleMacros/Parameters/ParameterExtractor.swift +++ b/Sources/PrincipleMacros/Parameters/ParameterExtractor.swift @@ -121,6 +121,32 @@ extension ParameterExtractor { } } +extension ParameterExtractor { + + public func rawBool( + withLabel label: TokenSyntax? + ) throws -> Bool? { + guard let expression = expression(withLabel: label) else { + return nil + } + + guard let bool = expression.as(BooleanLiteralExprSyntax.self) else { + throw ParameterExtractionError.unexpectedSyntaxType + } + + return bool.literal.tokenKind == .keyword(.true) + } + + public func requiredRawBool( + withLabel label: TokenSyntax? + ) throws -> Bool? { + guard let bool = try rawBool(withLabel: label) else { + throw ParameterExtractionError.missingRequirement + } + return bool + } +} + extension ParameterExtractor { public func rawString( diff --git a/Sources/PrincipleMacros/Syntax/Extensions/ClassDeclSyntax.swift b/Sources/PrincipleMacros/Syntax/Extensions/ClassDeclSyntax.swift index 94831da..f009022 100644 --- a/Sources/PrincipleMacros/Syntax/Extensions/ClassDeclSyntax.swift +++ b/Sources/PrincipleMacros/Syntax/Extensions/ClassDeclSyntax.swift @@ -10,6 +10,10 @@ import SwiftSyntaxMacros extension ClassDeclSyntax { + public var firstInheritedType: TypeSyntax? { + inheritanceClause?.inheritedTypes.first?.type + } + public func inferredSuperclass() -> TypeSyntax? { let superclassFinder = SuperclassFinder(for: self) return superclassFinder.find()?.trimmed @@ -29,9 +33,7 @@ extension ClassDeclSyntax { } func find() -> TypeSyntax? { - guard let inheritanceClause = classDecl.inheritanceClause, - let firstInheritedType = inheritanceClause.inheritedTypes.first?.type - else { + guard let firstInheritedType = classDecl.firstInheritedType else { return nil } diff --git a/Tests/PrincipleMacrosTests/Parameters/ParameterExtractorTests.swift b/Tests/PrincipleMacrosTests/Parameters/ParameterExtractorTests.swift index ff9adb9..8d3c278 100644 --- a/Tests/PrincipleMacrosTests/Parameters/ParameterExtractorTests.swift +++ b/Tests/PrincipleMacrosTests/Parameters/ParameterExtractorTests.swift @@ -96,6 +96,29 @@ extension ParameterExtractorTests { } } +extension ParameterExtractorTests { + + @Test( + arguments: [ + true, + false + ] + ) + func rawBoolExtraction(_ bool: Bool) throws { + let extractor = try makeExtractor(from: "#MyMacro(boolean: \(raw: bool))") + let extracted = try extractor.rawBool(withLabel: "boolean") + #expect(extracted == bool) + } + + @Test + func unexpectedSyntaxWhenPerformingRawBoolExtraction() throws { + let extractor = try makeExtractor(from: #"#MyMacro(boolean: value)"#) + #expect(throws: ParameterExtractionError.unexpectedSyntaxType) { + try extractor.rawBool(withLabel: "boolean") + } + } +} + extension ParameterExtractorTests { @Test From 46b177639aa92a1bf85c87300a320caca67fb187 Mon Sep 17 00:00:00 2001 From: Kamil Strzelecki Date: Sun, 23 Nov 2025 12:02:16 +0100 Subject: [PATCH 24/49] - --- .../PrincipleMacros/Diagnostics/DiagnosticsError.swift | 5 +++-- .../Diagnostics/MacroExpansionContext.swift | 10 ++++++---- .../Parameters/ParameterExtractor.swift | 4 ++-- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/Sources/PrincipleMacros/Diagnostics/DiagnosticsError.swift b/Sources/PrincipleMacros/Diagnostics/DiagnosticsError.swift index ba71be6..d251f7d 100644 --- a/Sources/PrincipleMacros/Diagnostics/DiagnosticsError.swift +++ b/Sources/PrincipleMacros/Diagnostics/DiagnosticsError.swift @@ -12,10 +12,11 @@ extension DiagnosticsError { public init( node: some SyntaxProtocol, - message: String + message: String, + fixIts: [FixIt] = [] ) { let message = MacroExpansionErrorMessage(message) - let diagnostic = Diagnostic(node: node, message: message) + let diagnostic = Diagnostic(node: node, message: message, fixIts: fixIts) self.init(diagnostics: [diagnostic]) } } diff --git a/Sources/PrincipleMacros/Diagnostics/MacroExpansionContext.swift b/Sources/PrincipleMacros/Diagnostics/MacroExpansionContext.swift index 3e3b5cc..1247afe 100644 --- a/Sources/PrincipleMacros/Diagnostics/MacroExpansionContext.swift +++ b/Sources/PrincipleMacros/Diagnostics/MacroExpansionContext.swift @@ -12,19 +12,21 @@ extension MacroExpansionContext { public func diagnose( node: some SyntaxProtocol, - errorMessage: String + errorMessage: String, + fixIts: [FixIt] = [] ) { let message = MacroExpansionErrorMessage(errorMessage) - let diagnostic = Diagnostic(node: node, message: message) + let diagnostic = Diagnostic(node: node, message: message, fixIts: fixIts) diagnose(diagnostic) } public func diagnose( node: some SyntaxProtocol, - warningMessage: String + warningMessage: String, + fixIts: [FixIt] = [] ) { let message = MacroExpansionWarningMessage(warningMessage) - let diagnostic = Diagnostic(node: node, message: message) + let diagnostic = Diagnostic(node: node, message: message, fixIts: fixIts) diagnose(diagnostic) } } diff --git a/Sources/PrincipleMacros/Parameters/ParameterExtractor.swift b/Sources/PrincipleMacros/Parameters/ParameterExtractor.swift index af8e50c..6710576 100644 --- a/Sources/PrincipleMacros/Parameters/ParameterExtractor.swift +++ b/Sources/PrincipleMacros/Parameters/ParameterExtractor.swift @@ -125,7 +125,7 @@ extension ParameterExtractor { public func rawBool( withLabel label: TokenSyntax? - ) throws -> Bool? { + ) throws -> Bool? { // swiftlint:disable:this discouraged_optional_boolean guard let expression = expression(withLabel: label) else { return nil } @@ -139,7 +139,7 @@ extension ParameterExtractor { public func requiredRawBool( withLabel label: TokenSyntax? - ) throws -> Bool? { + ) throws -> Bool { guard let bool = try rawBool(withLabel: label) else { throw ParameterExtractionError.missingRequirement } From ea7344a5bf6342fec92f83e3d7d5e6627a1e4a52 Mon Sep 17 00:00:00 2001 From: Kamil Strzelecki Date: Sun, 23 Nov 2025 12:13:41 +0100 Subject: [PATCH 25/49] - --- .../PrincipleMacros/Syntax/Extensions/SyntaxProtocol.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Sources/PrincipleMacros/Syntax/Extensions/SyntaxProtocol.swift b/Sources/PrincipleMacros/Syntax/Extensions/SyntaxProtocol.swift index 86e6fdd..ac95b7c 100644 --- a/Sources/PrincipleMacros/Syntax/Extensions/SyntaxProtocol.swift +++ b/Sources/PrincipleMacros/Syntax/Extensions/SyntaxProtocol.swift @@ -33,4 +33,9 @@ extension SyntaxProtocol { public func withLeadingNewlines(_ count: Int = 2) -> Self { with(\.leadingTrivia, .newlines(count)) } + + public func withTrivia(from other: some SyntaxProtocol) -> Self { + with(\.leadingTrivia, other.leadingTrivia) + .with(\.trailingTrivia, other.trailingTrivia) + } } From cd74d8516718e6cb6ba9d666766906340b02ab04 Mon Sep 17 00:00:00 2001 From: Kamil Strzelecki Date: Sun, 23 Nov 2025 12:19:10 +0100 Subject: [PATCH 26/49] - --- .github/workflows/pull-request.yml | 10 +++++----- .github/workflows/release.yml | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index b6e8af2..55b5592 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -12,7 +12,7 @@ concurrency: cancel-in-progress: true env: - XCODE_VERSION: "26.1" + XCODE_VERSION: "26.0" jobs: prepare: @@ -88,16 +88,16 @@ jobs: destination="platform=macOS,variant=Mac Catalyst" ;; ios) - destination="platform=iOS Simulator,name=iPhone 17 Pro Max,OS=26.1" + destination="platform=iOS Simulator,name=iPhone 17 Pro Max,OS=26.0" ;; tvos) - destination="platform=tvOS Simulator,name=Apple TV 4K (3rd generation),OS=26.1" + destination="platform=tvOS Simulator,name=Apple TV 4K (3rd generation),OS=26.0" ;; watchos) - destination="platform=watchOS Simulator,name=Apple Watch Series 11 (46mm),OS=26.1" + destination="platform=watchOS Simulator,name=Apple Watch Series 11 (46mm),OS=26.0" ;; visionos) - destination="platform=visionOS Simulator,name=Apple Vision Pro,OS=26.1" + destination="platform=visionOS Simulator,name=Apple Vision Pro,OS=26.0" ;; *) echo "Unknown platform: ${{ matrix.platform }}" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b076f96..80556d9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,7 +6,7 @@ on: - '*' env: - XCODE_VERSION: "26.1" + XCODE_VERSION: "26.0" jobs: release: From 8cf6797ddfdc8cf937cb320639a1b9cd7326d561 Mon Sep 17 00:00:00 2001 From: Kamil Strzelecki Date: Sun, 23 Nov 2025 12:24:40 +0100 Subject: [PATCH 27/49] - --- .github/workflows/pull-request.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 55b5592..ac7ec49 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -88,7 +88,7 @@ jobs: destination="platform=macOS,variant=Mac Catalyst" ;; ios) - destination="platform=iOS Simulator,name=iPhone 17 Pro Max,OS=26.0" + destination="platform=iOS Simulator,name=iPhone 17 Pro Max,OS=26.0.1" ;; tvos) destination="platform=tvOS Simulator,name=Apple TV 4K (3rd generation),OS=26.0" From b2ac2d1df8a98a6043b6bd591a6a1a0b47ffec7a Mon Sep 17 00:00:00 2001 From: Kamil Strzelecki Date: Sun, 23 Nov 2025 13:18:11 +0100 Subject: [PATCH 28/49] - --- .../Declarations/Types/ClassDeclBuilder.swift | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Sources/PrincipleMacros/Builders/Declarations/Types/ClassDeclBuilder.swift b/Sources/PrincipleMacros/Builders/Declarations/Types/ClassDeclBuilder.swift index e45daf4..0f1cbce 100644 --- a/Sources/PrincipleMacros/Builders/Declarations/Types/ClassDeclBuilder.swift +++ b/Sources/PrincipleMacros/Builders/Declarations/Types/ClassDeclBuilder.swift @@ -11,6 +11,7 @@ import SwiftSyntaxMacros public protocol ClassDeclBuilder: TypeDeclBuilder { var declaration: ClassDeclSyntax { get } + var inferredSuperclass: TypeSyntax? { get } } extension ClassDeclBuilder { @@ -18,4 +19,14 @@ extension ClassDeclBuilder { public var typeDeclaration: any TypeDeclSyntax { declaration } + + public var inferredSuperclass: TypeSyntax? { + nil + } + + public var inheritedOverrideModifier: TokenSyntax? { + inferredSuperclass != nil + ? TokenSyntax(.keyword(.override), presence: .present).withTrailingSpace + : nil + } } From 514fa368f72438885167e40440410a1426b4c3d7 Mon Sep 17 00:00:00 2001 From: Kamil Strzelecki Date: Sun, 23 Nov 2025 13:21:51 +0100 Subject: [PATCH 29/49] - --- .../Builders/Declarations/Types/ClassDeclBuilder.swift | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Sources/PrincipleMacros/Builders/Declarations/Types/ClassDeclBuilder.swift b/Sources/PrincipleMacros/Builders/Declarations/Types/ClassDeclBuilder.swift index 0f1cbce..5181b82 100644 --- a/Sources/PrincipleMacros/Builders/Declarations/Types/ClassDeclBuilder.swift +++ b/Sources/PrincipleMacros/Builders/Declarations/Types/ClassDeclBuilder.swift @@ -19,6 +19,9 @@ extension ClassDeclBuilder { public var typeDeclaration: any TypeDeclSyntax { declaration } +} + +extension ClassDeclBuilder { public var inferredSuperclass: TypeSyntax? { nil @@ -29,4 +32,8 @@ extension ClassDeclBuilder { ? TokenSyntax(.keyword(.override), presence: .present).withTrailingSpace : nil } + + public var inheritedFinalModifier: TokenSyntax? { + declaration.finalSpecifier?.trimmed.withTrailingSpace + } } From 3e565d4b9d3724f4031efa6da829d5e42b845e99 Mon Sep 17 00:00:00 2001 From: Kamil Strzelecki Date: Sun, 23 Nov 2025 13:37:17 +0100 Subject: [PATCH 30/49] - --- .../Syntax/Extensions/ClassDeclSyntax.swift | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Sources/PrincipleMacros/Syntax/Extensions/ClassDeclSyntax.swift b/Sources/PrincipleMacros/Syntax/Extensions/ClassDeclSyntax.swift index f009022..77392f2 100644 --- a/Sources/PrincipleMacros/Syntax/Extensions/ClassDeclSyntax.swift +++ b/Sources/PrincipleMacros/Syntax/Extensions/ClassDeclSyntax.swift @@ -10,35 +10,35 @@ import SwiftSyntaxMacros extension ClassDeclSyntax { - public var firstInheritedType: TypeSyntax? { - inheritanceClause?.inheritedTypes.first?.type + public var unverifiedInferredSuperclass: TypeSyntax? { + inheritanceClause?.inheritedTypes.first?.type.trimmed } public func inferredSuperclass() -> TypeSyntax? { - let superclassFinder = SuperclassFinder(for: self) - return superclassFinder.find()?.trimmed + let visitor = SubclassKeywordsVisitor(for: self) + return visitor.verifiedSuperclass() } } extension ClassDeclSyntax { - private final class SuperclassFinder: SyntaxVisitor { + private final class SubclassKeywordsVisitor: SyntaxVisitor { private let classDecl: ClassDeclSyntax - private var didFind = false + private var didVerify = false init(for classDecl: ClassDeclSyntax) { self.classDecl = classDecl super.init(viewMode: .sourceAccurate) } - func find() -> TypeSyntax? { - guard let firstInheritedType = classDecl.firstInheritedType else { + func verifiedSuperclass() -> TypeSyntax? { + guard let unverified = classDecl.unverifiedInferredSuperclass else { return nil } walk(classDecl) - return didFind ? firstInheritedType : nil + return didVerify ? unverified : nil } override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind { @@ -46,12 +46,12 @@ extension ClassDeclSyntax { } override func visit(_ node: DeclModifierSyntax) -> SyntaxVisitorContinueKind { - didFind = didFind || node.overrideSpecifier != nil + didVerify = didVerify || node.overrideSpecifier != nil return .visitChildren } override func visit(_: SuperExprSyntax) -> SyntaxVisitorContinueKind { - didFind = true + didVerify = true return .visitChildren } } From 310be1b9c4edaa9896c5727045884849d55cbcf4 Mon Sep 17 00:00:00 2001 From: Kamil Strzelecki Date: Sun, 23 Nov 2025 13:49:41 +0100 Subject: [PATCH 31/49] - --- .../Syntax/Extensions/ClassDeclSyntax.swift | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/Sources/PrincipleMacros/Syntax/Extensions/ClassDeclSyntax.swift b/Sources/PrincipleMacros/Syntax/Extensions/ClassDeclSyntax.swift index 77392f2..03b69a5 100644 --- a/Sources/PrincipleMacros/Syntax/Extensions/ClassDeclSyntax.swift +++ b/Sources/PrincipleMacros/Syntax/Extensions/ClassDeclSyntax.swift @@ -18,6 +18,25 @@ extension ClassDeclSyntax { let visitor = SubclassKeywordsVisitor(for: self) return visitor.verifiedSuperclass() } + + public func inferredSuperclass( + expectation: Bool? + ) throws -> TypeSyntax? { + switch expectation { + case true: + if let superclass = unverifiedInferredSuperclass { + return superclass + } + throw DiagnosticsError( + node: self, + message: "\(name.trimmed) should have a superclass" + ) + case false: + return nil + case nil: + return inferredSuperclass() + } + } } extension ClassDeclSyntax { From cea794f14fe2e4087f3476d1ddedc05174c869a1 Mon Sep 17 00:00:00 2001 From: Kamil Strzelecki Date: Sun, 23 Nov 2025 13:50:57 +0100 Subject: [PATCH 32/49] - --- .../PrincipleMacros/Syntax/Extensions/ClassDeclSyntax.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/PrincipleMacros/Syntax/Extensions/ClassDeclSyntax.swift b/Sources/PrincipleMacros/Syntax/Extensions/ClassDeclSyntax.swift index 03b69a5..b042164 100644 --- a/Sources/PrincipleMacros/Syntax/Extensions/ClassDeclSyntax.swift +++ b/Sources/PrincipleMacros/Syntax/Extensions/ClassDeclSyntax.swift @@ -20,9 +20,9 @@ extension ClassDeclSyntax { } public func inferredSuperclass( - expectation: Bool? + isExpected: Bool? ) throws -> TypeSyntax? { - switch expectation { + switch isExpected { case true: if let superclass = unverifiedInferredSuperclass { return superclass From 82a170916b03540d7ece83eb63d07412b14c0ace Mon Sep 17 00:00:00 2001 From: Kamil Strzelecki Date: Sun, 23 Nov 2025 14:17:39 +0100 Subject: [PATCH 33/49] - --- .../Builders/Declarations/Types/ClassDeclBuilder.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/PrincipleMacros/Builders/Declarations/Types/ClassDeclBuilder.swift b/Sources/PrincipleMacros/Builders/Declarations/Types/ClassDeclBuilder.swift index 5181b82..dfd7787 100644 --- a/Sources/PrincipleMacros/Builders/Declarations/Types/ClassDeclBuilder.swift +++ b/Sources/PrincipleMacros/Builders/Declarations/Types/ClassDeclBuilder.swift @@ -11,7 +11,7 @@ import SwiftSyntaxMacros public protocol ClassDeclBuilder: TypeDeclBuilder { var declaration: ClassDeclSyntax { get } - var inferredSuperclass: TypeSyntax? { get } + var trimmedSuperclassType: TypeSyntax? { get } } extension ClassDeclBuilder { @@ -23,12 +23,12 @@ extension ClassDeclBuilder { extension ClassDeclBuilder { - public var inferredSuperclass: TypeSyntax? { + public var trimmedSuperclassType: TypeSyntax? { nil } public var inheritedOverrideModifier: TokenSyntax? { - inferredSuperclass != nil + trimmedSuperclassType != nil ? TokenSyntax(.keyword(.override), presence: .present).withTrailingSpace : nil } From c7945847618bbedeaa1c1628c4609ce3d8f0483b Mon Sep 17 00:00:00 2001 From: Kamil Strzelecki Date: Sun, 23 Nov 2025 14:19:02 +0100 Subject: [PATCH 34/49] - --- .swiftlint.yml | 2 +- Sources/PrincipleMacros/Parameters/ParameterExtractor.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.swiftlint.yml b/.swiftlint.yml index fcb78ed..1da9f3b 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -27,7 +27,7 @@ opt_in_rules: - discouraged_assert - discouraged_none_name - discouraged_object_literal - - discouraged_optional_boolean + # discouraged_optional_boolean - discouraged_optional_collection - empty_collection_literal - empty_count diff --git a/Sources/PrincipleMacros/Parameters/ParameterExtractor.swift b/Sources/PrincipleMacros/Parameters/ParameterExtractor.swift index 6710576..159e119 100644 --- a/Sources/PrincipleMacros/Parameters/ParameterExtractor.swift +++ b/Sources/PrincipleMacros/Parameters/ParameterExtractor.swift @@ -125,7 +125,7 @@ extension ParameterExtractor { public func rawBool( withLabel label: TokenSyntax? - ) throws -> Bool? { // swiftlint:disable:this discouraged_optional_boolean + ) throws -> Bool? { guard let expression = expression(withLabel: label) else { return nil } From dd5d1aaea5abb3bb5907244fdb3d76ac7b4c1eae Mon Sep 17 00:00:00 2001 From: Kamil Strzelecki Date: Sun, 23 Nov 2025 14:41:32 +0100 Subject: [PATCH 35/49] - --- .../Syntax/Concepts/AccessControlLevel.swift | 31 ++++++++++++++++--- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/Sources/PrincipleMacros/Syntax/Concepts/AccessControlLevel.swift b/Sources/PrincipleMacros/Syntax/Concepts/AccessControlLevel.swift index cdae34f..c59857a 100644 --- a/Sources/PrincipleMacros/Syntax/Concepts/AccessControlLevel.swift +++ b/Sources/PrincipleMacros/Syntax/Concepts/AccessControlLevel.swift @@ -77,6 +77,28 @@ extension AccessControlLevel: Comparable { extension AccessControlLevel { + public var inheritedByMember: AccessControlLevel? { + switch self { + case .private: + nil + default: + self + } + } + + public var inheritedBySibling: AccessControlLevel { + switch self { + case .private: + .fileprivate + default: + self + } + } + + public var inheritedByPeer: AccessControlLevel { + self + } + public static func forMember( of declaration: some TypeDeclSyntax, preferred: Self? = nil, @@ -86,7 +108,7 @@ extension AccessControlLevel { from: declaration.accessControlLevel, preferred: preferred, maxAllowed: maxAllowed, - transform: { $0 == .private ? nil : $0 } + transform: \.inheritedByMember ) } @@ -99,7 +121,7 @@ extension AccessControlLevel { from: syntax.accessControlLevel, preferred: preferred, maxAllowed: maxAllowed, - transform: { $0 == .private ? .fileprivate : $0 } + transform: \.inheritedBySibling ) } @@ -111,7 +133,8 @@ extension AccessControlLevel { _resolved( from: syntax.accessControlLevel, preferred: preferred, - maxAllowed: maxAllowed + maxAllowed: maxAllowed, + transform: \.inheritedByPeer ) } @@ -119,7 +142,7 @@ extension AccessControlLevel { from attached: Self?, preferred: Self?, maxAllowed: Self, - transform: (Self) -> Self? = \.self + transform: (Self) -> Self? ) -> Self? { if let preferred { return min(preferred, maxAllowed) From 4d647d51d3cb3521f443127d71bde9c9e52874e5 Mon Sep 17 00:00:00 2001 From: Kamil Strzelecki Date: Sun, 23 Nov 2025 14:59:16 +0100 Subject: [PATCH 36/49] - --- .../Syntax/Concepts/AccessControlLevel.swift | 29 +++++++++++-------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/Sources/PrincipleMacros/Syntax/Concepts/AccessControlLevel.swift b/Sources/PrincipleMacros/Syntax/Concepts/AccessControlLevel.swift index c59857a..b5b26b8 100644 --- a/Sources/PrincipleMacros/Syntax/Concepts/AccessControlLevel.swift +++ b/Sources/PrincipleMacros/Syntax/Concepts/AccessControlLevel.swift @@ -77,26 +77,32 @@ extension AccessControlLevel: Comparable { extension AccessControlLevel { - public var inheritedByMember: AccessControlLevel? { + public func inheritedByMember( + maxAllowed: Self = .public + ) -> Self? { switch self { case .private: nil default: - self + min(self, maxAllowed) } } - public var inheritedBySibling: AccessControlLevel { + public func inheritedBySibling( + maxAllowed: Self = .public + ) -> Self { switch self { case .private: - .fileprivate + min(.fileprivate, maxAllowed) default: - self + min(self, maxAllowed) } } - public var inheritedByPeer: AccessControlLevel { - self + public func inheritedByPeer( + maxAllowed: Self = .public + ) -> Self { + min(self, maxAllowed) } public static func forMember( @@ -108,7 +114,7 @@ extension AccessControlLevel { from: declaration.accessControlLevel, preferred: preferred, maxAllowed: maxAllowed, - transform: \.inheritedByMember + transform: { $0.inheritedByMember(maxAllowed: maxAllowed) } ) } @@ -121,7 +127,7 @@ extension AccessControlLevel { from: syntax.accessControlLevel, preferred: preferred, maxAllowed: maxAllowed, - transform: \.inheritedBySibling + transform: { $0.inheritedBySibling(maxAllowed: maxAllowed) } ) } @@ -134,7 +140,7 @@ extension AccessControlLevel { from: syntax.accessControlLevel, preferred: preferred, maxAllowed: maxAllowed, - transform: \.inheritedByPeer + transform: { $0.inheritedByPeer(maxAllowed: maxAllowed) } ) } @@ -147,8 +153,7 @@ extension AccessControlLevel { if let preferred { return min(preferred, maxAllowed) } - if var attached { - attached = min(attached, maxAllowed) + if let attached { return transform(attached) } return nil From bad8a9fe80f260c3427f5f2c97b9a55509d64422 Mon Sep 17 00:00:00 2001 From: Kamil Strzelecki Date: Sun, 23 Nov 2025 16:13:42 +0100 Subject: [PATCH 37/49] - --- .../Builders/Declarations/Common/DeclBuilder.swift | 5 ----- .../Builders/Declarations/Common/MemberBuilding.swift | 9 ++++++++- .../Builders/Declarations/Common/PeerBuilding.swift | 9 ++++++++- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/Sources/PrincipleMacros/Builders/Declarations/Common/DeclBuilder.swift b/Sources/PrincipleMacros/Builders/Declarations/Common/DeclBuilder.swift index fc0dda1..6eaa345 100644 --- a/Sources/PrincipleMacros/Builders/Declarations/Common/DeclBuilder.swift +++ b/Sources/PrincipleMacros/Builders/Declarations/Common/DeclBuilder.swift @@ -15,7 +15,6 @@ public protocol DeclBuilder { var preferredGlobalActorIsolation: GlobalActorIsolation? { get } var preferredAccessControlLevel: AccessControlLevel? { get } - var maxAllowedAccessControlLevel: AccessControlLevel { get } func build() throws -> [DeclSyntax] } @@ -33,8 +32,4 @@ extension DeclBuilder { public var preferredAccessControlLevel: AccessControlLevel? { nil } - - public var maxAllowedAccessControlLevel: AccessControlLevel { - .public - } } diff --git a/Sources/PrincipleMacros/Builders/Declarations/Common/MemberBuilding.swift b/Sources/PrincipleMacros/Builders/Declarations/Common/MemberBuilding.swift index 2eae3db..ac72172 100644 --- a/Sources/PrincipleMacros/Builders/Declarations/Common/MemberBuilding.swift +++ b/Sources/PrincipleMacros/Builders/Declarations/Common/MemberBuilding.swift @@ -11,10 +11,17 @@ public protocol MemberBuilding {} extension MemberBuilding where Self: TypeDeclBuilder { public var inheritedAccessControlLevel: AccessControlLevel? { + .forMember( + of: typeDeclaration, + preferred: preferredAccessControlLevel + ) + } + + public var inheritedAccessControlLevelAllowingOpen: AccessControlLevel? { .forMember( of: typeDeclaration, preferred: preferredAccessControlLevel, - maxAllowed: maxAllowedAccessControlLevel + maxAllowed: .open ) } } diff --git a/Sources/PrincipleMacros/Builders/Declarations/Common/PeerBuilding.swift b/Sources/PrincipleMacros/Builders/Declarations/Common/PeerBuilding.swift index 17d6ece..9eb31d3 100644 --- a/Sources/PrincipleMacros/Builders/Declarations/Common/PeerBuilding.swift +++ b/Sources/PrincipleMacros/Builders/Declarations/Common/PeerBuilding.swift @@ -11,10 +11,17 @@ public protocol PeerBuilding {} extension PeerBuilding where Self: DeclBuilder { public var inheritedAccessControlLevel: AccessControlLevel? { + .forPeer( + of: basicDeclaration, + preferred: preferredAccessControlLevel + ) + } + + public var inheritedAccessControlLevelAllowingOpen: AccessControlLevel? { .forPeer( of: basicDeclaration, preferred: preferredAccessControlLevel, - maxAllowed: maxAllowedAccessControlLevel + maxAllowed: .open ) } } From 906bd5f9a8f7a229b208520fc40ad13c7822be2a Mon Sep 17 00:00:00 2001 From: Kamil Strzelecki Date: Sun, 23 Nov 2025 18:19:55 +0100 Subject: [PATCH 38/49] - --- .swiftlint.yml | 2 +- .../PrincipleMacros/Syntax/Concepts/AccessControlLevel.swift | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.swiftlint.yml b/.swiftlint.yml index 1da9f3b..a98bea7 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -67,7 +67,7 @@ opt_in_rules: - local_doc_comment - lower_acl_than_parent # missing_docs - - modifier_order + # modifier_order - multiline_arguments - multiline_arguments_brackets - multiline_function_chains diff --git a/Sources/PrincipleMacros/Syntax/Concepts/AccessControlLevel.swift b/Sources/PrincipleMacros/Syntax/Concepts/AccessControlLevel.swift index b5b26b8..105254a 100644 --- a/Sources/PrincipleMacros/Syntax/Concepts/AccessControlLevel.swift +++ b/Sources/PrincipleMacros/Syntax/Concepts/AccessControlLevel.swift @@ -104,6 +104,9 @@ extension AccessControlLevel { ) -> Self { min(self, maxAllowed) } +} + +extension AccessControlLevel { public static func forMember( of declaration: some TypeDeclSyntax, From 26a5f1497079e5e84884573514304e7c3d2c61f2 Mon Sep 17 00:00:00 2001 From: Kamil Strzelecki Date: Sun, 23 Nov 2025 20:13:18 +0100 Subject: [PATCH 39/49] - --- .../Parsers/Common/ParserResultsCollection.swift | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Sources/PrincipleMacros/Parsers/Common/ParserResultsCollection.swift b/Sources/PrincipleMacros/Parsers/Common/ParserResultsCollection.swift index c319954..bc64de4 100644 --- a/Sources/PrincipleMacros/Parsers/Common/ParserResultsCollection.swift +++ b/Sources/PrincipleMacros/Parsers/Common/ParserResultsCollection.swift @@ -26,11 +26,20 @@ extension ParserResultsCollection { all.endIndex } + public subscript(position: Int) -> Element { + all[position] + } +} + +extension ParserResultsCollection { + public init() { self.init([]) } - public subscript(position: Int) -> Element { - all[position] + public func filter( + _ isIncluded: (Element) throws -> Bool + ) rethrows -> Self { + try Self(filter(isIncluded)) } } From 0f7a8566868872a6826f299471c5a37a9639286e Mon Sep 17 00:00:00 2001 From: Kamil Strzelecki Date: Fri, 28 Nov 2025 11:32:59 +0100 Subject: [PATCH 40/49] - --- .../Members/PropertyDeclAccessorBuilder.swift | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 Sources/PrincipleMacros/Builders/Declarations/Members/PropertyDeclAccessorBuilder.swift diff --git a/Sources/PrincipleMacros/Builders/Declarations/Members/PropertyDeclAccessorBuilder.swift b/Sources/PrincipleMacros/Builders/Declarations/Members/PropertyDeclAccessorBuilder.swift new file mode 100644 index 0000000..9bde488 --- /dev/null +++ b/Sources/PrincipleMacros/Builders/Declarations/Members/PropertyDeclAccessorBuilder.swift @@ -0,0 +1,21 @@ +// +// PropertyDeclAccessorBuilder.swift +// PrincipleMacros +// +// Created by Kamil Strzelecki on 28/11/2025. +// Copyright © 2025 Kamil Strzelecki. All rights reserved. +// + +import SwiftSyntaxMacros + +public protocol PropertyDeclAccessorBuilder: PropertyDeclBuilder { + + func buildAccessors() throws -> [AccessorDeclSyntax] +} + +extension PropertyDeclAccessorBuilder { + + public func build() throws -> [DeclSyntax] { + try buildAccessors().map(DeclSyntax.init) + } +} From 1f5fd7fd2ad3e243e2c9fa4b3b40e9cda58886c1 Mon Sep 17 00:00:00 2001 From: Kamil Strzelecki Date: Fri, 28 Nov 2025 14:24:51 +0100 Subject: [PATCH 41/49] - --- .../Syntax/Extensions/ClassDeclSyntax.swift | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/Sources/PrincipleMacros/Syntax/Extensions/ClassDeclSyntax.swift b/Sources/PrincipleMacros/Syntax/Extensions/ClassDeclSyntax.swift index b042164..7ccfc08 100644 --- a/Sources/PrincipleMacros/Syntax/Extensions/ClassDeclSyntax.swift +++ b/Sources/PrincipleMacros/Syntax/Extensions/ClassDeclSyntax.swift @@ -10,22 +10,24 @@ import SwiftSyntaxMacros extension ClassDeclSyntax { - public var unverifiedInferredSuperclass: TypeSyntax? { + public var unverifiedInferredSuperclassType: TypeSyntax? { inheritanceClause?.inheritedTypes.first?.type.trimmed } - public func inferredSuperclass() -> TypeSyntax? { - let visitor = SubclassKeywordsVisitor(for: self) - return visitor.verifiedSuperclass() + public func inferredSuperclassType() -> TypeSyntax? { + let verifier = SuperclassVerifier(for: self) + return verifier.verifiedSuperclassType() } - public func inferredSuperclass( + public func inferredSuperclassType( isExpected: Bool? ) throws -> TypeSyntax? { switch isExpected { + case nil: + return inferredSuperclassType() case true: - if let superclass = unverifiedInferredSuperclass { - return superclass + if let type = unverifiedInferredSuperclassType { + return type } throw DiagnosticsError( node: self, @@ -33,15 +35,13 @@ extension ClassDeclSyntax { ) case false: return nil - case nil: - return inferredSuperclass() } } } extension ClassDeclSyntax { - private final class SubclassKeywordsVisitor: SyntaxVisitor { + private final class SuperclassVerifier: SyntaxVisitor { private let classDecl: ClassDeclSyntax private var didVerify = false @@ -51,13 +51,13 @@ extension ClassDeclSyntax { super.init(viewMode: .sourceAccurate) } - func verifiedSuperclass() -> TypeSyntax? { - guard let unverified = classDecl.unverifiedInferredSuperclass else { + func verifiedSuperclassType() -> TypeSyntax? { + if let unverified = classDecl.unverifiedInferredSuperclassType { + walk(classDecl) + return didVerify ? unverified : nil + } else { return nil } - - walk(classDecl) - return didVerify ? unverified : nil } override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind { From a1a517fe19dd5d7d3516cfc0e89da451c08a199d Mon Sep 17 00:00:00 2001 From: Kamil Strzelecki Date: Fri, 28 Nov 2025 14:31:01 +0100 Subject: [PATCH 42/49] - --- .../Extensions/ClassDeclSyntaxTests.swift | 79 +++---- .../Extensions/ClosureExprSyntaxTests.swift | 163 +++++++-------- .../Syntax/Extensions/ExprSyntaxTests.swift | 193 +++++++++--------- .../Extensions/IfConfigDeclSyntaxTests.swift | 107 +++++----- .../Syntax/Extensions/TypeSyntaxTests.swift | 125 ++++++------ 5 files changed, 341 insertions(+), 326 deletions(-) diff --git a/Tests/PrincipleMacrosTests/Syntax/Extensions/ClassDeclSyntaxTests.swift b/Tests/PrincipleMacrosTests/Syntax/Extensions/ClassDeclSyntaxTests.swift index 6d11ecc..b13faf5 100644 --- a/Tests/PrincipleMacrosTests/Syntax/Extensions/ClassDeclSyntaxTests.swift +++ b/Tests/PrincipleMacrosTests/Syntax/Extensions/ClassDeclSyntaxTests.swift @@ -9,42 +9,44 @@ @testable import PrincipleMacros import Testing -internal struct ClassDeclSyntaxTests { +internal enum ClassDeclSyntaxTests { - @Test - func withoutInheritanceClause() throws { - let decl: DeclSyntax = "class MyClass {}" - let classDecl = try #require(decl.as(ClassDeclSyntax.self)) - let inferredSuperclass = classDecl.inferredSuperclass() - #expect(inferredSuperclass == nil) - } + struct InferredSuperclassType { - @Test - func withProtocolConformance() throws { - let decl: DeclSyntax = "class MyClass: Equatable, Hashable {}" - let classDecl = try #require(decl.as(ClassDeclSyntax.self)) - let inferredSuperclass = classDecl.inferredSuperclass() - #expect(inferredSuperclass == nil) - } + @Test + func withoutInheritanceClause() throws { + let decl: DeclSyntax = "class MyClass {}" + let classDecl = try #require(decl.as(ClassDeclSyntax.self)) + let inferredSuperclass = classDecl.inferredSuperclassType() + #expect(inferredSuperclass == nil) + } - // swiftlint:disable empty_line_after_type_declaration + @Test + func withProtocolConformance() throws { + let decl: DeclSyntax = "class MyClass: Equatable, Hashable {}" + let classDecl = try #require(decl.as(ClassDeclSyntax.self)) + let inferredSuperclass = classDecl.inferredSuperclassType() + #expect(inferredSuperclass == nil) + } + + // swiftlint:disable empty_line_after_type_declaration - @Test - func withOverrideModifier() throws { - let decl: DeclSyntax = """ + @Test + func withOverrideModifier() throws { + let decl: DeclSyntax = """ class MyClass: BaseClass, Hashable { override func test() {} } """ - let classDecl = try #require(decl.as(ClassDeclSyntax.self)) - let inferredSuperclass = classDecl.inferredSuperclass() - #expect(inferredSuperclass?.description == "BaseClass") - } + let classDecl = try #require(decl.as(ClassDeclSyntax.self)) + let inferredSuperclass = classDecl.inferredSuperclassType() + #expect(inferredSuperclass?.description == "BaseClass") + } - @Test - func withSuperExpression() throws { - let decl: DeclSyntax = """ + @Test + func withSuperExpression() throws { + let decl: DeclSyntax = """ class MyClass: BaseClass, Hashable { init(value: Int) { super.init() @@ -52,14 +54,14 @@ internal struct ClassDeclSyntaxTests { } """ - let classDecl = try #require(decl.as(ClassDeclSyntax.self)) - let inferredSuperclass = classDecl.inferredSuperclass() - #expect(inferredSuperclass?.description == "BaseClass") - } + let classDecl = try #require(decl.as(ClassDeclSyntax.self)) + let inferredSuperclass = classDecl.inferredSuperclassType() + #expect(inferredSuperclass?.description == "BaseClass") + } - @Test - func withNestedClass() throws { - let decl: DeclSyntax = """ + @Test + func withNestedClass() throws { + let decl: DeclSyntax = """ class MyClass: Equatable, Hashable { class NestedClass: BaseClass { override func test() {} @@ -67,10 +69,11 @@ internal struct ClassDeclSyntaxTests { } """ - let classDecl = try #require(decl.as(ClassDeclSyntax.self)) - let inferredSuperclass = classDecl.inferredSuperclass() - #expect(inferredSuperclass == nil) - } + let classDecl = try #require(decl.as(ClassDeclSyntax.self)) + let inferredSuperclass = classDecl.inferredSuperclassType() + #expect(inferredSuperclass == nil) + } - // swiftlint:enable empty_line_after_type_declaration + // swiftlint:enable empty_line_after_type_declaration + } } diff --git a/Tests/PrincipleMacrosTests/Syntax/Extensions/ClosureExprSyntaxTests.swift b/Tests/PrincipleMacrosTests/Syntax/Extensions/ClosureExprSyntaxTests.swift index cc7e64e..dfffbcf 100644 --- a/Tests/PrincipleMacrosTests/Syntax/Extensions/ClosureExprSyntaxTests.swift +++ b/Tests/PrincipleMacrosTests/Syntax/Extensions/ClosureExprSyntaxTests.swift @@ -9,104 +9,107 @@ @testable import PrincipleMacros import Testing -internal struct ClosureExprSyntaxTests { +internal enum ClosureExprSyntaxTests { - @Test - func withoutSignature() { - let expr: ExprSyntax = """ - { Date.now } - """ + struct Expansion { - let interpolation: ExprSyntax = """ - .init( - parameter: .init( - closure: \(expr.expanded(nestingLevel: 2)), - value: "Foo" + @Test + func withoutSignature() { + let expr: ExprSyntax = """ + { Date.now } + """ + + let interpolation: ExprSyntax = """ + .init( + parameter: .init( + closure: \(expr.expanded(nestingLevel: 2)), + value: "Foo" + ) ) - ) - """ + """ - let expectation = """ - .init( - parameter: .init( - closure: { - Date.now - }, - value: "Foo" + let expectation = """ + .init( + parameter: .init( + closure: { + Date.now + }, + value: "Foo" + ) ) - ) - """ + """ - #expect(interpolation.description == expectation) - } + #expect(interpolation.description == expectation) + } - @Test - func withSignature() { - let expr: ExprSyntax = """ - { [weak self] arg0, _ -> String in arg0 } - """ + @Test + func withSignature() { + let expr: ExprSyntax = """ + { [weak self] arg0, _ -> String in arg0 } + """ - let interpolation: ExprSyntax = """ - .init( - parameter: .init( - closure: \(expr.expanded(nestingLevel: 2)), - value: "Foo" + let interpolation: ExprSyntax = """ + .init( + parameter: .init( + closure: \(expr.expanded(nestingLevel: 2)), + value: "Foo" + ) ) - ) - """ + """ - let expectation = """ - .init( - parameter: .init( - closure: { [weak self] arg0, _ -> String in - arg0 - }, - value: "Foo" + let expectation = """ + .init( + parameter: .init( + closure: { [weak self] arg0, _ -> String in + arg0 + }, + value: "Foo" + ) ) - ) - """ + """ - #expect(interpolation.description == expectation) - } + #expect(interpolation.description == expectation) + } - @Test - func multiline() { - let expr: ExprSyntax = """ - { [weak self] arg0, arg1 -> String in - if arg0 > 0 { - return String(arg0) - } else if arg1 { - return "Test" + @Test + func multiline() { + let expr: ExprSyntax = """ + { [weak self] arg0, arg1 -> String in + if arg0 > 0 { + return String(arg0) + } else if arg1 { + return "Test" + } + return "" } - return "" - } - """ + """ - let interpolation: ExprSyntax = """ - .init( - parameter: .init( - closure: \(expr.expanded(nestingLevel: 2)), - value: "Foo" + let interpolation: ExprSyntax = """ + .init( + parameter: .init( + closure: \(expr.expanded(nestingLevel: 2)), + value: "Foo" + ) ) - ) - """ + """ - let expectation = """ - .init( - parameter: .init( - closure: { [weak self] arg0, arg1 -> String in - if arg0 > 0 { - return String(arg0) - } else if arg1 { - return "Test" - } - return "" - }, - value: "Foo" + let expectation = """ + .init( + parameter: .init( + closure: { [weak self] arg0, arg1 -> String in + if arg0 > 0 { + return String(arg0) + } else if arg1 { + return "Test" + } + return "" + }, + value: "Foo" + ) ) - ) - """ + """ - #expect(interpolation.description == expectation) + #expect(interpolation.description == expectation) + } } } diff --git a/Tests/PrincipleMacrosTests/Syntax/Extensions/ExprSyntaxTests.swift b/Tests/PrincipleMacrosTests/Syntax/Extensions/ExprSyntaxTests.swift index b6f9ebf..7af0ef3 100644 --- a/Tests/PrincipleMacrosTests/Syntax/Extensions/ExprSyntaxTests.swift +++ b/Tests/PrincipleMacrosTests/Syntax/Extensions/ExprSyntaxTests.swift @@ -9,100 +9,103 @@ @testable import PrincipleMacros import Testing -internal struct ExprSyntaxTests { - - @Test - func optionalLiteral() { - let expr: ExprSyntax = "Int?" - #expect(expr.inferredType?.description == "Optional") - } - - @Test - func integerLiteral() { - let expr: ExprSyntax = "123" - #expect(expr.inferredType?.description == "Int") - } - - @Test - func floatLiteral() { - let expr: ExprSyntax = "1.23" - #expect(expr.inferredType?.description == "Double") - } - - @Test - func boolLiteral() { - let expr: ExprSyntax = "false" - #expect(expr.inferredType?.description == "Bool") - } - - @Test - func stringLiteral() { - let expr: ExprSyntax = "\"Hello\"" - #expect(expr.inferredType?.description == "String") - } - - @Test - func arrayLiteral() { - let expr: ExprSyntax = "[String]" - #expect(expr.inferredType?.description == "Array") - } - - @Test - func dictionaryLiteral() { - let expr: ExprSyntax = "[String: Int]" - #expect(expr.inferredType?.description == "Dictionary") - } - - @Test - func initializer() { - let expr: ExprSyntax = "UIView()" - #expect(expr.inferredType?.description == "UIView") - } - - @Test - func genericInitializer() { - let expr: ExprSyntax = "Dictionary()" - #expect(expr.inferredType?.description == "Dictionary") - } - - @Test - func memberAccess() { - let expr: ExprSyntax = "Options.first" - #expect(expr.inferredType?.description == "Options") - } - - @Test - func functionCall() { - let expr: ExprSyntax = "Model.create(arg: true)" - #expect(expr.inferredType?.description == "Model") - } - - @Test - func nestedFunctionCall() { - let expr: ExprSyntax = "Model.Default.create()" - #expect(expr.inferredType?.description == "Model.Default") - } - - @Test - func typeReference() { - let expr: ExprSyntax = "Model.self" - #expect(expr.inferredType?.description == "Model.Type") - } - - @Test( - arguments: [ - ( - "[String.Key: Int]()", - "Dictionary" - ), - ( - "Outer.Inner?", - "Optional>.Inner>" - ) - ] - ) - func composition(expr: String, expectation: String) { - let expr: ExprSyntax = "\(raw: expr)" - #expect(expr.inferredType?.description == expectation) +internal enum ExprSyntaxTests { + + struct InferredType { + + @Test + func optionalLiteral() { + let expr: ExprSyntax = "Int?" + #expect(expr.inferredType?.description == "Optional") + } + + @Test + func integerLiteral() { + let expr: ExprSyntax = "123" + #expect(expr.inferredType?.description == "Int") + } + + @Test + func floatLiteral() { + let expr: ExprSyntax = "1.23" + #expect(expr.inferredType?.description == "Double") + } + + @Test + func boolLiteral() { + let expr: ExprSyntax = "false" + #expect(expr.inferredType?.description == "Bool") + } + + @Test + func stringLiteral() { + let expr: ExprSyntax = "\"Hello\"" + #expect(expr.inferredType?.description == "String") + } + + @Test + func arrayLiteral() { + let expr: ExprSyntax = "[String]" + #expect(expr.inferredType?.description == "Array") + } + + @Test + func dictionaryLiteral() { + let expr: ExprSyntax = "[String: Int]" + #expect(expr.inferredType?.description == "Dictionary") + } + + @Test + func initializer() { + let expr: ExprSyntax = "UIView()" + #expect(expr.inferredType?.description == "UIView") + } + + @Test + func genericInitializer() { + let expr: ExprSyntax = "Dictionary()" + #expect(expr.inferredType?.description == "Dictionary") + } + + @Test + func memberAccess() { + let expr: ExprSyntax = "Options.first" + #expect(expr.inferredType?.description == "Options") + } + + @Test + func functionCall() { + let expr: ExprSyntax = "Model.create(arg: true)" + #expect(expr.inferredType?.description == "Model") + } + + @Test + func nestedFunctionCall() { + let expr: ExprSyntax = "Model.Default.create()" + #expect(expr.inferredType?.description == "Model.Default") + } + + @Test + func typeReference() { + let expr: ExprSyntax = "Model.self" + #expect(expr.inferredType?.description == "Model.Type") + } + + @Test( + arguments: [ + ( + "[String.Key: Int]()", + "Dictionary" + ), + ( + "Outer.Inner?", + "Optional>.Inner>" + ) + ] + ) + func composition(expr: String, expectation: String) { + let expr: ExprSyntax = "\(raw: expr)" + #expect(expr.inferredType?.description == expectation) + } } } diff --git a/Tests/PrincipleMacrosTests/Syntax/Extensions/IfConfigDeclSyntaxTests.swift b/Tests/PrincipleMacrosTests/Syntax/Extensions/IfConfigDeclSyntaxTests.swift index 214ae46..4a00b80 100644 --- a/Tests/PrincipleMacrosTests/Syntax/Extensions/IfConfigDeclSyntaxTests.swift +++ b/Tests/PrincipleMacrosTests/Syntax/Extensions/IfConfigDeclSyntaxTests.swift @@ -9,31 +9,33 @@ @testable import PrincipleMacros import Testing -internal struct IfConfigDeclSyntaxTests { - - private func parseLastProperty(in decl: DeclSyntax) throws -> Property { - let classDecl = try #require(decl.as(ClassDeclSyntax.self)) - let properties = try PropertiesParser.parse(memberBlock: classDecl.memberBlock) - return try #require(properties.last) - } +internal enum IfConfigDeclSyntaxTests { + + struct EnclosingIfConfig { + + private func parseLastProperty(in decl: DeclSyntax) throws -> Property { + let classDecl = try #require(decl.as(ClassDeclSyntax.self)) + let properties = try PropertiesParser.parse(memberBlock: classDecl.memberBlock) + return try #require(properties.last) + } - // swiftlint:disable empty_line_after_type_declaration + // swiftlint:disable empty_line_after_type_declaration - @Test - func withoutIfConfig() throws { - let decl: DeclSyntax = """ + @Test + func withoutIfConfig() throws { + let decl: DeclSyntax = """ class MyClass { var test = 123 } """ - let ifConfig = try parseLastProperty(in: decl).enclosingIfConfig - #expect(ifConfig == nil) - } + let ifConfig = try parseLastProperty(in: decl).enclosingIfConfig + #expect(ifConfig == nil) + } - @Test - func withIfConfig() throws { - let decl: DeclSyntax = """ + @Test + func withIfConfig() throws { + let decl: DeclSyntax = """ class MyClass { #if os(macOS) var other = "hello" @@ -42,19 +44,19 @@ internal struct IfConfigDeclSyntaxTests { } """ - let expectation = """ + let expectation = """ #if os(macOS) var test = 123 #endif """ - let ifConfig = try parseLastProperty(in: decl).enclosingIfConfig - #expect(ifConfig?.description == expectation) - } + let ifConfig = try parseLastProperty(in: decl).enclosingIfConfig + #expect(ifConfig?.description == expectation) + } - @Test - func withElseIfConfig() throws { - let decl: DeclSyntax = """ + @Test + func withElseIfConfig() throws { + let decl: DeclSyntax = """ class MyClass { #if os(iOS) var other = "hello" @@ -64,20 +66,20 @@ internal struct IfConfigDeclSyntaxTests { } """ - let expectation = """ + let expectation = """ #if os(iOS) #elseif os(macOS) var test = 123 #endif """ - let ifConfig = try parseLastProperty(in: decl).enclosingIfConfig - #expect(ifConfig?.description == expectation) - } + let ifConfig = try parseLastProperty(in: decl).enclosingIfConfig + #expect(ifConfig?.description == expectation) + } - @Test - func withNestedIfConfig() throws { - let decl: DeclSyntax = """ + @Test + func withNestedIfConfig() throws { + let decl: DeclSyntax = """ class MyClass { #if DEBUG var other = "hello" @@ -88,7 +90,7 @@ internal struct IfConfigDeclSyntaxTests { } """ - let expectation = """ + let expectation = """ #if DEBUG #if os(macOS) var test = 123 @@ -96,13 +98,13 @@ internal struct IfConfigDeclSyntaxTests { #endif """ - let ifConfig = try parseLastProperty(in: decl).enclosingIfConfig - #expect(ifConfig?.description == expectation) - } + let ifConfig = try parseLastProperty(in: decl).enclosingIfConfig + #expect(ifConfig?.description == expectation) + } - @Test - func withNestedElseConfig() throws { - let decl: DeclSyntax = """ + @Test + func withNestedElseConfig() throws { + let decl: DeclSyntax = """ class MyClass { #if DEBUG var other = "hello" @@ -114,7 +116,7 @@ internal struct IfConfigDeclSyntaxTests { } """ - let expectation = """ + let expectation = """ #if DEBUG #else #if os(macOS) @@ -123,13 +125,13 @@ internal struct IfConfigDeclSyntaxTests { #endif """ - let ifConfig = try parseLastProperty(in: decl).enclosingIfConfig - #expect(ifConfig?.description == expectation) - } + let ifConfig = try parseLastProperty(in: decl).enclosingIfConfig + #expect(ifConfig?.description == expectation) + } - @Test - func applyToNewMembers() throws { - let decl: DeclSyntax = """ + @Test + func applyToNewMembers() throws { + let decl: DeclSyntax = """ class MyClass { #if DEBUG var other = "hello" @@ -141,12 +143,12 @@ internal struct IfConfigDeclSyntaxTests { } """ - let newMembers: MemberBlockItemListSyntax = """ + let newMembers: MemberBlockItemListSyntax = """ var replacement = "Hello" func test() {} """ - let expectation = """ + let expectation = """ #if DEBUG #else #if os(macOS) @@ -156,10 +158,11 @@ internal struct IfConfigDeclSyntaxTests { #endif """ - let property = try parseLastProperty(in: decl) - let ifConfig = property.underlying.applyingEnclosingIfConfig(to: newMembers) - #expect(ifConfig?.description == expectation) - } + let property = try parseLastProperty(in: decl) + let ifConfig = property.underlying.applyingEnclosingIfConfig(to: newMembers) + #expect(ifConfig?.description == expectation) + } - // swiftlint:enable empty_line_after_type_declaration + // swiftlint:enable empty_line_after_type_declaration + } } diff --git a/Tests/PrincipleMacrosTests/Syntax/Extensions/TypeSyntaxTests.swift b/Tests/PrincipleMacrosTests/Syntax/Extensions/TypeSyntaxTests.swift index 32c20ea..8316c29 100644 --- a/Tests/PrincipleMacrosTests/Syntax/Extensions/TypeSyntaxTests.swift +++ b/Tests/PrincipleMacrosTests/Syntax/Extensions/TypeSyntaxTests.swift @@ -9,76 +9,79 @@ @testable import PrincipleMacros import Testing -internal struct TypeSyntaxTests { +internal enum TypeSyntaxTests { - @Test - func optionalLiteral() { - let type: TypeSyntax = "Int?" - #expect(type.standardized.description == "Optional") - } + struct Standardization { - @Test - func implicitlyUnwrappedOptionalLiteral() { - let type: TypeSyntax = "String!" - #expect(type.standardized.description == "Optional") - } + @Test + func optionalLiteral() { + let type: TypeSyntax = "Int?" + #expect(type.standardized.description == "Optional") + } - @Test - func arrayLiteral() { - let type: TypeSyntax = "[String]" - #expect(type.standardized.description == "Array") - } + @Test + func implicitlyUnwrappedOptionalLiteral() { + let type: TypeSyntax = "String!" + #expect(type.standardized.description == "Optional") + } - @Test - func dictionaryLiteral() { - let type: TypeSyntax = "[String: Int]" - #expect(type.standardized.description == "Dictionary") - } + @Test + func arrayLiteral() { + let type: TypeSyntax = "[String]" + #expect(type.standardized.description == "Array") + } - @Test - func basicType() { - let type: TypeSyntax = "UIView" - #expect(type.standardized.description == "UIView") - } + @Test + func dictionaryLiteral() { + let type: TypeSyntax = "[String: Int]" + #expect(type.standardized.description == "Dictionary") + } - @Test - func memberType() { - let type: TypeSyntax = "UIView.Constraints" - #expect(type.standardized.description == "UIView.Constraints") - } + @Test + func basicType() { + let type: TypeSyntax = "UIView" + #expect(type.standardized.description == "UIView") + } - @Test - func genericType() { - let type: TypeSyntax = "Cache" - #expect(type.standardized.description == "Cache") - } + @Test + func memberType() { + let type: TypeSyntax = "UIView.Constraints" + #expect(type.standardized.description == "UIView.Constraints") + } - @Test - func voidType() { - let type: TypeSyntax = "()" - #expect(type.standardized.description == "Void") - } + @Test + func genericType() { + let type: TypeSyntax = "Cache" + #expect(type.standardized.description == "Cache") + } - @Test - func tupleType() { - let type: TypeSyntax = "(_ first: String, second: Int, Bool)" - #expect(type.standardized.description == "(_ first: String, second: Int, Bool)") - } + @Test + func voidType() { + let type: TypeSyntax = "()" + #expect(type.standardized.description == "Void") + } + + @Test + func tupleType() { + let type: TypeSyntax = "(_ first: String, second: Int, Bool)" + #expect(type.standardized.description == "(_ first: String, second: Int, Bool)") + } - @Test( - arguments: [ - ( - "[String.Key: Cache]", - "Dictionary, Int>>" - ), - ( - "(_ first: String?, second secondArg: [Int: Value.Nested])", - "(_ first: Optional, second secondArg: Dictionary)" - ) - ] - ) - func composition(type: String, expectation: String) { - let type: TypeSyntax = "\(raw: type)" - #expect(type.standardized.description == expectation) + @Test( + arguments: [ + ( + "[String.Key: Cache]", + "Dictionary, Int>>" + ), + ( + "(_ first: String?, second secondArg: [Int: Value.Nested])", + "(_ first: Optional, second secondArg: Dictionary)" + ) + ] + ) + func composition(type: String, expectation: String) { + let type: TypeSyntax = "\(raw: type)" + #expect(type.standardized.description == expectation) + } } } From 78f41cf50cdb77e0d686c70d9ce8b5b4fe97e9d8 Mon Sep 17 00:00:00 2001 From: Kamil Strzelecki Date: Fri, 28 Nov 2025 14:31:01 +0100 Subject: [PATCH 43/49] [SwiftFormat] Applied formatting --- .../Extensions/ClassDeclSyntaxTests.swift | 28 ++-- .../Extensions/IfConfigDeclSyntaxTests.swift | 148 +++++++++--------- 2 files changed, 88 insertions(+), 88 deletions(-) diff --git a/Tests/PrincipleMacrosTests/Syntax/Extensions/ClassDeclSyntaxTests.swift b/Tests/PrincipleMacrosTests/Syntax/Extensions/ClassDeclSyntaxTests.swift index b13faf5..b327f31 100644 --- a/Tests/PrincipleMacrosTests/Syntax/Extensions/ClassDeclSyntaxTests.swift +++ b/Tests/PrincipleMacrosTests/Syntax/Extensions/ClassDeclSyntaxTests.swift @@ -34,10 +34,10 @@ internal enum ClassDeclSyntaxTests { @Test func withOverrideModifier() throws { let decl: DeclSyntax = """ - class MyClass: BaseClass, Hashable { - override func test() {} - } - """ + class MyClass: BaseClass, Hashable { + override func test() {} + } + """ let classDecl = try #require(decl.as(ClassDeclSyntax.self)) let inferredSuperclass = classDecl.inferredSuperclassType() @@ -47,12 +47,12 @@ internal enum ClassDeclSyntaxTests { @Test func withSuperExpression() throws { let decl: DeclSyntax = """ - class MyClass: BaseClass, Hashable { - init(value: Int) { - super.init() + class MyClass: BaseClass, Hashable { + init(value: Int) { + super.init() + } } - } - """ + """ let classDecl = try #require(decl.as(ClassDeclSyntax.self)) let inferredSuperclass = classDecl.inferredSuperclassType() @@ -62,12 +62,12 @@ internal enum ClassDeclSyntaxTests { @Test func withNestedClass() throws { let decl: DeclSyntax = """ - class MyClass: Equatable, Hashable { - class NestedClass: BaseClass { - override func test() {} + class MyClass: Equatable, Hashable { + class NestedClass: BaseClass { + override func test() {} + } } - } - """ + """ let classDecl = try #require(decl.as(ClassDeclSyntax.self)) let inferredSuperclass = classDecl.inferredSuperclassType() diff --git a/Tests/PrincipleMacrosTests/Syntax/Extensions/IfConfigDeclSyntaxTests.swift b/Tests/PrincipleMacrosTests/Syntax/Extensions/IfConfigDeclSyntaxTests.swift index 4a00b80..9550e75 100644 --- a/Tests/PrincipleMacrosTests/Syntax/Extensions/IfConfigDeclSyntaxTests.swift +++ b/Tests/PrincipleMacrosTests/Syntax/Extensions/IfConfigDeclSyntaxTests.swift @@ -12,7 +12,7 @@ import Testing internal enum IfConfigDeclSyntaxTests { struct EnclosingIfConfig { - + private func parseLastProperty(in decl: DeclSyntax) throws -> Property { let classDecl = try #require(decl.as(ClassDeclSyntax.self)) let properties = try PropertiesParser.parse(memberBlock: classDecl.memberBlock) @@ -24,10 +24,10 @@ internal enum IfConfigDeclSyntaxTests { @Test func withoutIfConfig() throws { let decl: DeclSyntax = """ - class MyClass { - var test = 123 - } - """ + class MyClass { + var test = 123 + } + """ let ifConfig = try parseLastProperty(in: decl).enclosingIfConfig #expect(ifConfig == nil) @@ -36,19 +36,19 @@ internal enum IfConfigDeclSyntaxTests { @Test func withIfConfig() throws { let decl: DeclSyntax = """ - class MyClass { + class MyClass { + #if os(macOS) + var other = "hello" + var test = 123 + #endif + } + """ + + let expectation = """ #if os(macOS) - var other = "hello" var test = 123 #endif - } - """ - - let expectation = """ - #if os(macOS) - var test = 123 - #endif - """ + """ let ifConfig = try parseLastProperty(in: decl).enclosingIfConfig #expect(ifConfig?.description == expectation) @@ -57,21 +57,21 @@ internal enum IfConfigDeclSyntaxTests { @Test func withElseIfConfig() throws { let decl: DeclSyntax = """ - class MyClass { + class MyClass { + #if os(iOS) + var other = "hello" + #elseif os(macOS) + var test = 123 + #endif + } + """ + + let expectation = """ #if os(iOS) - var other = "hello" #elseif os(macOS) var test = 123 #endif - } - """ - - let expectation = """ - #if os(iOS) - #elseif os(macOS) - var test = 123 - #endif - """ + """ let ifConfig = try parseLastProperty(in: decl).enclosingIfConfig #expect(ifConfig?.description == expectation) @@ -80,23 +80,23 @@ internal enum IfConfigDeclSyntaxTests { @Test func withNestedIfConfig() throws { let decl: DeclSyntax = """ - class MyClass { + class MyClass { + #if DEBUG + var other = "hello" + #if os(macOS) + var test = 123 + #endif + #endif + } + """ + + let expectation = """ #if DEBUG - var other = "hello" #if os(macOS) var test = 123 #endif #endif - } - """ - - let expectation = """ - #if DEBUG - #if os(macOS) - var test = 123 - #endif - #endif - """ + """ let ifConfig = try parseLastProperty(in: decl).enclosingIfConfig #expect(ifConfig?.description == expectation) @@ -105,25 +105,25 @@ internal enum IfConfigDeclSyntaxTests { @Test func withNestedElseConfig() throws { let decl: DeclSyntax = """ - class MyClass { - #if DEBUG - var other = "hello" - #else - #if os(macOS) - var test = 123 + class MyClass { + #if DEBUG + var other = "hello" + #else + #if os(macOS) + var test = 123 + #endif #endif - #endif - } - """ + } + """ let expectation = """ - #if DEBUG - #else - #if os(macOS) - var test = 123 - #endif - #endif - """ + #if DEBUG + #else + #if os(macOS) + var test = 123 + #endif + #endif + """ let ifConfig = try parseLastProperty(in: decl).enclosingIfConfig #expect(ifConfig?.description == expectation) @@ -132,31 +132,31 @@ internal enum IfConfigDeclSyntaxTests { @Test func applyToNewMembers() throws { let decl: DeclSyntax = """ - class MyClass { - #if DEBUG - var other = "hello" - #else - #if os(macOS) - var test = 123 + class MyClass { + #if DEBUG + var other = "hello" + #else + #if os(macOS) + var test = 123 + #endif #endif - #endif - } - """ + } + """ let newMembers: MemberBlockItemListSyntax = """ - var replacement = "Hello" - func test() {} - """ + var replacement = "Hello" + func test() {} + """ let expectation = """ - #if DEBUG - #else - #if os(macOS) - var replacement = "Hello" - func test() {} - #endif - #endif - """ + #if DEBUG + #else + #if os(macOS) + var replacement = "Hello" + func test() {} + #endif + #endif + """ let property = try parseLastProperty(in: decl) let ifConfig = property.underlying.applyingEnclosingIfConfig(to: newMembers) From 4715a6af307b80156f312f18e1346439eaf356b0 Mon Sep 17 00:00:00 2001 From: Kamil Strzelecki Date: Fri, 28 Nov 2025 14:36:58 +0100 Subject: [PATCH 44/49] - --- .../Parameters/ParameterExtractorTests.swift | 276 ++++++++++-------- 1 file changed, 150 insertions(+), 126 deletions(-) diff --git a/Tests/PrincipleMacrosTests/Parameters/ParameterExtractorTests.swift b/Tests/PrincipleMacrosTests/Parameters/ParameterExtractorTests.swift index 8d3c278..0ef1be2 100644 --- a/Tests/PrincipleMacrosTests/Parameters/ParameterExtractorTests.swift +++ b/Tests/PrincipleMacrosTests/Parameters/ParameterExtractorTests.swift @@ -9,183 +9,207 @@ @testable import PrincipleMacros import Testing -internal struct ParameterExtractorTests { +internal class ParameterExtractorTests { private func makeExtractor(from expr: ExprSyntax) throws -> ParameterExtractor { let macro = try #require(MacroExpansionExprSyntax(expr)) return ParameterExtractor(from: macro) } +} - @Test - func expressionExtraction() throws { - let extractor = try makeExtractor(from: "#MyMacro(value: Type.make())") - let extracted = extractor.expression(withLabel: "value") - let expected: ExprSyntax = "Type.make()" - #expect(extracted?.description == expected.description) - } +extension ParameterExtractorTests { - @Test - func unnamedExpressionExtraction() throws { - let extractor = try makeExtractor(from: "#MyMacro(value: Type.make(), 123)") - let extracted = extractor.expression(withLabel: nil) - let expected: ExprSyntax = "123" - #expect(extracted?.description == expected.description) - } + final class Expression: ParameterExtractorTests { - @Test - func overlappingExpressionExtraction() throws { - let extractor = try makeExtractor(from: "#MyMacro(Type.make(), 123)") - let firstExtracted = extractor.expression(withLabel: nil) - let firstExpected: ExprSyntax = "Type.make()" - #expect(firstExtracted?.description == firstExpected.description) + @Test + func extraction() throws { + let extractor = try makeExtractor(from: "#MyMacro(value: Type.make())") + let extracted = extractor.expression(withLabel: "value") + let expected: ExprSyntax = "Type.make()" + #expect(extracted?.description == expected.description) + } - let secondExtracted = extractor.expression(withLabel: nil) - let secondExpected: ExprSyntax = "123" - #expect(secondExtracted?.description == secondExpected.description) - } + @Test + func unnamedExtraction() throws { + let extractor = try makeExtractor(from: "#MyMacro(value: Type.make(), 123)") + let extracted = extractor.expression(withLabel: nil) + let expected: ExprSyntax = "123" + #expect(extracted?.description == expected.description) + } - @Test - func missingExpressionExtraction() throws { - let extractor = try makeExtractor(from: #"#MyMacro(arg: Type.make())"#) - let extracted = extractor.expression(withLabel: "value") - #expect(extracted == nil) + @Test + func overlappingExtraction() throws { + let extractor = try makeExtractor(from: "#MyMacro(Type.make(), 123)") + let firstExtracted = extractor.expression(withLabel: nil) + let firstExpected: ExprSyntax = "Type.make()" + #expect(firstExtracted?.description == firstExpected.description) + + let secondExtracted = extractor.expression(withLabel: nil) + let secondExpected: ExprSyntax = "123" + #expect(secondExtracted?.description == secondExpected.description) + } + + @Test + func missingExtraction() throws { + let extractor = try makeExtractor(from: #"#MyMacro(arg: Type.make())"#) + let extracted = extractor.expression(withLabel: "value") + #expect(extracted == nil) + } } } extension ParameterExtractorTests { - @Test - func trailingClosureExtraction() throws { - let extractor = try makeExtractor(from: "#MyMacro { _ in }") - let extracted = extractor.trailingClosure(withLabel: "operation") - #expect(extracted?.description == "{ _ in }") - } + final class TrailingClosure: ParameterExtractorTests { + + @Test + func extraction() throws { + let extractor = try makeExtractor(from: "#MyMacro { _ in }") + let extracted = extractor.trailingClosure(withLabel: "operation") + #expect(extracted?.description == "{ _ in }") + } - @Test - func trailingClosureReferenceExtraction() throws { - let extractor = try makeExtractor(from: "#MyMacro(operation: perform)") - let extracted = extractor.trailingClosure(withLabel: "operation") - #expect(extracted?.description == "perform") + @Test + func referenceExtraction() throws { + let extractor = try makeExtractor(from: "#MyMacro(operation: perform)") + let extracted = extractor.trailingClosure(withLabel: "operation") + #expect(extracted?.description == "perform") + } } } extension ParameterExtractorTests { - @Test( - arguments: [ - "private", - "fileprivate", - "internal", - "package", - "public", - "open" - ] - ) - func accessControlLevelExtraction(_ level: String) throws { - let extractor = try makeExtractor(from: "#MyMacro(.\(raw: level))") - let extracted = try #require(try extractor.accessControlLevel(withLabel: nil)) - #expect(String(describing: extracted).hasSuffix(level)) - } + final class AccessControlLevel: ParameterExtractorTests { + + @Test( + arguments: [ + "private", + "fileprivate", + "internal", + "package", + "public", + "open" + ] + ) + func extraction(_ level: String) throws { + let extractor = try makeExtractor(from: "#MyMacro(.\(raw: level))") + let extracted = try #require(try extractor.accessControlLevel(withLabel: nil)) + #expect(String(describing: extracted).hasSuffix(level)) + } - @Test - func unexpectedSyntaxWhenPerformingAccessControlLevelExtraction() throws { - let extractor = try makeExtractor(from: "#MyMacro(.didSet)") - #expect(throws: ParameterExtractionError.unexpectedSyntaxType) { - try extractor.accessControlLevel(withLabel: nil) + @Test + func unexpectedSyntax() throws { + let extractor = try makeExtractor(from: "#MyMacro(.didSet)") + #expect(throws: ParameterExtractionError.unexpectedSyntaxType) { + try extractor.accessControlLevel(withLabel: nil) + } } } } extension ParameterExtractorTests { - @Test( - arguments: [ - true, - false - ] - ) - func rawBoolExtraction(_ bool: Bool) throws { - let extractor = try makeExtractor(from: "#MyMacro(boolean: \(raw: bool))") - let extracted = try extractor.rawBool(withLabel: "boolean") - #expect(extracted == bool) - } + final class RawBool: ParameterExtractorTests { + + @Test( + arguments: [ + true, + false + ] + ) + func extraction(_ bool: Bool) throws { + let extractor = try makeExtractor(from: "#MyMacro(boolean: \(raw: bool))") + let extracted = try extractor.rawBool(withLabel: "boolean") + #expect(extracted == bool) + } - @Test - func unexpectedSyntaxWhenPerformingRawBoolExtraction() throws { - let extractor = try makeExtractor(from: #"#MyMacro(boolean: value)"#) - #expect(throws: ParameterExtractionError.unexpectedSyntaxType) { - try extractor.rawBool(withLabel: "boolean") + @Test + func unexpectedSyntax() throws { + let extractor = try makeExtractor(from: #"#MyMacro(boolean: value)"#) + #expect(throws: ParameterExtractionError.unexpectedSyntaxType) { + try extractor.rawBool(withLabel: "boolean") + } } } } extension ParameterExtractorTests { - @Test - func rawStringExtraction() throws { - let extractor = try makeExtractor(from: #"#MyMacro(string: "arg")"#) - let extracted = try extractor.rawString(withLabel: "string") - #expect(extracted == "arg") - } + final class RawString: ParameterExtractorTests { + + @Test + func extraction() throws { + let extractor = try makeExtractor(from: #"#MyMacro(string: "arg")"#) + let extracted = try extractor.rawString(withLabel: "string") + #expect(extracted == "arg") + } - @Test - func unexpectedSyntaxWhenPerformingRawStringExtraction() throws { - let extractor = try makeExtractor(from: #"#MyMacro(string: reference.arg)"#) - #expect(throws: ParameterExtractionError.unexpectedSyntaxType) { - try extractor.rawString(withLabel: "string") + @Test + func unexpectedSyntax() throws { + let extractor = try makeExtractor(from: #"#MyMacro(string: reference.arg)"#) + #expect(throws: ParameterExtractionError.unexpectedSyntaxType) { + try extractor.rawString(withLabel: "string") + } } } } extension ParameterExtractorTests { - @Test( - arguments: [ - "MainActor", - "SomeType.SomeActor" - ] - ) - func globalActorExtraction(_ isolation: String) throws { - let extractor = try makeExtractor(from: "#MyMacro(isolation: \(raw: isolation).self)") - let extracted = try extractor.globalActorIsolation(withLabel: "isolation") - #expect(extracted?.standardizedIsolationType?.trimmedDescription == isolation) - } + final class GlobalActorIsolation: ParameterExtractorTests { + + @Test( + arguments: [ + "MainActor", + "SomeType.SomeActor" + ] + ) + func extraction(_ isolation: String) throws { + let extractor = try makeExtractor(from: "#MyMacro(isolation: \(raw: isolation).self)") + let extracted = try extractor.globalActorIsolation(withLabel: "isolation") + #expect(extracted?.standardizedIsolationType?.trimmedDescription == isolation) + } - @Test - func explicitNilGlobalActorExtraction() throws { - let extractor = try makeExtractor(from: "#MyMacro(isolation: nil)") - let extracted = try extractor.globalActorIsolation(withLabel: "isolation") - #expect(extracted?.trimmedNonisolatedModifier?.trimmedDescription == "nonisolated") - } + @Test + func nonisolatedExtraction() throws { + let extractor = try makeExtractor(from: "#MyMacro(isolation: nil)") + let extracted = try extractor.globalActorIsolation(withLabel: "isolation") + #expect(extracted?.trimmedNonisolatedModifier?.trimmedDescription == "nonisolated") + } - @Test - func unexpectedSyntaxWhenPerformingGlobalActorExtraction() throws { - let extractor = try makeExtractor(from: #"#MyMacro(isolation: MainActor.Type)"#) - #expect(throws: ParameterExtractionError.unexpectedSyntaxType) { - try extractor.globalActorIsolation(withLabel: "isolation") + @Test + func unexpectedSyntax() throws { + let extractor = try makeExtractor(from: #"#MyMacro(isolation: MainActor.Type)"#) + #expect(throws: ParameterExtractionError.unexpectedSyntaxType) { + try extractor.globalActorIsolation(withLabel: "isolation") + } } } } extension ParameterExtractorTests { - @Test( - arguments: [ - "MyType", - "SomeType.MyType" - ] - ) - func typeExtraction(_ type: String) throws { - let extractor = try makeExtractor(from: "#MyMacro(type: \(raw: type).self)") - let extracted = try extractor.type(withLabel: "type") - #expect(extracted?.trimmedDescription == type) - } + final class TypeReference: ParameterExtractorTests { + + @Test( + arguments: [ + "MyType", + "SomeType.MyType" + ] + ) + func extraction(_ type: String) throws { + let extractor = try makeExtractor(from: "#MyMacro(type: \(raw: type).self)") + let extracted = try extractor.type(withLabel: "type") + #expect(extracted?.trimmedDescription == type) + } - @Test - func unexpectedSyntaxWhenPerformingTypeExtraction() throws { - let extractor = try makeExtractor(from: #"#MyMacro(type: MainActor.Type)"#) - #expect(throws: ParameterExtractionError.unexpectedSyntaxType) { - try extractor.type(withLabel: "type") + @Test + func unexpectedSyntax() throws { + let extractor = try makeExtractor(from: #"#MyMacro(type: MainActor.Type)"#) + #expect(throws: ParameterExtractionError.unexpectedSyntaxType) { + try extractor.type(withLabel: "type") + } } } } From fef7ff95d1a07a4b809f2418535e2777a41c7a8b Mon Sep 17 00:00:00 2001 From: Kamil Strzelecki Date: Fri, 28 Nov 2025 15:26:26 +0100 Subject: [PATCH 45/49] - --- ...IfConfigDeclSyntax+EnclosingIfConfig.swift | 19 +++++++---- .../Extensions/IfConfigDeclSyntaxTests.swift | 34 +++++++++++++++++++ 2 files changed, 47 insertions(+), 6 deletions(-) diff --git a/Sources/PrincipleMacros/Syntax/Extensions/IfConfigDeclSyntax+EnclosingIfConfig.swift b/Sources/PrincipleMacros/Syntax/Extensions/IfConfigDeclSyntax+EnclosingIfConfig.swift index d50b045..3933ad6 100644 --- a/Sources/PrincipleMacros/Syntax/Extensions/IfConfigDeclSyntax+EnclosingIfConfig.swift +++ b/Sources/PrincipleMacros/Syntax/Extensions/IfConfigDeclSyntax+EnclosingIfConfig.swift @@ -95,15 +95,22 @@ extension DeclSyntaxProtocol { public func applyingEnclosingIfConfig( to members: MemberBlockItemListSyntax ) -> IfConfigDeclSyntax? { - guard var parent = parent?.parent?.as(MemberBlockItemListSyntax.self) else { + guard var ancestor = parent?.parent?.parent?.as(IfConfigClauseSyntax.self) else { return nil } - parent.replaceSubrange( - parent.startIndex ..< parent.endIndex, - with: members - ) + ancestor = ancestor.with(\.elements, .decls(members.withLeadingNewline)) + return ancestor.enclosingIfConfig + } + + public func applyingEnclosingIfConfig( + to statements: CodeBlockItemListSyntax + ) -> IfConfigDeclSyntax? { + guard var ancestor = parent?.parent?.parent?.as(IfConfigClauseSyntax.self) else { + return nil + } - return parent.withLeadingNewline.enclosingIfConfig + ancestor = ancestor.with(\.elements, .statements(statements.withLeadingNewline)) + return ancestor.enclosingIfConfig } } diff --git a/Tests/PrincipleMacrosTests/Syntax/Extensions/IfConfigDeclSyntaxTests.swift b/Tests/PrincipleMacrosTests/Syntax/Extensions/IfConfigDeclSyntaxTests.swift index 9550e75..679d4a9 100644 --- a/Tests/PrincipleMacrosTests/Syntax/Extensions/IfConfigDeclSyntaxTests.swift +++ b/Tests/PrincipleMacrosTests/Syntax/Extensions/IfConfigDeclSyntaxTests.swift @@ -163,6 +163,40 @@ internal enum IfConfigDeclSyntaxTests { #expect(ifConfig?.description == expectation) } + @Test + func applyToNewStatements() throws { + let decl: DeclSyntax = """ + class MyClass { + #if DEBUG + var other = "hello" + #else + #if os(macOS) + var test = 123 + #endif + #endif + } + """ + + let newStatements: CodeBlockItemListSyntax = """ + var replacement = "Hello" + func test() {} + """ + + let expectation = """ + #if DEBUG + #else + #if os(macOS) + var replacement = "Hello" + func test() {} + #endif + #endif + """ + + let property = try parseLastProperty(in: decl) + let ifConfig = property.underlying.applyingEnclosingIfConfig(to: newStatements) + #expect(ifConfig?.description == expectation) + } + // swiftlint:enable empty_line_after_type_declaration } } From b6753bbeeeca4c1a55dea399e8c5299e4b226e4f Mon Sep 17 00:00:00 2001 From: Kamil Strzelecki Date: Fri, 28 Nov 2025 16:12:11 +0100 Subject: [PATCH 46/49] - --- .../Builders/Declarations/Common/DeclBuilder.swift | 4 ++++ .../Syntax/Extensions/AvailabilityTests.swift | 2 ++ 2 files changed, 6 insertions(+) diff --git a/Sources/PrincipleMacros/Builders/Declarations/Common/DeclBuilder.swift b/Sources/PrincipleMacros/Builders/Declarations/Common/DeclBuilder.swift index 6eaa345..b67b740 100644 --- a/Sources/PrincipleMacros/Builders/Declarations/Common/DeclBuilder.swift +++ b/Sources/PrincipleMacros/Builders/Declarations/Common/DeclBuilder.swift @@ -32,4 +32,8 @@ extension DeclBuilder { public var preferredAccessControlLevel: AccessControlLevel? { nil } + + public var inheritedAvailability: AttributeListSyntax? { + basicDeclaration.availability?.trimmed.withTrailingNewline + } } diff --git a/Tests/PrincipleMacrosTests/Syntax/Extensions/AvailabilityTests.swift b/Tests/PrincipleMacrosTests/Syntax/Extensions/AvailabilityTests.swift index 91613dc..50b2236 100644 --- a/Tests/PrincipleMacrosTests/Syntax/Extensions/AvailabilityTests.swift +++ b/Tests/PrincipleMacrosTests/Syntax/Extensions/AvailabilityTests.swift @@ -39,6 +39,7 @@ internal struct AvailabilityTests { let decl: DeclSyntax = """ #if os(macOS) @MainActor + @available(macOS 26, *) #else @Observable @available(iOS 26, *) @@ -48,6 +49,7 @@ internal struct AvailabilityTests { let expectation = """ #if os(macOS) + @available(macOS 26, *) #else @available(iOS 26, *) #endif From 2fb77b48c6ea646cf1144454d42a54e975745a34 Mon Sep 17 00:00:00 2001 From: Kamil Strzelecki Date: Sat, 29 Nov 2025 20:07:06 +0100 Subject: [PATCH 47/49] - --- .../Syntax/Extensions/ClassDeclSyntax.swift | 52 +++++++------------ 1 file changed, 19 insertions(+), 33 deletions(-) diff --git a/Sources/PrincipleMacros/Syntax/Extensions/ClassDeclSyntax.swift b/Sources/PrincipleMacros/Syntax/Extensions/ClassDeclSyntax.swift index 7ccfc08..f9090ce 100644 --- a/Sources/PrincipleMacros/Syntax/Extensions/ClassDeclSyntax.swift +++ b/Sources/PrincipleMacros/Syntax/Extensions/ClassDeclSyntax.swift @@ -10,53 +10,39 @@ import SwiftSyntaxMacros extension ClassDeclSyntax { - public var unverifiedInferredSuperclassType: TypeSyntax? { - inheritanceClause?.inheritedTypes.first?.type.trimmed - } - - public func inferredSuperclassType() -> TypeSyntax? { - let verifier = SuperclassVerifier(for: self) - return verifier.verifiedSuperclassType() - } - public func inferredSuperclassType( - isExpected: Bool? - ) throws -> TypeSyntax? { - switch isExpected { - case nil: - return inferredSuperclassType() - case true: - if let type = unverifiedInferredSuperclassType { - return type - } - throw DiagnosticsError( - node: self, - message: "\(name.trimmed) should have a superclass" - ) - case false: - return nil - } + isKnownToBeSubclass: Bool = false + ) -> TypeSyntax? { + let finder = SuperclassFinder(for: self) + let needsCheck = !isKnownToBeSubclass + return finder.find(checkAgainstSubclassSpecificKeywords: needsCheck) } } extension ClassDeclSyntax { - private final class SuperclassVerifier: SyntaxVisitor { + private final class SuperclassFinder: SyntaxVisitor { private let classDecl: ClassDeclSyntax - private var didVerify = false + private var didFind = false init(for classDecl: ClassDeclSyntax) { self.classDecl = classDecl super.init(viewMode: .sourceAccurate) } - func verifiedSuperclassType() -> TypeSyntax? { - if let unverified = classDecl.unverifiedInferredSuperclassType { + func find(checkAgainstSubclassSpecificKeywords: Bool) -> TypeSyntax? { + guard let inheritedTypes = classDecl.inheritanceClause?.inheritedTypes, + let superclassType = inheritedTypes.first?.type.trimmed + else { + return nil + } + + if checkAgainstSubclassSpecificKeywords { walk(classDecl) - return didVerify ? unverified : nil + return didFind ? superclassType : nil } else { - return nil + return superclassType } } @@ -65,12 +51,12 @@ extension ClassDeclSyntax { } override func visit(_ node: DeclModifierSyntax) -> SyntaxVisitorContinueKind { - didVerify = didVerify || node.overrideSpecifier != nil + didFind = didFind || node.overrideSpecifier != nil return .visitChildren } override func visit(_: SuperExprSyntax) -> SyntaxVisitorContinueKind { - didVerify = true + didFind = true return .visitChildren } } From 003b250be2913c5003328a1a330245897077e0cc Mon Sep 17 00:00:00 2001 From: Kamil Strzelecki Date: Sat, 29 Nov 2025 20:16:45 +0100 Subject: [PATCH 48/49] - --- .../Syntax/Extensions/ClassDeclSyntax.swift | 27 ++++++++----------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/Sources/PrincipleMacros/Syntax/Extensions/ClassDeclSyntax.swift b/Sources/PrincipleMacros/Syntax/Extensions/ClassDeclSyntax.swift index f9090ce..b988f8f 100644 --- a/Sources/PrincipleMacros/Syntax/Extensions/ClassDeclSyntax.swift +++ b/Sources/PrincipleMacros/Syntax/Extensions/ClassDeclSyntax.swift @@ -10,18 +10,19 @@ import SwiftSyntaxMacros extension ClassDeclSyntax { - public func inferredSuperclassType( - isKnownToBeSubclass: Bool = false - ) -> TypeSyntax? { - let finder = SuperclassFinder(for: self) - let needsCheck = !isKnownToBeSubclass - return finder.find(checkAgainstSubclassSpecificKeywords: needsCheck) + public var possibleSuperclassType: TypeSyntax? { + inheritanceClause?.inheritedTypes.first?.type.trimmed + } + + public func inferredSuperclassType() -> TypeSyntax? { + let inferrer = SuperclassTypeInferrer(for: self) + return inferrer.infer() } } extension ClassDeclSyntax { - private final class SuperclassFinder: SyntaxVisitor { + private final class SuperclassTypeInferrer: SyntaxVisitor { private let classDecl: ClassDeclSyntax private var didFind = false @@ -31,18 +32,12 @@ extension ClassDeclSyntax { super.init(viewMode: .sourceAccurate) } - func find(checkAgainstSubclassSpecificKeywords: Bool) -> TypeSyntax? { - guard let inheritedTypes = classDecl.inheritanceClause?.inheritedTypes, - let superclassType = inheritedTypes.first?.type.trimmed - else { - return nil - } - - if checkAgainstSubclassSpecificKeywords { + func infer() -> TypeSyntax? { + if let superclassType = classDecl.possibleSuperclassType { walk(classDecl) return didFind ? superclassType : nil } else { - return superclassType + return nil } } From 4d154d0baf2e030e012cef40c1884d3bbbbf2ac6 Mon Sep 17 00:00:00 2001 From: Kamil Strzelecki Date: Sat, 29 Nov 2025 21:05:12 +0100 Subject: [PATCH 49/49] - --- .../Parameters/ParameterExtractor.swift | 2 +- ...IfConfigDeclSyntax+EnclosingIfConfig.swift | 21 ++++++++++--------- .../Builders/SwitchExprBuilderTests.swift | 2 +- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/Sources/PrincipleMacros/Parameters/ParameterExtractor.swift b/Sources/PrincipleMacros/Parameters/ParameterExtractor.swift index 159e119..b8896bc 100644 --- a/Sources/PrincipleMacros/Parameters/ParameterExtractor.swift +++ b/Sources/PrincipleMacros/Parameters/ParameterExtractor.swift @@ -186,7 +186,7 @@ extension ParameterExtractor { return nil } - if NilLiteralExprSyntax(expression) != nil { + if expression.is(NilLiteralExprSyntax.self) { let isolation = DeclModifierSyntax(name: .keyword(.nonisolated)) return .nonisolated(trimmedModifer: isolation) } diff --git a/Sources/PrincipleMacros/Syntax/Extensions/IfConfigDeclSyntax+EnclosingIfConfig.swift b/Sources/PrincipleMacros/Syntax/Extensions/IfConfigDeclSyntax+EnclosingIfConfig.swift index 3933ad6..13bbba5 100644 --- a/Sources/PrincipleMacros/Syntax/Extensions/IfConfigDeclSyntax+EnclosingIfConfig.swift +++ b/Sources/PrincipleMacros/Syntax/Extensions/IfConfigDeclSyntax+EnclosingIfConfig.swift @@ -95,22 +95,23 @@ extension DeclSyntaxProtocol { public func applyingEnclosingIfConfig( to members: MemberBlockItemListSyntax ) -> IfConfigDeclSyntax? { - guard var ancestor = parent?.parent?.parent?.as(IfConfigClauseSyntax.self) else { - return nil - } - - ancestor = ancestor.with(\.elements, .decls(members.withLeadingNewline)) - return ancestor.enclosingIfConfig + applyingEnclosingIfConfig(to: .decls(members.withLeadingNewline)) } public func applyingEnclosingIfConfig( to statements: CodeBlockItemListSyntax ) -> IfConfigDeclSyntax? { - guard var ancestor = parent?.parent?.parent?.as(IfConfigClauseSyntax.self) else { + applyingEnclosingIfConfig(to: .statements(statements.withLeadingNewline)) + } + + private func applyingEnclosingIfConfig( + to elements: IfConfigClauseSyntax.Elements + ) -> IfConfigDeclSyntax? { + if var ancestor = parent?.parent?.parent?.as(IfConfigClauseSyntax.self) { + ancestor = ancestor.with(\.elements, elements) + return ancestor.enclosingIfConfig + } else { return nil } - - ancestor = ancestor.with(\.elements, .statements(statements.withLeadingNewline)) - return ancestor.enclosingIfConfig } } diff --git a/Tests/PrincipleMacrosTests/Builders/SwitchExprBuilderTests.swift b/Tests/PrincipleMacrosTests/Builders/SwitchExprBuilderTests.swift index 011c462..4709c49 100644 --- a/Tests/PrincipleMacrosTests/Builders/SwitchExprBuilderTests.swift +++ b/Tests/PrincipleMacrosTests/Builders/SwitchExprBuilderTests.swift @@ -18,7 +18,7 @@ internal struct SwitchExprBuilderTests { } @Test - func switchExpression() throws { + func build() throws { let enumCases = try EnumCasesList([ makeEnumCase(from: "case first"), makeEnumCase(from: "case second(Int)"),