From 254f401db04447bacaf3d3643c635934fc72d0f3 Mon Sep 17 00:00:00 2001 From: David Roman <2538074+davdroman@users.noreply.github.com> Date: Wed, 2 Sep 2026 14:45:33 +0100 Subject: [PATCH 01/11] chore: enforce 120-column SwiftFormat width Set the repository's formatting configuration to a 120-column maximum and apply the resulting formatter output. --- .swiftformat | 1 + Sources/Swizzling/AnyHook.swift | 5 ++- Sources/Swizzling/SwizzlingHook.swift | 9 +++-- Sources/SwizzlingMacro/SwizzleMacro.swift | 6 ++-- .../PatternBindingSyntax+Tests.swift | 35 +++++++++++++++---- 5 files changed, 44 insertions(+), 12 deletions(-) diff --git a/.swiftformat b/.swiftformat index 3c33396..e55e28f 100644 --- a/.swiftformat +++ b/.swiftformat @@ -3,6 +3,7 @@ --header ignore --indent tab +--maxwidth 120 --ifdef no-indent --ranges preserve --extensionacl on-declarations diff --git a/Sources/Swizzling/AnyHook.swift b/Sources/Swizzling/AnyHook.swift index fd8a919..0379e25 100644 --- a/Sources/Swizzling/AnyHook.swift +++ b/Sources/Swizzling/AnyHook.swift @@ -81,7 +81,10 @@ public class AnyHook { /// Validate that the selector exists on the active class. @discardableResult func validate(expectedState: State = .prepared) throws -> Method { - guard let method = class_getInstanceMethod(`class`, selector) else { throw SwizzlingError.methodNotFound(`class`, selector) } + guard let method = class_getInstanceMethod(`class`, selector) else { throw SwizzlingError.methodNotFound( + `class`, + selector, + ) } guard state == expectedState else { throw SwizzlingError.invalidState(expectedState: expectedState) } return method } diff --git a/Sources/Swizzling/SwizzlingHook.swift b/Sources/Swizzling/SwizzlingHook.swift index 49d5281..13b9f9d 100644 --- a/Sources/Swizzling/SwizzlingHook.swift +++ b/Sources/Swizzling/SwizzlingHook.swift @@ -28,7 +28,8 @@ final class SwizzlingHook: TypedHook) -> HookSignature?, // this must be optional or swift runtime will crash. Or swiftc may segfault. Compiler bug? + implementation: (SwizzlingHook) + -> HookSignature?, // this must be optional or swift runtime will crash. Or swiftc may segfault. Compiler bug? ) throws { try super.init(class: `class`, selector: selector) replacementIMP = imp_implementationWithBlock(implementation(self) as Any) @@ -45,7 +46,11 @@ final class SwizzlingHook: TypedHook Date: Wed, 2 Sep 2026 15:23:31 +0100 Subject: [PATCH 02/11] style: expand wrapped guard bodies Keep multiline thrown errors readable under the 120-column formatting policy. --- Sources/Swizzling/AnyHook.swift | 10 ++++++---- Sources/Swizzling/SwizzlingHook.swift | 12 +++++++----- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/Sources/Swizzling/AnyHook.swift b/Sources/Swizzling/AnyHook.swift index 0379e25..32549fa 100644 --- a/Sources/Swizzling/AnyHook.swift +++ b/Sources/Swizzling/AnyHook.swift @@ -81,10 +81,12 @@ public class AnyHook { /// Validate that the selector exists on the active class. @discardableResult func validate(expectedState: State = .prepared) throws -> Method { - guard let method = class_getInstanceMethod(`class`, selector) else { throw SwizzlingError.methodNotFound( - `class`, - selector, - ) } + guard let method = class_getInstanceMethod(`class`, selector) else { + throw SwizzlingError.methodNotFound( + `class`, + selector, + ) + } guard state == expectedState else { throw SwizzlingError.invalidState(expectedState: expectedState) } return method } diff --git a/Sources/Swizzling/SwizzlingHook.swift b/Sources/Swizzling/SwizzlingHook.swift index 13b9f9d..4620482 100644 --- a/Sources/Swizzling/SwizzlingHook.swift +++ b/Sources/Swizzling/SwizzlingHook.swift @@ -46,11 +46,13 @@ final class SwizzlingHook: TypedHook Date: Wed, 2 Sep 2026 15:26:30 +0100 Subject: [PATCH 03/11] style: improve wrapped declarations Keep the optional hook signature and parameter type chain readable under the 120-column policy. --- Sources/Swizzling/SwizzlingHook.swift | 4 ++-- Sources/SwizzlingMacro/SwizzleMacro.swift | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Sources/Swizzling/SwizzlingHook.swift b/Sources/Swizzling/SwizzlingHook.swift index 4620482..b02b2c2 100644 --- a/Sources/Swizzling/SwizzlingHook.swift +++ b/Sources/Swizzling/SwizzlingHook.swift @@ -28,8 +28,8 @@ final class SwizzlingHook: TypedHook) - -> HookSignature?, // this must be optional or swift runtime will crash. Or swiftc may segfault. Compiler bug? + // `HookSignature?` must be optional or the Swift runtime will crash. `swiftc` may also segfault. Compiler bug? + implementation: (SwizzlingHook) -> HookSignature?, ) throws { try super.init(class: `class`, selector: selector) replacementIMP = imp_implementationWithBlock(implementation(self) as Any) diff --git a/Sources/SwizzlingMacro/SwizzleMacro.swift b/Sources/SwizzlingMacro/SwizzleMacro.swift index a84f2d7..9a4bb3f 100644 --- a/Sources/SwizzlingMacro/SwizzleMacro.swift +++ b/Sources/SwizzlingMacro/SwizzleMacro.swift @@ -39,7 +39,8 @@ struct SwizzleMacro: ExpressionMacro { let params = node.arguments.dropFirst() .prefix(while: { $0.label?.text != "returning" && $0.label?.text != "implementation" }) let paramTypes = if !params.isEmpty { - ", " + params.map { $0.expression.trimmedDescription.replacingOccurrences(of: ".self", with: "") } + ", " + params + .map { $0.expression.trimmedDescription.replacingOccurrences(of: ".self", with: "") } .joined(separator: ", ") } else { "" From 765da87669715bebde9141471845d5cb277069b3 Mon Sep 17 00:00:00 2001 From: David Roman <2538074+davdroman@users.noreply.github.com> Date: Wed, 2 Sep 2026 22:01:01 +0100 Subject: [PATCH 04/11] style: count tabs as two columns Use a deterministic tab width for SwiftFormat's line-length calculation without constraining editor display preferences. --- .swiftformat | 1 + 1 file changed, 1 insertion(+) diff --git a/.swiftformat b/.swiftformat index e55e28f..961e0b9 100644 --- a/.swiftformat +++ b/.swiftformat @@ -4,6 +4,7 @@ --header ignore --indent tab --maxwidth 120 +--tabwidth 2 --ifdef no-indent --ranges preserve --extensionacl on-declarations From ff846483244702578e28417936d9614b74094e06 Mon Sep 17 00:00:00 2001 From: David Roman <2538074+davdroman@users.noreply.github.com> Date: Wed, 2 Sep 2026 22:33:24 +0100 Subject: [PATCH 05/11] style: order width options Keep tab-width configuration immediately before the maximum line width it affects. --- .swiftformat | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.swiftformat b/.swiftformat index 961e0b9..68c49dd 100644 --- a/.swiftformat +++ b/.swiftformat @@ -3,8 +3,8 @@ --header ignore --indent tab ---maxwidth 120 --tabwidth 2 +--maxwidth 120 --ifdef no-indent --ranges preserve --extensionacl on-declarations From 00269aa640ecffd9e3616c3817a13b8248c189ed Mon Sep 17 00:00:00 2001 From: David Roman <2538074+davdroman@users.noreply.github.com> Date: Wed, 2 Sep 2026 23:59:13 +0100 Subject: [PATCH 06/11] chore: disable system tool fallback Require Mise to resolve declared project tools instead of falling back to same-named system binaries. --- mise.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mise.toml b/mise.toml index fb5c9ba..a5307bf 100644 --- a/mise.toml +++ b/mise.toml @@ -1,5 +1,8 @@ min_version = "2026.8.6" +[settings] +not_found_system_fallback = false + [tool_config] locked = true From 3a1d98f244f2f75c6dc51db83afa01faae9fc8c4 Mon Sep 17 00:00:00 2001 From: David Roman <2538074+davdroman@users.noreply.github.com> Date: Thu, 3 Sep 2026 01:18:56 +0100 Subject: [PATCH 07/11] style: normalize multiline wrapping Configure SwiftFormat to avoid partial wrapping and normalize multiline assignments, chains, and comments. Keep return types intact and preserve readable documentation examples. --- .swiftformat | 10 ++++++++++ Sources/AssociationMacro/AssociatedMacro.swift | 18 ++++++++++-------- Sources/SwizzlingMacro/SwizzleMacro.swift | 18 ++++++++++-------- .../PatternBindingSyntax+Tests.swift | 8 ++++---- 4 files changed, 34 insertions(+), 20 deletions(-) diff --git a/.swiftformat b/.swiftformat index 68c49dd..7c1e2ec 100644 --- a/.swiftformat +++ b/.swiftformat @@ -5,15 +5,25 @@ --indent tab --tabwidth 2 --maxwidth 120 +--allow-partial-wrapping false --ifdef no-indent --ranges preserve --extensionacl on-declarations --trailing-commas always --nil-init insert --import-grouping alpha +--wrap-arguments before-first --wrap-conditions before-first +--wrap-parameters before-first +--wrap-return-type never +--wrap-string-interpolation true --guard-else next-line +# Opt-in rules +--enable blockComments +--enable wrapMultilineConditionalAssignment +--enable wrapMultilineFunctionChains + # Disabled rules --disable blankLinesAroundMark --disable blankLinesBetweenScopes diff --git a/Sources/AssociationMacro/AssociatedMacro.swift b/Sources/AssociationMacro/AssociatedMacro.swift index a0e7400..bbb2328 100644 --- a/Sources/AssociationMacro/AssociatedMacro.swift +++ b/Sources/AssociationMacro/AssociatedMacro.swift @@ -221,13 +221,14 @@ extension AssociatedMacro { policy: ExprSyntax, defaultValue: ExprSyntax?, ) -> AccessorDeclSyntax { - let typeWithoutOptional = if let type = type.as(ImplicitlyUnwrappedOptionalTypeSyntax.self) { - type.wrappedType - } else if let type = type.as(OptionalTypeSyntax.self) { - type.wrappedType - } else { - type - } + let typeWithoutOptional = + if let type = type.as(ImplicitlyUnwrappedOptionalTypeSyntax.self) { + type.wrappedType + } else if let type = type.as(OptionalTypeSyntax.self) { + type.wrappedType + } else { + type + } return AccessorDeclSyntax( accessorSpecifier: .keyword(.get), @@ -345,7 +346,8 @@ extension AssociatedMacro { type: type, accessor: didSet, body: body, - ).with(\.leadingTrivia, .newlines(2)) + ) + .with(\.leadingTrivia, .newlines(2)) Self.callDidSet() } diff --git a/Sources/SwizzlingMacro/SwizzleMacro.swift b/Sources/SwizzlingMacro/SwizzleMacro.swift index 9a4bb3f..a3438d5 100644 --- a/Sources/SwizzlingMacro/SwizzleMacro.swift +++ b/Sources/SwizzlingMacro/SwizzleMacro.swift @@ -36,15 +36,17 @@ struct SwizzleMacro: ExpressionMacro { let selector = firstArgument.with(\.trailingComma, nil).trimmedDescription.replacingOccurrences(of: #"\"#, with: "") - let params = node.arguments.dropFirst() + let params = node.arguments + .dropFirst() .prefix(while: { $0.label?.text != "returning" && $0.label?.text != "implementation" }) - let paramTypes = if !params.isEmpty { - ", " + params - .map { $0.expression.trimmedDescription.replacingOccurrences(of: ".self", with: "") } - .joined(separator: ", ") - } else { - "" - } + let paramTypes = + if !params.isEmpty { + ", " + params + .map { $0.expression.trimmedDescription.replacingOccurrences(of: ".self", with: "") } + .joined(separator: ", ") + } else { + "" + } let returning = node.arguments.first(where: { $0.label?.text == "returning" }) let returnType = returning?.expression.trimmedDescription.replacingOccurrences(of: ".self", with: "") ?? "Void" diff --git a/Tests/AssociationTests/PatternBindingSyntax+Tests.swift b/Tests/AssociationTests/PatternBindingSyntax+Tests.swift index 0732b4b..c739c47 100644 --- a/Tests/AssociationTests/PatternBindingSyntax+Tests.swift +++ b/Tests/AssociationTests/PatternBindingSyntax+Tests.swift @@ -65,7 +65,7 @@ struct PatternBindingSyntaxTests { #expect(getter.description == binding.getter?.description) - /* getter only */ + // getter only let body = try #require(getter.body, "body must not be nil") binding = .init( @@ -102,7 +102,7 @@ struct PatternBindingSyntaxTests { binding.setter = newSetter #expect(newSetter.description == binding.setter?.description) - /* getter only */ + // getter only binding = .init( pattern: IdentifierPatternSyntax(identifier: .identifier("value")), accessorBlock: .init( @@ -141,7 +141,7 @@ struct PatternBindingSyntaxTests { binding.getter = newGetter #expect(newGetter.description == binding.getter?.description) - /* getter only */ + // getter only binding = .init( pattern: IdentifierPatternSyntax(identifier: .identifier("value")), accessorBlock: .init( @@ -154,7 +154,7 @@ struct PatternBindingSyntaxTests { binding.getter = newGetter #expect(newGetter.description == binding.getter?.description) - /* setter only */ + // setter only binding = .init( pattern: IdentifierPatternSyntax(identifier: .identifier("value")), accessorBlock: .init( From 2fb0fea9110a80148651788cf6aaaa01a95e0eee Mon Sep 17 00:00:00 2001 From: David Roman <2538074+davdroman@users.noreply.github.com> Date: Thu, 3 Sep 2026 02:04:30 +0100 Subject: [PATCH 08/11] style: refine compact formatting Keep short nested arguments and simple expressions on one line. Let source-controlled multiline strings handle over-width literal content without changing runtime values. --- .swiftformat | 2 -- Sources/Swizzling/SwizzlingHook.swift | 6 +----- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/.swiftformat b/.swiftformat index 7c1e2ec..5f8bbc9 100644 --- a/.swiftformat +++ b/.swiftformat @@ -5,7 +5,6 @@ --indent tab --tabwidth 2 --maxwidth 120 ---allow-partial-wrapping false --ifdef no-indent --ranges preserve --extensionacl on-declarations @@ -16,7 +15,6 @@ --wrap-conditions before-first --wrap-parameters before-first --wrap-return-type never ---wrap-string-interpolation true --guard-else next-line # Opt-in rules diff --git a/Sources/Swizzling/SwizzlingHook.swift b/Sources/Swizzling/SwizzlingHook.swift index b02b2c2..fd84aa8 100644 --- a/Sources/Swizzling/SwizzlingHook.swift +++ b/Sources/Swizzling/SwizzlingHook.swift @@ -47,11 +47,7 @@ final class SwizzlingHook: TypedHook Date: Thu, 3 Sep 2026 02:12:02 +0100 Subject: [PATCH 09/11] style: compact error construction --- Sources/Swizzling/AnyHook.swift | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Sources/Swizzling/AnyHook.swift b/Sources/Swizzling/AnyHook.swift index 32549fa..8237e93 100644 --- a/Sources/Swizzling/AnyHook.swift +++ b/Sources/Swizzling/AnyHook.swift @@ -82,10 +82,7 @@ public class AnyHook { /// Validate that the selector exists on the active class. @discardableResult func validate(expectedState: State = .prepared) throws -> Method { guard let method = class_getInstanceMethod(`class`, selector) else { - throw SwizzlingError.methodNotFound( - `class`, - selector, - ) + throw SwizzlingError.methodNotFound(`class`, selector) } guard state == expectedState else { throw SwizzlingError.invalidState(expectedState: expectedState) } return method From 45728d4c033003d3a823166d574b5f1d6ee2133a Mon Sep 17 00:00:00 2001 From: David Roman <2538074+davdroman@users.noreply.github.com> Date: Thu, 3 Sep 2026 12:10:08 +0100 Subject: [PATCH 10/11] fix(macros): avoid deprecated closure capture initializer --- .../AssociationMacro/AssociatedMacro.swift | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/Sources/AssociationMacro/AssociatedMacro.swift b/Sources/AssociationMacro/AssociatedMacro.swift index bbb2328..fb153ed 100644 --- a/Sources/AssociationMacro/AssociatedMacro.swift +++ b/Sources/AssociationMacro/AssociatedMacro.swift @@ -355,6 +355,22 @@ extension AssociatedMacro { ) } + private static var selfCapture: ClosureCaptureSyntax { + let selfReference = DeclReferenceExprSyntax(baseName: .keyword(.`self`)) + #if canImport(SwiftSyntax601) + return ClosureCaptureSyntax( + name: .keyword(.`self`), + initializer: .init(value: selfReference), + ) + #else + return ClosureCaptureSyntax( + name: .keyword(.`self`), + equal: .equalToken(), + expression: selfReference, + ) + #endif + } + /// `willSet` closure /// /// Convert a willSet accessor to a closure variable in the following format. @@ -395,10 +411,7 @@ extension AssociatedMacro { value: ClosureExprSyntax( signature: .init( capture: .init { - ClosureCaptureSyntax( - name: .keyword(.`self`), - expression: DeclReferenceExprSyntax(baseName: .keyword(.`self`)), - ) + Self.selfCapture }, parameterClause: .init(ClosureShorthandParameterListSyntax { ClosureShorthandParameterSyntax(name: newValue) @@ -452,10 +465,7 @@ extension AssociatedMacro { value: ClosureExprSyntax( signature: .init( capture: .init { - ClosureCaptureSyntax( - name: .keyword(.`self`), - expression: DeclReferenceExprSyntax(baseName: .keyword(.`self`)), - ) + Self.selfCapture }, parameterClause: .init(ClosureShorthandParameterListSyntax { ClosureShorthandParameterSyntax(name: oldValue) From 6667fe7e9e3913c9e21f90b1db46f75b0cd16a86 Mon Sep 17 00:00:00 2001 From: David Roman <2538074+davdroman@users.noreply.github.com> Date: Thu, 3 Sep 2026 12:45:55 +0100 Subject: [PATCH 11/11] ci: test SwiftSyntax compatibility matrix --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6c63b82..e92a826 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -98,6 +98,6 @@ jobs: - name: Check Swift Syntax Compatibility uses: davdroman/swift-syntax-compatibility-check@v1 with: - run-tests: false + run-tests: true major-versions-only: true disable-prebuilts: true