Skip to content

Commit ed9de39

Browse files
authored
Adding Attributes (#72)
1 parent dde3763 commit ed9de39

File tree

16 files changed

+863
-41
lines changed

16 files changed

+863
-41
lines changed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
@objc
2+
class Foo {
3+
@Published var bar: String = "bar"
4+
5+
@available(iOS 17.0, *)
6+
func bar() {
7+
print("bar")
8+
}
9+
10+
@MainActor
11+
func baz() {
12+
print("baz")
13+
}
14+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Class("Foo") {
2+
Variable(.var, name: "bar", type: "String", defaultValue: "bar").attribute("Published")
3+
Function("bar") {
4+
print("bar")
5+
}.attribute("available", arguments: ["iOS 17.0", "*"])
6+
Function("baz") {
7+
}.attribute("objc")}.attribute("objc")

Examples/Remaining/attributes/syntax.json

Whitespace-only changes.

Sources/SyntaxKit/Attribute.swift

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
//
2+
// Attribute.swift
3+
// SyntaxKit
4+
//
5+
// Created by Leo Dion.
6+
// Copyright © 2025 BrightDigit.
7+
//
8+
// Permission is hereby granted, free of charge, to any person
9+
// obtaining a copy of this software and associated documentation
10+
// files (the “Software”), to deal in the Software without
11+
// restriction, including without limitation the rights to use,
12+
// copy, modify, merge, publish, distribute, sublicense, and/or
13+
// sell copies of the Software, and to permit persons to whom the
14+
// Software is furnished to do so, subject to the following
15+
// conditions:
16+
//
17+
// The above copyright notice and this permission notice shall be
18+
// included in all copies or substantial portions of the Software.
19+
//
20+
// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
21+
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
22+
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23+
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
24+
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
25+
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26+
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
27+
// OTHER DEALINGS IN THE SOFTWARE.
28+
//
29+
30+
import SwiftSyntax
31+
32+
/// Internal representation of a Swift attribute with its arguments.
33+
internal struct AttributeInfo {
34+
let name: String
35+
let arguments: [String]
36+
37+
init(name: String, arguments: [String] = []) {
38+
self.name = name
39+
self.arguments = arguments
40+
}
41+
}
42+
43+
/// A Swift attribute that can be used as a property wrapper.
44+
public struct Attribute: CodeBlock {
45+
private let name: String
46+
private let arguments: [String]
47+
48+
/// Creates an attribute with the given name and optional arguments.
49+
/// - Parameters:
50+
/// - name: The attribute name (without the @ symbol).
51+
/// - arguments: The arguments for the attribute, if any.
52+
public init(_ name: String, arguments: [String] = []) {
53+
self.name = name
54+
self.arguments = arguments
55+
}
56+
57+
/// Creates an attribute with a name and a single argument.
58+
/// - Parameters:
59+
/// - name: The name of the attribute (without the @ symbol).
60+
/// - argument: The argument for the attribute.
61+
public init(_ name: String, argument: String) {
62+
self.name = name
63+
self.arguments = [argument]
64+
}
65+
66+
public var syntax: SyntaxProtocol {
67+
var leftParen: TokenSyntax?
68+
var rightParen: TokenSyntax?
69+
var argumentsSyntax: AttributeSyntax.Arguments?
70+
71+
if !arguments.isEmpty {
72+
leftParen = .leftParenToken()
73+
rightParen = .rightParenToken()
74+
75+
let argumentList = arguments.map { argument in
76+
DeclReferenceExprSyntax(baseName: .identifier(argument))
77+
}
78+
79+
argumentsSyntax = .argumentList(
80+
LabeledExprListSyntax(
81+
argumentList.enumerated().map { index, expr in
82+
var element = LabeledExprSyntax(expression: ExprSyntax(expr))
83+
if index < argumentList.count - 1 {
84+
element = element.with(\.trailingComma, .commaToken(trailingTrivia: .space))
85+
}
86+
return element
87+
}
88+
)
89+
)
90+
}
91+
92+
return AttributeSyntax(
93+
atSign: .atSignToken(),
94+
attributeName: IdentifierTypeSyntax(name: .identifier(name)),
95+
leftParen: leftParen,
96+
arguments: argumentsSyntax,
97+
rightParen: rightParen
98+
)
99+
}
100+
}

Sources/SyntaxKit/Class.swift

Lines changed: 77 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -36,28 +36,32 @@ public struct Class: CodeBlock {
3636
private var inheritance: [String] = []
3737
private var genericParameters: [String] = []
3838
private var isFinal: Bool = false
39+
private var attributes: [AttributeInfo] = []
3940

4041
/// Creates a `class` declaration.
4142
/// - Parameters:
4243
/// - name: The name of the class.
43-
/// - generics: A list of generic parameters for the class.
4444
/// - content: A ``CodeBlockBuilder`` that provides the members of the class.
45-
public init(
46-
_ name: String,
47-
generics: [String] = [],
48-
@CodeBlockBuilderResult _ content: () -> [CodeBlock]
49-
) {
45+
public init(_ name: String, @CodeBlockBuilderResult _ content: () -> [CodeBlock]) {
5046
self.name = name
5147
self.members = content()
52-
self.genericParameters = generics
5348
}
5449

55-
/// Sets one or more inherited types (superclass first followed by any protocols).
56-
/// - Parameter types: The list of types to inherit from.
50+
/// Sets the generic parameters for the class.
51+
/// - Parameter generics: The list of generic parameter names.
52+
/// - Returns: A copy of the class with the generic parameters set.
53+
public func generic(_ generics: String...) -> Self {
54+
var copy = self
55+
copy.genericParameters = generics
56+
return copy
57+
}
58+
59+
/// Sets the inheritance for the class.
60+
/// - Parameter type: The type to inherit from.
5761
/// - Returns: A copy of the class with the inheritance set.
58-
public func inherits(_ types: String...) -> Self {
62+
public func inherits(_ type: String) -> Self {
5963
var copy = self
60-
copy.inheritance = types
64+
copy.inheritance = [type]
6165
return copy
6266
}
6367

@@ -69,10 +73,24 @@ public struct Class: CodeBlock {
6973
return copy
7074
}
7175

76+
/// Adds an attribute to the class declaration.
77+
/// - Parameters:
78+
/// - attribute: The attribute name (without the @ symbol).
79+
/// - arguments: The arguments for the attribute, if any.
80+
/// - Returns: A copy of the class with the attribute added.
81+
public func attribute(_ attribute: String, arguments: [String] = []) -> Self {
82+
var copy = self
83+
copy.attributes.append(AttributeInfo(name: attribute, arguments: arguments))
84+
return copy
85+
}
86+
7287
public var syntax: SyntaxProtocol {
7388
let classKeyword = TokenSyntax.keyword(.class, trailingTrivia: .space)
7489
let identifier = TokenSyntax.identifier(name)
7590

91+
// Build attributes
92+
let attributeList = buildAttributeList(from: attributes)
93+
7694
// Generic parameter clause
7795
var genericParameterClause: GenericParameterClauseSyntax?
7896
if !genericParameters.isEmpty {
@@ -139,6 +157,7 @@ public struct Class: CodeBlock {
139157
}
140158

141159
return ClassDeclSyntax(
160+
attributes: attributeList,
142161
modifiers: modifiers,
143162
classKeyword: classKeyword,
144163
name: identifier,
@@ -147,4 +166,51 @@ public struct Class: CodeBlock {
147166
memberBlock: memberBlock
148167
)
149168
}
169+
170+
private func buildAttributeList(from attributes: [AttributeInfo]) -> AttributeListSyntax {
171+
if attributes.isEmpty {
172+
return AttributeListSyntax([])
173+
}
174+
175+
let attributeElements = attributes.map { attribute in
176+
let arguments = attribute.arguments
177+
178+
var leftParen: TokenSyntax?
179+
var rightParen: TokenSyntax?
180+
var argumentsSyntax: AttributeSyntax.Arguments?
181+
182+
if !arguments.isEmpty {
183+
leftParen = .leftParenToken()
184+
rightParen = .rightParenToken()
185+
186+
let argumentList = arguments.map { argument in
187+
DeclReferenceExprSyntax(baseName: .identifier(argument))
188+
}
189+
190+
argumentsSyntax = .argumentList(
191+
LabeledExprListSyntax(
192+
argumentList.enumerated().map { index, expr in
193+
var element = LabeledExprSyntax(expression: ExprSyntax(expr))
194+
if index < argumentList.count - 1 {
195+
element = element.with(\.trailingComma, .commaToken(trailingTrivia: .space))
196+
}
197+
return element
198+
}
199+
)
200+
)
201+
}
202+
203+
return AttributeListSyntax.Element(
204+
AttributeSyntax(
205+
atSign: .atSignToken(),
206+
attributeName: IdentifierTypeSyntax(name: .identifier(attribute.name)),
207+
leftParen: leftParen,
208+
arguments: argumentsSyntax,
209+
rightParen: rightParen
210+
)
211+
)
212+
}
213+
214+
return AttributeListSyntax(attributeElements)
215+
}
150216
}

Sources/SyntaxKit/Enum.swift

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ public struct Enum: CodeBlock {
3434
private let name: String
3535
private let members: [CodeBlock]
3636
private var inheritance: String?
37+
private var attributes: [AttributeInfo] = []
3738

3839
/// Creates an `enum` declaration.
3940
/// - Parameters:
@@ -53,6 +54,17 @@ public struct Enum: CodeBlock {
5354
return copy
5455
}
5556

57+
/// Adds an attribute to the enum declaration.
58+
/// - Parameters:
59+
/// - attribute: The attribute name (without the @ symbol).
60+
/// - arguments: The arguments for the attribute, if any.
61+
/// - Returns: A copy of the enum with the attribute added.
62+
public func attribute(_ attribute: String, arguments: [String] = []) -> Self {
63+
var copy = self
64+
copy.attributes.append(AttributeInfo(name: attribute, arguments: arguments))
65+
return copy
66+
}
67+
5668
public var syntax: SyntaxProtocol {
5769
let enumKeyword = TokenSyntax.keyword(.enum, trailingTrivia: .space)
5870
let identifier = TokenSyntax.identifier(name, trailingTrivia: .space)
@@ -76,12 +88,58 @@ public struct Enum: CodeBlock {
7688
)
7789

7890
return EnumDeclSyntax(
91+
attributes: buildAttributeList(from: attributes),
7992
enumKeyword: enumKeyword,
8093
name: identifier,
8194
inheritanceClause: inheritanceClause,
8295
memberBlock: memberBlock
8396
)
8497
}
98+
99+
private func buildAttributeList(from attributes: [AttributeInfo]) -> AttributeListSyntax {
100+
if attributes.isEmpty {
101+
return AttributeListSyntax([])
102+
}
103+
let attributeElements = attributes.map { attributeInfo in
104+
let arguments = attributeInfo.arguments
105+
106+
var leftParen: TokenSyntax?
107+
var rightParen: TokenSyntax?
108+
var argumentsSyntax: AttributeSyntax.Arguments?
109+
110+
if !arguments.isEmpty {
111+
leftParen = .leftParenToken()
112+
rightParen = .rightParenToken()
113+
114+
let argumentList = arguments.map { argument in
115+
DeclReferenceExprSyntax(baseName: .identifier(argument))
116+
}
117+
118+
argumentsSyntax = .argumentList(
119+
LabeledExprListSyntax(
120+
argumentList.enumerated().map { index, expr in
121+
var element = LabeledExprSyntax(expression: ExprSyntax(expr))
122+
if index < argumentList.count - 1 {
123+
element = element.with(\.trailingComma, .commaToken(trailingTrivia: .space))
124+
}
125+
return element
126+
}
127+
)
128+
)
129+
}
130+
131+
return AttributeListSyntax.Element(
132+
AttributeSyntax(
133+
atSign: .atSignToken(),
134+
attributeName: IdentifierTypeSyntax(name: .identifier(attributeInfo.name)),
135+
leftParen: leftParen,
136+
arguments: argumentsSyntax,
137+
rightParen: rightParen
138+
)
139+
)
140+
}
141+
return AttributeListSyntax(attributeElements)
142+
}
85143
}
86144

87145
/// A Swift `case` declaration inside an `enum`.

0 commit comments

Comments
 (0)