diff --git a/Examples/Completed/conditionals/dsl.swift b/Examples/Completed/conditionals/dsl.swift index a666c2a..70263e0 100644 --- a/Examples/Completed/conditionals/dsl.swift +++ b/Examples/Completed/conditionals/dsl.swift @@ -23,7 +23,7 @@ Group { Call("print", "Good job!") } If { - Infix("score", ">=", 70) + try Infix("score", ">=", 70) } then: { Call("print", "Passing") } @@ -172,24 +172,24 @@ Infix("board[24]", "-=", 8) Variable(.var, name: "square", equals: 0) Variable(.var, name: "diceRoll", equals: 0) While { - Infix("square", "!=", "finalSquare") + try Infix("square", "!=", "finalSquare") } then: { Assignment("diceRoll", "+", 1) If { - Infix("diceRoll", "==", 7) + try Infix("diceRoll", "==", 7) } then: { Assignment("diceRoll", 1) } - Switch(Infix("square", "+", "diceRoll")) { + Switch(try Infix("square", "+", "diceRoll")) { SwitchCase("finalSquare") { Break() } - SwitchCase(Infix("newSquare", ">", "finalSquare")) { + SwitchCase(try Infix("newSquare", ">", "finalSquare")) { Continue() } Default { - Infix("square", "+=", "diceRoll") - Infix("square", "+=", "board[square]") + try Infix("square", "+=", "diceRoll") + try Infix("square", "+=", "board[square]") } } } @@ -216,7 +216,7 @@ For { } in: { Literal.array([Literal.integer(1), Literal.integer(2), Literal.integer(3), Literal.integer(4), Literal.integer(5), Literal.integer(6), Literal.integer(7), Literal.integer(8), Literal.integer(9), Literal.integer(10)]) } where: { - Infix("number", "%", 2) + try Infix("number", "%", 2) } then: { Call("print", "Even number: \\(number)") } diff --git a/Examples/Completed/for_loops/dsl.swift b/Examples/Completed/for_loops/dsl.swift index 4c8bf36..db06009 100644 --- a/Examples/Completed/for_loops/dsl.swift +++ b/Examples/Completed/for_loops/dsl.swift @@ -40,8 +40,8 @@ Group { Variable(.let, name: "numbers", equals: Literal.array([Literal.integer(1), Literal.integer(2), Literal.integer(3), Literal.integer(4), Literal.integer(5), Literal.integer(6), Literal.integer(7), Literal.integer(8), Literal.integer(9), Literal.integer(10)])) For(VariableExp("number"), in: VariableExp("numbers"), where: { - Infix("==") { - Infix("%") { + try Infix("==") { + try Infix("%") { VariableExp("number") Literal.integer(2) } @@ -68,4 +68,4 @@ Group { ParameterExp(unlabeled: "\"\\(name): \\(score)\"") } }) -} \ No newline at end of file +} diff --git a/Macros/SKSampleMacro/Sources/SKSampleMacroMacros/SKSampleMacroMacro.swift b/Macros/SKSampleMacro/Sources/SKSampleMacroMacros/SKSampleMacroMacro.swift index 014fb7e..e531bec 100644 --- a/Macros/SKSampleMacro/Sources/SKSampleMacroMacros/SKSampleMacroMacro.swift +++ b/Macros/SKSampleMacro/Sources/SKSampleMacroMacros/SKSampleMacroMacro.swift @@ -25,7 +25,7 @@ public struct StringifyMacro: ExpressionMacro { } return Tuple{ - Infix("+") { + try Infix("+") { VariableExp(first.description) VariableExp(second.description) } diff --git a/Sources/SyntaxKit/CodeBlocks/CodeBlock+ExprSyntax.swift b/Sources/SyntaxKit/CodeBlocks/CodeBlock+ExprSyntax.swift index b8b9f30..679e112 100644 --- a/Sources/SyntaxKit/CodeBlocks/CodeBlock+ExprSyntax.swift +++ b/Sources/SyntaxKit/CodeBlocks/CodeBlock+ExprSyntax.swift @@ -35,8 +35,8 @@ extension CodeBlock { /// If the underlying syntax already *is* an `ExprSyntax`, it is returned directly. If the /// underlying syntax is a bare `TokenSyntax` (commonly the case for `VariableExp` which /// produces an identifier token), we wrap it in a `DeclReferenceExprSyntax` so that it becomes - /// a valid expression node. Any other kind of syntax results in a runtime error, because it - /// cannot be represented as an expression (e.g. declarations or statements). + /// a valid expression node. For any other kind of syntax, we create a default empty expression + /// to prevent crashes while still allowing code generation to continue. public var expr: ExprSyntax { if let expr = self.syntax.as(ExprSyntax.self) { return expr @@ -46,6 +46,11 @@ extension CodeBlock { return ExprSyntax(DeclReferenceExprSyntax(baseName: .identifier(token.text))) } - fatalError("CodeBlock of type \(type(of: self.syntax)) cannot be represented as ExprSyntax") + // Fallback for unsupported syntax types - create a default expression + // This prevents crashes while still allowing code generation to continue + #warning( + "TODO: Review fallback for unsupported syntax types - consider if this should be an error instead" + ) + return ExprSyntax(DeclReferenceExprSyntax(baseName: .identifier(""))) } } diff --git a/Sources/SyntaxKit/CodeBlocks/CodeBlock+Generate.swift b/Sources/SyntaxKit/CodeBlocks/CodeBlock+Generate.swift index adfd488..aaaa936 100644 --- a/Sources/SyntaxKit/CodeBlocks/CodeBlock+Generate.swift +++ b/Sources/SyntaxKit/CodeBlocks/CodeBlock+Generate.swift @@ -45,10 +45,13 @@ extension CodeBlock { if let convertedItem = CodeBlockItemSyntax.Item.create(from: self.syntax) { item = convertedItem } else { - fatalError( - "Unsupported syntax type at top level: \(type(of: self.syntax)) (\(self.syntax)) " - + "generating from \(self)" + // Fallback for unsupported syntax types - create an empty code block + // This prevents crashes while still allowing code generation to continue + #warning( + "TODO: Review fallback for unsupported syntax types - consider if this should be an error instead" ) + let emptyExpr = ExprSyntax(DeclReferenceExprSyntax(baseName: .identifier(""))) + item = .expr(emptyExpr) } statements = CodeBlockItemListSyntax([ CodeBlockItemSyntax(item: item, trailingTrivia: .newline) diff --git a/Sources/SyntaxKit/CodeBlocks/CodeBlockItemSyntax.Item.swift b/Sources/SyntaxKit/CodeBlocks/CodeBlockItemSyntax.Item.swift index 17e72fe..1ed448a 100644 --- a/Sources/SyntaxKit/CodeBlocks/CodeBlockItemSyntax.Item.swift +++ b/Sources/SyntaxKit/CodeBlocks/CodeBlockItemSyntax.Item.swift @@ -47,6 +47,9 @@ extension CodeBlockItemSyntax.Item { } else if let switchCase = syntax.as(SwitchCaseSyntax.self) { // Wrap SwitchCaseSyntax in a SwitchExprSyntax and treat it as an expression // This is a fallback for when SwitchCase is used standalone + #warning( + "TODO: Review fallback for SwitchCase used standalone - consider if this should be an error instead" + ) let switchExpr = SwitchExprSyntax( switchKeyword: .keyword(.switch, trailingTrivia: .space), subject: ExprSyntax(DeclReferenceExprSyntax(baseName: .identifier("_"))), diff --git a/Sources/SyntaxKit/CodeBlocks/CommentedCodeBlock.swift b/Sources/SyntaxKit/CodeBlocks/CommentedCodeBlock.swift index 4b3d653..1df7dcd 100644 --- a/Sources/SyntaxKit/CodeBlocks/CommentedCodeBlock.swift +++ b/Sources/SyntaxKit/CodeBlocks/CommentedCodeBlock.swift @@ -60,6 +60,7 @@ internal struct CommentedCodeBlock: CodeBlock { guard let firstToken = base.syntax.firstToken(viewMode: .sourceAccurate) else { // Fallback – no tokens? return original syntax + #warning("TODO: Review fallback for no tokens - consider if this should be an error instead") return base.syntax } diff --git a/Sources/SyntaxKit/Collections/Array+LiteralValue.swift b/Sources/SyntaxKit/Collections/Array+LiteralValue.swift index 9b75416..64ad042 100644 --- a/Sources/SyntaxKit/Collections/Array+LiteralValue.swift +++ b/Sources/SyntaxKit/Collections/Array+LiteralValue.swift @@ -29,10 +29,15 @@ import Foundation -extension Array: LiteralValue where Element == String { +extension Array: LiteralValue, CodeBlockable where Element == String { /// The Swift type name for an array of strings. public var typeName: String { "[String]" } + /// The code block representation of this array of strings. + public var codeBlock: CodeBlock { + Literal.array(self.map { .string($0) }) + } + /// Renders this array as a Swift literal string with proper escaping. public var literalString: String { let elements = self.map { element in diff --git a/Sources/SyntaxKit/Collections/ArrayLiteral.swift b/Sources/SyntaxKit/Collections/ArrayLiteral.swift index 4117858..f419dae 100644 --- a/Sources/SyntaxKit/Collections/ArrayLiteral.swift +++ b/Sources/SyntaxKit/Collections/ArrayLiteral.swift @@ -30,7 +30,7 @@ import Foundation /// An array literal value that can be used as a literal. -public struct ArrayLiteral: LiteralValue { +public struct ArrayLiteral: LiteralValue, CodeBlockable { public let elements: [Literal] /// Creates an array with the given elements. @@ -39,6 +39,11 @@ public struct ArrayLiteral: LiteralValue { self.elements = elements } + /// The code block representation of this array literal. + public var codeBlock: CodeBlock { + Literal.array(elements) + } + /// The Swift type name for this array. public var typeName: String { if elements.isEmpty { diff --git a/Sources/SyntaxKit/Collections/Dictionary+LiteralValue.swift b/Sources/SyntaxKit/Collections/Dictionary+LiteralValue.swift index 183492f..48082e1 100644 --- a/Sources/SyntaxKit/Collections/Dictionary+LiteralValue.swift +++ b/Sources/SyntaxKit/Collections/Dictionary+LiteralValue.swift @@ -29,10 +29,15 @@ import Foundation -extension Dictionary: LiteralValue where Key == Int, Value == String { +extension Dictionary: LiteralValue, CodeBlockable where Key == Int, Value == String { /// The Swift type name for a dictionary mapping integers to strings. public var typeName: String { "[Int: String]" } + /// The code block representation of this dictionary. + public var codeBlock: CodeBlock { + Literal.dictionary(self.map { (.integer($0.key), .string($0.value)) }) + } + /// Renders this dictionary as a Swift literal string with proper escaping. public var literalString: String { let elements = self.map { key, value in diff --git a/Sources/SyntaxKit/Collections/DictionaryExpr.swift b/Sources/SyntaxKit/Collections/DictionaryExpr.swift index 0006f55..2b751b7 100644 --- a/Sources/SyntaxKit/Collections/DictionaryExpr.swift +++ b/Sources/SyntaxKit/Collections/DictionaryExpr.swift @@ -30,7 +30,7 @@ import SwiftSyntax /// A dictionary expression that can contain both Literal types and CodeBlock types. -public struct DictionaryExpr: CodeBlock, LiteralValue { +public struct DictionaryExpr: CodeBlock, LiteralValue, CodeBlockable { private let elements: [(DictionaryValue, DictionaryValue)] /// Creates a dictionary expression with the given key-value pairs. @@ -39,6 +39,11 @@ public struct DictionaryExpr: CodeBlock, LiteralValue { self.elements = elements } + /// The code block representation of this dictionary expression. + public var codeBlock: CodeBlock { + self + } + /// The Swift type name for this dictionary. public var typeName: String { if elements.isEmpty { diff --git a/Sources/SyntaxKit/Collections/DictionaryLiteral.swift b/Sources/SyntaxKit/Collections/DictionaryLiteral.swift index 03dc8a8..2843ec2 100644 --- a/Sources/SyntaxKit/Collections/DictionaryLiteral.swift +++ b/Sources/SyntaxKit/Collections/DictionaryLiteral.swift @@ -30,7 +30,7 @@ import Foundation /// A dictionary literal value that can be used as a literal. -public struct DictionaryLiteral: LiteralValue { +public struct DictionaryLiteral: LiteralValue, CodeBlockable { public let elements: [(Literal, Literal)] /// Creates a dictionary with the given key-value pairs. @@ -39,6 +39,11 @@ public struct DictionaryLiteral: LiteralValue { self.elements = elements } + /// The code block representation of this dictionary literal. + public var codeBlock: CodeBlock { + Literal.dictionary(elements) + } + /// The Swift type name for this dictionary. public var typeName: String { if elements.isEmpty { diff --git a/Sources/SyntaxKit/Collections/DictionaryValue.swift b/Sources/SyntaxKit/Collections/DictionaryValue.swift index b8bb8e2..b1ac438 100644 --- a/Sources/SyntaxKit/Collections/DictionaryValue.swift +++ b/Sources/SyntaxKit/Collections/DictionaryValue.swift @@ -48,7 +48,7 @@ extension CodeBlock where Self: DictionaryValue { /// Converts this code block to an expression syntax. /// If the code block is already an expression, returns it directly. /// If it's a token, wraps it in a declaration reference expression. - /// Otherwise, throws a fatal error. + /// Otherwise, creates a default empty expression to prevent crashes. public var exprSyntax: ExprSyntax { if let expr = self.syntax.as(ExprSyntax.self) { return expr @@ -58,7 +58,12 @@ extension CodeBlock where Self: DictionaryValue { return ExprSyntax(DeclReferenceExprSyntax(baseName: .identifier(token.text))) } - fatalError("CodeBlock of type \(type(of: self.syntax)) cannot be represented as ExprSyntax") + // Fallback for unsupported syntax types - create a default expression + // This prevents crashes while still allowing dictionary operations to continue + #warning( + "TODO: Review fallback for unsupported syntax types - consider if this should be an error instead" + ) + return ExprSyntax(DeclReferenceExprSyntax(baseName: .identifier(""))) } } diff --git a/Sources/SyntaxKit/Collections/Tuple.swift b/Sources/SyntaxKit/Collections/Tuple.swift index 622673e..ab71880 100644 --- a/Sources/SyntaxKit/Collections/Tuple.swift +++ b/Sources/SyntaxKit/Collections/Tuple.swift @@ -35,12 +35,10 @@ public struct Tuple: CodeBlock { private var isAsync: Bool = false private var isThrowing: Bool = false - /// Creates a tuple expression comprising the supplied elements. - /// - Parameter content: A ``CodeBlockBuilder`` producing the tuple elements **in order**. - /// Elements may be any `CodeBlock` that can be represented as an expression (see - /// `CodeBlock.expr`). - public init(@CodeBlockBuilderResult _ content: () -> [CodeBlock]) { - self.elements = content() + /// Creates a tuple. + /// - Parameter content: A ``CodeBlockBuilder`` that provides the elements of the tuple. + public init(@CodeBlockBuilderResult _ content: () throws -> [CodeBlock]) rethrows { + self.elements = try content() } /// Creates a tuple pattern for switch cases. @@ -80,10 +78,6 @@ public struct Tuple: CodeBlock { } public var syntax: SyntaxProtocol { - guard !elements.isEmpty else { - fatalError("Tuple must contain at least one element.") - } - let list = TupleExprElementListSyntax( elements.enumerated().map { index, block in let elementExpr: ExprSyntax diff --git a/Sources/SyntaxKit/Collections/TupleAssignment.swift b/Sources/SyntaxKit/Collections/TupleAssignment.swift index 7a96427..e765f21 100644 --- a/Sources/SyntaxKit/Collections/TupleAssignment.swift +++ b/Sources/SyntaxKit/Collections/TupleAssignment.swift @@ -83,9 +83,12 @@ public struct TupleAssignment: CodeBlock { private func generateAsyncSetSyntax() -> SyntaxProtocol { // Generate a single async let tuple destructuring assignment guard let tuple = value as? Tuple, elements.count == tuple.elements.count else { - fatalError( - "asyncSet requires a Tuple value with the same number of elements as the assignment." + // Fallback to regular syntax if conditions aren't met for asyncSet + // This provides a more robust API instead of crashing + #warning( + "TODO: Review fallback for asyncSet conditions - consider if this should be an error instead" ) + return generateRegularSyntax() } // Use helpers from AsyncSet diff --git a/Sources/SyntaxKit/Collections/TupleLiteral.swift b/Sources/SyntaxKit/Collections/TupleLiteral.swift index 4bbba1a..0c3efaf 100644 --- a/Sources/SyntaxKit/Collections/TupleLiteral.swift +++ b/Sources/SyntaxKit/Collections/TupleLiteral.swift @@ -30,7 +30,7 @@ import Foundation /// A tuple literal value that can be used as a literal. -public struct TupleLiteral: LiteralValue { +public struct TupleLiteral: LiteralValue, CodeBlockable { public let elements: [Literal?] /// Creates a tuple with the given elements. @@ -39,6 +39,11 @@ public struct TupleLiteral: LiteralValue { self.elements = elements } + /// The code block representation of this tuple literal. + public var codeBlock: CodeBlock { + Literal.tuple(elements) + } + /// The Swift type name for this tuple. public var typeName: String { let elementTypes = elements.map { element in diff --git a/Sources/SyntaxKit/ControlFlow/Do.swift b/Sources/SyntaxKit/ControlFlow/Do.swift index d9279db..42307f3 100644 --- a/Sources/SyntaxKit/ControlFlow/Do.swift +++ b/Sources/SyntaxKit/ControlFlow/Do.swift @@ -35,6 +35,13 @@ public struct Do: CodeBlock { private let body: [CodeBlock] private let catchClauses: CatchClauseListSyntax + /// Creates a `do` statement. + /// - Parameter body: A ``CodeBlockBuilder`` that provides the body of the `do` block. + public init(@CodeBlockBuilderResult _ body: () throws -> [CodeBlock]) rethrows { + self.body = try body() + self.catchClauses = CatchClauseListSyntax([]) + } + /// Creates a `do-catch` statement. /// - Parameters: /// - body: A ``CodeBlockBuilder`` that provides the body of the do block. diff --git a/Sources/SyntaxKit/ControlFlow/For.swift b/Sources/SyntaxKit/ControlFlow/For.swift index ff3a5d1..0bcc4b5 100644 --- a/Sources/SyntaxKit/ControlFlow/For.swift +++ b/Sources/SyntaxKit/ControlFlow/For.swift @@ -40,45 +40,37 @@ public struct For: CodeBlock { /// - Parameters: /// - pattern: A `CodeBlock` that also conforms to `PatternConvertible` for the loop variable(s). /// - sequence: A `CodeBlock` that produces the sequence to iterate over. - /// - whereClause: An optional `CodeBlockBuilder` that produces the where clause condition. + /// - whereClause: A `CodeBlockBuilder` that produces the where clause condition. /// - then: A ``CodeBlockBuilder`` that provides the body of the loop. public init( _ pattern: any CodeBlock & PatternConvertible, in sequence: CodeBlock, - @CodeBlockBuilderResult where whereClause: () -> [CodeBlock] = { [] }, - @CodeBlockBuilderResult then: () -> [CodeBlock] - ) { + @CodeBlockBuilderResult where whereClause: () throws -> [CodeBlock], + @CodeBlockBuilderResult then: () throws -> [CodeBlock] + ) rethrows { self.pattern = pattern self.sequence = sequence - let whereBlocks = whereClause() + let whereBlocks = try whereClause() self.whereClause = whereBlocks.isEmpty ? nil : whereBlocks[0] - self.body = then() + self.body = try then() } - /// Creates a `for-in` loop statement with a closure-based pattern. + /// Creates a `for-in` loop statement without a where clause. /// - Parameters: - /// - pattern: A `CodeBlockBuilder` that produces the pattern for the loop variable(s). + /// - pattern: A `CodeBlock` that also conforms to `PatternConvertible` for the loop variable(s). /// - sequence: A `CodeBlock` that produces the sequence to iterate over. - /// - whereClause: An optional `CodeBlockBuilder` that produces the where clause condition. /// - then: A ``CodeBlockBuilder`` that provides the body of the loop. public init( - @CodeBlockBuilderResult _ pattern: () -> [CodeBlock], + _ pattern: any CodeBlock & PatternConvertible, in sequence: CodeBlock, - @CodeBlockBuilderResult where whereClause: () -> [CodeBlock] = { [] }, - @CodeBlockBuilderResult then: () -> [CodeBlock] - ) { - let patterns = pattern() - guard patterns.count == 1 else { - fatalError("For requires exactly one pattern CodeBlock") - } - guard let patternBlock = patterns[0] as? (any CodeBlock & PatternConvertible) else { - fatalError("For pattern must implement both CodeBlock and PatternConvertible protocols") - } - self.pattern = patternBlock - self.sequence = sequence - let whereBlocks = whereClause() - self.whereClause = whereBlocks.isEmpty ? nil : whereBlocks[0] - self.body = then() + @CodeBlockBuilderResult then: () throws -> [CodeBlock] + ) rethrows { + try self.init( + pattern, + in: sequence, + where: [CodeBlock].init, + then: then + ) } public var syntax: SyntaxProtocol { diff --git a/Sources/SyntaxKit/ControlFlow/Guard.swift b/Sources/SyntaxKit/ControlFlow/Guard.swift index b5e9bf5..c64cbe9 100644 --- a/Sources/SyntaxKit/ControlFlow/Guard.swift +++ b/Sources/SyntaxKit/ControlFlow/Guard.swift @@ -36,27 +36,52 @@ public struct Guard: CodeBlock { /// Creates a `guard` statement. /// - Parameters: - /// - condition: A builder that returns one or more ``CodeBlock`` items representing the guard - /// conditions. - /// - elseBody: Builder that produces the statements inside the `else` block. + /// - condition: A ``CodeBlockBuilder`` that provides the condition expression. + /// - else: A ``CodeBlockBuilder`` that provides the body when the condition is false. public init( - @CodeBlockBuilderResult _ condition: () -> [CodeBlock], - @CodeBlockBuilderResult else elseBody: () -> [CodeBlock] - ) { - let allConditions = condition() - guard !allConditions.isEmpty else { - fatalError("Guard requires at least one condition CodeBlock") + @CodeBlockBuilderResult _ condition: () throws -> [CodeBlock], + @CodeBlockBuilderResult else elseBody: () throws -> [CodeBlock] + ) rethrows { + let allConditions = try condition() + if allConditions.isEmpty { + // Use true as default condition when no conditions are provided + self.conditions = [Literal.boolean(true)] + } else { + self.conditions = allConditions } - self.conditions = allConditions - self.elseBody = elseBody() + self.elseBody = try elseBody() + } + + /// Creates a `guard` statement without a condition (uses true as default). + /// - Parameters: + /// - else: A ``CodeBlockBuilder`` that provides the body when the condition is false. + public init( + @CodeBlockBuilderResult else elseBody: () throws -> [CodeBlock] + ) rethrows { + try self.init( + [CodeBlock].init, + else: elseBody + ) + } + + /// Creates a `guard` statement with a string condition. + /// - Parameters: + /// - condition: The condition as a string. + /// - else: A ``CodeBlockBuilder`` that provides the body when the condition is false. + public init( + _ condition: String, + @CodeBlockBuilderResult else elseBody: () throws -> [CodeBlock] + ) rethrows { + self.conditions = [VariableExp(condition)] + self.elseBody = try elseBody() } /// Convenience initializer that accepts a single condition ``CodeBlock``. public init( _ condition: CodeBlock, - @CodeBlockBuilderResult else elseBody: () -> [CodeBlock] - ) { - self.init({ condition }, else: elseBody) + @CodeBlockBuilderResult else elseBody: () throws -> [CodeBlock] + ) rethrows { + try self.init({ condition }, else: elseBody) } public var syntax: SyntaxProtocol { diff --git a/Sources/SyntaxKit/ControlFlow/If+ElseBody.swift b/Sources/SyntaxKit/ControlFlow/If+ElseBody.swift index 7abd91c..223cd5b 100644 --- a/Sources/SyntaxKit/ControlFlow/If+ElseBody.swift +++ b/Sources/SyntaxKit/ControlFlow/If+ElseBody.swift @@ -84,6 +84,8 @@ extension If { return IfExprSyntax.ElseBody(nestedIf) } else { // Fallback to empty code block + #warning( + "TODO: Review fallback to empty code block - consider if this should be an error instead") return IfExprSyntax.ElseBody( CodeBlockSyntax( leftBrace: .leftBraceToken(leadingTrivia: .space, trailingTrivia: .newline), diff --git a/Sources/SyntaxKit/ControlFlow/If.swift b/Sources/SyntaxKit/ControlFlow/If.swift index 65bcca7..d5d87a8 100644 --- a/Sources/SyntaxKit/ControlFlow/If.swift +++ b/Sources/SyntaxKit/ControlFlow/If.swift @@ -35,6 +35,36 @@ public struct If: CodeBlock { internal let body: [CodeBlock] internal let elseBody: [CodeBlock]? + /// Convenience initializer that keeps the previous API: pass the condition directly. + public init( + @CodeBlockBuilderResult then: () throws -> [CodeBlock] + ) rethrows { + try self.init({ Literal.boolean(true) }, then: then) + } + + /// Convenience initializer that keeps the previous API: pass the condition directly. + public init( + @CodeBlockBuilderResult then: () throws -> [CodeBlock], + @CodeBlockBuilderResult else elseBody: () throws -> [CodeBlock] + ) rethrows { + try self.init({ Literal.boolean(true) }, then: then, else: elseBody) + } + + /// Creates an `if` statement with optional `else`. + /// - Parameters: + /// - condition: A single `CodeBlock` produced by the builder that describes the `if` condition. + /// - then: Builder that produces the body for the `if` branch. + public init( + @CodeBlockBuilderResult _ condition: () -> [CodeBlock], + @CodeBlockBuilderResult then: () -> [CodeBlock] + ) { + self.init( + condition, + then: then, + else: [CodeBlock].init + ) + } + /// Creates an `if` statement with optional `else`. /// - Parameters: /// - condition: A single `CodeBlock` produced by the builder that describes the `if` condition. @@ -45,13 +75,15 @@ public struct If: CodeBlock { public init( @CodeBlockBuilderResult _ condition: () -> [CodeBlock], @CodeBlockBuilderResult then: () -> [CodeBlock], - @CodeBlockBuilderResult else elseBody: () -> [CodeBlock] = { [] } + @CodeBlockBuilderResult else elseBody: () -> [CodeBlock] ) { let allConditions = condition() - guard !allConditions.isEmpty else { - fatalError("If requires at least one condition CodeBlock") + if allConditions.isEmpty { + // Use true as default condition when no conditions are provided + self.conditions = [Literal.boolean(true)] + } else { + self.conditions = allConditions } - self.conditions = allConditions self.body = then() let generatedElse = elseBody() self.elseBody = generatedElse.isEmpty ? nil : generatedElse @@ -60,10 +92,86 @@ public struct If: CodeBlock { /// Convenience initializer that keeps the previous API: pass the condition directly. public init( _ condition: CodeBlock, - @CodeBlockBuilderResult then: () -> [CodeBlock], - @CodeBlockBuilderResult else elseBody: () -> [CodeBlock] = { [] } - ) { - self.init({ condition }, then: then, else: elseBody) + @CodeBlockBuilderResult then: () throws -> [CodeBlock] + ) rethrows { + try self.init({ condition }, then: then) + } + + /// Convenience initializer that keeps the previous API: pass the condition directly. + public init( + _ condition: CodeBlock, + @CodeBlockBuilderResult then: () throws -> [CodeBlock], + @CodeBlockBuilderResult else elseBody: () throws -> [CodeBlock] + ) rethrows { + try self.init({ condition }, then: then, else: elseBody) + } + + /// Creates an `if` statement. + /// - Parameters: + /// - condition: A ``CodeBlockBuilder`` that provides the condition expression. + /// - then: A ``CodeBlockBuilder`` that provides the body when the condition is true. + public init( + @CodeBlockBuilderResult _ condition: () throws -> [CodeBlock], + @CodeBlockBuilderResult then: () throws -> [CodeBlock] + ) rethrows { + try self.init( + condition, + then: then, + else: [CodeBlock].init + ) + } + + /// Creates an `if` statement. + /// - Parameters: + /// - condition: A ``CodeBlockBuilder`` that provides the condition expression. + /// - then: A ``CodeBlockBuilder`` that provides the body when the condition is true. + /// - else: A ``CodeBlockBuilder`` that provides the body when the condition is false. + public init( + @CodeBlockBuilderResult _ condition: () throws -> [CodeBlock], + @CodeBlockBuilderResult then: () throws -> [CodeBlock], + @CodeBlockBuilderResult else elseBody: () throws -> [CodeBlock] + ) rethrows { + let allConditions = try condition() + if allConditions.isEmpty { + // Use true as default condition when no conditions are provided + self.conditions = [Literal.boolean(true)] + } else { + self.conditions = allConditions + } + self.body = try then() + let generatedElse = try elseBody() + self.elseBody = generatedElse.isEmpty ? nil : generatedElse + } + + /// Creates an `if` statement with a string condition. + /// - Parameters: + /// - condition: The condition as a string. + /// - then: A ``CodeBlockBuilder`` that provides the body when the condition is true. + public init( + _ condition: String, + @CodeBlockBuilderResult then: () throws -> [CodeBlock] + ) rethrows { + try self.init( + condition, + then: then, + else: [CodeBlock].init + ) + } + + /// Creates an `if` statement with a string condition. + /// - Parameters: + /// - condition: The condition as a string. + /// - then: A ``CodeBlockBuilder`` that provides the body when the condition is true. + /// - else: A ``CodeBlockBuilder`` that provides the body when the condition is false. + public init( + _ condition: String, + @CodeBlockBuilderResult then: () throws -> [CodeBlock], + @CodeBlockBuilderResult else elseBody: () throws -> [CodeBlock] + ) rethrows { + self.conditions = [VariableExp(condition)] + self.body = try then() + let generatedElse = try elseBody() + self.elseBody = generatedElse.isEmpty ? nil : generatedElse } public var syntax: SyntaxProtocol { diff --git a/Sources/SyntaxKit/ControlFlow/Switch.swift b/Sources/SyntaxKit/ControlFlow/Switch.swift index 1def183..79d9ad3 100644 --- a/Sources/SyntaxKit/ControlFlow/Switch.swift +++ b/Sources/SyntaxKit/ControlFlow/Switch.swift @@ -38,18 +38,22 @@ public struct Switch: CodeBlock { /// - Parameters: /// - expression: The expression to switch on. /// - content: A ``CodeBlockBuilder`` that provides the cases for the switch. - public init(_ expression: CodeBlock, @CodeBlockBuilderResult _ content: () -> [CodeBlock]) { + public init(_ expression: CodeBlock, @CodeBlockBuilderResult _ content: () throws -> [CodeBlock]) + rethrows + { self.expression = expression - self.cases = content() + self.cases = try content() } /// Convenience initializer that accepts a string expression. /// - Parameters: /// - expression: The string expression to switch on. /// - content: A ``CodeBlockBuilder`` that provides the cases for the switch. - public init(_ expression: String, @CodeBlockBuilderResult _ content: () -> [CodeBlock]) { + public init(_ expression: String, @CodeBlockBuilderResult _ content: () throws -> [CodeBlock]) + rethrows + { self.expression = VariableExp(expression) - self.cases = content() + self.cases = try content() } public var syntax: SyntaxProtocol { diff --git a/Sources/SyntaxKit/ControlFlow/SwitchCase.swift b/Sources/SyntaxKit/ControlFlow/SwitchCase.swift index 5e10279..4999e8c 100644 --- a/Sources/SyntaxKit/ControlFlow/SwitchCase.swift +++ b/Sources/SyntaxKit/ControlFlow/SwitchCase.swift @@ -39,9 +39,11 @@ public struct SwitchCase: CodeBlock { /// - patterns: The patterns to match for the case. Can be `PatternConvertible`, /// `CodeBlock`, or `SwitchLet` for value binding. /// - content: A ``CodeBlockBuilder`` that provides the body of the case. - public init(_ patterns: Any..., @CodeBlockBuilderResult content: () -> [CodeBlock]) { + public init(_ patterns: Any..., @CodeBlockBuilderResult content: () throws -> [CodeBlock]) + rethrows + { self.patterns = patterns - self.body = content() + self.body = try content() } /// Creates a `case` for a `switch` statement with a builder closure for the conditional. @@ -49,11 +51,11 @@ public struct SwitchCase: CodeBlock { /// - conditional: A ``CodeBlockBuilder`` that provides the conditional patterns for the case. /// - content: A ``CodeBlockBuilder`` that provides the body of the case. public init( - @CodeBlockBuilderResult conditional: () -> [Any], - @CodeBlockBuilderResult content: () -> [CodeBlock] - ) { - self.patterns = conditional() - self.body = content() + @CodeBlockBuilderResult conditional: () throws -> [CodeBlock], + @CodeBlockBuilderResult content: () throws -> [CodeBlock] + ) rethrows { + self.patterns = try conditional() + self.body = try content() } public var switchCaseSyntax: SwitchCaseSyntax { diff --git a/Sources/SyntaxKit/ControlFlow/While.swift b/Sources/SyntaxKit/ControlFlow/While.swift index 44080be..4bd03b6 100644 --- a/Sources/SyntaxKit/ControlFlow/While.swift +++ b/Sources/SyntaxKit/ControlFlow/While.swift @@ -31,41 +31,87 @@ import SwiftSyntax /// A `while` loop statement. public struct While: CodeBlock { - private let condition: CodeBlock + public enum Kind { + case `while` + case repeatWhile + } + + private let condition: any ExprCodeBlock private let body: [CodeBlock] + private let kind: Kind - /// Creates a `while` loop statement. + /// Creates a `while` loop statement with an expression condition. /// - Parameters: - /// - condition: A `CodeBlockBuilder` that produces the condition expression. + /// - condition: The condition expression that conforms to ExprCodeBlock. + /// - kind: The kind of loop (default is `.while`). /// - then: A ``CodeBlockBuilder`` that provides the body of the loop. public init( - @CodeBlockBuilderResult _ condition: () -> [CodeBlock], - @CodeBlockBuilderResult then: () -> [CodeBlock] - ) { - let conditions = condition() - guard conditions.count == 1 else { - fatalError("While requires exactly one condition CodeBlock") - } - self.condition = conditions[0] - self.body = then() + _ condition: any ExprCodeBlock, + kind: Kind = .while, + @CodeBlockBuilderResult then: () throws -> [CodeBlock] + ) rethrows { + self.condition = condition + self.body = try then() + self.kind = kind } - /// Convenience initializer that accepts a single condition directly. + /// Creates a `while` loop statement with a builder closure for the condition. /// - Parameters: - /// - condition: The condition expression. + /// - condition: A `CodeBlockBuilder` that produces exactly one condition expression. + /// - kind: The kind of loop (default is `.while`). /// - then: A ``CodeBlockBuilder`` that provides the body of the loop. public init( - _ condition: CodeBlock, - @CodeBlockBuilderResult then: () -> [CodeBlock] - ) { - self.init({ condition }, then: then) + kind: Kind = .while, + @ExprCodeBlockBuilder _ condition: () throws -> any ExprCodeBlock, + @CodeBlockBuilderResult then: () throws -> [CodeBlock] + ) rethrows { + self.condition = try condition() + self.body = try then() + self.kind = kind + } + + /// Creates a `while` loop. + /// - Parameters: + /// - condition: A ``CodeBlockBuilder`` that provides the condition expression. + /// - kind: The kind of loop (default is `.while`). + /// - then: A ``CodeBlockBuilder`` that provides the body of the loop. + @available( + *, deprecated, + message: "Use While(kind:condition:) with ExprCodeBlockBuilder instead for better type safety" + ) + public init( + kind: Kind = .while, + @CodeBlockBuilderResult _ condition: () throws -> [CodeBlock], + @CodeBlockBuilderResult then: () throws -> [CodeBlock] + ) rethrows { + let conditionBlocks = try condition() + let firstCondition = conditionBlocks.first as? any ExprCodeBlock ?? Literal.boolean(true) + self.condition = firstCondition + self.body = try then() + self.kind = kind + } + + /// Creates a `while` loop with a string condition. + /// - Parameters: + /// - condition: The condition as a string. + /// - kind: The kind of loop (default is `.while`). + /// - then: A ``CodeBlockBuilder`` that provides the body of the loop. + @available( + *, deprecated, + message: "Use While(VariableExp(condition), kind:then:) instead for better type safety" + ) + public init( + _ condition: String, + kind: Kind = .while, + @CodeBlockBuilderResult then: () throws -> [CodeBlock] + ) rethrows { + self.condition = VariableExp(condition) + self.body = try then() + self.kind = kind } public var syntax: SyntaxProtocol { - let conditionExpr = ExprSyntax( - fromProtocol: condition.syntax.as(ExprSyntax.self) - ?? DeclReferenceExprSyntax(baseName: .identifier("")) - ) + let conditionExpr = condition.exprSyntax let bodyBlock = CodeBlockSyntax( leftBrace: .leftBraceToken(leadingTrivia: .space, trailingTrivia: .newline), @@ -85,18 +131,30 @@ public struct While: CodeBlock { rightBrace: .rightBraceToken(leadingTrivia: .newline) ) - return StmtSyntax( - WhileStmtSyntax( - whileKeyword: .keyword(.while, trailingTrivia: .space), - conditions: ConditionElementListSyntax( - [ - ConditionElementSyntax( - condition: .expression(conditionExpr) - ) - ] - ), - body: bodyBlock + switch kind { + case .repeatWhile: + return StmtSyntax( + RepeatWhileStmtSyntax( + repeatKeyword: .keyword(.repeat, trailingTrivia: .space), + body: bodyBlock, + whileKeyword: .keyword(.while, trailingTrivia: .space), + condition: conditionExpr + ) ) - ) + case .while: + return StmtSyntax( + WhileStmtSyntax( + whileKeyword: .keyword(.while, trailingTrivia: .space), + conditions: ConditionElementListSyntax( + [ + ConditionElementSyntax( + condition: .expression(conditionExpr) + ) + ] + ), + body: bodyBlock + ) + ) + } } } diff --git a/Sources/SyntaxKit/Core/AccessModifier.swift b/Sources/SyntaxKit/Core/AccessModifier.swift new file mode 100644 index 0000000..edd3631 --- /dev/null +++ b/Sources/SyntaxKit/Core/AccessModifier.swift @@ -0,0 +1,63 @@ +// +// AccessModifier.swift +// SyntaxKit +// +// Created by Leo Dion. +// Copyright © 2025 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 SwiftSyntax + +/// Represents Swift access modifiers. +public enum AccessModifier: CaseIterable { + case `public` + case `private` + case `internal` + case `fileprivate` + case `open` + + /// Returns the corresponding SwiftSyntax Keyword for this access modifier. + public var keyword: Keyword { + switch self { + case .public: + return .public + case .private: + return .private + case .internal: + return .internal + case .fileprivate: + return .fileprivate + case .open: + return .open + } + } +} + +extension Keyword { + /// Creates a Keyword from an AccessModifier. + /// - Parameter accessModifier: The access modifier to convert. + public init(_ accessModifier: AccessModifier) { + self = accessModifier.keyword + } +} diff --git a/Sources/SyntaxKit/Core/CaptureReferenceType.swift b/Sources/SyntaxKit/Core/CaptureReferenceType.swift new file mode 100644 index 0000000..582eba6 --- /dev/null +++ b/Sources/SyntaxKit/Core/CaptureReferenceType.swift @@ -0,0 +1,54 @@ +// +// CaptureReferenceType.swift +// SyntaxKit +// +// Created by Leo Dion. +// Copyright © 2025 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 SwiftSyntax + +/// Represents Swift capture reference types for closures. +public enum CaptureReferenceType: CaseIterable { + case weak + case unowned + + /// Returns the corresponding SwiftSyntax Keyword for this capture reference type. + public var keyword: Keyword { + switch self { + case .weak: + return .weak + case .unowned: + return .unowned + } + } +} + +extension Keyword { + /// Creates a Keyword from a CaptureReferenceType. + /// - Parameter captureReferenceType: The capture reference type to convert. + public init(_ captureReferenceType: CaptureReferenceType) { + self = captureReferenceType.keyword + } +} diff --git a/Sources/SyntaxKit/Core/ExprCodeBlockBuilder.swift b/Sources/SyntaxKit/Core/ExprCodeBlockBuilder.swift new file mode 100644 index 0000000..317a0d8 --- /dev/null +++ b/Sources/SyntaxKit/Core/ExprCodeBlockBuilder.swift @@ -0,0 +1,63 @@ +// +// ExprCodeBlockBuilder.swift +// SyntaxKit +// +// Created by Leo Dion. +// Copyright © 2025 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 SwiftSyntax + +/// A result builder that produces exactly one `ExprCodeBlock`. +/// This ensures compile-time type safety for expression-based constructs. +@resultBuilder +public enum ExprCodeBlockBuilder { + /// Builds a single expression code block from the provided expression. + /// - Parameter expression: The expression code block to build. + /// - Returns: The expression code block unchanged. + public static func buildBlock(_ expression: any ExprCodeBlock) -> any ExprCodeBlock { + expression + } + + /// Builds an expression code block from a single expression. + /// - Parameter expression: The expression code block to build. + /// - Returns: The expression code block unchanged. + public static func buildExpression(_ expression: any ExprCodeBlock) -> any ExprCodeBlock { + expression + } + + /// Builds an expression code block from the first branch of a conditional. + /// - Parameter first: The expression code block from the first branch. + /// - Returns: The expression code block from the first branch. + public static func buildEither(first: any ExprCodeBlock) -> any ExprCodeBlock { + first + } + + /// Builds an expression code block from the second branch of a conditional. + /// - Parameter second: The expression code block from the second branch. + /// - Returns: The expression code block from the second branch. + public static func buildEither(second: any ExprCodeBlock) -> any ExprCodeBlock { + second + } +} diff --git a/Sources/SyntaxKit/Core/PatternConvertibleBuilder.swift b/Sources/SyntaxKit/Core/PatternConvertibleBuilder.swift new file mode 100644 index 0000000..283a065 --- /dev/null +++ b/Sources/SyntaxKit/Core/PatternConvertibleBuilder.swift @@ -0,0 +1,71 @@ +// +// PatternConvertibleBuilder.swift +// SyntaxKit +// +// Created by Leo Dion. +// Copyright © 2025 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 SwiftSyntax + +/// A result builder that produces exactly one `CodeBlock & PatternConvertible`. +/// This ensures compile-time type safety for pattern-based constructs. +@resultBuilder +public enum PatternConvertibleBuilder { + /// Builds a single pattern convertible code block from the provided pattern. + /// - Parameter pattern: The pattern convertible code block to build. + /// - Returns: The pattern convertible code block unchanged. + public static func buildBlock(_ pattern: any CodeBlock & PatternConvertible) -> any CodeBlock + & PatternConvertible + { + pattern + } + + /// Builds a pattern convertible code block from a single pattern. + /// - Parameter pattern: The pattern convertible code block to build. + /// - Returns: The pattern convertible code block unchanged. + public static func buildExpression(_ pattern: any CodeBlock & PatternConvertible) -> any CodeBlock + & PatternConvertible + { + pattern + } + + /// Builds a pattern convertible code block from the first branch of a conditional. + /// - Parameter first: The pattern convertible code block from the first branch. + /// - Returns: The pattern convertible code block from the first branch. + public static func buildEither(first: any CodeBlock & PatternConvertible) -> any CodeBlock + & PatternConvertible + { + first + } + + /// Builds a pattern convertible code block from the second branch of a conditional. + /// - Parameter second: The pattern convertible code block from the second branch. + /// - Returns: The pattern convertible code block from the second branch. + public static func buildEither(second: any CodeBlock & PatternConvertible) -> any CodeBlock + & PatternConvertible + { + second + } +} diff --git a/Sources/SyntaxKit/Declarations/Class.swift b/Sources/SyntaxKit/Declarations/Class.swift index 56446f3..f38ae5e 100644 --- a/Sources/SyntaxKit/Declarations/Class.swift +++ b/Sources/SyntaxKit/Declarations/Class.swift @@ -38,13 +38,14 @@ public struct Class: CodeBlock { private var isFinal: Bool = false private var attributes: [AttributeInfo] = [] - /// Creates a `class` declaration. + /// Creates a class declaration. /// - Parameters: /// - name: The name of the class. - /// - content: A ``CodeBlockBuilder`` that provides the members of the class. - public init(_ name: String, @CodeBlockBuilderResult _ content: () -> [CodeBlock]) { + /// - content: A ``CodeBlockBuilder`` that provides the body of the class. + public init(_ name: String, @CodeBlockBuilderResult _ content: () throws -> [CodeBlock]) rethrows + { self.name = name - self.members = content() + self.members = try content() } /// Sets the generic parameters for the class. diff --git a/Sources/SyntaxKit/Declarations/Enum.swift b/Sources/SyntaxKit/Declarations/Enum.swift index 1945142..a8e82c8 100644 --- a/Sources/SyntaxKit/Declarations/Enum.swift +++ b/Sources/SyntaxKit/Declarations/Enum.swift @@ -36,13 +36,14 @@ public struct Enum: CodeBlock { private var inheritance: [String] = [] private var attributes: [AttributeInfo] = [] - /// Creates an `enum` declaration. + /// Creates an enum declaration. /// - Parameters: /// - name: The name of the enum. - /// - content: A ``CodeBlockBuilder`` that provides the members of the enum. - public init(_ name: String, @CodeBlockBuilderResult _ content: () -> [CodeBlock]) { + /// - content: A ``CodeBlockBuilder`` that provides the body of the enum. + public init(_ name: String, @CodeBlockBuilderResult _ content: () throws -> [CodeBlock]) rethrows + { self.name = name - self.members = content() + self.members = try content() } /// Sets the inheritance for the enum. diff --git a/Sources/SyntaxKit/Declarations/Extension.swift b/Sources/SyntaxKit/Declarations/Extension.swift index af53db4..1ff7deb 100644 --- a/Sources/SyntaxKit/Declarations/Extension.swift +++ b/Sources/SyntaxKit/Declarations/Extension.swift @@ -39,10 +39,12 @@ public struct Extension: CodeBlock { /// Creates an extension declaration. /// - Parameters: /// - extendedType: The type being extended. - /// - content: A ``CodeBlockBuilder`` that provides the members of the extension. - public init(_ extendedType: String, @CodeBlockBuilderResult _ content: () -> [CodeBlock]) { + /// - content: A ``CodeBlockBuilder`` that provides the body of the extension. + public init(_ extendedType: String, @CodeBlockBuilderResult _ content: () throws -> [CodeBlock]) + rethrows + { self.extendedType = extendedType - self.members = content() + self.members = try content() } /// Sets one or more inherited protocols. diff --git a/Sources/SyntaxKit/Declarations/Import.swift b/Sources/SyntaxKit/Declarations/Import.swift index 191a037..37334d1 100644 --- a/Sources/SyntaxKit/Declarations/Import.swift +++ b/Sources/SyntaxKit/Declarations/Import.swift @@ -32,7 +32,7 @@ import SwiftSyntax /// A Swift `import` declaration. public struct Import: CodeBlock { private let moduleName: String - private var accessModifier: String? + private var accessModifier: AccessModifier? private var attributes: [AttributeInfo] = [] /// Creates an `import` declaration. @@ -42,9 +42,9 @@ public struct Import: CodeBlock { } /// Sets the access modifier for the import declaration. - /// - Parameter access: The access modifier (e.g., "public", "private"). + /// - Parameter access: The access modifier. /// - Returns: A copy of the import with the access modifier set. - public func access(_ access: String) -> Self { + public func access(_ access: AccessModifier) -> Self { var copy = self copy.accessModifier = access return copy @@ -65,21 +65,8 @@ public struct Import: CodeBlock { // Build access modifier var modifiers: DeclModifierListSyntax = [] if let access = accessModifier { - let keyword: Keyword - switch access { - case "public": - keyword = .public - case "private": - keyword = .private - case "internal": - keyword = .internal - case "fileprivate": - keyword = .fileprivate - default: - keyword = .public // fallback - } modifiers = DeclModifierListSyntax([ - DeclModifierSyntax(name: .keyword(keyword, trailingTrivia: .space)) + DeclModifierSyntax(name: .keyword(access.keyword, trailingTrivia: .space)) ]) } diff --git a/Sources/SyntaxKit/Declarations/Init.swift b/Sources/SyntaxKit/Declarations/Init.swift index b9b760d..400d38a 100644 --- a/Sources/SyntaxKit/Declarations/Init.swift +++ b/Sources/SyntaxKit/Declarations/Init.swift @@ -30,7 +30,7 @@ import SwiftSyntax /// An initializer expression. -public struct Init: CodeBlock, ExprCodeBlock, LiteralValue { +public struct Init: CodeBlock, ExprCodeBlock, LiteralValue, CodeBlockable { private let type: String private let parameters: [ParameterExp] @@ -45,9 +45,16 @@ public struct Init: CodeBlock, ExprCodeBlock, LiteralValue { /// - Parameters: /// - type: The type to initialize. /// - params: A ``ParameterExpBuilder`` that provides the parameters for the initializer. - public init(_ type: String, @ParameterExpBuilderResult _ params: () -> [ParameterExp]) { + public init(_ type: String, @ParameterExpBuilderResult _ params: () throws -> [ParameterExp]) + rethrows + { self.type = type - self.parameters = params() + self.parameters = try params() + } + + /// The code block representation of this initializer expression. + public var codeBlock: CodeBlock { + self } public var exprSyntax: ExprSyntax { diff --git a/Sources/SyntaxKit/Declarations/Protocol.swift b/Sources/SyntaxKit/Declarations/Protocol.swift index 018af73..83d2db3 100644 --- a/Sources/SyntaxKit/Declarations/Protocol.swift +++ b/Sources/SyntaxKit/Declarations/Protocol.swift @@ -36,13 +36,14 @@ public struct Protocol: CodeBlock { private var inheritance: [String] = [] private var attributes: [AttributeInfo] = [] - /// Creates a `protocol` declaration. + /// Creates a protocol declaration. /// - Parameters: /// - name: The name of the protocol. - /// - content: A ``CodeBlockBuilder`` that provides the members of the protocol. - public init(_ name: String, @CodeBlockBuilderResult _ content: () -> [CodeBlock]) { + /// - content: A ``CodeBlockBuilder`` that provides the body of the protocol. + public init(_ name: String, @CodeBlockBuilderResult _ content: () throws -> [CodeBlock]) rethrows + { self.name = name - self.members = content() + self.members = try content() } /// Sets one or more inherited protocols. diff --git a/Sources/SyntaxKit/Declarations/Struct.swift b/Sources/SyntaxKit/Declarations/Struct.swift index 4b2dc56..82e9135 100644 --- a/Sources/SyntaxKit/Declarations/Struct.swift +++ b/Sources/SyntaxKit/Declarations/Struct.swift @@ -36,15 +36,16 @@ public struct Struct: CodeBlock { private var genericParameter: String? private var inheritance: [String] = [] private var attributes: [AttributeInfo] = [] - private var accessModifier: String? + private var accessModifier: AccessModifier? - /// Creates a `struct` declaration. + /// Creates a struct declaration. /// - Parameters: /// - name: The name of the struct. - /// - content: A ``CodeBlockBuilder`` that provides the members of the struct. - public init(_ name: String, @CodeBlockBuilderResult _ content: () -> [CodeBlock]) { + /// - content: A ``CodeBlockBuilder`` that provides the body of the struct. + public init(_ name: String, @CodeBlockBuilderResult _ content: () throws -> [CodeBlock]) rethrows + { self.name = name - self.members = content() + self.members = try content() } /// Sets the generic parameter for the struct. @@ -66,9 +67,9 @@ public struct Struct: CodeBlock { } /// Sets the access modifier for the struct declaration. - /// - Parameter access: The access modifier (e.g., "public", "private"). + /// - Parameter access: The access modifier. /// - Returns: A copy of the struct with the access modifier set. - public func access(_ access: String) -> Self { + public func access(_ access: AccessModifier) -> Self { var copy = self copy.accessModifier = access return copy @@ -142,21 +143,8 @@ public struct Struct: CodeBlock { // Build access modifier var modifiers: DeclModifierListSyntax = [] if let access = accessModifier { - let keyword: Keyword - switch access { - case "public": - keyword = .public - case "private": - keyword = .private - case "internal": - keyword = .internal - case "fileprivate": - keyword = .fileprivate - default: - keyword = .public // fallback - } modifiers = DeclModifierListSyntax([ - DeclModifierSyntax(name: .keyword(keyword, trailingTrivia: .space)) + DeclModifierSyntax(name: .keyword(access.keyword, trailingTrivia: .space)) ]) } diff --git a/Sources/SyntaxKit/ErrorHandling/Catch.swift b/Sources/SyntaxKit/ErrorHandling/Catch.swift index edad97f..4262002 100644 --- a/Sources/SyntaxKit/ErrorHandling/Catch.swift +++ b/Sources/SyntaxKit/ErrorHandling/Catch.swift @@ -65,6 +65,13 @@ public struct Catch: CodeBlock { Catch(enumCase, content) } + /// Creates a catch clause. + /// - Parameter content: A ``CodeBlockBuilder`` that provides the body of the catch clause. + public init(@CodeBlockBuilderResult _ content: () throws -> [CodeBlock]) rethrows { + self.pattern = nil + self.body = try content() + } + public var catchClauseSyntax: CatchClauseSyntax { // Build catch items (patterns) var catchItems: CatchItemListSyntax? diff --git a/Sources/SyntaxKit/Expressions/Assignment.swift b/Sources/SyntaxKit/Expressions/Assignment.swift index b3e26ac..7dda967 100644 --- a/Sources/SyntaxKit/Expressions/Assignment.swift +++ b/Sources/SyntaxKit/Expressions/Assignment.swift @@ -34,13 +34,16 @@ public struct Assignment: CodeBlock { private let target: String private let valueExpr: ExprSyntax + /// Creates an assignment where the value is an expression. + public init(_ target: String, _ value: any ExprCodeBlock) { + self.target = target + self.valueExpr = value.exprSyntax + } + /// Creates an assignment where the value is a literal. public init(_ target: String, _ literal: Literal) { self.target = target - guard let expr = literal.syntax.as(ExprSyntax.self) else { - fatalError("Literal.syntax did not produce ExprSyntax") - } - self.valueExpr = expr + self.valueExpr = literal.exprSyntax } /// Creates an assignment with an integer literal value. diff --git a/Sources/SyntaxKit/Expressions/Call.swift b/Sources/SyntaxKit/Expressions/Call.swift index a72843f..8154697 100644 --- a/Sources/SyntaxKit/Expressions/Call.swift +++ b/Sources/SyntaxKit/Expressions/Call.swift @@ -47,9 +47,11 @@ public struct Call: CodeBlock { /// - Parameters: /// - functionName: The name of the function to call. /// - params: A ``ParameterExpBuilder`` that provides the parameters for the function call. - public init(_ functionName: String, @ParameterExpBuilderResult _ params: () -> [ParameterExp]) { + public init( + _ functionName: String, @ParameterExpBuilderResult _ params: () throws -> [ParameterExp] + ) rethrows { self.functionName = functionName - self.parameters = params() + self.parameters = try params() } /// Marks this function call as throwing. @@ -71,7 +73,7 @@ public struct Call: CodeBlock { public var syntax: SyntaxProtocol { let function = TokenSyntax.identifier(functionName) let args = LabeledExprListSyntax( - parameters.enumerated().map { index, param in + parameters.enumerated().compactMap { index, param in let expr = param.syntax if let labeled = expr as? LabeledExprSyntax { var element = labeled @@ -92,7 +94,7 @@ public struct Call: CodeBlock { : nil ) } else { - fatalError("ParameterExp.syntax must return LabeledExprSyntax or ExprSyntax") + return nil } } ) diff --git a/Sources/SyntaxKit/Expressions/Closure+Capture.swift b/Sources/SyntaxKit/Expressions/Closure+Capture.swift index 87fd215..864ec10 100644 --- a/Sources/SyntaxKit/Expressions/Closure+Capture.swift +++ b/Sources/SyntaxKit/Expressions/Closure+Capture.swift @@ -43,15 +43,7 @@ private struct CaptureInfo { } private init(fromReference refExp: ReferenceExp) { - let keyword: Keyword - switch refExp.captureReferenceType.lowercased() { - case "weak": - keyword = .weak - case "unowned": - keyword = .unowned - default: - keyword = .weak // fallback to weak - } + let keyword = refExp.captureReferenceType.keyword self.specifier = ClosureCaptureSpecifierSyntax( specifier: .keyword(keyword, trailingTrivia: .space) @@ -61,6 +53,9 @@ private struct CaptureInfo { self.name = .identifier(varExp.name) } else { self.name = .identifier("self") // fallback + #warning( + "TODO: Review fallback for non-VariableExp capture expression - consider if this should be an error instead" + ) } } @@ -71,6 +66,9 @@ private struct CaptureInfo { self.name = .identifier(varExp.name) } else { self.name = .identifier("self") // fallback + #warning( + "TODO: Review fallback for non-VariableExp parameter value - consider if this should be an error instead" + ) } } } diff --git a/Sources/SyntaxKit/Expressions/Closure.swift b/Sources/SyntaxKit/Expressions/Closure.swift index 59be944..e5b3ee8 100644 --- a/Sources/SyntaxKit/Expressions/Closure.swift +++ b/Sources/SyntaxKit/Expressions/Closure.swift @@ -41,16 +41,133 @@ public struct Closure: CodeBlock { !parameters.isEmpty || returnType != nil || !capture.isEmpty || !attributes.isEmpty } + /// Creates a closure with all parameters. + /// - Parameters: + /// - capture: A ``ParameterExpBuilder`` that provides the capture list. + /// - parameters: A ``ClosureParameterBuilder`` that provides the closure parameters. + /// - returnType: The return type of the closure. + /// - body: A ``CodeBlockBuilder`` that provides the body of the closure. public init( - @ParameterExpBuilderResult capture: () -> [ParameterExp] = { [] }, - @ClosureParameterBuilderResult parameters: () -> [ClosureParameter] = { [] }, - returns returnType: String? = nil, - @CodeBlockBuilderResult body: () -> [CodeBlock] - ) { + @ParameterExpBuilderResult capture: () -> [ParameterExp], + @ClosureParameterBuilderResult parameters: () -> [ClosureParameter], + returns returnType: String?, + @CodeBlockBuilderResult body: () throws -> [CodeBlock] + ) rethrows { self.capture = capture() self.parameters = parameters() self.returnType = returnType - self.body = body() + self.body = try body() + } + + /// Creates a closure without a return type. + /// - Parameters: + /// - capture: A ``ParameterExpBuilder`` that provides the capture list. + /// - parameters: A ``ClosureParameterBuilder`` that provides the closure parameters. + /// - body: A ``CodeBlockBuilder`` that provides the body of the closure. + public init( + @ParameterExpBuilderResult capture: () -> [ParameterExp], + @ClosureParameterBuilderResult parameters: () -> [ClosureParameter], + @CodeBlockBuilderResult body: () throws -> [CodeBlock] + ) rethrows { + try self.init(capture: capture, parameters: parameters, returns: nil, body: body) + } + + /// Creates a closure without parameters. + /// - Parameters: + /// - capture: A ``ParameterExpBuilder`` that provides the capture list. + /// - returnType: The return type of the closure. + /// - body: A ``CodeBlockBuilder`` that provides the body of the closure. + public init( + @ParameterExpBuilderResult capture: () -> [ParameterExp], + returns returnType: String?, + @CodeBlockBuilderResult body: () throws -> [CodeBlock] + ) rethrows { + try self.init( + capture: capture, + parameters: [ClosureParameter].init, + returns: returnType, + body: body + ) + } + + /// Creates a closure without parameters and return type. + /// - Parameters: + /// - capture: A ``ParameterExpBuilder`` that provides the capture list. + /// - body: A ``CodeBlockBuilder`` that provides the body of the closure. + public init( + @ParameterExpBuilderResult capture: () -> [ParameterExp], + @CodeBlockBuilderResult body: () throws -> [CodeBlock] + ) rethrows { + try self.init( + capture: capture, + parameters: [ClosureParameter].init, + returns: nil, + body: body + ) + } + + /// Creates a closure without capture list and return type. + /// - Parameters: + /// - parameters: A ``ClosureParameterBuilder`` that provides the closure parameters. + /// - body: A ``CodeBlockBuilder`` that provides the body of the closure. + public init( + @ClosureParameterBuilderResult parameters: () -> [ClosureParameter], + @CodeBlockBuilderResult body: () throws -> [CodeBlock] + ) rethrows { + try self.init( + capture: [ParameterExp].init, + parameters: parameters, + returns: nil, + body: body + ) + } + + /// Creates a closure without capture list. + /// - Parameters: + /// - parameters: A ``ClosureParameterBuilder`` that provides the closure parameters. + /// - returnType: The return type of the closure. + /// - body: A ``CodeBlockBuilder`` that provides the body of the closure. + public init( + @ClosureParameterBuilderResult parameters: () -> [ClosureParameter], + returns returnType: String?, + @CodeBlockBuilderResult body: () throws -> [CodeBlock] + ) rethrows { + try self.init( + capture: [ParameterExp].init, + parameters: parameters, + returns: returnType, + body: body + ) + } + + /// Creates a simple closure with only a body. + /// - Parameters: + /// - body: A ``CodeBlockBuilder`` that provides the body of the closure. + public init( + @CodeBlockBuilderResult body: () throws -> [CodeBlock] + ) rethrows { + try self.init( + capture: [ParameterExp].init, + parameters: [ClosureParameter].init, + returns: nil, + body: body + ) + } + + /// Creates a closure without capture list and parameters. + /// - Parameters: + /// - returnType: The return type of the closure. + /// - body: A ``CodeBlockBuilder`` that provides the body of the closure. + public init( + returns returnType: String?, + @CodeBlockBuilderResult body: () throws -> [CodeBlock] + ) rethrows { + try self.init( + capture: [ParameterExp].init, + parameters: [ClosureParameter].init, + returns: returnType, + body: body + ) } public func attribute(_ attribute: String, arguments: [String] = []) -> Self { diff --git a/Sources/SyntaxKit/Expressions/FunctionCallExp.swift b/Sources/SyntaxKit/Expressions/FunctionCallExp.swift index c720e7f..78753d5 100644 --- a/Sources/SyntaxKit/Expressions/FunctionCallExp.swift +++ b/Sources/SyntaxKit/Expressions/FunctionCallExp.swift @@ -94,7 +94,7 @@ public struct FunctionCallExp: CodeBlock { } let labeledArgs = LabeledExprListSyntax( - args.enumerated().map { index, param in + args.enumerated().compactMap { index, param in let expr = param.syntax if let labeled = expr as? LabeledExprSyntax { var element = labeled @@ -106,6 +106,8 @@ public struct FunctionCallExp: CodeBlock { } return element } else if let unlabeled = expr as? ExprSyntax { + // ParameterExp.syntax is guaranteed to return either LabeledExprSyntax or ExprSyntax + return LabeledExprSyntax( label: nil, colon: nil, @@ -115,12 +117,12 @@ public struct FunctionCallExp: CodeBlock { : nil ) } else { - fatalError("ParameterExp.syntax must return LabeledExprSyntax or ExprSyntax") + return nil } } ) - var functionCall = FunctionCallExprSyntax( + let functionCall = FunctionCallExprSyntax( calledExpression: ExprSyntax( MemberAccessExprSyntax( base: baseExpr, diff --git a/Sources/SyntaxKit/Expressions/Infix.swift b/Sources/SyntaxKit/Expressions/Infix.swift index 432f82f..261cf1f 100644 --- a/Sources/SyntaxKit/Expressions/Infix.swift +++ b/Sources/SyntaxKit/Expressions/Infix.swift @@ -30,28 +30,65 @@ import SwiftSyntax /// A generic binary (infix) operator expression, e.g. `a + b`. -public struct Infix: CodeBlock { +public struct Infix: CodeBlock, ExprCodeBlock { + public enum InfixError: Error, CustomStringConvertible { + case wrongOperandCount(expected: Int, got: Int) + case nonExprCodeBlockOperand + + public var description: String { + switch self { + case let .wrongOperandCount(expected, got): + return "Infix expects exactly \(expected) operands, got \(got)." + case .nonExprCodeBlockOperand: + return "Infix operands must conform to ExprCodeBlock protocol" + } + } + } + private let operation: String - private let operands: [CodeBlock] + private let leftOperand: any ExprCodeBlock + private let rightOperand: any ExprCodeBlock /// Creates an infix operator expression. /// - Parameters: /// - operation: The operator symbol as it should appear in source (e.g. "+", "-", "&&"). - /// - content: A ``CodeBlockBuilder`` that supplies the two operand expressions. + /// - lhs: The left-hand side expression that conforms to ExprCodeBlock. + /// - rhs: The right-hand side expression that conforms to ExprCodeBlock. + public init(_ operation: String, lhs: any ExprCodeBlock, rhs: any ExprCodeBlock) { + self.operation = operation + self.leftOperand = lhs + self.rightOperand = rhs + } + + /// Creates an infix operator expression with a builder closure. + /// - Parameters: + /// - operation: The operator symbol as it should appear in source (e.g. "+", "-", "&&"). + /// - content: A ``CodeBlockBuilder`` that supplies exactly two operand expressions. /// /// Exactly two operands must be supplied – a left-hand side and a right-hand side. - public init(_ operation: String, @CodeBlockBuilderResult _ content: () -> [CodeBlock]) { + /// Each operand must conform to ExprCodeBlock. + @available(*, deprecated, message: "Use separate lhs and rhs parameters for compile-time safety") + public init(_ operation: String, @CodeBlockBuilderResult _ content: () throws -> [CodeBlock]) + throws + { self.operation = operation - self.operands = content() - } + let operands = try content() - public var syntax: SyntaxProtocol { guard operands.count == 2 else { - fatalError("Infix expects exactly two operands, got \(operands.count).") + throw InfixError.wrongOperandCount(expected: 2, got: operands.count) } + guard let lhs = operands[0] as? any ExprCodeBlock, + let rhs = operands[1] as? any ExprCodeBlock + else { + throw InfixError.nonExprCodeBlockOperand + } + self.leftOperand = lhs + self.rightOperand = rhs + } - let left = operands[0].expr - let right = operands[1].expr + public var exprSyntax: ExprSyntax { + let left = leftOperand.exprSyntax + let right = rightOperand.exprSyntax let operatorExpr = ExprSyntax( BinaryOperatorExprSyntax( @@ -59,14 +96,20 @@ public struct Infix: CodeBlock { ) ) - return SequenceExprSyntax( - elements: ExprListSyntax([ - left, - operatorExpr, - right, - ]) + return ExprSyntax( + SequenceExprSyntax( + elements: ExprListSyntax([ + left, + operatorExpr, + right, + ]) + ) ) } + + public var syntax: SyntaxProtocol { + exprSyntax + } } // MARK: - Comparison Operators @@ -90,8 +133,9 @@ extension Infix { /// - operator: The comparison operator to use. /// - lhs: The left-hand side expression. /// - rhs: The right-hand side expression. - public init(_ operator: ComparisonOperator, lhs: CodeBlock, rhs: CodeBlock) { + public init(_ operator: ComparisonOperator, lhs: any ExprCodeBlock, rhs: any ExprCodeBlock) { self.operation = `operator`.symbol - self.operands = [lhs, rhs] + self.leftOperand = lhs + self.rightOperand = rhs } } diff --git a/Sources/SyntaxKit/Expressions/Literal+PatternConvertible.swift b/Sources/SyntaxKit/Expressions/Literal+PatternConvertible.swift index 39b85fc..24464d5 100644 --- a/Sources/SyntaxKit/Expressions/Literal+PatternConvertible.swift +++ b/Sources/SyntaxKit/Expressions/Literal+PatternConvertible.swift @@ -32,9 +32,7 @@ import SwiftSyntax extension Literal: PatternConvertible { /// SwiftSyntax representation of the literal as a pattern. public var patternSyntax: PatternSyntax { - guard let expr = self.syntax.as(ExprSyntax.self) else { - fatalError("Literal.syntax did not return ExprSyntax") - } + let expr = self.exprSyntax return PatternSyntax(ExpressionPatternSyntax(expression: expr)) } } diff --git a/Sources/SyntaxKit/Expressions/Literal.swift b/Sources/SyntaxKit/Expressions/Literal.swift index 0a2dbee..96ae3bc 100644 --- a/Sources/SyntaxKit/Expressions/Literal.swift +++ b/Sources/SyntaxKit/Expressions/Literal.swift @@ -30,7 +30,7 @@ import SwiftSyntax /// A literal value. -public enum Literal: CodeBlock { +public enum Literal: CodeBlock, CodeBlockable { /// A string literal. case string(String) /// A floating-point literal. @@ -50,6 +50,11 @@ public enum Literal: CodeBlock { /// A dictionary literal. case dictionary([(Literal, Literal)]) + /// The code block representation of this literal. + public var codeBlock: CodeBlock { + self + } + /// The Swift type name for this literal. public var typeName: String { switch self { diff --git a/Sources/SyntaxKit/Expressions/NegatedPropertyAccessExp.swift b/Sources/SyntaxKit/Expressions/NegatedPropertyAccessExp.swift index 7b9e1c2..1297506 100644 --- a/Sources/SyntaxKit/Expressions/NegatedPropertyAccessExp.swift +++ b/Sources/SyntaxKit/Expressions/NegatedPropertyAccessExp.swift @@ -30,7 +30,7 @@ import SwiftSyntax /// An expression that negates a property access. -public struct NegatedPropertyAccessExp: CodeBlock { +public struct NegatedPropertyAccessExp: CodeBlock, ExprCodeBlock { internal let base: CodeBlock /// Creates a negated property access expression. @@ -44,7 +44,7 @@ public struct NegatedPropertyAccessExp: CodeBlock { self.base = PropertyAccessExp(baseName: baseName, propertyName: propertyName) } - public var syntax: SyntaxProtocol { + public var exprSyntax: ExprSyntax { let memberAccess = base.syntax.as(ExprSyntax.self) ?? ExprSyntax( @@ -61,4 +61,8 @@ public struct NegatedPropertyAccessExp: CodeBlock { ) ) } + + public var syntax: SyntaxProtocol { + exprSyntax + } } diff --git a/Sources/SyntaxKit/Expressions/OptionalChainingExp.swift b/Sources/SyntaxKit/Expressions/OptionalChainingExp.swift index 433ebc4..36925a2 100644 --- a/Sources/SyntaxKit/Expressions/OptionalChainingExp.swift +++ b/Sources/SyntaxKit/Expressions/OptionalChainingExp.swift @@ -46,6 +46,9 @@ public struct OptionalChainingExp: CodeBlock { baseExpr = exprSyntax } else { // Fallback to a default expression if conversion fails + #warning( + "TODO: Review fallback for failed expression conversion - consider if this should be an error instead" + ) baseExpr = ExprSyntax(DeclReferenceExprSyntax(baseName: .identifier(""))) } diff --git a/Sources/SyntaxKit/Expressions/PlusAssign.swift b/Sources/SyntaxKit/Expressions/PlusAssign.swift index 7c978e7..8572b81 100644 --- a/Sources/SyntaxKit/Expressions/PlusAssign.swift +++ b/Sources/SyntaxKit/Expressions/PlusAssign.swift @@ -34,13 +34,16 @@ public struct PlusAssign: CodeBlock { private let target: String private let valueExpr: ExprSyntax + /// Creates a `+=` expression with an expression value. + public init(_ target: String, _ value: any ExprCodeBlock) { + self.target = target + self.valueExpr = value.exprSyntax + } + /// Creates a `+=` expression with a literal value. public init(_ target: String, _ literal: Literal) { self.target = target - guard let expr = literal.syntax.as(ExprSyntax.self) else { - fatalError("Literal.syntax did not produce ExprSyntax") - } - self.valueExpr = expr + self.valueExpr = literal.exprSyntax } /// Creates a `+=` expression with an integer literal value. diff --git a/Sources/SyntaxKit/Expressions/PropertyAccessExp.swift b/Sources/SyntaxKit/Expressions/PropertyAccessExp.swift index a23b53f..ad0d488 100644 --- a/Sources/SyntaxKit/Expressions/PropertyAccessExp.swift +++ b/Sources/SyntaxKit/Expressions/PropertyAccessExp.swift @@ -30,7 +30,7 @@ import SwiftSyntax /// An expression that accesses a property on a base expression. -public struct PropertyAccessExp: CodeBlock { +public struct PropertyAccessExp: CodeBlock, ExprCodeBlock { internal let base: CodeBlock internal let propertyName: String @@ -62,7 +62,7 @@ public struct PropertyAccessExp: CodeBlock { NegatedPropertyAccessExp(base: self) } - public var syntax: SyntaxProtocol { + public var exprSyntax: ExprSyntax { let baseSyntax = base.syntax.as(ExprSyntax.self) ?? ExprSyntax( @@ -77,4 +77,8 @@ public struct PropertyAccessExp: CodeBlock { ) ) } + + public var syntax: SyntaxProtocol { + exprSyntax + } } diff --git a/Sources/SyntaxKit/Expressions/ReferenceExp.swift b/Sources/SyntaxKit/Expressions/ReferenceExp.swift index 19b1c6b..ad017be 100644 --- a/Sources/SyntaxKit/Expressions/ReferenceExp.swift +++ b/Sources/SyntaxKit/Expressions/ReferenceExp.swift @@ -32,13 +32,13 @@ import SwiftSyntax /// A Swift reference expression (e.g., `weak self`, `unowned self`). public struct ReferenceExp: CodeBlock { private let base: CodeBlock - private let referenceType: String + private let referenceType: CaptureReferenceType /// Creates a reference expression. /// - Parameters: /// - base: The base expression to reference. - /// - referenceType: The type of reference (e.g., "weak", "unowned"). - public init(base: CodeBlock, referenceType: String) { + /// - referenceType: The type of reference. + public init(base: CodeBlock, referenceType: CaptureReferenceType) { self.base = base self.referenceType = referenceType } @@ -68,7 +68,7 @@ public struct ReferenceExp: CodeBlock { } /// Returns the reference type for use in capture lists - internal var captureReferenceType: String { + internal var captureReferenceType: CaptureReferenceType { referenceType } } diff --git a/Sources/SyntaxKit/Expressions/Return.swift b/Sources/SyntaxKit/Expressions/Return.swift index 78c05d4..c07447f 100644 --- a/Sources/SyntaxKit/Expressions/Return.swift +++ b/Sources/SyntaxKit/Expressions/Return.swift @@ -35,22 +35,43 @@ public struct Return: CodeBlock { /// Creates a `return` statement. /// - Parameter content: A ``CodeBlockBuilder`` that provides the expression to return. - public init(@CodeBlockBuilderResult _ content: () -> [CodeBlock]) { - self.exprs = content() + public init(@CodeBlockBuilderResult _ content: () throws -> [CodeBlock]) rethrows { + self.exprs = try content() } public var syntax: SyntaxProtocol { - guard let expr = exprs.first else { - fatalError("Return must have at least one expression.") - } - if let varExp = expr as? VariableExp { + if let expr = exprs.first { + if let varExp = expr as? VariableExp { + return ReturnStmtSyntax( + returnKeyword: .keyword(.return, trailingTrivia: .space), + expression: ExprSyntax(DeclReferenceExprSyntax(baseName: .identifier(varExp.name))) + ) + } + + // Try to get ExprSyntax from the expression + let exprSyntax: ExprSyntax + if let exprCodeBlock = expr as? ExprCodeBlock { + exprSyntax = exprCodeBlock.exprSyntax + } else if let syntax = expr.syntax.as(ExprSyntax.self) { + exprSyntax = syntax + } else { + // fallback: no valid expression + #warning( + "TODO: Review fallback for no valid expression - consider if this should be an error instead" + ) + return ReturnStmtSyntax( + returnKeyword: .keyword(.return, trailingTrivia: .space) + ) + } + return ReturnStmtSyntax( returnKeyword: .keyword(.return, trailingTrivia: .space), - expression: ExprSyntax(DeclReferenceExprSyntax(baseName: .identifier(varExp.name))) + expression: exprSyntax + ) + } else { + // Bare return + return ReturnStmtSyntax( + returnKeyword: .keyword(.return, trailingTrivia: .space) ) } - return ReturnStmtSyntax( - returnKeyword: .keyword(.return, trailingTrivia: .space), - expression: ExprSyntax(expr.syntax) - ) } } diff --git a/Sources/SyntaxKit/Expressions/Task.swift b/Sources/SyntaxKit/Expressions/Task.swift index 56b1b32..9775b78 100644 --- a/Sources/SyntaxKit/Expressions/Task.swift +++ b/Sources/SyntaxKit/Expressions/Task.swift @@ -34,10 +34,10 @@ public struct Task: CodeBlock { private let body: [CodeBlock] private var attributes: [AttributeInfo] = [] - /// Creates a Task expression. - /// - Parameter content: A ``CodeBlockBuilder`` that provides the body of the task. - public init(@CodeBlockBuilderResult _ content: () -> [CodeBlock]) { - self.body = content() + /// Creates a Task block. + /// - Parameter content: A ``CodeBlockBuilder`` that provides the body of the Task. + public init(@CodeBlockBuilderResult _ content: () throws -> [CodeBlock]) rethrows { + self.body = try content() } /// Adds an attribute to the task. diff --git a/Sources/SyntaxKit/Functions/Function.swift b/Sources/SyntaxKit/Functions/Function.swift index 038eb09..0d1e798 100644 --- a/Sources/SyntaxKit/Functions/Function.swift +++ b/Sources/SyntaxKit/Functions/Function.swift @@ -48,12 +48,12 @@ public struct Function: CodeBlock { public init( _ name: String, returns returnType: String? = nil, - @CodeBlockBuilderResult _ content: () -> [CodeBlock] - ) { + @CodeBlockBuilderResult _ content: () throws -> [CodeBlock] + ) rethrows { self.name = name self.parameters = [] self.returnType = returnType - self.body = content() + self.body = try content() } /// Creates a `func` declaration. @@ -66,12 +66,12 @@ public struct Function: CodeBlock { _ name: String, returns returnType: String? = nil, @ParameterBuilderResult _ params: () -> [Parameter], - @CodeBlockBuilderResult _ content: () -> [CodeBlock] - ) { + @CodeBlockBuilderResult _ content: () throws -> [CodeBlock] + ) rethrows { self.name = name self.parameters = params() self.returnType = returnType - self.body = content() + self.body = try content() } /// Creates a `func` declaration with parameters and body using the DSL syntax. @@ -82,11 +82,11 @@ public struct Function: CodeBlock { public init( _ name: String, @ParameterBuilderResult _ params: () -> [Parameter], - @CodeBlockBuilderResult _ body: () -> [CodeBlock] - ) { + @CodeBlockBuilderResult _ body: () throws -> [CodeBlock] + ) rethrows { self.name = name self.parameters = params() self.returnType = nil - self.body = body() + self.body = try body() } } diff --git a/Sources/SyntaxKit/Parameters/ParameterExp.swift b/Sources/SyntaxKit/Parameters/ParameterExp.swift index 66c5876..2c09f87 100644 --- a/Sources/SyntaxKit/Parameters/ParameterExp.swift +++ b/Sources/SyntaxKit/Parameters/ParameterExp.swift @@ -47,6 +47,10 @@ public struct ParameterExp: CodeBlock { /// - Parameters: /// - name: The name of the parameter. /// - value: The string value of the parameter. + @available( + *, deprecated, + message: "Use ParameterExp(name:value:) with Literal.string() or VariableExp() instead" + ) public init(name: String, value: String) { self.name = name self.value = VariableExp(value) @@ -59,6 +63,10 @@ public struct ParameterExp: CodeBlock { } /// Convenience initializer for unlabeled parameter with a String value. + @available( + *, deprecated, + message: "Use ParameterExp(unlabeled:) with Literal.string() or VariableExp() instead" + ) public init(unlabeled value: String) { self.name = "" self.value = VariableExp(value) diff --git a/Sources/SyntaxKit/Utilities/Case.swift b/Sources/SyntaxKit/Utilities/Case.swift index 9f5eb7a..fc05929 100644 --- a/Sources/SyntaxKit/Utilities/Case.swift +++ b/Sources/SyntaxKit/Utilities/Case.swift @@ -37,14 +37,15 @@ public struct Case: CodeBlock { private let enumCaseName: String? private var associatedValue: (name: String, type: String)? - /// Creates a `case` for a `switch` statement. + /// Creates a case declaration. /// - Parameters: - /// - patterns: The patterns to match for the case. + /// - patterns: The patterns for the case. /// - content: A ``CodeBlockBuilder`` that provides the body of the case. - public init(_ patterns: PatternConvertible..., @CodeBlockBuilderResult content: () -> [CodeBlock]) - { + public init( + _ patterns: PatternConvertible..., @CodeBlockBuilderResult content: () throws -> [CodeBlock] + ) rethrows { self.patterns = patterns - self.body = content() + self.body = try content() self.isEnumCase = false self.enumCaseName = nil self.associatedValue = nil diff --git a/Sources/SyntaxKit/Utilities/CodeBlockable.swift b/Sources/SyntaxKit/Utilities/CodeBlockable.swift new file mode 100644 index 0000000..c9a406b --- /dev/null +++ b/Sources/SyntaxKit/Utilities/CodeBlockable.swift @@ -0,0 +1,34 @@ +// +// CodeBlockable.swift +// SyntaxKit +// +// Created by Leo Dion. +// Copyright © 2025 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. +// + +/// Can export a `CodeBlock`. +public protocol CodeBlockable { + /// Returns a `CodeBlock`. + var codeBlock: CodeBlock { get } +} diff --git a/Sources/SyntaxKit/Utilities/Default.swift b/Sources/SyntaxKit/Utilities/Default.swift index 9ccb5b3..6ee3ca8 100644 --- a/Sources/SyntaxKit/Utilities/Default.swift +++ b/Sources/SyntaxKit/Utilities/Default.swift @@ -33,10 +33,10 @@ import SwiftSyntax public struct Default: CodeBlock { private let body: [CodeBlock] - /// Creates a `default` case for a `switch` statement. - /// - Parameter content: A ``CodeBlockBuilder`` that provides the body of the case. - public init(@CodeBlockBuilderResult _ content: () -> [CodeBlock]) { - self.body = content() + /// Creates a default case declaration. + /// - Parameter content: A ``CodeBlockBuilder`` that provides the body of the default case. + public init(@CodeBlockBuilderResult _ content: () throws -> [CodeBlock]) rethrows { + self.body = try content() } public var switchCaseSyntax: SwitchCaseSyntax { let statements = CodeBlockItemListSyntax( diff --git a/Sources/SyntaxKit/Utilities/Group.swift b/Sources/SyntaxKit/Utilities/Group.swift index 329574a..8edfd10 100644 --- a/Sources/SyntaxKit/Utilities/Group.swift +++ b/Sources/SyntaxKit/Utilities/Group.swift @@ -35,8 +35,8 @@ public struct Group: CodeBlock { /// Creates a group of code blocks. /// - Parameter content: A ``CodeBlockBuilder`` that provides the members of the group. - public init(@CodeBlockBuilderResult _ content: () -> [CodeBlock]) { - self.members = content() + public init(@CodeBlockBuilderResult _ content: () throws -> [CodeBlock]) rethrows { + self.members = try content() } public var syntax: SyntaxProtocol { @@ -53,7 +53,12 @@ public struct Group: CodeBlock { } else if let expr = block.syntax.as(ExprSyntax.self) { item = .expr(expr) } else { - fatalError("Unsupported syntax type in group: \(type(of: block.syntax)) from \(block)") + // Skip unsupported syntax types instead of crashing + // This allows the group to continue processing other valid blocks + #warning( + "TODO: Review fallback for unsupported syntax types - consider if this should be an error instead" + ) + return [] } return [CodeBlockItemSyntax(item: item, trailingTrivia: .newline)] } diff --git a/Sources/SyntaxKit/Utilities/Parenthesized.swift b/Sources/SyntaxKit/Utilities/Parenthesized.swift index be8959b..d7aa6f0 100644 --- a/Sources/SyntaxKit/Utilities/Parenthesized.swift +++ b/Sources/SyntaxKit/Utilities/Parenthesized.swift @@ -30,18 +30,18 @@ import SwiftSyntax /// A code block that wraps its content in parentheses. -public struct Parenthesized: CodeBlock { +public struct Parenthesized: CodeBlock, ExprCodeBlock { private let content: CodeBlock /// Creates a parenthesized code block. /// - Parameter content: The code block to wrap in parentheses. - public init(@CodeBlockBuilderResult _ content: () -> [CodeBlock]) { - let blocks = content() + public init(@CodeBlockBuilderResult _ content: () throws -> [CodeBlock]) rethrows { + let blocks = try content() precondition(blocks.count == 1, "Parenthesized expects exactly one code block.") self.content = blocks[0] } - public var syntax: SyntaxProtocol { + public var exprSyntax: ExprSyntax { ExprSyntax( TupleExprSyntax( leftParen: .leftParenToken(), @@ -52,4 +52,8 @@ public struct Parenthesized: CodeBlock { ) ) } + + public var syntax: SyntaxProtocol { + exprSyntax + } } diff --git a/Sources/SyntaxKit/Utilities/Then.swift b/Sources/SyntaxKit/Utilities/Then.swift index dd2fc2e..710d5b9 100644 --- a/Sources/SyntaxKit/Utilities/Then.swift +++ b/Sources/SyntaxKit/Utilities/Then.swift @@ -46,8 +46,10 @@ public struct Then: CodeBlock { /// The statements that make up the `else` body. public let body: [CodeBlock] - public init(@CodeBlockBuilderResult _ content: () -> [CodeBlock]) { - self.body = content() + /// Creates a then block. + /// - Parameter content: A ``CodeBlockBuilder`` that provides the body of the then block. + public init(@CodeBlockBuilderResult _ content: () throws -> [CodeBlock]) rethrows { + self.body = try content() } public var syntax: SyntaxProtocol { diff --git a/Sources/SyntaxKit/Variables/ComputedProperty.swift b/Sources/SyntaxKit/Variables/ComputedProperty.swift index 7b6cdb6..fa963aa 100644 --- a/Sources/SyntaxKit/Variables/ComputedProperty.swift +++ b/Sources/SyntaxKit/Variables/ComputedProperty.swift @@ -34,7 +34,7 @@ public struct ComputedProperty: CodeBlock { private let name: String private let type: String private let body: [CodeBlock] - private var accessModifier: String? + private var accessModifier: AccessModifier? private let explicitType: Bool /// Creates a computed property declaration. @@ -47,18 +47,18 @@ public struct ComputedProperty: CodeBlock { _ name: String, type: String, explicitType: Bool = true, - @CodeBlockBuilderResult _ content: () -> [CodeBlock] - ) { + @CodeBlockBuilderResult _ content: () throws -> [CodeBlock] + ) rethrows { self.name = name self.type = type self.explicitType = explicitType - self.body = content() + self.body = try content() } /// Sets the access modifier for the computed property declaration. - /// - Parameter access: The access modifier (e.g., "public", "private"). + /// - Parameter access: The access modifier. /// - Returns: A copy of the computed property with the access modifier set. - public func access(_ access: String) -> Self { + public func access(_ access: AccessModifier) -> Self { var copy = self copy.accessModifier = access return copy @@ -96,21 +96,8 @@ public struct ComputedProperty: CodeBlock { // Build modifiers var modifiers: DeclModifierListSyntax = [] if let access = accessModifier { - let keyword: Keyword - switch access { - case "public": - keyword = .public - case "private": - keyword = .private - case "internal": - keyword = .internal - case "fileprivate": - keyword = .fileprivate - default: - keyword = .public // fallback - } modifiers = DeclModifierListSyntax([ - DeclModifierSyntax(name: .keyword(keyword, trailingTrivia: .space)) + DeclModifierSyntax(name: .keyword(access.keyword, trailingTrivia: .space)) ]) } diff --git a/Sources/SyntaxKit/Variables/Variable+LiteralInitializers.swift b/Sources/SyntaxKit/Variables/Variable+LiteralInitializers.swift index 1b4fc38..3b4224e 100644 --- a/Sources/SyntaxKit/Variables/Variable+LiteralInitializers.swift +++ b/Sources/SyntaxKit/Variables/Variable+LiteralInitializers.swift @@ -32,53 +32,23 @@ import Foundation // MARK: - Variable Literal Initializers extension Variable { - // swiftlint:disable cyclomatic_complexity /// Creates a `let` or `var` declaration with a literal value. /// - Parameters: /// - kind: The kind of variable, either ``VariableKind/let`` or ``VariableKind/var``. /// - name: The name of the variable. /// - equals: A literal value that conforms to ``LiteralValue``. - public init( + public init( _ kind: VariableKind, name: String, equals value: T ) { - let defaultValue: CodeBlock - if let literal = value as? Literal { - defaultValue = literal - } else if let tuple = value as? TupleLiteral { - defaultValue = Literal.tuple(tuple.elements) - } else if let array = value as? ArrayLiteral { - defaultValue = Literal.array(array.elements) - } else if let dict = value as? DictionaryLiteral { - defaultValue = Literal.dictionary(dict.elements) - } else if let array = value as? [String] { - defaultValue = Literal.array(array.map { .string($0) }) - } else if let dict = value as? [Int: String] { - defaultValue = Literal.dictionary(dict.map { (.integer($0.key), .string($0.value)) }) - } else if let dictExpr = value as? DictionaryExpr { - defaultValue = dictExpr - } else if let initExpr = value as? Init { - defaultValue = initExpr - } else if let codeBlock = value as? CodeBlock { - defaultValue = codeBlock - } else { - // For any other LiteralValue type that doesn't conform to CodeBlock, - // create a fallback or throw an error - fatalError( - "Variable: Unsupported LiteralValue type that doesn't conform to CodeBlock: \(T.self)" - ) - } - self.init( kind: kind, name: name, type: value.typeName, - defaultValue: defaultValue, + defaultValue: value.codeBlock, explicitType: false ) } - // swiftlint:enable cyclomatic_complexity - /// Creates a `let` or `var` declaration with a string literal value. /// - Parameters: /// - kind: The kind of variable, either ``VariableKind/let`` or ``VariableKind/var``. @@ -173,14 +143,14 @@ extension Variable { public init( _ kind: VariableKind, name: String, - @CodeBlockBuilderResult value: () -> [CodeBlock], + @CodeBlockBuilderResult value: () throws -> [CodeBlock], explicitType: Bool? = nil - ) { + ) rethrows { self.init( kind: kind, name: name, type: "", - defaultValue: value().first ?? EmptyCodeBlock(), + defaultValue: try value().first ?? EmptyCodeBlock(), explicitType: explicitType ?? false ) } diff --git a/Sources/SyntaxKit/Variables/Variable+Modifiers.swift b/Sources/SyntaxKit/Variables/Variable+Modifiers.swift index cdbf079..bfaf90d 100644 --- a/Sources/SyntaxKit/Variables/Variable+Modifiers.swift +++ b/Sources/SyntaxKit/Variables/Variable+Modifiers.swift @@ -60,21 +60,7 @@ extension Variable { } /// Builds an access modifier. - private func buildAccessModifier(_ access: String) -> DeclModifierSyntax { - let keyword: Keyword - switch access { - case "public": - keyword = .public - case "private": - keyword = .private - case "internal": - keyword = .internal - case "fileprivate": - keyword = .fileprivate - default: - keyword = .public // fallback - } - - return DeclModifierSyntax(name: .keyword(keyword, trailingTrivia: .space)) + private func buildAccessModifier(_ access: AccessModifier) -> DeclModifierSyntax { + DeclModifierSyntax(name: .keyword(access.keyword, trailingTrivia: .space)) } } diff --git a/Sources/SyntaxKit/Variables/Variable.swift b/Sources/SyntaxKit/Variables/Variable.swift index c0cda6a..ec2e133 100644 --- a/Sources/SyntaxKit/Variables/Variable.swift +++ b/Sources/SyntaxKit/Variables/Variable.swift @@ -40,7 +40,7 @@ public struct Variable: CodeBlock { internal var isAsync: Bool = false private var attributes: [AttributeInfo] = [] private var explicitType: Bool = false - internal var accessModifier: String? + internal var accessModifier: AccessModifier? /// Internal initializer used by extension initializers to reduce code duplication. /// - Parameters: @@ -86,9 +86,9 @@ public struct Variable: CodeBlock { } /// Sets the access modifier for the variable declaration. - /// - Parameter access: The access modifier (e.g., "public", "private"). + /// - Parameter access: The access modifier. /// - Returns: A copy of the variable with the access modifier set. - public func access(_ access: String) -> Self { + public func access(_ access: AccessModifier) -> Self { var copy = self copy.accessModifier = access return copy diff --git a/Sources/SyntaxKit/Variables/VariableExp.swift b/Sources/SyntaxKit/Variables/VariableExp.swift index 30d73c2..d8803b3 100644 --- a/Sources/SyntaxKit/Variables/VariableExp.swift +++ b/Sources/SyntaxKit/Variables/VariableExp.swift @@ -30,7 +30,7 @@ import SwiftSyntax /// An expression that refers to a variable. -public struct VariableExp: CodeBlock, PatternConvertible { +public struct VariableExp: CodeBlock, PatternConvertible, ExprCodeBlock { internal let name: String /// Creates a variable expression. @@ -65,9 +65,9 @@ public struct VariableExp: CodeBlock, PatternConvertible { } /// Creates a reference to this variable. - /// - Parameter referenceType: The type of reference (e.g., "weak", "unowned"). + /// - Parameter referenceType: The type of reference. /// - Returns: A reference expression. - public func reference(_ referenceType: String) -> CodeBlock { + public func reference(_ referenceType: CaptureReferenceType) -> CodeBlock { ReferenceExp(base: self, referenceType: referenceType) } @@ -81,6 +81,10 @@ public struct VariableExp: CodeBlock, PatternConvertible { ExprSyntax(DeclReferenceExprSyntax(baseName: .identifier(name))) } + public var exprSyntax: ExprSyntax { + ExprSyntax(DeclReferenceExprSyntax(baseName: .identifier(name))) + } + public var patternSyntax: PatternSyntax { PatternSyntax(IdentifierPatternSyntax(identifier: .identifier(name))) } diff --git a/Tests/SyntaxKitTests/Integration/ConcurrencyExampleTests.swift b/Tests/SyntaxKitTests/Integration/ConcurrencyExampleTests.swift index d8352c1..b312c83 100644 --- a/Tests/SyntaxKitTests/Integration/ConcurrencyExampleTests.swift +++ b/Tests/SyntaxKitTests/Integration/ConcurrencyExampleTests.swift @@ -9,7 +9,7 @@ import Testing // Build DSL equivalent of Examples/Remaining/concurrency/dsl.swift // Note: This test includes the Item struct that's referenced but not defined in the original DSL - let program = Group { + let program = try Group { // Item struct (needed for the vending machine) Struct("Item") { Variable(.let, name: "price", type: "Int").withExplicitType() @@ -25,7 +25,7 @@ import Testing .inherits("Error") // VendingMachine class - Class("VendingMachine") { + try Class("VendingMachine") { Variable( .var, name: "inventory", @@ -55,33 +55,35 @@ import Testing ) Variable(.var, name: "coinsDeposited", equals: 0) - Function("vend") { + try Function("vend") { Parameter("name", labeled: "itemNamed", type: "String") } _: { Guard { Let("item", "inventory[name]") } else: { - Throw(VariableExp("VendingMachineError.invalidSelection")) + Throw( + EnumCase("VendingMachineError.invalidSelection") + ) } - Guard { - Infix(">") { + try Guard { + try Infix(">") { VariableExp("item").property("count") Literal.integer(0) } } else: { - Throw(VariableExp("VendingMachineError.outOfStock")) + Throw(EnumCase("VendingMachineError.outOfStock")) } - Guard { - Infix("<=") { + try Guard { + try Infix("<=") { VariableExp("item").property("price") VariableExp("coinsDeposited") } } else: { Throw( - Call("VendingMachineError.insufficientFunds") { + try Call("VendingMachineError.insufficientFunds") { ParameterExp( name: "coinsNeeded", - value: Infix("-") { + value: try Infix("-") { VariableExp("item").property("price") VariableExp("coinsDeposited") } @@ -89,18 +91,18 @@ import Testing } ) } - Infix("-=") { + try Infix("-=") { VariableExp("coinsDeposited") VariableExp("item").property("price") } Variable(.var, name: "newItem", equals: Literal.ref("item")) - Infix("-=") { + try Infix("-=") { VariableExp("newItem").property("count") Literal.integer(1) } Assignment("inventory[name]", .ref("newItem")) Call("print") { - ParameterExp(unlabeled: "\"Dispensing \\(name)\"") + ParameterExp(unlabeled: Literal.string("Dispensing \\(name)")) } } .throws() diff --git a/Tests/SyntaxKitTests/Integration/ConditionalsExampleTests.swift b/Tests/SyntaxKitTests/Integration/ConditionalsExampleTests.swift index f5170c6..47d0821 100644 --- a/Tests/SyntaxKitTests/Integration/ConditionalsExampleTests.swift +++ b/Tests/SyntaxKitTests/Integration/ConditionalsExampleTests.swift @@ -8,21 +8,21 @@ import Testing internal func testCompletedConditionalsExample() throws { // Build DSL equivalent of Examples/Completed/conditionals/dsl.swift - let program = Group { + let program = try Group { // MARK: Basic If Statements Variable(.let, name: "temperature", equals: 25) .comment { Line("Simple if statement") } - If { - Infix(">") { + try If { + try Infix(">") { VariableExp("temperature") Literal.integer(30) } } then: { Call("print") { - ParameterExp(name: "", value: "\"It's hot outside!\"") + ParameterExp(unlabeled: "\"It's hot outside!\"") } } @@ -32,41 +32,39 @@ import Testing Line("If-else statement") } - If { - Infix(">=") { + try If { + try Infix(">=") { VariableExp("score") Literal.integer(90) } } then: { Call("print") { - ParameterExp(name: "", value: "\"Excellent!\"") + ParameterExp(unlabeled: "\"Excellent!\"") } } else: { - If { - Infix(">=") { + try If { + try Infix(">=") { VariableExp("score") Literal.integer(80) } } then: { Call("print") { - ParameterExp(name: "", value: "\"Good job!\"") - } - } - - If { - Infix(">=") { - VariableExp("score") - Literal.integer(70) - } - } then: { - Call("print") { - ParameterExp(name: "", value: "\"Passing\"") + ParameterExp(unlabeled: "\"Good job!\"") } - } - - Then { - Call("print") { - ParameterExp(name: "", value: "\"Needs improvement\"") + } else: { + try If { + try Infix(">=") { + VariableExp("score") + Literal.integer(70) + } + } then: { + Call("print") { + ParameterExp(unlabeled: "\"Passing\"") + } + } else: { + Call("print") { + ParameterExp(unlabeled: "\"Needs improvement\"") + } } } } @@ -118,7 +116,7 @@ import Testing } // MARK: - Guard Statements - Function( + try Function( "greet", { Parameter(name: "person", type: "[String: String]") @@ -275,12 +273,12 @@ import Testing Line("MARK: - Labeled Statements") Line("Using labeled statements with break") } - Variable(.var, name: "board") { - Init("[Int]") { + try Variable(.var, name: "board") { + try Init("[Int]") { ParameterExp(name: "repeating", value: Literal.integer(0)) ParameterExp( name: "count", - value: Infix("+") { + value: try Infix("+") { VariableExp("finalSquare") Literal.integer(1) } @@ -300,45 +298,45 @@ import Testing Variable(.var, name: "square", equals: Literal.integer(0)) Variable(.var, name: "diceRoll", equals: Literal.integer(0)) - While { - Infix("!=") { + try While( + try Infix("!=") { VariableExp("square") VariableExp("finalSquare") } - } then: { + ) { PlusAssign("diceRoll", 1) - If { - Infix("==") { + try If { + try Infix("==") { VariableExp("diceRoll") Literal.integer(7) } } then: { Assignment("diceRoll", 1) } - Switch( - Infix("+") { + try Switch( + try Infix("+") { VariableExp("square") VariableExp("diceRoll") } ) { - SwitchCase(VariableExp("finalSquare")) { + SwitchCase("finalSquare") { Break() } - SwitchCase { + try SwitchCase { SwitchLet("newSquare") - Infix(">") { + try Infix(">") { VariableExp("newSquare") VariableExp("finalSquare") } } content: { Continue() } - Default { - Infix("+=") { + try Default { + try Infix("+=") { VariableExp("square") VariableExp("diceRoll") } - Infix("+=") { + try Infix("+=") { VariableExp("square") VariableExp("board[square]") } @@ -498,4 +496,56 @@ import Testing #expect(generated == expected) } + + @Test("Conditionals example generates correct syntax") + internal func testConditionalsExample() throws { + _ = try If { + try Infix(">") { + VariableExp("temperature") + Literal.integer(30) + } + } then: { + Call("print") { + ParameterExp(unlabeled: "It's hot!") + } + } else: { + try If { + try Infix(">=") { + VariableExp("score") + Literal.integer(90) + } + } then: { + Call("print") { + ParameterExp(unlabeled: "Excellent!") + } + } else: { + try If { + try Infix(">=") { + VariableExp("score") + Literal.integer(80) + } + } then: { + Call("print") { + ParameterExp(unlabeled: "Good!") + } + } else: { + try If { + try Infix(">=") { + VariableExp("score") + Literal.integer(70) + } + } then: { + Call("print") { + ParameterExp(unlabeled: "Pass") + } + } else: { + Call("print") { + ParameterExp(unlabeled: "Fail") + } + } + } + } + } + // ... rest of the test ... + } } diff --git a/Tests/SyntaxKitTests/Integration/ForLoopsExampleTests.swift b/Tests/SyntaxKitTests/Integration/ForLoopsExampleTests.swift index 3c8aabb..c510e0d 100644 --- a/Tests/SyntaxKitTests/Integration/ForLoopsExampleTests.swift +++ b/Tests/SyntaxKitTests/Integration/ForLoopsExampleTests.swift @@ -8,7 +8,7 @@ import Testing internal func testCompletedForLoopsExample() throws { // Build DSL equivalent of Examples/Completed/for_loops/dsl.swift - let program = Group { + let program = try Group { // MARK: - Basic For-in Loop Variable( .let, @@ -80,12 +80,12 @@ import Testing ]) ) - For( + try For( VariableExp("number"), in: VariableExp("numbers"), where: { - Infix("==") { - Infix("%") { + try Infix("==") { + try Infix("%") { VariableExp("number") Literal.integer(2) } diff --git a/Tests/SyntaxKitTests/Integration/SwiftUIExampleTests.swift b/Tests/SyntaxKitTests/Integration/SwiftUIExampleTests.swift index 9edf180..05806bd 100644 --- a/Tests/SyntaxKitTests/Integration/SwiftUIExampleTests.swift +++ b/Tests/SyntaxKitTests/Integration/SwiftUIExampleTests.swift @@ -37,7 +37,7 @@ import Testing internal func testSwiftUIExample() throws { // Test the onToggle variable with closure type and attributes let onToggleVariable = Variable(.let, name: "onToggle", type: "(Date) -> Void") - .access("private") + .access(.private) let generatedCode = onToggleVariable.generateCode() #expect(generatedCode.contains("private let onToggle")) @@ -68,10 +68,10 @@ import Testing internal func testSwiftUITodoItemRowExample() throws { // Use the full DSL from Examples/Completed/swiftui/dsl.swift let dsl = Group { - Import("SwiftUI").access("public") + Import("SwiftUI").access(.public) Struct("TodoItemRow") { - Variable(.let, name: "item", type: "TodoItem").access("private") + Variable(.let, name: "item", type: "TodoItem").access(.private) Variable( .let, @@ -83,7 +83,7 @@ import Testing .attribute("MainActor") .attribute("Sendable") ) - .access("private") + .access(.private) ComputedProperty("body", type: "some View") { Init("HStack") { @@ -124,7 +124,7 @@ import Testing ParameterExp( unlabeled: Closure( capture: { - ParameterExp(unlabeled: VariableExp("self").reference("weak")) + ParameterExp(unlabeled: VariableExp("self").reference(.weak)) }, body: { VariableExp("self").optional().call("onToggle") { @@ -151,10 +151,10 @@ import Testing ) } } - .access("public") + .access(.public) } .inherits("View") - .access("public") + .access(.public) } // Expected Swift code from Examples/Completed/swiftui/code.swift diff --git a/Tests/SyntaxKitTests/Unit/ControlFlow/ConditionalsTests.swift b/Tests/SyntaxKitTests/Unit/ControlFlow/ConditionalsTests.swift index f2f87f6..a442096 100644 --- a/Tests/SyntaxKitTests/Unit/ControlFlow/ConditionalsTests.swift +++ b/Tests/SyntaxKitTests/Unit/ControlFlow/ConditionalsTests.swift @@ -6,11 +6,11 @@ import Testing @Test("If / else-if / else chain generates correct syntax") internal func testIfElseChain() throws { // Arrange: build the DSL example using the updated APIs - let conditional = Group { + let conditional = try Group { Variable(.let, name: "score", type: "Int", equals: "85") - If { - Infix(">=") { + try If { + try Infix(">=") { VariableExp("score") Literal.integer(90) } @@ -19,8 +19,8 @@ import Testing ParameterExp(name: "", value: "\"Excellent!\"") } } else: { - If { - Infix(">=") { + try If { + try Infix(">=") { VariableExp("score") Literal.integer(80) } @@ -47,4 +47,32 @@ import Testing #expect(generated.contains("else if score >= 80".normalize())) #expect(generated.contains("else {".normalize())) } + + @Test("If with multiple conditions generates correct syntax") + internal func testIfWithMultipleConditions() throws { + let ifStatement = try If { + try Infix(">=") { + VariableExp("score") + Literal.integer(90) + } + } then: { + Call("print") { + ParameterExp(unlabeled: "Excellent!") + } + } else: { + try If { + try Infix(">=") { + VariableExp("score") + Literal.integer(80) + } + } then: { + Call("print") { + ParameterExp(unlabeled: "Good!") + } + } + } + let generated = ifStatement.generateCode() + #expect(generated.contains("if score >= 90")) + #expect(generated.contains("else if score >= 80")) + } } diff --git a/Tests/SyntaxKitTests/Unit/ControlFlow/ForLoopTests.swift b/Tests/SyntaxKitTests/Unit/ControlFlow/ForLoopTests.swift index 3b922cf..1cd25fd 100644 --- a/Tests/SyntaxKitTests/Unit/ControlFlow/ForLoopTests.swift +++ b/Tests/SyntaxKitTests/Unit/ControlFlow/ForLoopTests.swift @@ -5,7 +5,7 @@ import Testing @Suite internal final class ForLoopTests { @Test - internal func testSimpleForInLoop() { + internal func testSimpleForInLoop() throws { let forLoop = For( VariableExp("item"), in: VariableExp("items"), @@ -22,12 +22,12 @@ internal final class ForLoopTests { } @Test - internal func testForInWithWhereClause() { - let forLoop = For( + internal func testForInWithWhereClause() throws { + let forLoop = try For( VariableExp("number"), in: VariableExp("numbers"), where: { - Infix("%") { + try Infix("%") { VariableExp("number") Literal.integer(2) } diff --git a/Tests/SyntaxKitTests/Unit/EdgeCases/EdgeCaseTests.swift b/Tests/SyntaxKitTests/Unit/EdgeCases/EdgeCaseTests.swift index c4a5720..8e570df 100644 --- a/Tests/SyntaxKitTests/Unit/EdgeCases/EdgeCaseTests.swift +++ b/Tests/SyntaxKitTests/Unit/EdgeCases/EdgeCaseTests.swift @@ -5,31 +5,119 @@ import Testing internal struct EdgeCaseTests { // MARK: - Error Handling Tests - @Test("Infix with wrong number of operands throws fatal error") - internal func testInfixWrongOperandCount() { - // This test documents the expected behavior - // In a real scenario, this would cause a fatal error - // We can't easily test fatalError in unit tests, but we can document it - let infix = Infix("+") { - // Only one operand - should cause fatal error - VariableExp("x") + @Test("Infix with wrong number of operands throws error") + internal func testInfixWrongOperandCount() throws { + // Test that Infix throws an error when given wrong number of operands + do { + _ = try Infix("+") { + // Only one operand - should throw error + VariableExp("x") + } + // If we reach here, no error was thrown, which is unexpected + #expect(false, "Expected error to be thrown for wrong operand count") + } catch let error as Infix.InfixError { + // Verify it's the correct error type + switch error { + case let .wrongOperandCount(expected, got): + #expect(expected == 2) + #expect(got == 1) + case .nonExprCodeBlockOperand: + #expect(false, "Expected wrongOperandCount error, got nonExprCodeBlockOperand") + } + } catch { + #expect(false, "Expected InfixError, got \(type(of: error))") } + } + + // MARK: - Default Condition Tests + + @Test("If with no conditions uses true as default") + internal func testIfWithNoConditionsUsesTrueAsDefault() throws { + let ifStatement = If { + Return { + Literal.string("executed") + } + } + + let generated = ifStatement.generateCode() + #expect(generated.contains("if true")) + #expect(generated.contains("return \"executed\"")) + } - // The fatal error would occur when accessing .syntax - // This test documents the expected behavior - #expect(true) // Placeholder - fatal errors can't be easily tested + @Test("Guard with no conditions uses true as default") + internal func testGuardWithNoConditionsUsesTrueAsDefault() throws { + let guardStatement = Guard { + Return { + Literal.string("executed") + } + } + + let generated = guardStatement.generateCode() + #expect(generated.contains("guard true")) + #expect(generated.contains("return \"executed\"")) } - @Test("Return with no expressions throws fatal error") + // MARK: - Return Statement Tests + + @Test("Return with expression generates correct syntax") + internal func testReturnWithExpression() { + let returnStatement = Return { + Literal.string("hello") + } + + let generated = returnStatement.generateCode() + #expect(generated.contains("return \"hello\"")) + } + + @Test("Return with variable expression generates correct syntax") + internal func testReturnWithVariableExpression() { + let returnStatement = Return { + VariableExp("value") + } + + let generated = returnStatement.generateCode() + #expect(generated.contains("return value")) + } + + @Test("Return with no expressions generates bare return") internal func testReturnWithNoExpressions() { - // This test documents the expected behavior - // In a real scenario, this would cause a fatal error - let returnStmt = Return { - // Empty - should cause fatal error + let returnStatement = Return { + // Empty - should generate bare return } - // The fatal error would occur when accessing .syntax - // This test documents the expected behavior - #expect(true) // Placeholder - fatal errors can't be easily tested + let generated = returnStatement.generateCode() + #expect(generated.contains("return")) + #expect(!generated.contains("return ")) + #expect(!generated.contains("return \"\"")) + #expect(!generated.contains("return nil")) + } + + // MARK: - TupleAssignment Tests + + @Test("TupleAssignment asyncSet falls back to regular syntax when conditions not met") + internal func testTupleAssignmentAsyncSetFallback() { + // Test case 1: Value is not a Tuple + let tupleAssignment1 = TupleAssignment( + ["a", "b"], + equals: Literal.string("not a tuple") + ).asyncSet() + + let generated1 = tupleAssignment1.generateCode() + #expect(generated1.contains("let (a, b) = \"not a tuple\"")) + #expect(!generated1.contains("async let")) + + // Test case 2: Element count mismatch + let tupleAssignment2 = TupleAssignment( + ["a", "b", "c"], // 3 elements + equals: Tuple { + Literal.integer(1) + Literal.integer(2) + // Missing third element + } + ).asyncSet() + + let generated2 = tupleAssignment2.generateCode() + #expect(generated2.contains("let (a, b, c) = (1, 2)")) + #expect(!generated2.contains("async let")) } } diff --git a/Tests/SyntaxKitTests/Unit/EdgeCases/EdgeCaseTestsExpressions.swift b/Tests/SyntaxKitTests/Unit/EdgeCases/EdgeCaseTestsExpressions.swift index 8296755..659e43d 100644 --- a/Tests/SyntaxKitTests/Unit/EdgeCases/EdgeCaseTestsExpressions.swift +++ b/Tests/SyntaxKitTests/Unit/EdgeCases/EdgeCaseTestsExpressions.swift @@ -36,15 +36,15 @@ internal struct EdgeCaseTestsExpressions { @Test("Infix with complex expressions generates correct syntax") internal func testInfixWithComplexExpressions() throws { - let infix = Infix("*") { - Parenthesized { - Infix("+") { + let infix = try Infix("*") { + try Parenthesized { + try Infix("+") { VariableExp("a") VariableExp("b") } } - Parenthesized { - Infix("-") { + try Parenthesized { + try Infix("-") { VariableExp("c") VariableExp("d") } @@ -67,8 +67,8 @@ internal struct EdgeCaseTestsExpressions { @Test("Return with complex expression generates correct syntax") internal func testReturnWithComplexExpression() throws { - let returnStmt = Return { - Infix("+") { + let returnStmt = try Return { + try Infix("+") { VariableExp("a") VariableExp("b") } diff --git a/Tests/SyntaxKitTests/Unit/Expressions/ClosureCaptureCoverageTests.swift b/Tests/SyntaxKitTests/Unit/Expressions/ClosureCaptureCoverageTests.swift index d913c4d..98c5376 100644 --- a/Tests/SyntaxKitTests/Unit/Expressions/ClosureCaptureCoverageTests.swift +++ b/Tests/SyntaxKitTests/Unit/Expressions/ClosureCaptureCoverageTests.swift @@ -39,34 +39,12 @@ import Testing internal final class ClosureCaptureCoverageTests { // MARK: - Closure+Capture.swift Coverage Tests - /// Tests fallback cases in CaptureInfo initializers. - @Test("Capture info fallback cases") - internal func testCaptureInfoFallbackCases() { - // Test fallback cases in CaptureInfo initializers - - // Test with unknown reference type (should fallback to weak) - let unknownRef = ReferenceExp(base: VariableExp("self"), referenceType: "unknown") - let paramWithUnknownRef = ParameterExp(name: "self", value: unknownRef) - let closure = Closure( - capture: { paramWithUnknownRef }, - body: { - Variable(.let, name: "result", equals: "value") - } - ) - - let syntax = closure.syntax - let description = syntax.description - - // Should fallback to weak - #expect(description.contains("[weak self]")) - } - /// Tests CaptureInfo with non-VariableExp capture expression. @Test("Capture info with non VariableExp") internal func testCaptureInfoWithNonVariableExp() { // Test CaptureInfo with non-VariableExp capture expression let initBlock = Init("String") - let ref = ReferenceExp(base: initBlock, referenceType: "weak") + let ref = ReferenceExp(base: initBlock, referenceType: .weak) let param = ParameterExp(name: "self", value: ref) let closure = Closure( capture: { param }, diff --git a/Tests/SyntaxKitTests/Unit/Expressions/ReferenceExp/ReferenceExpBasicTests.swift b/Tests/SyntaxKitTests/Unit/Expressions/ReferenceExp/ReferenceExpBasicTests.swift index 6129335..2f7a95b 100644 --- a/Tests/SyntaxKitTests/Unit/Expressions/ReferenceExp/ReferenceExpBasicTests.swift +++ b/Tests/SyntaxKitTests/Unit/Expressions/ReferenceExp/ReferenceExpBasicTests.swift @@ -42,14 +42,14 @@ internal final class ReferenceExpBasicTests { internal func testBasicWeakReference() { let reference = ReferenceExp( base: VariableExp("self"), - referenceType: "weak" + referenceType: .weak ) let syntax = reference.syntax let description = syntax.description #expect(description.contains("self")) - #expect(reference.captureReferenceType == "weak") + #expect(reference.captureReferenceType == .weak) } /// Tests basic unowned reference expression. @@ -57,14 +57,14 @@ internal final class ReferenceExpBasicTests { internal func testBasicUnownedReference() { let reference = ReferenceExp( base: VariableExp("self"), - referenceType: "unowned" + referenceType: .unowned ) let syntax = reference.syntax let description = syntax.description #expect(description.contains("self")) - #expect(reference.captureReferenceType == "unowned") + #expect(reference.captureReferenceType == .unowned) } /// Tests reference expression with variable base. @@ -72,26 +72,24 @@ internal final class ReferenceExpBasicTests { internal func testReferenceWithVariableBase() { let reference = ReferenceExp( base: VariableExp("delegate"), - referenceType: "weak" + referenceType: .weak ) let syntax = reference.syntax let description = syntax.description #expect(description.contains("delegate")) - #expect(reference.captureReferenceType == "weak") + #expect(reference.captureReferenceType == .weak) } /// Tests reference expression with different reference types. @Test("Reference expression with different reference types generates correct syntax") internal func testReferenceWithDifferentReferenceTypes() { - let weakRef = ReferenceExp(base: VariableExp("self"), referenceType: "weak") - let unownedRef = ReferenceExp(base: VariableExp("self"), referenceType: "unowned") - let strongRef = ReferenceExp(base: VariableExp("self"), referenceType: "strong") + let weakRef = ReferenceExp(base: VariableExp("self"), referenceType: .weak) + let unownedRef = ReferenceExp(base: VariableExp("self"), referenceType: .unowned) - #expect(weakRef.captureReferenceType == "weak") - #expect(unownedRef.captureReferenceType == "unowned") - #expect(strongRef.captureReferenceType == "strong") + #expect(weakRef.captureReferenceType == .weak) + #expect(unownedRef.captureReferenceType == .unowned) } /// Tests capture expression property access. @@ -100,7 +98,7 @@ internal final class ReferenceExpBasicTests { let base = VariableExp("self") let reference = ReferenceExp( base: base, - referenceType: "weak" + referenceType: .weak ) #expect(reference.captureExpression.syntax.description == base.syntax.description) @@ -111,39 +109,9 @@ internal final class ReferenceExpBasicTests { internal func testCaptureReferenceTypePropertyAccess() { let reference = ReferenceExp( base: VariableExp("self"), - referenceType: "unowned" + referenceType: .unowned ) - #expect(reference.captureReferenceType == "unowned") - } - - /// Tests reference expression with empty string reference type. - @Test("Reference expression with empty string reference type generates correct syntax") - internal func testReferenceWithEmptyStringReferenceType() { - let reference = ReferenceExp( - base: VariableExp("self"), - referenceType: "" - ) - - let syntax = reference.syntax - let description = syntax.description - - #expect(description.contains("self")) - #expect(reference.captureReferenceType.isEmpty) - } - - /// Tests reference expression with custom reference type. - @Test("Reference expression with custom reference type generates correct syntax") - internal func testReferenceWithCustomReferenceType() { - let reference = ReferenceExp( - base: VariableExp("self"), - referenceType: "custom" - ) - - let syntax = reference.syntax - let description = syntax.description - - #expect(description.contains("self")) - #expect(reference.captureReferenceType == "custom") + #expect(reference.captureReferenceType == .unowned) } } diff --git a/Tests/SyntaxKitTests/Unit/Expressions/ReferenceExp/ReferenceExpComplexTests.swift b/Tests/SyntaxKitTests/Unit/Expressions/ReferenceExp/ReferenceExpComplexTests.swift index ac4b1ad..ea754db 100644 --- a/Tests/SyntaxKitTests/Unit/Expressions/ReferenceExp/ReferenceExpComplexTests.swift +++ b/Tests/SyntaxKitTests/Unit/Expressions/ReferenceExp/ReferenceExpComplexTests.swift @@ -48,14 +48,14 @@ internal final class ReferenceExpComplexTests { let reference = ReferenceExp( base: conditional, - referenceType: "weak" + referenceType: .weak ) let syntax = reference.syntax let description = syntax.description #expect(description.contains("isEnabled ? enabledValue : disabledValue")) - #expect(reference.captureReferenceType == "weak") + #expect(reference.captureReferenceType == .weak) } /// Tests reference expression with closure base. @@ -64,14 +64,14 @@ internal final class ReferenceExpComplexTests { let closure = Closure(body: { VariableExp("result") }) let reference = ReferenceExp( base: closure, - referenceType: "weak" + referenceType: .weak ) let syntax = reference.syntax let description = syntax.description.normalize() #expect(description.contains("{ result }".normalize())) - #expect(reference.captureReferenceType == "weak") + #expect(reference.captureReferenceType == .weak) } /// Tests reference expression with enum case base. @@ -80,13 +80,13 @@ internal final class ReferenceExpComplexTests { let enumCase = EnumCase("active") let reference = ReferenceExp( base: enumCase, - referenceType: "weak" + referenceType: .weak ) let syntax = reference.syntax let description = syntax.description.normalize() #expect(description.contains(".active".normalize())) - #expect(reference.captureReferenceType == "weak") + #expect(reference.captureReferenceType == .weak) } } diff --git a/Tests/SyntaxKitTests/Unit/Expressions/ReferenceExp/ReferenceExpFunctionTests.swift b/Tests/SyntaxKitTests/Unit/Expressions/ReferenceExp/ReferenceExpFunctionTests.swift index 5eb42ed..c1f162a 100644 --- a/Tests/SyntaxKitTests/Unit/Expressions/ReferenceExp/ReferenceExpFunctionTests.swift +++ b/Tests/SyntaxKitTests/Unit/Expressions/ReferenceExp/ReferenceExpFunctionTests.swift @@ -42,14 +42,14 @@ internal final class ReferenceExpFunctionTests { internal func testReferenceWithFunctionCallBase() { let reference = ReferenceExp( base: Call("getCurrentUser"), - referenceType: "weak" + referenceType: .weak ) let syntax = reference.syntax let description = syntax.description #expect(description.contains("getCurrentUser()")) - #expect(reference.captureReferenceType == "weak") + #expect(reference.captureReferenceType == .weak) } /// Tests reference expression with method call base. @@ -57,14 +57,14 @@ internal final class ReferenceExpFunctionTests { internal func testReferenceWithMethodCallBase() { let reference = ReferenceExp( base: Call("getData"), - referenceType: "weak" + referenceType: .weak ) let syntax = reference.syntax let description = syntax.description #expect(description.contains("getData()")) - #expect(reference.captureReferenceType == "weak") + #expect(reference.captureReferenceType == .weak) } /// Tests reference expression with init call base. @@ -73,13 +73,13 @@ internal final class ReferenceExpFunctionTests { let initCall = Init("String") let reference = ReferenceExp( base: initCall, - referenceType: "weak" + referenceType: .weak ) let syntax = reference.syntax let description = syntax.description #expect(description.contains("String()")) - #expect(reference.captureReferenceType == "weak") + #expect(reference.captureReferenceType == .weak) } } diff --git a/Tests/SyntaxKitTests/Unit/Expressions/ReferenceExp/ReferenceExpLiteralTests.swift b/Tests/SyntaxKitTests/Unit/Expressions/ReferenceExp/ReferenceExpLiteralTests.swift index 156a5f5..831379e 100644 --- a/Tests/SyntaxKitTests/Unit/Expressions/ReferenceExp/ReferenceExpLiteralTests.swift +++ b/Tests/SyntaxKitTests/Unit/Expressions/ReferenceExp/ReferenceExpLiteralTests.swift @@ -42,14 +42,14 @@ internal final class ReferenceExpLiteralTests { internal func testReferenceWithLiteralBase() { let reference = ReferenceExp( base: Literal.ref("constant"), - referenceType: "weak" + referenceType: .weak ) let syntax = reference.syntax let description = syntax.description #expect(description.contains("constant")) - #expect(reference.captureReferenceType == "weak") + #expect(reference.captureReferenceType == .weak) } /// Tests reference expression with array literal base. @@ -57,14 +57,14 @@ internal final class ReferenceExpLiteralTests { internal func testReferenceWithArrayLiteralBase() { let reference = ReferenceExp( base: Literal.array([Literal.string("item1"), Literal.string("item2")]), - referenceType: "weak" + referenceType: .weak ) let syntax = reference.syntax let description = syntax.description #expect(description.contains("[\"item1\", \"item2\"]")) - #expect(reference.captureReferenceType == "weak") + #expect(reference.captureReferenceType == .weak) } /// Tests reference expression with dictionary literal base. @@ -72,14 +72,14 @@ internal final class ReferenceExpLiteralTests { internal func testReferenceWithDictionaryLiteralBase() { let reference = ReferenceExp( base: Literal.dictionary([(Literal.string("key"), Literal.string("value"))]), - referenceType: "weak" + referenceType: .weak ) let syntax = reference.syntax let description = syntax.description.replacingOccurrences(of: " ", with: "") #expect(description.contains("[\"key\":\"value\"]".replacingOccurrences(of: " ", with: ""))) - #expect(reference.captureReferenceType == "weak") + #expect(reference.captureReferenceType == .weak) } /// Tests reference expression with tuple literal base. @@ -87,13 +87,13 @@ internal final class ReferenceExpLiteralTests { internal func testReferenceWithTupleLiteralBase() { let reference = ReferenceExp( base: Literal.tuple([Literal.string("first"), Literal.string("second")]), - referenceType: "weak" + referenceType: .weak ) let syntax = reference.syntax let description = syntax.description #expect(description.contains("(\"first\", \"second\")")) - #expect(reference.captureReferenceType == "weak") + #expect(reference.captureReferenceType == .weak) } } diff --git a/Tests/SyntaxKitTests/Unit/Expressions/ReferenceExp/ReferenceExpPropertyTests.swift b/Tests/SyntaxKitTests/Unit/Expressions/ReferenceExp/ReferenceExpPropertyTests.swift index 8da4632..f720efa 100644 --- a/Tests/SyntaxKitTests/Unit/Expressions/ReferenceExp/ReferenceExpPropertyTests.swift +++ b/Tests/SyntaxKitTests/Unit/Expressions/ReferenceExp/ReferenceExpPropertyTests.swift @@ -42,14 +42,14 @@ internal final class ReferenceExpPropertyTests { internal func testReferenceWithPropertyAccessBase() { let reference = ReferenceExp( base: PropertyAccessExp(base: VariableExp("viewController"), propertyName: "delegate"), - referenceType: "weak" + referenceType: .weak ) let syntax = reference.syntax let description = syntax.description #expect(description.contains("viewController.delegate")) - #expect(reference.captureReferenceType == "weak") + #expect(reference.captureReferenceType == .weak) } /// Tests reference expression with nested property access base. @@ -60,14 +60,14 @@ internal final class ReferenceExpPropertyTests { base: PropertyAccessExp(base: VariableExp("user"), propertyName: "profile"), propertyName: "settings" ), - referenceType: "weak" + referenceType: .weak ) let syntax = reference.syntax let description = syntax.description #expect(description.contains("user.profile.settings")) - #expect(reference.captureReferenceType == "weak") + #expect(reference.captureReferenceType == .weak) } /// Tests reference expression with complex base expression. @@ -78,14 +78,14 @@ internal final class ReferenceExpPropertyTests { base: Call("getUserManager"), propertyName: "currentUser" ), - referenceType: "weak" + referenceType: .weak ) let syntax = reference.syntax let description = syntax.description #expect(description.contains("getUserManager().currentUser")) - #expect(reference.captureReferenceType == "weak") + #expect(reference.captureReferenceType == .weak) } /// Tests reference expression with complex nested expression base. @@ -96,13 +96,13 @@ internal final class ReferenceExpPropertyTests { base: Call("getUserManager"), propertyName: "currentUser" ), - referenceType: "weak" + referenceType: .weak ) let syntax = reference.syntax let description = syntax.description #expect(description.contains("getUserManager().currentUser")) - #expect(reference.captureReferenceType == "weak") + #expect(reference.captureReferenceType == .weak) } } diff --git a/Tests/SyntaxKitTests/Unit/Integration/FrameworkCompatibilityTests.swift b/Tests/SyntaxKitTests/Unit/Integration/FrameworkCompatibilityTests.swift index fe34cd5..6061f0d 100644 --- a/Tests/SyntaxKitTests/Unit/Integration/FrameworkCompatibilityTests.swift +++ b/Tests/SyntaxKitTests/Unit/Integration/FrameworkCompatibilityTests.swift @@ -29,7 +29,7 @@ internal struct FrameworkCompatibilityTests { // MARK: - Error Handling Compatibility Tests - @Test internal func testThrowingTestCompatibility() throws { + @Test internal func testThrowingTestCompatibility() { // Ensure throws declaration works properly with @Test let function = Function("throwingFunction", returns: "String") { Parameter(name: "input", type: "String") @@ -39,7 +39,7 @@ internal struct FrameworkCompatibilityTests { } } - let generated = try function.syntax.description + let generated = function.syntax.description #expect(generated.contains("func throwingFunction")) } diff --git a/Tests/SyntaxKitTests/Unit/Integration/OptionsMacroIntegrationTests.swift b/Tests/SyntaxKitTests/Unit/Integration/OptionsMacroIntegrationTests.swift index 3319c29..f487e4d 100644 --- a/Tests/SyntaxKitTests/Unit/Integration/OptionsMacroIntegrationTests.swift +++ b/Tests/SyntaxKitTests/Unit/Integration/OptionsMacroIntegrationTests.swift @@ -79,24 +79,16 @@ internal struct OptionsMacroIntegrationTests { @Test internal func testCompleteOptionsMacroWorkflow() { // This test demonstrates the complete workflow that the Options macro would use + // for an enum WITH raw values - // Step 1: Determine if enum has raw values (simulated) - let hasRawValues = true let enumName = "TestEnum" - // Step 2: Create the appropriate mappedValues variable - let mappedValuesVariable: Variable - if hasRawValues { - let keyValues: [Int: String] = [1: "first", 2: "second", 3: "third"] - mappedValuesVariable = Variable(.let, name: "mappedValues", equals: keyValues) - .withExplicitType().static() - } else { - let caseNames: [String] = ["first", "second", "third"] - mappedValuesVariable = Variable(.let, name: "mappedValues", equals: caseNames) - .withExplicitType().static() - } - - // Step 3: Create the extension + // Create the mappedValues variable for enum with raw values + let keyValues: [Int: String] = [1: "first", 2: "second", 3: "third"] + let mappedValuesVariable = Variable(.let, name: "mappedValues", equals: keyValues) + .withExplicitType().static() + + // Create the extension let extensionDecl = Extension(enumName) { TypeAlias("MappedType", equals: "String") mappedValuesVariable @@ -117,19 +109,12 @@ internal struct OptionsMacroIntegrationTests { @Test internal func testOptionsMacroWorkflowWithoutRawValues() { // Test the workflow for enums without raw values - let hasRawValues = false let enumName = "SimpleEnum" - let mappedValuesVariable: Variable - if hasRawValues { - let keyValues: [Int: String] = [1: "first", 2: "second"] - mappedValuesVariable = Variable(.let, name: "mappedValues", equals: keyValues) - .withExplicitType().static() - } else { - let caseNames: [String] = ["first", "second"] - mappedValuesVariable = Variable(.let, name: "mappedValues", equals: caseNames) - .withExplicitType().static() - } + // Create the mappedValues variable for enum without raw values + let caseNames: [String] = ["first", "second"] + let mappedValuesVariable = Variable(.let, name: "mappedValues", equals: caseNames) + .withExplicitType().static() let extensionDecl = Extension(enumName) { TypeAlias("MappedType", equals: "String") diff --git a/Tests/SyntaxKitTests/Unit/SwiftUIFeatureTests.swift b/Tests/SyntaxKitTests/Unit/SwiftUIFeatureTests.swift index 480bcc4..42de7ed 100644 --- a/Tests/SyntaxKitTests/Unit/SwiftUIFeatureTests.swift +++ b/Tests/SyntaxKitTests/Unit/SwiftUIFeatureTests.swift @@ -37,7 +37,7 @@ import Testing internal func testSwiftUIExample() throws { // Test the onToggle variable with closure type and attributes let onToggleVariable = Variable(.let, name: "onToggle", type: "(Date) -> Void") - .access("private") + .access(.private) let generatedCode = onToggleVariable.generateCode() let expectedCode = "private let onToggle: (Date) -> Void" @@ -87,25 +87,25 @@ import Testing @Test("Reference method supports different reference types") internal func testReferenceMethodSupportsDifferentTypes() throws { // Test weak reference - let weakRef = VariableExp("self").reference("weak") + let weakRef = VariableExp("self").reference(.weak) // The ReferenceExp itself just shows the base variable name let weakGenerated = weakRef.syntax.description #expect(weakGenerated.contains("self")) // Test unowned reference - let unownedRef = VariableExp("self").reference("unowned") + let unownedRef = VariableExp("self").reference(.unowned) let unownedGenerated = unownedRef.syntax.description #expect(unownedGenerated.contains("self")) // Verify that the reference types are stored correctly if let weakRefExp = weakRef as? ReferenceExp { - #expect(weakRefExp.captureReferenceType == "weak") + #expect(weakRefExp.captureReferenceType == .weak) } else { #expect(false, "Expected ReferenceExp type") } if let unownedRefExp = unownedRef as? ReferenceExp { - #expect(unownedRefExp.captureReferenceType == "unowned") + #expect(unownedRefExp.captureReferenceType == .unowned) } else { #expect(false, "Expected ReferenceExp type") } @@ -116,7 +116,7 @@ import Testing // Test weak reference in closure capture let weakClosure = Closure( capture: { - ParameterExp(unlabeled: VariableExp("self").reference("weak")) + ParameterExp(unlabeled: VariableExp("self").reference(.weak)) }, body: { VariableExp("self").optional().call("handleData") { @@ -126,13 +126,12 @@ import Testing ) let weakGenerated = weakClosure.syntax.description - print("Weak closure generated:\n\(weakGenerated)") #expect(weakGenerated.contains("[weak self]")) // Test unowned reference in closure capture let unownedClosure = Closure( capture: { - ParameterExp(unlabeled: VariableExp("self").reference("unowned")) + ParameterExp(unlabeled: VariableExp("self").reference(.unowned)) }, body: { VariableExp("self").call("handleData") { @@ -142,7 +141,6 @@ import Testing ) let unownedGenerated = unownedClosure.syntax.description - print("Unowned closure generated:\n\(unownedGenerated)") #expect(unownedGenerated.contains("[unowned self]")) } } diff --git a/Tests/SyntaxKitTests/Unit/Variables/VariableCoverageTests.swift b/Tests/SyntaxKitTests/Unit/Variables/VariableCoverageTests.swift index a8bb171..795dba7 100644 --- a/Tests/SyntaxKitTests/Unit/Variables/VariableCoverageTests.swift +++ b/Tests/SyntaxKitTests/Unit/Variables/VariableCoverageTests.swift @@ -44,7 +44,7 @@ internal final class VariableCoverageTests { internal func testBuildAccessModifierPublic() { // Test the "public" case in buildAccessModifier let variable = Variable(.let, name: "test", equals: "value") - .access("public") + .access(.public) let syntax = variable.syntax let description = syntax.description @@ -58,7 +58,7 @@ internal final class VariableCoverageTests { internal func testBuildAccessModifierInternal() { // Test the "internal" case in buildAccessModifier let variable = Variable(.let, name: "test", equals: "value") - .access("internal") + .access(.internal) let syntax = variable.syntax let description = syntax.description @@ -72,7 +72,7 @@ internal final class VariableCoverageTests { internal func testBuildAccessModifierFileprivate() { // Test the "fileprivate" case in buildAccessModifier let variable = Variable(.let, name: "test", equals: "value") - .access("fileprivate") + .access(.fileprivate) let syntax = variable.syntax let description = syntax.description @@ -81,20 +81,6 @@ internal final class VariableCoverageTests { #expect(description.contains("fileprivate let test = \"value\"")) } - /// Tests the default case in buildAccessModifier (unknown access modifier). - @Test("Build access modifier default") - internal func testBuildAccessModifierDefault() { - // Test the default case in buildAccessModifier (unknown access modifier) - let variable = Variable(.let, name: "test", equals: "value") - .access("unknown") - - let syntax = variable.syntax - let description = syntax.description - - // Should fallback to public - #expect(description.contains("public let test = \"value\"")) - } - // MARK: - Variable.swift Coverage Tests /// Tests the fallback type case in Variable initializer.