From 82016224cdd6ae7e5ac8620859adeb2b39195160 Mon Sep 17 00:00:00 2001 From: Kamil Strzelecki Date: Sun, 30 Nov 2025 18:52:28 +0100 Subject: [PATCH 1/9] - --- .swiftlint.tests.yml | 1 + .swiftlint.yml | 17 ++++----- .../PrincipleMacros/Diagnostics/FixIt.swift | 35 +++++++++++++++++++ 3 files changed, 43 insertions(+), 10 deletions(-) create mode 100644 Sources/PrincipleMacros/Diagnostics/FixIt.swift diff --git a/.swiftlint.tests.yml b/.swiftlint.tests.yml index ee516cf..504622d 100644 --- a/.swiftlint.tests.yml +++ b/.swiftlint.tests.yml @@ -1,3 +1,4 @@ disabled_rules: - function_body_length + - type_body_length - no_magic_numbers \ No newline at end of file diff --git a/.swiftlint.yml b/.swiftlint.yml index a98bea7..54988a0 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -8,7 +8,7 @@ opt_in_rules: - anonymous_argument_in_multiline_closure - array_init - async_without_await - # attributes + # attributes (swiftformat) # balanced_xctest_lifecycle # closure_body_length - closure_end_indentation @@ -55,9 +55,9 @@ opt_in_rules: - ibinspectable_in_extension - identical_operands - implicit_return - # implicitly_unwrapped_optional + - implicitly_unwrapped_optional # incompatible_concurrency_annotation - # indentation_width + # indentation_width (swiftformat) - joined_default_parameter - last_where - legacy_multiple @@ -67,7 +67,7 @@ opt_in_rules: - local_doc_comment - lower_acl_than_parent # missing_docs - # modifier_order + # modifier_order (swiftformat) - multiline_arguments - multiline_arguments_brackets - multiline_function_chains @@ -144,7 +144,7 @@ opt_in_rules: # vertical_whitespace_opening_braces - weak_delegate - xct_specific_matcher - # yoda_condition + # yoda_condition (swiftformat) analyzer_rules: - capture_variable @@ -173,6 +173,7 @@ identifier_name: excluded: [id, ui, x, y, z, dx, dy, dz] line_length: + ignores_multiline_strings: true ignores_comments: true nesting: @@ -189,10 +190,6 @@ type_contents_order: order: [[case], [type_alias, associated_type], [subtype], [type_property], [instance_property], [ib_inspectable], [ib_outlet], [initializer], [deinitializer], [type_method], [view_life_cycle_method], [ib_action, ib_segue_action], [other_method], [subscript]] custom_rules: - global_actor_attribute_order: - name: "Global actor attribute order" - message: "Global actor should be the first attribute." - regex: "(?-s)(@.+[^,\\s]\\s+@.*Actor\\s)" sendable_attribute_order: name: "Sendable attribute order" message: "Sendable should be the first attribute." @@ -204,4 +201,4 @@ custom_rules: empty_line_after_type_declaration: name: "Empty line after type declaration" message: "Type declaration should start with an empty line." - regex: "( |^)(actor|class|struct|enum|protocol|extension) (?!var)[^\\{]*? \\{(?!\\s*\\}) *\\n? *\\S" + regex: "( |^)(actor|class|struct|enum|protocol|extension) (?!var)[^\\n\\{]*? \\{(?!\\s*\\}) *\\n? *\\S" diff --git a/Sources/PrincipleMacros/Diagnostics/FixIt.swift b/Sources/PrincipleMacros/Diagnostics/FixIt.swift new file mode 100644 index 0000000..55a6c8d --- /dev/null +++ b/Sources/PrincipleMacros/Diagnostics/FixIt.swift @@ -0,0 +1,35 @@ +// +// FixIt.swift +// PrincipleMacros +// +// Created by Kamil Strzelecki on 30/11/2025. +// Copyright © 2025 Kamil Strzelecki. All rights reserved. +// + +import SwiftSyntaxMacros + +extension FixIt { + + public static func remove( + message: String, + oldNode: some SyntaxProtocol + ) -> Self { + .replace( + message: MacroExpansionFixItMessage(message), + oldNode: oldNode, + newNode: "\(oldNode.leadingTrivia)" as TokenSyntax + ) + } + + public static func replace( + message: String, + oldNode: some SyntaxProtocol, + newNode: some SyntaxProtocol + ) -> Self { + .replace( + message: MacroExpansionFixItMessage(message), + oldNode: oldNode, + newNode: newNode.withTrivia(from: oldNode) + ) + } +} From 2fa57c653293403aac84b9a98cab5c8264d74a8a Mon Sep 17 00:00:00 2001 From: Kamil Strzelecki Date: Sun, 30 Nov 2025 19:09:16 +0100 Subject: [PATCH 2/9] - --- Sources/PrincipleMacros/Parsers/EnumCases/EnumCasesParser.swift | 2 +- .../PrincipleMacros/Parsers/Properties/PropertiesParser.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/PrincipleMacros/Parsers/EnumCases/EnumCasesParser.swift b/Sources/PrincipleMacros/Parsers/EnumCases/EnumCasesParser.swift index 09d896a..0172c24 100644 --- a/Sources/PrincipleMacros/Parsers/EnumCases/EnumCasesParser.swift +++ b/Sources/PrincipleMacros/Parsers/EnumCases/EnumCasesParser.swift @@ -14,7 +14,7 @@ public enum EnumCasesParser: Parser { declaration: some DeclSyntaxProtocol ) -> EnumCasesList { guard let declaration = EnumCaseDeclSyntax(declaration) else { - return .init() + return EnumCasesList() } return EnumCasesList( diff --git a/Sources/PrincipleMacros/Parsers/Properties/PropertiesParser.swift b/Sources/PrincipleMacros/Parsers/Properties/PropertiesParser.swift index 106cc98..26e5da8 100644 --- a/Sources/PrincipleMacros/Parsers/Properties/PropertiesParser.swift +++ b/Sources/PrincipleMacros/Parsers/Properties/PropertiesParser.swift @@ -14,7 +14,7 @@ public enum PropertiesParser: Parser { declaration: some DeclSyntaxProtocol ) throws -> PropertiesList { guard let declaration = VariableDeclSyntax(declaration) else { - return .init() + return PropertiesList() } return try PropertiesList( From eaccbf0da2f83def22400deee58da2d937744e92 Mon Sep 17 00:00:00 2001 From: Kamil Strzelecki Date: Sun, 30 Nov 2025 19:15:03 +0100 Subject: [PATCH 3/9] - --- .../PrincipleMacros/Parameters/ParameterExtractor.swift | 3 +-- .../Syntax/Concepts/GlobalActorIsolation.swift | 8 ++++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/Sources/PrincipleMacros/Parameters/ParameterExtractor.swift b/Sources/PrincipleMacros/Parameters/ParameterExtractor.swift index b8896bc..6dad151 100644 --- a/Sources/PrincipleMacros/Parameters/ParameterExtractor.swift +++ b/Sources/PrincipleMacros/Parameters/ParameterExtractor.swift @@ -187,8 +187,7 @@ extension ParameterExtractor { } if expression.is(NilLiteralExprSyntax.self) { - let isolation = DeclModifierSyntax(name: .keyword(.nonisolated)) - return .nonisolated(trimmedModifer: isolation) + return .nonisolated } guard let memberAccessExpression = MemberAccessExprSyntax(expression), diff --git a/Sources/PrincipleMacros/Syntax/Concepts/GlobalActorIsolation.swift b/Sources/PrincipleMacros/Syntax/Concepts/GlobalActorIsolation.swift index bd62dd0..c86f376 100644 --- a/Sources/PrincipleMacros/Syntax/Concepts/GlobalActorIsolation.swift +++ b/Sources/PrincipleMacros/Syntax/Concepts/GlobalActorIsolation.swift @@ -13,6 +13,14 @@ public enum GlobalActorIsolation { case nonisolated(trimmedModifer: DeclModifierSyntax) case isolated(standardizedType: TypeSyntax) + public static var nonisolated: Self { + let modifier = DeclModifierSyntax(name: .keyword(.nonisolated)) + return .nonisolated(trimmedModifer: modifier) + } +} + +extension GlobalActorIsolation { + public var trimmedNonisolatedModifier: DeclModifierSyntax? { switch self { case let .nonisolated(trimmedModifer): From 68a66d8b4ec116a63e24338c0ed7e2c8d9c2ab57 Mon Sep 17 00:00:00 2001 From: Kamil Strzelecki Date: Sun, 30 Nov 2025 19:16:30 +0100 Subject: [PATCH 4/9] - --- .../Syntax/Extensions/AttributedTypeSyntax.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/PrincipleMacros/Syntax/Extensions/AttributedTypeSyntax.swift b/Sources/PrincipleMacros/Syntax/Extensions/AttributedTypeSyntax.swift index b2593da..2c4b905 100644 --- a/Sources/PrincipleMacros/Syntax/Extensions/AttributedTypeSyntax.swift +++ b/Sources/PrincipleMacros/Syntax/Extensions/AttributedTypeSyntax.swift @@ -12,7 +12,7 @@ extension AttributedTypeSyntax { public init( globalActorIsolation: GlobalActorIsolation?, - baseType: some TypeSyntaxProtocol + baseType: TypeSyntax ) { let specifiers: TypeSpecifierListSyntax = switch globalActorIsolation { From 77751a74e6d6fbc1464d6a13dfa63f0f5dd1ffca Mon Sep 17 00:00:00 2001 From: Kamil Strzelecki Date: Sun, 30 Nov 2025 21:39:04 +0100 Subject: [PATCH 5/9] - --- .../Parsers/Properties/Property.swift | 4 ++++ ...WithModifiersSyntax+AccessControlLevel.swift | 17 +++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/Sources/PrincipleMacros/Parsers/Properties/Property.swift b/Sources/PrincipleMacros/Parsers/Properties/Property.swift index 871706a..57fe392 100644 --- a/Sources/PrincipleMacros/Parsers/Properties/Property.swift +++ b/Sources/PrincipleMacros/Parsers/Properties/Property.swift @@ -13,6 +13,8 @@ public final class Property: ParserResult { public let underlying: VariableDeclSyntax public let binding: PatternBindingSyntax + + public let name: TokenSyntax public let trimmedName: TokenSyntax public let inferredType: TypeSyntax @@ -29,6 +31,8 @@ public final class Property: ParserResult { ) { self.underlying = declaration self.binding = binding + + self.name = name self.trimmedName = name.trimmed self.inferredType = inferredType diff --git a/Sources/PrincipleMacros/Syntax/Extensions/WithModifiersSyntax+AccessControlLevel.swift b/Sources/PrincipleMacros/Syntax/Extensions/WithModifiersSyntax+AccessControlLevel.swift index f9614f5..4db229c 100644 --- a/Sources/PrincipleMacros/Syntax/Extensions/WithModifiersSyntax+AccessControlLevel.swift +++ b/Sources/PrincipleMacros/Syntax/Extensions/WithModifiersSyntax+AccessControlLevel.swift @@ -23,6 +23,23 @@ extension WithModifiersSyntax { } } +extension DeclModifierListSyntax { + + public func withAccessControlLevel(_ level: AccessControlLevel?) -> Self { + var modifiers = filter { modifier in + modifier.accessControlLevel == nil + && modifier.setterAccessControlLevel == nil + } + + if let level { + let modifier = DeclModifierSyntax(name: level.tokenSyntax) + modifiers.insert(modifier, at: modifiers.startIndex) + } + + return modifiers + } +} + extension DeclModifierSyntax { public var accessControlLevel: AccessControlLevel? { From 39b7dc5c765e8752d3bfad768d975fde869c1c01 Mon Sep 17 00:00:00 2001 From: Kamil Strzelecki Date: Sun, 30 Nov 2025 22:29:22 +0100 Subject: [PATCH 6/9] - --- Sources/PrincipleMacros/Parsers/Properties/Property.swift | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Sources/PrincipleMacros/Parsers/Properties/Property.swift b/Sources/PrincipleMacros/Parsers/Properties/Property.swift index 57fe392..871706a 100644 --- a/Sources/PrincipleMacros/Parsers/Properties/Property.swift +++ b/Sources/PrincipleMacros/Parsers/Properties/Property.swift @@ -13,8 +13,6 @@ public final class Property: ParserResult { public let underlying: VariableDeclSyntax public let binding: PatternBindingSyntax - - public let name: TokenSyntax public let trimmedName: TokenSyntax public let inferredType: TypeSyntax @@ -31,8 +29,6 @@ public final class Property: ParserResult { ) { self.underlying = declaration self.binding = binding - - self.name = name self.trimmedName = name.trimmed self.inferredType = inferredType From fcf39f7792bcf83eaf37fcaefd97e02c5acde8a8 Mon Sep 17 00:00:00 2001 From: Kamil Strzelecki Date: Mon, 1 Dec 2025 15:30:09 +0100 Subject: [PATCH 7/9] - --- .../Parsers/Common/Parser.swift | 28 +--- .../Parsers/Properties/PropertiesParser.swift | 18 +++ .../Syntax/Concepts/AccessControlLevel.swift | 2 +- .../Concepts/GlobalActorIsolation.swift | 2 +- .../Extensions/AttributedTypeSyntax.swift | 10 ++ ...IfConfigDeclSyntax+EnclosingIfConfig.swift | 43 +++--- .../MemberBlockItemListSyntax.swift | 41 ++++++ .../Parsers/PropertiesListTests.swift | 2 +- .../Concepts/AccessControlLevelTests.swift | 127 ++++++++++++++++++ .../Extensions/IfConfigDeclSyntaxTests.swift | 14 +- .../MemberBlockItemListSyntaxTests.swift | 64 +++++++++ 11 files changed, 303 insertions(+), 48 deletions(-) create mode 100644 Sources/PrincipleMacros/Syntax/Extensions/MemberBlockItemListSyntax.swift create mode 100644 Tests/PrincipleMacrosTests/Syntax/Concepts/AccessControlLevelTests.swift create mode 100644 Tests/PrincipleMacrosTests/Syntax/Extensions/MemberBlockItemListSyntaxTests.swift diff --git a/Sources/PrincipleMacros/Parsers/Common/Parser.swift b/Sources/PrincipleMacros/Parsers/Common/Parser.swift index d523dcc..2ede4ab 100644 --- a/Sources/PrincipleMacros/Parsers/Common/Parser.swift +++ b/Sources/PrincipleMacros/Parsers/Common/Parser.swift @@ -20,37 +20,19 @@ public protocol Parser { extension Parser { public static func parse( - ifConfig: IfConfigDeclSyntax + declarationGroup: some DeclGroupSyntax ) throws -> ResultsCollection { - try ResultsCollection( - ifConfig.clauses.flatMap { clause in - switch clause.elements { - case let .decls(members): - try parse(members: members) - default: - ResultsCollection() - } - } - ) + let members = declarationGroup.memberBlock.members.flattened + return try parse(members: members) } public static func parse( - members: MemberBlockItemListSyntax + members: some Sequence ) throws -> ResultsCollection { try ResultsCollection( members.flatMap { member in - if let ifConfig = member.decl.as(IfConfigDeclSyntax.self) { - try parse(ifConfig: ifConfig) - } else { - try parse(declaration: member.decl) - } + try parse(declaration: member.decl) } ) } - - public static func parse( - memberBlock: MemberBlockSyntax - ) throws -> ResultsCollection { - try parse(members: memberBlock.members) - } } diff --git a/Sources/PrincipleMacros/Parsers/Properties/PropertiesParser.swift b/Sources/PrincipleMacros/Parsers/Properties/PropertiesParser.swift index 26e5da8..2c0c13b 100644 --- a/Sources/PrincipleMacros/Parsers/Properties/PropertiesParser.swift +++ b/Sources/PrincipleMacros/Parsers/Properties/PropertiesParser.swift @@ -42,4 +42,22 @@ public enum PropertiesParser: Parser { } ) } + + public static func parseStandalone( + declaration: some DeclSyntaxProtocol + ) throws -> Property? { + let properties = try parse(declaration: declaration) + guard let first = properties.first else { + return nil + } + + guard properties.count == 1 else { + throw DiagnosticsError( + node: declaration, + message: "Property must have only one binding" + ) + } + + return first + } } diff --git a/Sources/PrincipleMacros/Syntax/Concepts/AccessControlLevel.swift b/Sources/PrincipleMacros/Syntax/Concepts/AccessControlLevel.swift index 105254a..dd6c50e 100644 --- a/Sources/PrincipleMacros/Syntax/Concepts/AccessControlLevel.swift +++ b/Sources/PrincipleMacros/Syntax/Concepts/AccessControlLevel.swift @@ -6,7 +6,7 @@ // Copyright © 2025 Kamil Strzelecki. All rights reserved. // -public enum AccessControlLevel: Int, Hashable, CaseIterable { +public enum AccessControlLevel: Int, Hashable, CaseIterable, Sendable { case `private` case `fileprivate` diff --git a/Sources/PrincipleMacros/Syntax/Concepts/GlobalActorIsolation.swift b/Sources/PrincipleMacros/Syntax/Concepts/GlobalActorIsolation.swift index c86f376..3fefc30 100644 --- a/Sources/PrincipleMacros/Syntax/Concepts/GlobalActorIsolation.swift +++ b/Sources/PrincipleMacros/Syntax/Concepts/GlobalActorIsolation.swift @@ -71,7 +71,7 @@ extension GlobalActorIsolation { } private static func _resolved( - in fullContext: some Collection, + in fullContext: some Sequence, preferred: Self? ) -> Self? { if let preferred { diff --git a/Sources/PrincipleMacros/Syntax/Extensions/AttributedTypeSyntax.swift b/Sources/PrincipleMacros/Syntax/Extensions/AttributedTypeSyntax.swift index 2c4b905..fc53468 100644 --- a/Sources/PrincipleMacros/Syntax/Extensions/AttributedTypeSyntax.swift +++ b/Sources/PrincipleMacros/Syntax/Extensions/AttributedTypeSyntax.swift @@ -35,4 +35,14 @@ extension AttributedTypeSyntax { baseType: baseType ) } + + public init( + globalActorIsolation: GlobalActorIsolation?, + baseType: some TypeSyntaxProtocol + ) { + self.init( + globalActorIsolation: globalActorIsolation, + baseType: TypeSyntax(baseType) + ) + } } diff --git a/Sources/PrincipleMacros/Syntax/Extensions/IfConfigDeclSyntax+EnclosingIfConfig.swift b/Sources/PrincipleMacros/Syntax/Extensions/IfConfigDeclSyntax+EnclosingIfConfig.swift index 13bbba5..eac96c4 100644 --- a/Sources/PrincipleMacros/Syntax/Extensions/IfConfigDeclSyntax+EnclosingIfConfig.swift +++ b/Sources/PrincipleMacros/Syntax/Extensions/IfConfigDeclSyntax+EnclosingIfConfig.swift @@ -6,7 +6,7 @@ // Copyright © 2025 Kamil Strzelecki. All rights reserved. // -import SwiftSyntax +import SwiftSyntaxMacros extension IfConfigDeclSyntax { @@ -65,6 +65,17 @@ extension MemberBlockItemListSyntax { } return nil } + + @MemberBlockItemListBuilder + public func withIfConfigIfPresent( + from declaration: some DeclSyntaxProtocol + ) -> Self { + if let ifConfig = declaration.applyEnclosingIfConfig(to: .decls(self)) { + ifConfig + } else { + self + } + } } extension MemberBlockItemSyntax { @@ -83,6 +94,20 @@ extension MemberBlockItemSyntax { } } +extension CodeBlockItemListSyntax { + + @CodeBlockItemListBuilder + public func withIfConfigIfPresent( + from declaration: some DeclSyntaxProtocol + ) -> Self { + if let ifConfig = declaration.applyEnclosingIfConfig(to: .statements(self)) { + ifConfig + } else { + self + } + } +} + extension DeclSyntaxProtocol { public var enclosingIfConfig: IfConfigDeclSyntax? { @@ -92,23 +117,11 @@ extension DeclSyntaxProtocol { return nil } - public func applyingEnclosingIfConfig( - to members: MemberBlockItemListSyntax - ) -> IfConfigDeclSyntax? { - applyingEnclosingIfConfig(to: .decls(members.withLeadingNewline)) - } - - public func applyingEnclosingIfConfig( - to statements: CodeBlockItemListSyntax - ) -> IfConfigDeclSyntax? { - applyingEnclosingIfConfig(to: .statements(statements.withLeadingNewline)) - } - - private func applyingEnclosingIfConfig( + fileprivate func applyEnclosingIfConfig( to elements: IfConfigClauseSyntax.Elements ) -> IfConfigDeclSyntax? { if var ancestor = parent?.parent?.parent?.as(IfConfigClauseSyntax.self) { - ancestor = ancestor.with(\.elements, elements) + ancestor = ancestor.with(\.elements, elements.withLeadingNewline) return ancestor.enclosingIfConfig } else { return nil diff --git a/Sources/PrincipleMacros/Syntax/Extensions/MemberBlockItemListSyntax.swift b/Sources/PrincipleMacros/Syntax/Extensions/MemberBlockItemListSyntax.swift new file mode 100644 index 0000000..e2b06f4 --- /dev/null +++ b/Sources/PrincipleMacros/Syntax/Extensions/MemberBlockItemListSyntax.swift @@ -0,0 +1,41 @@ +// +// MemberBlockItemListSyntax.swift +// PrincipleMacros +// +// Created by Kamil Strzelecki on 30/11/2025. +// Copyright © 2025 Kamil Strzelecki. All rights reserved. +// + +import SwiftSyntaxMacros + +extension MemberBlockItemListSyntax { + + public var flattened: some Sequence { + lazy.flatMap { member in + if let ifConfig = member.decl.as(IfConfigDeclSyntax.self) { + AnySequence(ifConfig.flattenedMembers) + } else { + AnySequence(CollectionOfOne(member)) + } + } + } +} + +extension IfConfigDeclSyntax { + + public var flattenedMembers: some Sequence { + clauses.lazy.flatMap(\.flattenedMembers) + } +} + +extension IfConfigClauseSyntax { + + public var flattenedMembers: some Sequence { + switch elements { + case .decls(let members): + AnySequence(members.flattened) + default: + AnySequence(EmptyCollection()) + } + } +} diff --git a/Tests/PrincipleMacrosTests/Parsers/PropertiesListTests.swift b/Tests/PrincipleMacrosTests/Parsers/PropertiesListTests.swift index 4214982..4c98af4 100644 --- a/Tests/PrincipleMacrosTests/Parsers/PropertiesListTests.swift +++ b/Tests/PrincipleMacrosTests/Parsers/PropertiesListTests.swift @@ -34,7 +34,7 @@ internal struct PropertiesListTests { """ let classDecl = try #require(decl.as(ClassDeclSyntax.self)) - self.list = try PropertiesParser.parse(memberBlock: classDecl.memberBlock) + self.list = try PropertiesParser.parse(declarationGroup: classDecl) } @Test diff --git a/Tests/PrincipleMacrosTests/Syntax/Concepts/AccessControlLevelTests.swift b/Tests/PrincipleMacrosTests/Syntax/Concepts/AccessControlLevelTests.swift new file mode 100644 index 0000000..1e91c31 --- /dev/null +++ b/Tests/PrincipleMacrosTests/Syntax/Concepts/AccessControlLevelTests.swift @@ -0,0 +1,127 @@ +// +// AccessControlLevelTests.swift +// PrincipleMacros +// +// Created by Kamil Strzelecki on 01/12/2025. +// Copyright © 2025 Kamil Strzelecki. All rights reserved. +// + +import Testing +@testable import PrincipleMacros + +internal struct AccessControlLevelTests { + + @Test( + arguments: [ + Keyword.private, + Keyword.fileprivate, + Keyword.internal, + Keyword.package, + Keyword.public, + Keyword.open + ] + ) + func conversion(_ keyword: Keyword) { + let tokenSyntax = TokenSyntax(.keyword(keyword), presence: .present) + let level = AccessControlLevel(tokenSyntax: tokenSyntax) + #expect(tokenSyntax.description == level?.tokenSyntax.description) + } + + @Test + func comparison() { + #expect(AccessControlLevel.private < .fileprivate) + #expect(AccessControlLevel.fileprivate < .internal) + #expect(AccessControlLevel.internal < .package) + #expect(AccessControlLevel.package < .public) + #expect(AccessControlLevel.public < .open) + } +} + +extension AccessControlLevelTests { + + struct MemberInheritance { + + func makeDecl(with level: AccessControlLevel) throws -> ClassDeclSyntax { + let decl: DeclSyntax = "\(level)class MyClass {}" + return try #require(decl.as(ClassDeclSyntax.self)) + } + + @Test + func privateShouldBeRemoved() throws { + let decl = try makeDecl(with: .private) + let inherited = AccessControlLevel.forMember(of: decl) + #expect(inherited == nil) + } + + @Test + func openShouldBecomePublicByDefault() throws { + let decl = try makeDecl(with: .open) + let inherited = AccessControlLevel.forMember(of: decl) + #expect(inherited == .public) + } + + @Test(arguments: AccessControlLevel.allCases.dropFirst().dropLast()) + func othersShouldBeKept(_ level: AccessControlLevel) throws { + let decl = try makeDecl(with: level) + let inherited = AccessControlLevel.forMember(of: decl) + #expect(inherited == level) + } + } +} + +extension AccessControlLevelTests { + + struct SiblingInheritance { + + func makeDecl(with level: AccessControlLevel) throws -> VariableDeclSyntax { + let decl: DeclSyntax = "\(level)var myVar = 123" + return try #require(decl.as(VariableDeclSyntax.self)) + } + + @Test + func privateShouldBecomeFileprivate() throws { + let decl = try makeDecl(with: .private) + let inherited = AccessControlLevel.forSibling(of: decl) + #expect(inherited == .fileprivate) + } + + @Test + func openShouldBecomePublicByDefault() throws { + let decl = try makeDecl(with: .open) + let inherited = AccessControlLevel.forSibling(of: decl) + #expect(inherited == .public) + } + + @Test(arguments: AccessControlLevel.allCases.dropFirst().dropLast()) + func othersShouldBeKept(_ level: AccessControlLevel) throws { + let decl = try makeDecl(with: level) + let inherited = AccessControlLevel.forSibling(of: decl) + #expect(inherited == level) + } + } +} + +extension AccessControlLevelTests { + + struct PeerInheritance { + + func makeDecl(with level: AccessControlLevel) throws -> VariableDeclSyntax { + let decl: DeclSyntax = "\(level)var myVar = 123" + return try #require(decl.as(VariableDeclSyntax.self)) + } + + @Test + func openShouldBecomePublicByDefault() throws { + let decl = try makeDecl(with: .open) + let inherited = AccessControlLevel.forPeer(of: decl) + #expect(inherited == .public) + } + + @Test(arguments: AccessControlLevel.allCases.dropLast()) + func othersShouldBeKept(_ level: AccessControlLevel) throws { + let decl = try makeDecl(with: level) + let inherited = AccessControlLevel.forPeer(of: decl) + #expect(inherited == level) + } + } +} diff --git a/Tests/PrincipleMacrosTests/Syntax/Extensions/IfConfigDeclSyntaxTests.swift b/Tests/PrincipleMacrosTests/Syntax/Extensions/IfConfigDeclSyntaxTests.swift index 679d4a9..2f848e9 100644 --- a/Tests/PrincipleMacrosTests/Syntax/Extensions/IfConfigDeclSyntaxTests.swift +++ b/Tests/PrincipleMacrosTests/Syntax/Extensions/IfConfigDeclSyntaxTests.swift @@ -15,7 +15,7 @@ internal enum 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) + let properties = try PropertiesParser.parse(declarationGroup: classDecl) return try #require(properties.last) } @@ -130,7 +130,7 @@ internal enum IfConfigDeclSyntaxTests { } @Test - func applyToNewMembers() throws { + func applyToMemberBlock() throws { let decl: DeclSyntax = """ class MyClass { #if DEBUG @@ -159,12 +159,12 @@ internal enum IfConfigDeclSyntaxTests { """ let property = try parseLastProperty(in: decl) - let ifConfig = property.underlying.applyingEnclosingIfConfig(to: newMembers) - #expect(ifConfig?.description == expectation) + let ifConfig = newMembers.withIfConfigIfPresent(from: property.underlying) + #expect(ifConfig.description == expectation) } @Test - func applyToNewStatements() throws { + func applyToCodeBlock() throws { let decl: DeclSyntax = """ class MyClass { #if DEBUG @@ -193,8 +193,8 @@ internal enum IfConfigDeclSyntaxTests { """ let property = try parseLastProperty(in: decl) - let ifConfig = property.underlying.applyingEnclosingIfConfig(to: newStatements) - #expect(ifConfig?.description == expectation) + let ifConfig = newStatements.withIfConfigIfPresent(from: property.underlying) + #expect(ifConfig.description == expectation) } // swiftlint:enable empty_line_after_type_declaration diff --git a/Tests/PrincipleMacrosTests/Syntax/Extensions/MemberBlockItemListSyntaxTests.swift b/Tests/PrincipleMacrosTests/Syntax/Extensions/MemberBlockItemListSyntaxTests.swift new file mode 100644 index 0000000..5a954ff --- /dev/null +++ b/Tests/PrincipleMacrosTests/Syntax/Extensions/MemberBlockItemListSyntaxTests.swift @@ -0,0 +1,64 @@ +// +// MemberBlockItemListSyntaxTests.swift +// PrincipleMacros +// +// Created by Kamil Strzelecki on 01/12/2025. +// Copyright © 2025 Kamil Strzelecki. All rights reserved. +// + +@testable import PrincipleMacros +import Testing + +internal enum MemberBlockItemListSyntaxTests { + + struct Flattening { + + @Test + func members() { + let members: MemberBlockItemListSyntax = """ + let a = "" + var b = 123 + func c() {} + """ + + let flattened = members.flattened + #expect(members.elementsEqual(flattened)) + } + + @Test + func ifConfig() { + let members: MemberBlockItemListSyntax = """ + let a = "" + #if os(macOS) + var b = 123 + #else + func c() {} + #endif + func d() {} + """ + + let flattened = Array(members.flattened) + #expect(flattened.count == 4) + } + + @Test + func nestedIfConfig() { + let members: MemberBlockItemListSyntax = """ + let a = "" + #if os(macOS) + var b = 123 + #else + #if os(iOS) + func c() {} + #else + func d() {} + #endif + #endif + func e() {} + """ + + let flattened = Array(members.flattened) + #expect(flattened.count == 5) + } + } +} From 760697b4a376553d9bb9d12c0e2f80f7f0fb63dc Mon Sep 17 00:00:00 2001 From: Kamil Strzelecki Date: Mon, 1 Dec 2025 15:30:09 +0100 Subject: [PATCH 8/9] [SwiftFormat] Applied formatting --- .../Syntax/Extensions/MemberBlockItemListSyntax.swift | 2 +- .../Syntax/Concepts/AccessControlLevelTests.swift | 2 +- .../Syntax/Extensions/MemberBlockItemListSyntaxTests.swift | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/PrincipleMacros/Syntax/Extensions/MemberBlockItemListSyntax.swift b/Sources/PrincipleMacros/Syntax/Extensions/MemberBlockItemListSyntax.swift index e2b06f4..83f1059 100644 --- a/Sources/PrincipleMacros/Syntax/Extensions/MemberBlockItemListSyntax.swift +++ b/Sources/PrincipleMacros/Syntax/Extensions/MemberBlockItemListSyntax.swift @@ -32,7 +32,7 @@ extension IfConfigClauseSyntax { public var flattenedMembers: some Sequence { switch elements { - case .decls(let members): + case let .decls(members): AnySequence(members.flattened) default: AnySequence(EmptyCollection()) diff --git a/Tests/PrincipleMacrosTests/Syntax/Concepts/AccessControlLevelTests.swift b/Tests/PrincipleMacrosTests/Syntax/Concepts/AccessControlLevelTests.swift index 1e91c31..45eebca 100644 --- a/Tests/PrincipleMacrosTests/Syntax/Concepts/AccessControlLevelTests.swift +++ b/Tests/PrincipleMacrosTests/Syntax/Concepts/AccessControlLevelTests.swift @@ -6,8 +6,8 @@ // Copyright © 2025 Kamil Strzelecki. All rights reserved. // -import Testing @testable import PrincipleMacros +import Testing internal struct AccessControlLevelTests { diff --git a/Tests/PrincipleMacrosTests/Syntax/Extensions/MemberBlockItemListSyntaxTests.swift b/Tests/PrincipleMacrosTests/Syntax/Extensions/MemberBlockItemListSyntaxTests.swift index 5a954ff..061db8b 100644 --- a/Tests/PrincipleMacrosTests/Syntax/Extensions/MemberBlockItemListSyntaxTests.swift +++ b/Tests/PrincipleMacrosTests/Syntax/Extensions/MemberBlockItemListSyntaxTests.swift @@ -10,7 +10,7 @@ import Testing internal enum MemberBlockItemListSyntaxTests { - + struct Flattening { @Test From c4733a38941999e3fb306e54cf05b08455ab9e35 Mon Sep 17 00:00:00 2001 From: Kamil Strzelecki Date: Mon, 1 Dec 2025 15:42:29 +0100 Subject: [PATCH 9/9] - --- .../Concepts/AccessControlLevelTests.swift | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Tests/PrincipleMacrosTests/Syntax/Concepts/AccessControlLevelTests.swift b/Tests/PrincipleMacrosTests/Syntax/Concepts/AccessControlLevelTests.swift index 45eebca..c01ac35 100644 --- a/Tests/PrincipleMacrosTests/Syntax/Concepts/AccessControlLevelTests.swift +++ b/Tests/PrincipleMacrosTests/Syntax/Concepts/AccessControlLevelTests.swift @@ -41,27 +41,27 @@ extension AccessControlLevelTests { struct MemberInheritance { - func makeDecl(with level: AccessControlLevel) throws -> ClassDeclSyntax { + private func makeDecl(with level: AccessControlLevel) throws -> ClassDeclSyntax { let decl: DeclSyntax = "\(level)class MyClass {}" return try #require(decl.as(ClassDeclSyntax.self)) } @Test - func privateShouldBeRemoved() throws { + func shouldRemovePrivate() throws { let decl = try makeDecl(with: .private) let inherited = AccessControlLevel.forMember(of: decl) #expect(inherited == nil) } @Test - func openShouldBecomePublicByDefault() throws { + func shouldChangeOpenToPublicByDefault() throws { let decl = try makeDecl(with: .open) let inherited = AccessControlLevel.forMember(of: decl) #expect(inherited == .public) } @Test(arguments: AccessControlLevel.allCases.dropFirst().dropLast()) - func othersShouldBeKept(_ level: AccessControlLevel) throws { + func shouldKeep(level: AccessControlLevel) throws { let decl = try makeDecl(with: level) let inherited = AccessControlLevel.forMember(of: decl) #expect(inherited == level) @@ -73,27 +73,27 @@ extension AccessControlLevelTests { struct SiblingInheritance { - func makeDecl(with level: AccessControlLevel) throws -> VariableDeclSyntax { + private func makeDecl(with level: AccessControlLevel) throws -> VariableDeclSyntax { let decl: DeclSyntax = "\(level)var myVar = 123" return try #require(decl.as(VariableDeclSyntax.self)) } @Test - func privateShouldBecomeFileprivate() throws { + func shouldChangePrivateToFileprivate() throws { let decl = try makeDecl(with: .private) let inherited = AccessControlLevel.forSibling(of: decl) #expect(inherited == .fileprivate) } @Test - func openShouldBecomePublicByDefault() throws { + func shouldChangeOpenToPublicByDefault() throws { let decl = try makeDecl(with: .open) let inherited = AccessControlLevel.forSibling(of: decl) #expect(inherited == .public) } @Test(arguments: AccessControlLevel.allCases.dropFirst().dropLast()) - func othersShouldBeKept(_ level: AccessControlLevel) throws { + func shouldKeep(level: AccessControlLevel) throws { let decl = try makeDecl(with: level) let inherited = AccessControlLevel.forSibling(of: decl) #expect(inherited == level) @@ -105,20 +105,20 @@ extension AccessControlLevelTests { struct PeerInheritance { - func makeDecl(with level: AccessControlLevel) throws -> VariableDeclSyntax { + private func makeDecl(with level: AccessControlLevel) throws -> VariableDeclSyntax { let decl: DeclSyntax = "\(level)var myVar = 123" return try #require(decl.as(VariableDeclSyntax.self)) } @Test - func openShouldBecomePublicByDefault() throws { + func shouldChangeOpenToPublicByDefault() throws { let decl = try makeDecl(with: .open) let inherited = AccessControlLevel.forPeer(of: decl) #expect(inherited == .public) } @Test(arguments: AccessControlLevel.allCases.dropLast()) - func othersShouldBeKept(_ level: AccessControlLevel) throws { + func shouldKeep(level: AccessControlLevel) throws { let decl = try makeDecl(with: level) let inherited = AccessControlLevel.forPeer(of: decl) #expect(inherited == level)