From 6e9df4224563f398982554d9b2e9580d54d1d791 Mon Sep 17 00:00:00 2001 From: leogdion Date: Fri, 12 Jun 2026 16:44:57 -0400 Subject: [PATCH 1/4] Implement AuthenticationMiddleware for Anthropic API auth (#117) Adds a public ClientMiddleware that injects the x-api-key and anthropic-version headers into every outgoing ClaudeKit request. Co-Authored-By: Claude Fable 5 --- .../AiSTKit/AuthenticationMiddleware.swift | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 Sources/AiSTKit/AuthenticationMiddleware.swift diff --git a/Sources/AiSTKit/AuthenticationMiddleware.swift b/Sources/AiSTKit/AuthenticationMiddleware.swift new file mode 100644 index 0000000..8139f00 --- /dev/null +++ b/Sources/AiSTKit/AuthenticationMiddleware.swift @@ -0,0 +1,80 @@ +// +// AuthenticationMiddleware.swift +// SyntaxKit +// +// Created by Leo Dion. +// Copyright © 2026 BrightDigit. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the “Software”), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +public import HTTPTypes +public import OpenAPIRuntime + +#if os(Linux) + @preconcurrency public import struct Foundation.URL +#else + public import struct Foundation.URL +#endif + +/// OpenAPI client middleware that adds Anthropic API authentication headers. +/// +/// Injects the `x-api-key` and `anthropic-version` headers into every +/// outgoing request before forwarding it to the next handler in the chain. +public struct AuthenticationMiddleware: ClientMiddleware { + /// The Anthropic API version sent with every request. + private static let anthropicVersion = "2023-06-01" + + /// The Anthropic API key used to authenticate requests. + private let apiKey: String + + /// Creates a middleware that authenticates requests with the given API key. + /// - Parameter apiKey: The Anthropic API key to send in the `x-api-key` header. + public init(apiKey: String) { + self.apiKey = apiKey + } + + /// Adds the Anthropic authentication headers, then forwards the request. + /// - Parameters: + /// - request: The outgoing HTTP request. + /// - body: The outgoing HTTP request body, if any. + /// - baseURL: The base URL of the server. + /// - operationID: The OpenAPI operation identifier for the request. + /// - next: The next handler in the middleware chain. + /// - Returns: The HTTP response and body from the next handler. + public func intercept( + _ request: HTTPRequest, + body: HTTPBody?, + baseURL: URL, + operationID: String, + next: @Sendable (HTTPRequest, HTTPBody?, URL) async throws -> (HTTPResponse, HTTPBody?) + ) async throws -> (HTTPResponse, HTTPBody?) { + var request = request + if let name = HTTPField.Name("x-api-key") { + request.headerFields[name] = apiKey + } + if let name = HTTPField.Name("anthropic-version") { + request.headerFields[name] = Self.anthropicVersion + } + return try await next(request, body, baseURL) + } +} From bd70fdb8398291dfb4a3cc4deb3b24644a3f86e6 Mon Sep 17 00:00:00 2001 From: Leo Dion Date: Sun, 14 Jun 2026 19:21:58 -0400 Subject: [PATCH 2/4] Add unit tests for AuthenticationMiddleware Adds an AiSTKitTests target with tests that drive intercept(...) via a stub next handler, verifying x-api-key and anthropic-version injection, preservation of existing headers, and request forwarding. Also documents the throwing behavior of intercept. Co-Authored-By: Claude Opus 4.8 (1M context) --- Package.swift | 5 + .../AiSTKit/AuthenticationMiddleware.swift | 1 + .../AuthenticationMiddlewareTests.swift | 111 ++++++++++++++++++ 3 files changed, 117 insertions(+) create mode 100644 Tests/AiSTKitTests/AuthenticationMiddlewareTests.swift diff --git a/Package.swift b/Package.swift index 8bf8f29..fe3937d 100644 --- a/Package.swift +++ b/Package.swift @@ -204,5 +204,10 @@ let package = Package( dependencies: ["SyntaxKit", "DocumentationHarness"], swiftSettings: swiftSettings ), + .testTarget( + name: "AiSTKitTests", + dependencies: ["AiSTKit"], + swiftSettings: swiftSettings + ), ] ) diff --git a/Sources/AiSTKit/AuthenticationMiddleware.swift b/Sources/AiSTKit/AuthenticationMiddleware.swift index 8139f00..4aee6a3 100644 --- a/Sources/AiSTKit/AuthenticationMiddleware.swift +++ b/Sources/AiSTKit/AuthenticationMiddleware.swift @@ -61,6 +61,7 @@ public struct AuthenticationMiddleware: ClientMiddleware { /// - operationID: The OpenAPI operation identifier for the request. /// - next: The next handler in the middleware chain. /// - Returns: The HTTP response and body from the next handler. + /// - Throws: Any error thrown by the `next` handler. public func intercept( _ request: HTTPRequest, body: HTTPBody?, diff --git a/Tests/AiSTKitTests/AuthenticationMiddlewareTests.swift b/Tests/AiSTKitTests/AuthenticationMiddlewareTests.swift new file mode 100644 index 0000000..a158185 --- /dev/null +++ b/Tests/AiSTKitTests/AuthenticationMiddlewareTests.swift @@ -0,0 +1,111 @@ +// +// AuthenticationMiddlewareTests.swift +// SyntaxKit +// +// Created by Leo Dion. +// Copyright © 2026 BrightDigit. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the “Software”), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +import Foundation +import HTTPTypes +import OpenAPIRuntime +import Testing + +@testable import AiSTKit + +internal struct AuthenticationMiddlewareTests { + private static let baseURLString = "https://api.anthropic.com" + + /// Thread-safe one-shot holder, so the `@Sendable` `next` closure can hand + /// the request it received back to the test. + private final class Box: @unchecked Sendable { + private let lock = NSLock() + private var stored: Value? + + var value: Value? { + lock.withLock { stored } + } + + func set(_ newValue: Value) { + lock.withLock { stored = newValue } + } + } + + /// Invokes the middleware with a stub `next` handler and returns the request + /// the handler received, so tests can assert on the injected headers. + private func interceptedRequest( + apiKey: String, + seedingRequest seed: (inout HTTPRequest) -> Void = { _ in } + ) async throws -> HTTPRequest { + let middleware = AuthenticationMiddleware(apiKey: apiKey) + var request = HTTPRequest( + method: .post, + scheme: "https", + authority: "api.anthropic.com", + path: "/v1/messages" + ) + seed(&request) + + let baseURL = try #require(URL(string: Self.baseURLString)) + let captured = Box() + _ = try await middleware.intercept( + request, + body: nil, + baseURL: baseURL, + operationID: "createMessage" + ) { forwarded, body, url in + captured.set(forwarded) + #expect(body == nil) + #expect(url == baseURL) + return (HTTPResponse(status: .ok), nil) + } + + return try #require(captured.value, "next handler was never invoked") + } + + @Test internal func injectsAPIKeyHeader() async throws { + let request = try await interceptedRequest(apiKey: "test-key-123") + #expect(request.headerFields[try #require(.init("x-api-key"))] == "test-key-123") + } + + @Test internal func injectsAnthropicVersionHeader() async throws { + let request = try await interceptedRequest(apiKey: "test-key-123") + #expect(request.headerFields[try #require(.init("anthropic-version"))] == "2023-06-01") + } + + @Test internal func preservesExistingHeaders() async throws { + let contentType = try #require(HTTPField.Name("content-type")) + let request = try await interceptedRequest(apiKey: "test-key-123") { request in + request.headerFields[contentType] = "application/json" + } + #expect(request.headerFields[contentType] == "application/json") + #expect(request.headerFields[try #require(.init("x-api-key"))] == "test-key-123") + } + + @Test internal func forwardsRequestToNextHandler() async throws { + let request = try await interceptedRequest(apiKey: "test-key-123") + #expect(request.method == .post) + #expect(request.path == "/v1/messages") + } +} From e1b8d34390e3a5068980aa7285f1e44f3667bcc5 Mon Sep 17 00:00:00 2001 From: Leo Dion Date: Mon, 15 Jun 2026 09:34:53 -0400 Subject: [PATCH 3/4] Replace PoundIf.Condition enum with self-rendering protocol [skip ci] Turn PoundIf.Condition from a 12-case enum into a nested protocol whose conforming types render themselves, eliminating the switch-based renderLeaf/renderCombinator/renderHelper that tripped SwiftLint's cyclomatic_complexity rule. - Condition (base) requires render(atTopLevel:); LeafCondition and BinaryCondition refinements carry default render implementations. - One small struct per former case; leading-dot factories preserve the existing .os(.iOS) DSL including nested composition. - renderCondition now dispatches to condition.render(atTopLevel: true). Output is byte-for-byte unchanged (PoundIfTests pass). Co-Authored-By: Claude Opus 4.8 (1M context) --- .../Declarations/PoundIf+Condition.swift | 81 +++++---- .../Declarations/PoundIf+ConditionTypes.swift | 159 ++++++++++++++++++ .../Declarations/PoundIf+Rendering.swift | 41 +---- .../PoundIf.Condition+Factories.swift | 148 ++++++++++++++++ Sources/SyntaxKit/Declarations/PoundIf.swift | 6 +- 5 files changed, 360 insertions(+), 75 deletions(-) create mode 100644 Sources/SyntaxKit/Declarations/PoundIf+ConditionTypes.swift create mode 100644 Sources/SyntaxKit/Declarations/PoundIf.Condition+Factories.swift diff --git a/Sources/SyntaxKit/Declarations/PoundIf+Condition.swift b/Sources/SyntaxKit/Declarations/PoundIf+Condition.swift index 70fe247..344e914 100644 --- a/Sources/SyntaxKit/Declarations/PoundIf+Condition.swift +++ b/Sources/SyntaxKit/Declarations/PoundIf+Condition.swift @@ -28,38 +28,6 @@ // extension PoundIf { - // swiftlint:disable identifier_name - - /// Canonical `#if` checks, mirroring Swift's conditional-compilation grammar. - public indirect enum Condition: Sendable { - /// `canImport()` - case canImport(String) - /// A bare compilation flag such as `DEBUG`. - case flag(String) - /// `os()` - case os(OperatingSystem) - /// `arch()` - case arch(Architecture) - /// `targetEnvironment()` - case targetEnvironment(TargetEnvironment) - /// `swift(>=5.9)` and friends. - case swift(VersionCheck) - /// `compiler(>=5.9)` and friends. - case compiler(VersionCheck) - /// `hasFeature()` - case hasFeature(String) - /// `hasAttribute()` - case hasAttribute(String) - /// ` && ` - case and(Condition, Condition) - /// ` || ` - case or(Condition, Condition) - /// `!` - case not(Condition) - } - - // swiftlint:enable identifier_name - /// Operating-system identifiers used in `os(...)` checks. public enum OperatingSystem: String, Sendable { /// `os(iOS)` @@ -160,3 +128,52 @@ extension PoundIf { } } } + +extension PoundIf { + /// A canonical `#if` check that can render itself as conditional-compilation + /// syntax, mirroring Swift's grammar. + public protocol Condition: Sendable { + /// Render this condition as `#if` source text. + /// - Parameter atTopLevel: When `true`, a binary combinator omits its + /// surrounding parentheses (it is the outermost expression). + /// - Returns: The rendered conditional-compilation expression. + func render(atTopLevel: Bool) -> String + } + + /// A single check rendered as `keyword(argument)`, or a bare `argument` when + /// ``LeafCondition/keyword`` is `nil` (a flag such as `DEBUG`). + public protocol LeafCondition: Condition { + /// The leading keyword, e.g. `os`, or `nil` for a bare flag. + var keyword: String? { get } + /// The text placed inside the parentheses, or the bare flag identifier. + var argument: String { get } + } + + /// A `lhs rhs` combinator, parenthesized unless at the top level. + public protocol BinaryCondition: Condition { + /// The infix operator rendered between the operands, e.g. `&&`. + var symbol: String { get } + /// The left-hand operand. + var lhs: any Condition { get } + /// The right-hand operand. + var rhs: any Condition { get } + } +} + +extension PoundIf.LeafCondition { + /// Render the leaf as `keyword(argument)`, or bare `argument` for a flag. + public func render(atTopLevel _: Bool) -> String { + guard let keyword else { + return argument + } + return "\(keyword)(\(argument))" + } +} + +extension PoundIf.BinaryCondition { + /// Render as `lhs symbol rhs`, wrapping in parentheses unless at the top level. + public func render(atTopLevel: Bool) -> String { + let inner = "\(lhs.render(atTopLevel: false)) \(symbol) \(rhs.render(atTopLevel: false))" + return atTopLevel ? inner : "(\(inner))" + } +} diff --git a/Sources/SyntaxKit/Declarations/PoundIf+ConditionTypes.swift b/Sources/SyntaxKit/Declarations/PoundIf+ConditionTypes.swift new file mode 100644 index 0000000..cea08cd --- /dev/null +++ b/Sources/SyntaxKit/Declarations/PoundIf+ConditionTypes.swift @@ -0,0 +1,159 @@ +// +// PoundIf+ConditionTypes.swift +// SyntaxKit +// +// Created by Leo Dion. +// Copyright © 2026 BrightDigit. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the “Software”), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +extension PoundIf { + /// `canImport()` + public struct CanImport: LeafCondition { + /// The module name passed to `canImport`. + public let module: String + /// The `canImport` keyword. + public var keyword: String? { "canImport" } + /// The module name rendered inside the parentheses. + public var argument: String { module } + } + + /// A bare compilation flag such as `DEBUG`. + public struct Flag: LeafCondition { + /// The flag identifier. + public let name: String + /// A flag has no keyword and renders bare. + public var keyword: String? { nil } + /// The flag identifier rendered as-is. + public var argument: String { name } + } + + /// `os()` + public struct OSCheck: LeafCondition { + /// The operating system being checked. + public let value: OperatingSystem + /// The `os` keyword. + public var keyword: String? { "os" } + /// The operating-system identifier rendered inside the parentheses. + public var argument: String { value.rawValue } + } + + /// `arch()` + public struct ArchCheck: LeafCondition { + /// The CPU architecture being checked. + public let value: Architecture + /// The `arch` keyword. + public var keyword: String? { "arch" } + /// The architecture identifier rendered inside the parentheses. + public var argument: String { value.rawValue } + } + + /// `targetEnvironment()` + public struct TargetEnvironmentCheck: LeafCondition { + /// The target environment being checked. + public let value: TargetEnvironment + /// The `targetEnvironment` keyword. + public var keyword: String? { "targetEnvironment" } + /// The environment identifier rendered inside the parentheses. + public var argument: String { value.rawValue } + } + + /// `swift(>=5.9)` and friends. + public struct SwiftCheck: LeafCondition { + /// The Swift language version check. + public let check: VersionCheck + /// The `swift` keyword. + public var keyword: String? { "swift" } + /// The version comparison rendered inside the parentheses. + public var argument: String { check.rendered } + } + + /// `compiler(>=5.9)` and friends. + public struct CompilerCheck: LeafCondition { + /// The compiler version check. + public let check: VersionCheck + /// The `compiler` keyword. + public var keyword: String? { "compiler" } + /// The version comparison rendered inside the parentheses. + public var argument: String { check.rendered } + } + + /// `hasFeature()` + public struct HasFeature: LeafCondition { + /// The upcoming/experimental feature name. + public let name: String + /// The `hasFeature` keyword. + public var keyword: String? { "hasFeature" } + /// The feature name rendered inside the parentheses. + public var argument: String { name } + } + + /// `hasAttribute()` + public struct HasAttribute: LeafCondition { + /// The attribute name. + public let name: String + /// The `hasAttribute` keyword. + public var keyword: String? { "hasAttribute" } + /// The attribute name rendered inside the parentheses. + public var argument: String { name } + } +} + +// MARK: - Combinators + +extension PoundIf { + // swiftlint:disable type_name + + /// ` && ` + public struct And: BinaryCondition { + /// The left-hand operand. + public let lhs: any Condition + /// The right-hand operand. + public let rhs: any Condition + /// The `&&` operator. + public var symbol: String { "&&" } + } + + /// ` || ` + public struct Or: BinaryCondition { + /// The left-hand operand. + public let lhs: any Condition + /// The right-hand operand. + public let rhs: any Condition + /// The `||` operator. + public var symbol: String { "||" } + } + + // swiftlint:enable type_name + + /// `!` + public struct Not: Condition { + /// The condition being negated. + public let operand: any Condition + + /// Render as `!operand`, with the operand parenthesized as needed. + public func render(atTopLevel _: Bool) -> String { + "!\(operand.render(atTopLevel: false))" + } + } +} diff --git a/Sources/SyntaxKit/Declarations/PoundIf+Rendering.swift b/Sources/SyntaxKit/Declarations/PoundIf+Rendering.swift index 2a71131..9b433df 100644 --- a/Sources/SyntaxKit/Declarations/PoundIf+Rendering.swift +++ b/Sources/SyntaxKit/Declarations/PoundIf+Rendering.swift @@ -57,7 +57,7 @@ extension PoundIf { private static func renderCondition(_ form: ConditionForm) -> ExprSyntax? { switch form { case .helper(let condition): - return parseExpression(renderHelper(condition, atTopLevel: true)) + return parseExpression(condition.render(atTopLevel: true)) case .raw(let text): return parseExpression(text) case .codeBlock(let block): @@ -77,43 +77,4 @@ extension PoundIf { } return nil } - - private static func renderHelper(_ condition: Condition, atTopLevel: Bool) -> String { - if let leaf = renderLeaf(condition) { - return leaf - } - return renderCombinator(condition, atTopLevel: atTopLevel) - } - - private static func renderLeaf(_ condition: Condition) -> String? { - switch condition { - case .canImport(let module): return "canImport(\(module))" - case .flag(let name): return name - case .os(let value): return "os(\(value.rawValue))" - case .arch(let value): return "arch(\(value.rawValue))" - case .targetEnvironment(let value): return "targetEnvironment(\(value.rawValue))" - case .swift(let check): return "swift(\(check.rendered))" - case .compiler(let check): return "compiler(\(check.rendered))" - case .hasFeature(let name): return "hasFeature(\(name))" - case .hasAttribute(let name): return "hasAttribute(\(name))" - case .and, .or, .not: return nil - } - } - - private static func renderCombinator(_ condition: Condition, atTopLevel: Bool) -> String { - switch condition { - case .and(let lhs, let rhs): - let inner = - "\(renderHelper(lhs, atTopLevel: false)) && \(renderHelper(rhs, atTopLevel: false))" - return atTopLevel ? inner : "(\(inner))" - case .or(let lhs, let rhs): - let inner = - "\(renderHelper(lhs, atTopLevel: false)) || \(renderHelper(rhs, atTopLevel: false))" - return atTopLevel ? inner : "(\(inner))" - case .not(let operand): - return "!\(renderHelper(operand, atTopLevel: false))" - default: - return "" - } - } } diff --git a/Sources/SyntaxKit/Declarations/PoundIf.Condition+Factories.swift b/Sources/SyntaxKit/Declarations/PoundIf.Condition+Factories.swift new file mode 100644 index 0000000..0c2a312 --- /dev/null +++ b/Sources/SyntaxKit/Declarations/PoundIf.Condition+Factories.swift @@ -0,0 +1,148 @@ +// +// PoundIf.Condition+Factories.swift +// SyntaxKit +// +// Created by Leo Dion. +// Copyright © 2026 BrightDigit. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the “Software”), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +extension PoundIf.Condition where Self == PoundIf.CanImport { + /// `canImport()` + /// - Parameter module: The module name to test for. + /// - Returns: A `canImport` condition. + public static func canImport(_ module: String) -> PoundIf.CanImport { + .init(module: module) + } +} + +extension PoundIf.Condition where Self == PoundIf.Flag { + /// A bare compilation flag such as `DEBUG`. + /// - Parameter name: The flag identifier. + /// - Returns: A flag condition. + public static func flag(_ name: String) -> PoundIf.Flag { + .init(name: name) + } +} + +extension PoundIf.Condition where Self == PoundIf.OSCheck { + /// `os()` + /// - Parameter value: The operating system to test for. + /// - Returns: An `os` condition. + public static func os(_ value: PoundIf.OperatingSystem) -> PoundIf.OSCheck { + .init(value: value) + } +} + +extension PoundIf.Condition where Self == PoundIf.ArchCheck { + /// `arch()` + /// - Parameter value: The CPU architecture to test for. + /// - Returns: An `arch` condition. + public static func arch(_ value: PoundIf.Architecture) -> PoundIf.ArchCheck { + .init(value: value) + } +} + +extension PoundIf.Condition where Self == PoundIf.TargetEnvironmentCheck { + /// `targetEnvironment()` + /// - Parameter value: The target environment to test for. + /// - Returns: A `targetEnvironment` condition. + public static func targetEnvironment( + _ value: PoundIf.TargetEnvironment + ) -> PoundIf.TargetEnvironmentCheck { + .init(value: value) + } +} + +extension PoundIf.Condition where Self == PoundIf.SwiftCheck { + /// `swift(>=5.9)` and friends. + /// - Parameter check: The Swift language version comparison. + /// - Returns: A `swift` version condition. + public static func swift(_ check: PoundIf.VersionCheck) -> PoundIf.SwiftCheck { + .init(check: check) + } +} + +extension PoundIf.Condition where Self == PoundIf.CompilerCheck { + /// `compiler(>=5.9)` and friends. + /// - Parameter check: The compiler version comparison. + /// - Returns: A `compiler` version condition. + public static func compiler(_ check: PoundIf.VersionCheck) -> PoundIf.CompilerCheck { + .init(check: check) + } +} + +extension PoundIf.Condition where Self == PoundIf.HasFeature { + /// `hasFeature()` + /// - Parameter name: The feature name to test for. + /// - Returns: A `hasFeature` condition. + public static func hasFeature(_ name: String) -> PoundIf.HasFeature { + .init(name: name) + } +} + +extension PoundIf.Condition where Self == PoundIf.HasAttribute { + /// `hasAttribute()` + /// - Parameter name: The attribute name to test for. + /// - Returns: A `hasAttribute` condition. + public static func hasAttribute(_ name: String) -> PoundIf.HasAttribute { + .init(name: name) + } +} + +extension PoundIf.Condition where Self == PoundIf.And { + /// ` && ` + /// - Parameters: + /// - lhs: The left-hand condition. + /// - rhs: The right-hand condition. + /// - Returns: A conjunction condition. + public static func and( + _ lhs: L, + _ rhs: R + ) -> PoundIf.And { + .init(lhs: lhs, rhs: rhs) + } +} + +extension PoundIf.Condition where Self == PoundIf.Or { + /// ` || ` + /// - Parameters: + /// - lhs: The left-hand condition. + /// - rhs: The right-hand condition. + /// - Returns: A disjunction condition. + public static func or( + _ lhs: L, + _ rhs: R + ) -> PoundIf.Or { + .init(lhs: lhs, rhs: rhs) + } +} + +extension PoundIf.Condition where Self == PoundIf.Not { + /// `!` + /// - Parameter operand: The condition to negate. + /// - Returns: A negation condition. + public static func not(_ operand: C) -> PoundIf.Not { + .init(operand: operand) + } +} diff --git a/Sources/SyntaxKit/Declarations/PoundIf.swift b/Sources/SyntaxKit/Declarations/PoundIf.swift index a88d2f6..fdbb193 100644 --- a/Sources/SyntaxKit/Declarations/PoundIf.swift +++ b/Sources/SyntaxKit/Declarations/PoundIf.swift @@ -33,7 +33,7 @@ public import SwiftSyntax public struct PoundIf: CodeBlock, Sendable { /// One of the three accepted condition forms attached to a `#if` / `#elseif` clause. internal enum ConditionForm: Sendable { - case helper(Condition) + case helper(any Condition) case raw(String) case codeBlock(any CodeBlock) } @@ -89,7 +89,7 @@ public struct PoundIf: CodeBlock, Sendable { /// - condition: The structured condition for the `#if` clause. /// - content: The code blocks to emit when the condition is satisfied. public init( - _ condition: Condition, + _ condition: some Condition, @CodeBlockBuilderResult _ content: () -> [any CodeBlock] ) { self.head = Clause(condition: .helper(condition), body: content()) @@ -123,7 +123,7 @@ public struct PoundIf: CodeBlock, Sendable { /// - content: The code blocks to emit when the condition is satisfied. /// - Returns: A copy of `self` with the new clause appended. public func elseif( - _ condition: Condition, + _ condition: some Condition, @CodeBlockBuilderResult _ content: () -> [any CodeBlock] ) -> Self { var copy = self From 188e48f132984ef4115657e9bfe2da29cacce0c5 Mon Sep 17 00:00:00 2001 From: leogdion Date: Mon, 15 Jun 2026 11:49:36 -0400 Subject: [PATCH 4/4] Split PoundIf condition types into one file per type Move the entire PoundIf family into a Sources/SyntaxKit/Declarations/PoundIf/ folder, one type per file. Each condition struct is co-located with its factory method; the LeafCondition/BinaryCondition default render implementations live in PoundIf+Rendering.swift. Co-Authored-By: Claude Fable 5 --- .../Declarations/PoundIf+Condition.swift | 179 ------------------ .../Declarations/PoundIf+ConditionTypes.swift | 159 ---------------- .../PoundIf.Condition+Factories.swift | 148 --------------- .../{ => PoundIf}/PoundIf+Rendering.swift | 18 ++ .../Declarations/PoundIf/PoundIf.And.swift | 54 ++++++ .../PoundIf/PoundIf.ArchCheck.swift | 49 +++++ .../PoundIf/PoundIf.Architecture.swift | 44 +++++ .../PoundIf/PoundIf.BinaryCondition.swift | 40 ++++ .../PoundIf/PoundIf.CanImport.swift | 49 +++++ .../PoundIf/PoundIf.CompilerCheck.swift | 49 +++++ .../PoundIf/PoundIf.Condition.swift | 40 ++++ .../Declarations/PoundIf/PoundIf.Flag.swift | 49 +++++ .../PoundIf/PoundIf.HasAttribute.swift | 49 +++++ .../PoundIf/PoundIf.HasFeature.swift | 49 +++++ .../PoundIf/PoundIf.LeafCondition.swift | 39 ++++ .../Declarations/PoundIf/PoundIf.Not.swift | 50 +++++ .../PoundIf/PoundIf.OSCheck.swift | 49 +++++ .../PoundIf/PoundIf.OperatingSystem.swift | 54 ++++++ .../Declarations/PoundIf/PoundIf.Or.swift | 58 ++++++ .../PoundIf/PoundIf.SwiftCheck.swift | 49 +++++ .../PoundIf/PoundIf.TargetEnvironment.swift | 38 ++++ .../PoundIf.TargetEnvironmentCheck.swift | 51 +++++ .../PoundIf/PoundIf.VersionCheck.swift | 84 ++++++++ .../Declarations/{ => PoundIf}/PoundIf.swift | 0 24 files changed, 962 insertions(+), 486 deletions(-) delete mode 100644 Sources/SyntaxKit/Declarations/PoundIf+Condition.swift delete mode 100644 Sources/SyntaxKit/Declarations/PoundIf+ConditionTypes.swift delete mode 100644 Sources/SyntaxKit/Declarations/PoundIf.Condition+Factories.swift rename Sources/SyntaxKit/Declarations/{ => PoundIf}/PoundIf+Rendering.swift (82%) create mode 100644 Sources/SyntaxKit/Declarations/PoundIf/PoundIf.And.swift create mode 100644 Sources/SyntaxKit/Declarations/PoundIf/PoundIf.ArchCheck.swift create mode 100644 Sources/SyntaxKit/Declarations/PoundIf/PoundIf.Architecture.swift create mode 100644 Sources/SyntaxKit/Declarations/PoundIf/PoundIf.BinaryCondition.swift create mode 100644 Sources/SyntaxKit/Declarations/PoundIf/PoundIf.CanImport.swift create mode 100644 Sources/SyntaxKit/Declarations/PoundIf/PoundIf.CompilerCheck.swift create mode 100644 Sources/SyntaxKit/Declarations/PoundIf/PoundIf.Condition.swift create mode 100644 Sources/SyntaxKit/Declarations/PoundIf/PoundIf.Flag.swift create mode 100644 Sources/SyntaxKit/Declarations/PoundIf/PoundIf.HasAttribute.swift create mode 100644 Sources/SyntaxKit/Declarations/PoundIf/PoundIf.HasFeature.swift create mode 100644 Sources/SyntaxKit/Declarations/PoundIf/PoundIf.LeafCondition.swift create mode 100644 Sources/SyntaxKit/Declarations/PoundIf/PoundIf.Not.swift create mode 100644 Sources/SyntaxKit/Declarations/PoundIf/PoundIf.OSCheck.swift create mode 100644 Sources/SyntaxKit/Declarations/PoundIf/PoundIf.OperatingSystem.swift create mode 100644 Sources/SyntaxKit/Declarations/PoundIf/PoundIf.Or.swift create mode 100644 Sources/SyntaxKit/Declarations/PoundIf/PoundIf.SwiftCheck.swift create mode 100644 Sources/SyntaxKit/Declarations/PoundIf/PoundIf.TargetEnvironment.swift create mode 100644 Sources/SyntaxKit/Declarations/PoundIf/PoundIf.TargetEnvironmentCheck.swift create mode 100644 Sources/SyntaxKit/Declarations/PoundIf/PoundIf.VersionCheck.swift rename Sources/SyntaxKit/Declarations/{ => PoundIf}/PoundIf.swift (100%) diff --git a/Sources/SyntaxKit/Declarations/PoundIf+Condition.swift b/Sources/SyntaxKit/Declarations/PoundIf+Condition.swift deleted file mode 100644 index 344e914..0000000 --- a/Sources/SyntaxKit/Declarations/PoundIf+Condition.swift +++ /dev/null @@ -1,179 +0,0 @@ -// -// PoundIf+Condition.swift -// SyntaxKit -// -// Created by Leo Dion. -// Copyright © 2026 BrightDigit. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the “Software”), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or -// sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// - -extension PoundIf { - /// Operating-system identifiers used in `os(...)` checks. - public enum OperatingSystem: String, Sendable { - /// `os(iOS)` - case iOS - /// `os(macOS)` - case macOS - /// `os(tvOS)` - case tvOS - /// `os(watchOS)` - case watchOS - /// `os(visionOS)` - case visionOS - /// `os(Linux)` - case linux = "Linux" - /// `os(Windows)` - case windows = "Windows" - /// `os(FreeBSD)` - case freeBSD = "FreeBSD" - /// `os(Android)` - case android = "Android" - /// `os(WASI)` - case wasi = "WASI" - } - - /// CPU-architecture identifiers used in `arch(...)` checks. - public enum Architecture: String, Sendable { - /// `arch(arm64)` - case arm64 - /// `arch(x86_64)` - case x86 = "x86_64" - /// `arch(i386)` - case i386 - /// `arch(arm)` - case arm - /// `arch(wasm32)` - case wasm32 - } - - /// Target-environment identifiers used in `targetEnvironment(...)` checks. - public enum TargetEnvironment: String, Sendable { - /// `targetEnvironment(simulator)` - case simulator - /// `targetEnvironment(macCatalyst)` - case macCatalyst - } - - /// A `swift(>=5.9)` / `compiler(>=5.9)`-style version check. - public struct VersionCheck: Sendable { - /// The comparison operator used between the keyword and the version. - public enum Comparison: String, Sendable { - /// `>=` - case greaterThanOrEqual = ">=" - /// `>` - case greaterThan = ">" - /// `<=` - case lessThanOrEqual = "<=" - /// `<` - case lessThan = "<" - /// `==` - case equal = "==" - } - - /// The comparison operator that precedes the version. - public let comparison: Comparison - /// The major version component. - public let major: Int - /// The optional minor version component. - public let minor: Int? - /// The optional patch version component. - public let patch: Int? - - internal var versionString: String { - var result = String(major) - if let minor = minor { - result += ".\(minor)" - if let patch = patch { - result += ".\(patch)" - } - } - return result - } - - internal var rendered: String { - "\(comparison.rawValue)\(versionString)" - } - - /// Build a `>= major.minor[.patch]` check. - public static func atLeast(_ major: Int, _ minor: Int? = nil, _ patch: Int? = nil) - -> VersionCheck - { - VersionCheck(comparison: .greaterThanOrEqual, major: major, minor: minor, patch: patch) - } - - /// Build an `== major.minor[.patch]` check. - public static func exact(_ major: Int, _ minor: Int? = nil, _ patch: Int? = nil) -> VersionCheck - { - VersionCheck(comparison: .equal, major: major, minor: minor, patch: patch) - } - } -} - -extension PoundIf { - /// A canonical `#if` check that can render itself as conditional-compilation - /// syntax, mirroring Swift's grammar. - public protocol Condition: Sendable { - /// Render this condition as `#if` source text. - /// - Parameter atTopLevel: When `true`, a binary combinator omits its - /// surrounding parentheses (it is the outermost expression). - /// - Returns: The rendered conditional-compilation expression. - func render(atTopLevel: Bool) -> String - } - - /// A single check rendered as `keyword(argument)`, or a bare `argument` when - /// ``LeafCondition/keyword`` is `nil` (a flag such as `DEBUG`). - public protocol LeafCondition: Condition { - /// The leading keyword, e.g. `os`, or `nil` for a bare flag. - var keyword: String? { get } - /// The text placed inside the parentheses, or the bare flag identifier. - var argument: String { get } - } - - /// A `lhs rhs` combinator, parenthesized unless at the top level. - public protocol BinaryCondition: Condition { - /// The infix operator rendered between the operands, e.g. `&&`. - var symbol: String { get } - /// The left-hand operand. - var lhs: any Condition { get } - /// The right-hand operand. - var rhs: any Condition { get } - } -} - -extension PoundIf.LeafCondition { - /// Render the leaf as `keyword(argument)`, or bare `argument` for a flag. - public func render(atTopLevel _: Bool) -> String { - guard let keyword else { - return argument - } - return "\(keyword)(\(argument))" - } -} - -extension PoundIf.BinaryCondition { - /// Render as `lhs symbol rhs`, wrapping in parentheses unless at the top level. - public func render(atTopLevel: Bool) -> String { - let inner = "\(lhs.render(atTopLevel: false)) \(symbol) \(rhs.render(atTopLevel: false))" - return atTopLevel ? inner : "(\(inner))" - } -} diff --git a/Sources/SyntaxKit/Declarations/PoundIf+ConditionTypes.swift b/Sources/SyntaxKit/Declarations/PoundIf+ConditionTypes.swift deleted file mode 100644 index cea08cd..0000000 --- a/Sources/SyntaxKit/Declarations/PoundIf+ConditionTypes.swift +++ /dev/null @@ -1,159 +0,0 @@ -// -// PoundIf+ConditionTypes.swift -// SyntaxKit -// -// Created by Leo Dion. -// Copyright © 2026 BrightDigit. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the “Software”), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or -// sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// - -extension PoundIf { - /// `canImport()` - public struct CanImport: LeafCondition { - /// The module name passed to `canImport`. - public let module: String - /// The `canImport` keyword. - public var keyword: String? { "canImport" } - /// The module name rendered inside the parentheses. - public var argument: String { module } - } - - /// A bare compilation flag such as `DEBUG`. - public struct Flag: LeafCondition { - /// The flag identifier. - public let name: String - /// A flag has no keyword and renders bare. - public var keyword: String? { nil } - /// The flag identifier rendered as-is. - public var argument: String { name } - } - - /// `os()` - public struct OSCheck: LeafCondition { - /// The operating system being checked. - public let value: OperatingSystem - /// The `os` keyword. - public var keyword: String? { "os" } - /// The operating-system identifier rendered inside the parentheses. - public var argument: String { value.rawValue } - } - - /// `arch()` - public struct ArchCheck: LeafCondition { - /// The CPU architecture being checked. - public let value: Architecture - /// The `arch` keyword. - public var keyword: String? { "arch" } - /// The architecture identifier rendered inside the parentheses. - public var argument: String { value.rawValue } - } - - /// `targetEnvironment()` - public struct TargetEnvironmentCheck: LeafCondition { - /// The target environment being checked. - public let value: TargetEnvironment - /// The `targetEnvironment` keyword. - public var keyword: String? { "targetEnvironment" } - /// The environment identifier rendered inside the parentheses. - public var argument: String { value.rawValue } - } - - /// `swift(>=5.9)` and friends. - public struct SwiftCheck: LeafCondition { - /// The Swift language version check. - public let check: VersionCheck - /// The `swift` keyword. - public var keyword: String? { "swift" } - /// The version comparison rendered inside the parentheses. - public var argument: String { check.rendered } - } - - /// `compiler(>=5.9)` and friends. - public struct CompilerCheck: LeafCondition { - /// The compiler version check. - public let check: VersionCheck - /// The `compiler` keyword. - public var keyword: String? { "compiler" } - /// The version comparison rendered inside the parentheses. - public var argument: String { check.rendered } - } - - /// `hasFeature()` - public struct HasFeature: LeafCondition { - /// The upcoming/experimental feature name. - public let name: String - /// The `hasFeature` keyword. - public var keyword: String? { "hasFeature" } - /// The feature name rendered inside the parentheses. - public var argument: String { name } - } - - /// `hasAttribute()` - public struct HasAttribute: LeafCondition { - /// The attribute name. - public let name: String - /// The `hasAttribute` keyword. - public var keyword: String? { "hasAttribute" } - /// The attribute name rendered inside the parentheses. - public var argument: String { name } - } -} - -// MARK: - Combinators - -extension PoundIf { - // swiftlint:disable type_name - - /// ` && ` - public struct And: BinaryCondition { - /// The left-hand operand. - public let lhs: any Condition - /// The right-hand operand. - public let rhs: any Condition - /// The `&&` operator. - public var symbol: String { "&&" } - } - - /// ` || ` - public struct Or: BinaryCondition { - /// The left-hand operand. - public let lhs: any Condition - /// The right-hand operand. - public let rhs: any Condition - /// The `||` operator. - public var symbol: String { "||" } - } - - // swiftlint:enable type_name - - /// `!` - public struct Not: Condition { - /// The condition being negated. - public let operand: any Condition - - /// Render as `!operand`, with the operand parenthesized as needed. - public func render(atTopLevel _: Bool) -> String { - "!\(operand.render(atTopLevel: false))" - } - } -} diff --git a/Sources/SyntaxKit/Declarations/PoundIf.Condition+Factories.swift b/Sources/SyntaxKit/Declarations/PoundIf.Condition+Factories.swift deleted file mode 100644 index 0c2a312..0000000 --- a/Sources/SyntaxKit/Declarations/PoundIf.Condition+Factories.swift +++ /dev/null @@ -1,148 +0,0 @@ -// -// PoundIf.Condition+Factories.swift -// SyntaxKit -// -// Created by Leo Dion. -// Copyright © 2026 BrightDigit. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the “Software”), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or -// sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// - -extension PoundIf.Condition where Self == PoundIf.CanImport { - /// `canImport()` - /// - Parameter module: The module name to test for. - /// - Returns: A `canImport` condition. - public static func canImport(_ module: String) -> PoundIf.CanImport { - .init(module: module) - } -} - -extension PoundIf.Condition where Self == PoundIf.Flag { - /// A bare compilation flag such as `DEBUG`. - /// - Parameter name: The flag identifier. - /// - Returns: A flag condition. - public static func flag(_ name: String) -> PoundIf.Flag { - .init(name: name) - } -} - -extension PoundIf.Condition where Self == PoundIf.OSCheck { - /// `os()` - /// - Parameter value: The operating system to test for. - /// - Returns: An `os` condition. - public static func os(_ value: PoundIf.OperatingSystem) -> PoundIf.OSCheck { - .init(value: value) - } -} - -extension PoundIf.Condition where Self == PoundIf.ArchCheck { - /// `arch()` - /// - Parameter value: The CPU architecture to test for. - /// - Returns: An `arch` condition. - public static func arch(_ value: PoundIf.Architecture) -> PoundIf.ArchCheck { - .init(value: value) - } -} - -extension PoundIf.Condition where Self == PoundIf.TargetEnvironmentCheck { - /// `targetEnvironment()` - /// - Parameter value: The target environment to test for. - /// - Returns: A `targetEnvironment` condition. - public static func targetEnvironment( - _ value: PoundIf.TargetEnvironment - ) -> PoundIf.TargetEnvironmentCheck { - .init(value: value) - } -} - -extension PoundIf.Condition where Self == PoundIf.SwiftCheck { - /// `swift(>=5.9)` and friends. - /// - Parameter check: The Swift language version comparison. - /// - Returns: A `swift` version condition. - public static func swift(_ check: PoundIf.VersionCheck) -> PoundIf.SwiftCheck { - .init(check: check) - } -} - -extension PoundIf.Condition where Self == PoundIf.CompilerCheck { - /// `compiler(>=5.9)` and friends. - /// - Parameter check: The compiler version comparison. - /// - Returns: A `compiler` version condition. - public static func compiler(_ check: PoundIf.VersionCheck) -> PoundIf.CompilerCheck { - .init(check: check) - } -} - -extension PoundIf.Condition where Self == PoundIf.HasFeature { - /// `hasFeature()` - /// - Parameter name: The feature name to test for. - /// - Returns: A `hasFeature` condition. - public static func hasFeature(_ name: String) -> PoundIf.HasFeature { - .init(name: name) - } -} - -extension PoundIf.Condition where Self == PoundIf.HasAttribute { - /// `hasAttribute()` - /// - Parameter name: The attribute name to test for. - /// - Returns: A `hasAttribute` condition. - public static func hasAttribute(_ name: String) -> PoundIf.HasAttribute { - .init(name: name) - } -} - -extension PoundIf.Condition where Self == PoundIf.And { - /// ` && ` - /// - Parameters: - /// - lhs: The left-hand condition. - /// - rhs: The right-hand condition. - /// - Returns: A conjunction condition. - public static func and( - _ lhs: L, - _ rhs: R - ) -> PoundIf.And { - .init(lhs: lhs, rhs: rhs) - } -} - -extension PoundIf.Condition where Self == PoundIf.Or { - /// ` || ` - /// - Parameters: - /// - lhs: The left-hand condition. - /// - rhs: The right-hand condition. - /// - Returns: A disjunction condition. - public static func or( - _ lhs: L, - _ rhs: R - ) -> PoundIf.Or { - .init(lhs: lhs, rhs: rhs) - } -} - -extension PoundIf.Condition where Self == PoundIf.Not { - /// `!` - /// - Parameter operand: The condition to negate. - /// - Returns: A negation condition. - public static func not(_ operand: C) -> PoundIf.Not { - .init(operand: operand) - } -} diff --git a/Sources/SyntaxKit/Declarations/PoundIf+Rendering.swift b/Sources/SyntaxKit/Declarations/PoundIf/PoundIf+Rendering.swift similarity index 82% rename from Sources/SyntaxKit/Declarations/PoundIf+Rendering.swift rename to Sources/SyntaxKit/Declarations/PoundIf/PoundIf+Rendering.swift index 9b433df..daf7f7e 100644 --- a/Sources/SyntaxKit/Declarations/PoundIf+Rendering.swift +++ b/Sources/SyntaxKit/Declarations/PoundIf/PoundIf+Rendering.swift @@ -78,3 +78,21 @@ extension PoundIf { return nil } } + +extension PoundIf.LeafCondition { + /// Render the leaf as `keyword(argument)`, or bare `argument` for a flag. + public func render(atTopLevel _: Bool) -> String { + guard let keyword else { + return argument + } + return "\(keyword)(\(argument))" + } +} + +extension PoundIf.BinaryCondition { + /// Render as `lhs symbol rhs`, wrapping in parentheses unless at the top level. + public func render(atTopLevel: Bool) -> String { + let inner = "\(lhs.render(atTopLevel: false)) \(symbol) \(rhs.render(atTopLevel: false))" + return atTopLevel ? inner : "(\(inner))" + } +} diff --git a/Sources/SyntaxKit/Declarations/PoundIf/PoundIf.And.swift b/Sources/SyntaxKit/Declarations/PoundIf/PoundIf.And.swift new file mode 100644 index 0000000..b590913 --- /dev/null +++ b/Sources/SyntaxKit/Declarations/PoundIf/PoundIf.And.swift @@ -0,0 +1,54 @@ +// +// PoundIf.And.swift +// SyntaxKit +// +// Created by Leo Dion. +// Copyright © 2026 BrightDigit. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the “Software”), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +extension PoundIf { + /// ` && ` + public struct And: BinaryCondition { + /// The left-hand operand. + public let lhs: any Condition + /// The right-hand operand. + public let rhs: any Condition + /// The `&&` operator. + public var symbol: String { "&&" } + } +} + +extension PoundIf.Condition where Self == PoundIf.And { + /// ` && ` + /// - Parameters: + /// - lhs: The left-hand condition. + /// - rhs: The right-hand condition. + /// - Returns: A conjunction condition. + public static func and( + _ lhs: L, + _ rhs: R + ) -> PoundIf.And { + .init(lhs: lhs, rhs: rhs) + } +} diff --git a/Sources/SyntaxKit/Declarations/PoundIf/PoundIf.ArchCheck.swift b/Sources/SyntaxKit/Declarations/PoundIf/PoundIf.ArchCheck.swift new file mode 100644 index 0000000..3bd3440 --- /dev/null +++ b/Sources/SyntaxKit/Declarations/PoundIf/PoundIf.ArchCheck.swift @@ -0,0 +1,49 @@ +// +// PoundIf.ArchCheck.swift +// SyntaxKit +// +// Created by Leo Dion. +// Copyright © 2026 BrightDigit. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the “Software”), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +extension PoundIf { + /// `arch()` + public struct ArchCheck: LeafCondition { + /// The CPU architecture being checked. + public let value: Architecture + /// The `arch` keyword. + public var keyword: String? { "arch" } + /// The architecture identifier rendered inside the parentheses. + public var argument: String { value.rawValue } + } +} + +extension PoundIf.Condition where Self == PoundIf.ArchCheck { + /// `arch()` + /// - Parameter value: The CPU architecture to test for. + /// - Returns: An `arch` condition. + public static func arch(_ value: PoundIf.Architecture) -> PoundIf.ArchCheck { + .init(value: value) + } +} diff --git a/Sources/SyntaxKit/Declarations/PoundIf/PoundIf.Architecture.swift b/Sources/SyntaxKit/Declarations/PoundIf/PoundIf.Architecture.swift new file mode 100644 index 0000000..f3227f5 --- /dev/null +++ b/Sources/SyntaxKit/Declarations/PoundIf/PoundIf.Architecture.swift @@ -0,0 +1,44 @@ +// +// PoundIf.Architecture.swift +// SyntaxKit +// +// Created by Leo Dion. +// Copyright © 2026 BrightDigit. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the “Software”), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +extension PoundIf { + /// CPU-architecture identifiers used in `arch(...)` checks. + public enum Architecture: String, Sendable { + /// `arch(arm64)` + case arm64 + /// `arch(x86_64)` + case x86 = "x86_64" + /// `arch(i386)` + case i386 + /// `arch(arm)` + case arm + /// `arch(wasm32)` + case wasm32 + } +} diff --git a/Sources/SyntaxKit/Declarations/PoundIf/PoundIf.BinaryCondition.swift b/Sources/SyntaxKit/Declarations/PoundIf/PoundIf.BinaryCondition.swift new file mode 100644 index 0000000..0f5534b --- /dev/null +++ b/Sources/SyntaxKit/Declarations/PoundIf/PoundIf.BinaryCondition.swift @@ -0,0 +1,40 @@ +// +// PoundIf.BinaryCondition.swift +// SyntaxKit +// +// Created by Leo Dion. +// Copyright © 2026 BrightDigit. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the “Software”), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +extension PoundIf { + /// A `lhs rhs` combinator, parenthesized unless at the top level. + public protocol BinaryCondition: Condition { + /// The infix operator rendered between the operands, e.g. `&&`. + var symbol: String { get } + /// The left-hand operand. + var lhs: any Condition { get } + /// The right-hand operand. + var rhs: any Condition { get } + } +} diff --git a/Sources/SyntaxKit/Declarations/PoundIf/PoundIf.CanImport.swift b/Sources/SyntaxKit/Declarations/PoundIf/PoundIf.CanImport.swift new file mode 100644 index 0000000..392123f --- /dev/null +++ b/Sources/SyntaxKit/Declarations/PoundIf/PoundIf.CanImport.swift @@ -0,0 +1,49 @@ +// +// PoundIf.CanImport.swift +// SyntaxKit +// +// Created by Leo Dion. +// Copyright © 2026 BrightDigit. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the “Software”), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +extension PoundIf { + /// `canImport()` + public struct CanImport: LeafCondition { + /// The module name passed to `canImport`. + public let module: String + /// The `canImport` keyword. + public var keyword: String? { "canImport" } + /// The module name rendered inside the parentheses. + public var argument: String { module } + } +} + +extension PoundIf.Condition where Self == PoundIf.CanImport { + /// `canImport()` + /// - Parameter module: The module name to test for. + /// - Returns: A `canImport` condition. + public static func canImport(_ module: String) -> PoundIf.CanImport { + .init(module: module) + } +} diff --git a/Sources/SyntaxKit/Declarations/PoundIf/PoundIf.CompilerCheck.swift b/Sources/SyntaxKit/Declarations/PoundIf/PoundIf.CompilerCheck.swift new file mode 100644 index 0000000..46e1df6 --- /dev/null +++ b/Sources/SyntaxKit/Declarations/PoundIf/PoundIf.CompilerCheck.swift @@ -0,0 +1,49 @@ +// +// PoundIf.CompilerCheck.swift +// SyntaxKit +// +// Created by Leo Dion. +// Copyright © 2026 BrightDigit. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the “Software”), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +extension PoundIf { + /// `compiler(>=5.9)` and friends. + public struct CompilerCheck: LeafCondition { + /// The compiler version check. + public let check: VersionCheck + /// The `compiler` keyword. + public var keyword: String? { "compiler" } + /// The version comparison rendered inside the parentheses. + public var argument: String { check.rendered } + } +} + +extension PoundIf.Condition where Self == PoundIf.CompilerCheck { + /// `compiler(>=5.9)` and friends. + /// - Parameter check: The compiler version comparison. + /// - Returns: A `compiler` version condition. + public static func compiler(_ check: PoundIf.VersionCheck) -> PoundIf.CompilerCheck { + .init(check: check) + } +} diff --git a/Sources/SyntaxKit/Declarations/PoundIf/PoundIf.Condition.swift b/Sources/SyntaxKit/Declarations/PoundIf/PoundIf.Condition.swift new file mode 100644 index 0000000..c24fd66 --- /dev/null +++ b/Sources/SyntaxKit/Declarations/PoundIf/PoundIf.Condition.swift @@ -0,0 +1,40 @@ +// +// PoundIf.Condition.swift +// SyntaxKit +// +// Created by Leo Dion. +// Copyright © 2026 BrightDigit. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the “Software”), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +extension PoundIf { + /// A canonical `#if` check that can render itself as conditional-compilation + /// syntax, mirroring Swift's grammar. + public protocol Condition: Sendable { + /// Render this condition as `#if` source text. + /// - Parameter atTopLevel: When `true`, a binary combinator omits its + /// surrounding parentheses (it is the outermost expression). + /// - Returns: The rendered conditional-compilation expression. + func render(atTopLevel: Bool) -> String + } +} diff --git a/Sources/SyntaxKit/Declarations/PoundIf/PoundIf.Flag.swift b/Sources/SyntaxKit/Declarations/PoundIf/PoundIf.Flag.swift new file mode 100644 index 0000000..01ac87e --- /dev/null +++ b/Sources/SyntaxKit/Declarations/PoundIf/PoundIf.Flag.swift @@ -0,0 +1,49 @@ +// +// PoundIf.Flag.swift +// SyntaxKit +// +// Created by Leo Dion. +// Copyright © 2026 BrightDigit. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the “Software”), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +extension PoundIf { + /// A bare compilation flag such as `DEBUG`. + public struct Flag: LeafCondition { + /// The flag identifier. + public let name: String + /// A flag has no keyword and renders bare. + public var keyword: String? { nil } + /// The flag identifier rendered as-is. + public var argument: String { name } + } +} + +extension PoundIf.Condition where Self == PoundIf.Flag { + /// A bare compilation flag such as `DEBUG`. + /// - Parameter name: The flag identifier. + /// - Returns: A flag condition. + public static func flag(_ name: String) -> PoundIf.Flag { + .init(name: name) + } +} diff --git a/Sources/SyntaxKit/Declarations/PoundIf/PoundIf.HasAttribute.swift b/Sources/SyntaxKit/Declarations/PoundIf/PoundIf.HasAttribute.swift new file mode 100644 index 0000000..6c9f05a --- /dev/null +++ b/Sources/SyntaxKit/Declarations/PoundIf/PoundIf.HasAttribute.swift @@ -0,0 +1,49 @@ +// +// PoundIf.HasAttribute.swift +// SyntaxKit +// +// Created by Leo Dion. +// Copyright © 2026 BrightDigit. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the “Software”), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +extension PoundIf { + /// `hasAttribute()` + public struct HasAttribute: LeafCondition { + /// The attribute name. + public let name: String + /// The `hasAttribute` keyword. + public var keyword: String? { "hasAttribute" } + /// The attribute name rendered inside the parentheses. + public var argument: String { name } + } +} + +extension PoundIf.Condition where Self == PoundIf.HasAttribute { + /// `hasAttribute()` + /// - Parameter name: The attribute name to test for. + /// - Returns: A `hasAttribute` condition. + public static func hasAttribute(_ name: String) -> PoundIf.HasAttribute { + .init(name: name) + } +} diff --git a/Sources/SyntaxKit/Declarations/PoundIf/PoundIf.HasFeature.swift b/Sources/SyntaxKit/Declarations/PoundIf/PoundIf.HasFeature.swift new file mode 100644 index 0000000..4bcc179 --- /dev/null +++ b/Sources/SyntaxKit/Declarations/PoundIf/PoundIf.HasFeature.swift @@ -0,0 +1,49 @@ +// +// PoundIf.HasFeature.swift +// SyntaxKit +// +// Created by Leo Dion. +// Copyright © 2026 BrightDigit. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the “Software”), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +extension PoundIf { + /// `hasFeature()` + public struct HasFeature: LeafCondition { + /// The upcoming/experimental feature name. + public let name: String + /// The `hasFeature` keyword. + public var keyword: String? { "hasFeature" } + /// The feature name rendered inside the parentheses. + public var argument: String { name } + } +} + +extension PoundIf.Condition where Self == PoundIf.HasFeature { + /// `hasFeature()` + /// - Parameter name: The feature name to test for. + /// - Returns: A `hasFeature` condition. + public static func hasFeature(_ name: String) -> PoundIf.HasFeature { + .init(name: name) + } +} diff --git a/Sources/SyntaxKit/Declarations/PoundIf/PoundIf.LeafCondition.swift b/Sources/SyntaxKit/Declarations/PoundIf/PoundIf.LeafCondition.swift new file mode 100644 index 0000000..8188841 --- /dev/null +++ b/Sources/SyntaxKit/Declarations/PoundIf/PoundIf.LeafCondition.swift @@ -0,0 +1,39 @@ +// +// PoundIf.LeafCondition.swift +// SyntaxKit +// +// Created by Leo Dion. +// Copyright © 2026 BrightDigit. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the “Software”), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +extension PoundIf { + /// A single check rendered as `keyword(argument)`, or a bare `argument` when + /// ``LeafCondition/keyword`` is `nil` (a flag such as `DEBUG`). + public protocol LeafCondition: Condition { + /// The leading keyword, e.g. `os`, or `nil` for a bare flag. + var keyword: String? { get } + /// The text placed inside the parentheses, or the bare flag identifier. + var argument: String { get } + } +} diff --git a/Sources/SyntaxKit/Declarations/PoundIf/PoundIf.Not.swift b/Sources/SyntaxKit/Declarations/PoundIf/PoundIf.Not.swift new file mode 100644 index 0000000..7dccd32 --- /dev/null +++ b/Sources/SyntaxKit/Declarations/PoundIf/PoundIf.Not.swift @@ -0,0 +1,50 @@ +// +// PoundIf.Not.swift +// SyntaxKit +// +// Created by Leo Dion. +// Copyright © 2026 BrightDigit. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the “Software”), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +extension PoundIf { + /// `!` + public struct Not: Condition { + /// The condition being negated. + public let operand: any Condition + + /// Render as `!operand`, with the operand parenthesized as needed. + public func render(atTopLevel _: Bool) -> String { + "!\(operand.render(atTopLevel: false))" + } + } +} + +extension PoundIf.Condition where Self == PoundIf.Not { + /// `!` + /// - Parameter operand: The condition to negate. + /// - Returns: A negation condition. + public static func not(_ operand: C) -> PoundIf.Not { + .init(operand: operand) + } +} diff --git a/Sources/SyntaxKit/Declarations/PoundIf/PoundIf.OSCheck.swift b/Sources/SyntaxKit/Declarations/PoundIf/PoundIf.OSCheck.swift new file mode 100644 index 0000000..630ed2f --- /dev/null +++ b/Sources/SyntaxKit/Declarations/PoundIf/PoundIf.OSCheck.swift @@ -0,0 +1,49 @@ +// +// PoundIf.OSCheck.swift +// SyntaxKit +// +// Created by Leo Dion. +// Copyright © 2026 BrightDigit. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the “Software”), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +extension PoundIf { + /// `os()` + public struct OSCheck: LeafCondition { + /// The operating system being checked. + public let value: OperatingSystem + /// The `os` keyword. + public var keyword: String? { "os" } + /// The operating-system identifier rendered inside the parentheses. + public var argument: String { value.rawValue } + } +} + +extension PoundIf.Condition where Self == PoundIf.OSCheck { + /// `os()` + /// - Parameter value: The operating system to test for. + /// - Returns: An `os` condition. + public static func os(_ value: PoundIf.OperatingSystem) -> PoundIf.OSCheck { + .init(value: value) + } +} diff --git a/Sources/SyntaxKit/Declarations/PoundIf/PoundIf.OperatingSystem.swift b/Sources/SyntaxKit/Declarations/PoundIf/PoundIf.OperatingSystem.swift new file mode 100644 index 0000000..00b5c3c --- /dev/null +++ b/Sources/SyntaxKit/Declarations/PoundIf/PoundIf.OperatingSystem.swift @@ -0,0 +1,54 @@ +// +// PoundIf.OperatingSystem.swift +// SyntaxKit +// +// Created by Leo Dion. +// Copyright © 2026 BrightDigit. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the “Software”), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +extension PoundIf { + /// Operating-system identifiers used in `os(...)` checks. + public enum OperatingSystem: String, Sendable { + /// `os(iOS)` + case iOS + /// `os(macOS)` + case macOS + /// `os(tvOS)` + case tvOS + /// `os(watchOS)` + case watchOS + /// `os(visionOS)` + case visionOS + /// `os(Linux)` + case linux = "Linux" + /// `os(Windows)` + case windows = "Windows" + /// `os(FreeBSD)` + case freeBSD = "FreeBSD" + /// `os(Android)` + case android = "Android" + /// `os(WASI)` + case wasi = "WASI" + } +} diff --git a/Sources/SyntaxKit/Declarations/PoundIf/PoundIf.Or.swift b/Sources/SyntaxKit/Declarations/PoundIf/PoundIf.Or.swift new file mode 100644 index 0000000..b59d25c --- /dev/null +++ b/Sources/SyntaxKit/Declarations/PoundIf/PoundIf.Or.swift @@ -0,0 +1,58 @@ +// +// PoundIf.Or.swift +// SyntaxKit +// +// Created by Leo Dion. +// Copyright © 2026 BrightDigit. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the “Software”), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +extension PoundIf { + // swiftlint:disable type_name + + /// ` || ` + public struct Or: BinaryCondition { + /// The left-hand operand. + public let lhs: any Condition + /// The right-hand operand. + public let rhs: any Condition + /// The `||` operator. + public var symbol: String { "||" } + } + + // swiftlint:enable type_name +} + +extension PoundIf.Condition where Self == PoundIf.Or { + /// ` || ` + /// - Parameters: + /// - lhs: The left-hand condition. + /// - rhs: The right-hand condition. + /// - Returns: A disjunction condition. + public static func or( + _ lhs: L, + _ rhs: R + ) -> PoundIf.Or { + .init(lhs: lhs, rhs: rhs) + } +} diff --git a/Sources/SyntaxKit/Declarations/PoundIf/PoundIf.SwiftCheck.swift b/Sources/SyntaxKit/Declarations/PoundIf/PoundIf.SwiftCheck.swift new file mode 100644 index 0000000..beddb80 --- /dev/null +++ b/Sources/SyntaxKit/Declarations/PoundIf/PoundIf.SwiftCheck.swift @@ -0,0 +1,49 @@ +// +// PoundIf.SwiftCheck.swift +// SyntaxKit +// +// Created by Leo Dion. +// Copyright © 2026 BrightDigit. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the “Software”), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +extension PoundIf { + /// `swift(>=5.9)` and friends. + public struct SwiftCheck: LeafCondition { + /// The Swift language version check. + public let check: VersionCheck + /// The `swift` keyword. + public var keyword: String? { "swift" } + /// The version comparison rendered inside the parentheses. + public var argument: String { check.rendered } + } +} + +extension PoundIf.Condition where Self == PoundIf.SwiftCheck { + /// `swift(>=5.9)` and friends. + /// - Parameter check: The Swift language version comparison. + /// - Returns: A `swift` version condition. + public static func swift(_ check: PoundIf.VersionCheck) -> PoundIf.SwiftCheck { + .init(check: check) + } +} diff --git a/Sources/SyntaxKit/Declarations/PoundIf/PoundIf.TargetEnvironment.swift b/Sources/SyntaxKit/Declarations/PoundIf/PoundIf.TargetEnvironment.swift new file mode 100644 index 0000000..7d74c9a --- /dev/null +++ b/Sources/SyntaxKit/Declarations/PoundIf/PoundIf.TargetEnvironment.swift @@ -0,0 +1,38 @@ +// +// PoundIf.TargetEnvironment.swift +// SyntaxKit +// +// Created by Leo Dion. +// Copyright © 2026 BrightDigit. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the “Software”), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +extension PoundIf { + /// Target-environment identifiers used in `targetEnvironment(...)` checks. + public enum TargetEnvironment: String, Sendable { + /// `targetEnvironment(simulator)` + case simulator + /// `targetEnvironment(macCatalyst)` + case macCatalyst + } +} diff --git a/Sources/SyntaxKit/Declarations/PoundIf/PoundIf.TargetEnvironmentCheck.swift b/Sources/SyntaxKit/Declarations/PoundIf/PoundIf.TargetEnvironmentCheck.swift new file mode 100644 index 0000000..89d94d8 --- /dev/null +++ b/Sources/SyntaxKit/Declarations/PoundIf/PoundIf.TargetEnvironmentCheck.swift @@ -0,0 +1,51 @@ +// +// PoundIf.TargetEnvironmentCheck.swift +// SyntaxKit +// +// Created by Leo Dion. +// Copyright © 2026 BrightDigit. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the “Software”), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +extension PoundIf { + /// `targetEnvironment()` + public struct TargetEnvironmentCheck: LeafCondition { + /// The target environment being checked. + public let value: TargetEnvironment + /// The `targetEnvironment` keyword. + public var keyword: String? { "targetEnvironment" } + /// The environment identifier rendered inside the parentheses. + public var argument: String { value.rawValue } + } +} + +extension PoundIf.Condition where Self == PoundIf.TargetEnvironmentCheck { + /// `targetEnvironment()` + /// - Parameter value: The target environment to test for. + /// - Returns: A `targetEnvironment` condition. + public static func targetEnvironment( + _ value: PoundIf.TargetEnvironment + ) -> PoundIf.TargetEnvironmentCheck { + .init(value: value) + } +} diff --git a/Sources/SyntaxKit/Declarations/PoundIf/PoundIf.VersionCheck.swift b/Sources/SyntaxKit/Declarations/PoundIf/PoundIf.VersionCheck.swift new file mode 100644 index 0000000..753cdd7 --- /dev/null +++ b/Sources/SyntaxKit/Declarations/PoundIf/PoundIf.VersionCheck.swift @@ -0,0 +1,84 @@ +// +// PoundIf.VersionCheck.swift +// SyntaxKit +// +// Created by Leo Dion. +// Copyright © 2026 BrightDigit. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the “Software”), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +extension PoundIf { + /// A `swift(>=5.9)` / `compiler(>=5.9)`-style version check. + public struct VersionCheck: Sendable { + /// The comparison operator used between the keyword and the version. + public enum Comparison: String, Sendable { + /// `>=` + case greaterThanOrEqual = ">=" + /// `>` + case greaterThan = ">" + /// `<=` + case lessThanOrEqual = "<=" + /// `<` + case lessThan = "<" + /// `==` + case equal = "==" + } + + /// The comparison operator that precedes the version. + public let comparison: Comparison + /// The major version component. + public let major: Int + /// The optional minor version component. + public let minor: Int? + /// The optional patch version component. + public let patch: Int? + + internal var versionString: String { + var result = String(major) + if let minor = minor { + result += ".\(minor)" + if let patch = patch { + result += ".\(patch)" + } + } + return result + } + + internal var rendered: String { + "\(comparison.rawValue)\(versionString)" + } + + /// Build a `>= major.minor[.patch]` check. + public static func atLeast(_ major: Int, _ minor: Int? = nil, _ patch: Int? = nil) + -> VersionCheck + { + VersionCheck(comparison: .greaterThanOrEqual, major: major, minor: minor, patch: patch) + } + + /// Build an `== major.minor[.patch]` check. + public static func exact(_ major: Int, _ minor: Int? = nil, _ patch: Int? = nil) -> VersionCheck + { + VersionCheck(comparison: .equal, major: major, minor: minor, patch: patch) + } + } +} diff --git a/Sources/SyntaxKit/Declarations/PoundIf.swift b/Sources/SyntaxKit/Declarations/PoundIf/PoundIf.swift similarity index 100% rename from Sources/SyntaxKit/Declarations/PoundIf.swift rename to Sources/SyntaxKit/Declarations/PoundIf/PoundIf.swift