@expo/expo-modules-macros-plugin 0.0.8 → 0.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Binary file
@@ -25,6 +25,7 @@ let package = Package(
25
25
  dependencies: [
26
26
  "ExpoModulesMacros",
27
27
  .product(name: "SwiftSyntaxMacrosTestSupport", package: "swift-syntax"),
28
+ .product(name: "SwiftSyntaxMacrosGenericTestSupport", package: "swift-syntax"),
28
29
  ]
29
30
  ),
30
31
  ]
@@ -0,0 +1,95 @@
1
+ import SwiftSyntax
2
+ import SwiftSyntaxBuilder
3
+ import SwiftSyntaxMacros
4
+
5
+ /**
6
+ Member macro applied to a `Module` subclass. Scans the class body for declarations
7
+ marked with `@JS` and synthesizes a framework-internal `_exposedDefinition()`
8
+ method returning `[AnyDefinition]`. `expo-modules-core` calls it automatically
9
+ and merges the result into the module's definition.
10
+
11
+ @ExpoModule
12
+ public final class MyModule: Module {
13
+ public func definition() -> ModuleDefinition {
14
+ Name("MyModule")
15
+ }
16
+
17
+ @JS
18
+ func greet(name: String) -> String { "Hi, \(name)" }
19
+ }
20
+ */
21
+ public struct ExpoModuleMacro: MemberMacro {
22
+ public static func expansion(
23
+ of node: AttributeSyntax,
24
+ providingMembersOf declaration: some DeclGroupSyntax,
25
+ conformingTo protocols: [TypeSyntax],
26
+ in context: some MacroExpansionContext
27
+ ) throws -> [DeclSyntax] {
28
+ guard let classDecl = declaration.as(ClassDeclSyntax.self) else {
29
+ throw MacroExpansionErrorMessage("@ExpoModule can only be applied to a class")
30
+ }
31
+
32
+ let moduleName = jsNameArgument(of: node) ?? classDecl.name.text
33
+ var entries: [String] = ["Name(\"\(moduleName)\")"]
34
+
35
+ for typeName in classListArgument(of: node, label: "classes") {
36
+ entries.append("\(typeName)._exposedClassDefinition()")
37
+ }
38
+
39
+ for member in classDecl.memberBlock.members {
40
+ let decl = member.decl
41
+
42
+ if let funcDecl = decl.as(FunctionDeclSyntax.self),
43
+ let attribute = funcDecl.attributes.firstAttribute(named: "JS") {
44
+ entries.append(buildFunctionEntry(funcDecl: funcDecl, attribute: attribute))
45
+ continue
46
+ }
47
+
48
+ if let varDecl = decl.as(VariableDeclSyntax.self),
49
+ let attribute = varDecl.attributes.firstAttribute(named: "JS") {
50
+ entries.append(contentsOf: buildPropertyEntries(varDecl: varDecl, attribute: attribute))
51
+ }
52
+ }
53
+
54
+ let lines = entries.map { " \($0)" }.joined(separator: ",\n")
55
+ let body = " return [\n\(lines)\n ]"
56
+
57
+ let method: DeclSyntax = """
58
+ public func _exposedDefinition() -> [AnyDefinition] {
59
+ \(raw: body)
60
+ }
61
+ """
62
+
63
+ return [method]
64
+ }
65
+ }
66
+
67
+ // MARK: - Member builders
68
+
69
+ private func buildFunctionEntry(
70
+ funcDecl: FunctionDeclSyntax,
71
+ attribute: AttributeSyntax
72
+ ) -> String {
73
+ let swiftName = funcDecl.name.text
74
+ let jsName = jsNameArgument(of: attribute) ?? swiftName
75
+ let isAsync = funcDecl.signature.effectSpecifiers?.asyncSpecifier != nil
76
+ let dslEntry = isAsync ? "AsyncFunction" : "Function"
77
+ return "\(dslEntry)(\"\(jsName)\", \(swiftName))"
78
+ }
79
+
80
+ private func buildPropertyEntries(
81
+ varDecl: VariableDeclSyntax,
82
+ attribute: AttributeSyntax
83
+ ) -> [String] {
84
+ let jsNameOverride = jsNameArgument(of: attribute)
85
+
86
+ return varDecl.bindings.compactMap { binding in
87
+ guard let ident = binding.pattern.as(IdentifierPatternSyntax.self) else {
88
+ return nil
89
+ }
90
+ let swiftName = ident.identifier.text
91
+ let jsName = jsNameOverride ?? swiftName
92
+ return "Property(\"\(jsName)\") { self.\(swiftName) }"
93
+ }
94
+ }
95
+
@@ -0,0 +1,29 @@
1
+ import SwiftSyntax
2
+ import SwiftSyntaxMacros
3
+
4
+ /**
5
+ Marker macro applied to module / shared-object members that should be exposed to JavaScript.
6
+ Expands to nothing on its own; `@ExpoModule` and `@SharedObject` discover declarations
7
+ carrying this attribute and generate the corresponding `Function` / `AsyncFunction` /
8
+ `Property` / `Constructor` registrations.
9
+
10
+ Usage:
11
+
12
+ @JS
13
+ func greet(name: String) -> String { ... }
14
+
15
+ @JS("doWork")
16
+ func performWork() async throws { ... }
17
+
18
+ @JS
19
+ var status: String { "ok" }
20
+ */
21
+ public struct JSMacro: PeerMacro {
22
+ public static func expansion(
23
+ of node: AttributeSyntax,
24
+ providingPeersOf declaration: some DeclSyntaxProtocol,
25
+ in context: some MacroExpansionContext
26
+ ) throws -> [DeclSyntax] {
27
+ return []
28
+ }
29
+ }
@@ -0,0 +1,52 @@
1
+ import SwiftSyntax
2
+
3
+ /**
4
+ Reads the first string-literal argument of an attribute, e.g. `@JS("doWork")` -> "doWork".
5
+ Returns nil if the attribute has no arguments or the first argument is not a string literal.
6
+ */
7
+ internal func jsNameArgument(of attribute: AttributeSyntax) -> String? {
8
+ guard let args = attribute.arguments?.as(LabeledExprListSyntax.self),
9
+ let first = args.first,
10
+ let str = first.expression.as(StringLiteralExprSyntax.self),
11
+ let segment = str.segments.first?.as(StringSegmentSyntax.self) else {
12
+ return nil
13
+ }
14
+ return segment.content.text
15
+ }
16
+
17
+ /**
18
+ Reads a labeled array-literal argument of an attribute, e.g. `@ExpoModule(classes: [Foo.self, Bar.self])`,
19
+ and returns the type names referenced (e.g. `["Foo", "Bar"]`). Each element must be a
20
+ `<TypeName>.self` member-access expression; non-conforming elements are skipped silently.
21
+ */
22
+ internal func classListArgument(of attribute: AttributeSyntax, label: String) -> [String] {
23
+ guard let args = attribute.arguments?.as(LabeledExprListSyntax.self) else {
24
+ return []
25
+ }
26
+ for arg in args where arg.label?.text == label {
27
+ guard let array = arg.expression.as(ArrayExprSyntax.self) else {
28
+ return []
29
+ }
30
+ return array.elements.compactMap { element -> String? in
31
+ guard let memberAccess = element.expression.as(MemberAccessExprSyntax.self),
32
+ memberAccess.declName.baseName.text == "self",
33
+ let base = memberAccess.base?.as(DeclReferenceExprSyntax.self) else {
34
+ return nil
35
+ }
36
+ return base.baseName.text
37
+ }
38
+ }
39
+ return []
40
+ }
41
+
42
+ extension AttributeListSyntax {
43
+ internal func firstAttribute(named name: String) -> AttributeSyntax? {
44
+ for element in self {
45
+ if let attr = element.as(AttributeSyntax.self),
46
+ attr.attributeName.trimmedDescription == name {
47
+ return attr
48
+ }
49
+ }
50
+ return nil
51
+ }
52
+ }
@@ -5,5 +5,9 @@ import SwiftSyntaxMacros
5
5
  struct ExpoModulesMacrosPlugin: CompilerPlugin {
6
6
  let providingMacros: [Macro.Type] = [
7
7
  OptimizedFunctionAttachedMacro.self,
8
+ JSMacro.self,
9
+ ExpoModuleMacro.self,
10
+ SharedObjectMacro.self,
11
+ RecordMacro.self,
8
12
  ]
9
13
  }
@@ -0,0 +1,234 @@
1
+ import SwiftSyntax
2
+ import SwiftSyntaxBuilder
3
+ import SwiftSyntaxMacros
4
+
5
+ /**
6
+ Member macro applied to a `Record` type. Scans the body for `@Field`-annotated
7
+ stored properties and synthesizes `_recordFields(of:)`, returning each field
8
+ paired with its compile-time key. This replaces the runtime `Mirror` walk in
9
+ `fieldsOf(_:)` and lets the framework drop the lazy keying that today protects
10
+ the field's options with a `Mutex`.
11
+
12
+ @Record
13
+ struct Options: Record {
14
+ @Field var name: String = ""
15
+ @Field(.required) var count: Int = 0
16
+ @Field(.keyed("custom_key")) var flag: Bool = false
17
+ }
18
+
19
+ Expansion:
20
+
21
+ public static func _recordFields(of instance: Self) -> [RecordFieldDescriptor] {
22
+ var fields: [RecordFieldDescriptor] = []
23
+ fields.append(RecordFieldDescriptor(key: "name", isRequired: false, field: instance._name))
24
+ fields.append(RecordFieldDescriptor(key: "count", isRequired: true, field: instance._count))
25
+ fields.append(RecordFieldDescriptor(key: "custom_key", isRequired: false, field: instance._flag))
26
+ return fields
27
+ }
28
+
29
+ `isRequired` is computed from the `@Field` attribute arguments at compile time so the
30
+ framework can avoid a `Mutex.withLock` per field per call.
31
+
32
+ For classes that inherit from another `@Record`-annotated class, the macro
33
+ prepends `super._recordFields(of: instance)` so inherited fields appear first.
34
+ */
35
+ public struct RecordMacro: MemberMacro, ExtensionMacro {
36
+ public static func expansion(
37
+ of node: AttributeSyntax,
38
+ providingMembersOf declaration: some DeclGroupSyntax,
39
+ conformingTo protocols: [TypeSyntax],
40
+ in context: some MacroExpansionContext
41
+ ) throws -> [DeclSyntax] {
42
+ let isClass = declaration.is(ClassDeclSyntax.self)
43
+ guard declaration.is(StructDeclSyntax.self) || isClass else {
44
+ throw MacroExpansionErrorMessage("@Record can only be applied to a struct or class")
45
+ }
46
+
47
+ var entries: [(propertyName: String, key: String, isRequired: Bool)] = []
48
+
49
+ for member in declaration.memberBlock.members {
50
+ guard let varDecl = member.decl.as(VariableDeclSyntax.self),
51
+ let attribute = varDecl.attributes.firstAttribute(named: "Field") else {
52
+ continue
53
+ }
54
+ for binding in varDecl.bindings {
55
+ guard let ident = binding.pattern.as(IdentifierPatternSyntax.self) else {
56
+ continue
57
+ }
58
+ let propertyName = ident.identifier.text
59
+ let key = explicitKeyArgument(of: attribute) ?? propertyName
60
+ let isRequired = hasRequiredArgument(of: attribute)
61
+ entries.append((propertyName, key, isRequired))
62
+ }
63
+ }
64
+
65
+ let inheritsRecord = isClass && classHasInheritance(declaration)
66
+ let overrideKeyword = inheritsRecord ? "override " : ""
67
+
68
+ var lines: [String] = []
69
+ if inheritsRecord {
70
+ lines.append(" var fields: [RecordFieldDescriptor] = super._recordFields(of: instance)")
71
+ } else {
72
+ lines.append(" var fields: [RecordFieldDescriptor] = []")
73
+ }
74
+ for entry in entries {
75
+ let requiredLiteral = entry.isRequired ? "true" : "false"
76
+ lines.append(" fields.append(RecordFieldDescriptor(key: \"\(entry.key)\", isRequired: \(requiredLiteral), field: instance._\(entry.propertyName)))")
77
+ }
78
+ lines.append(" return fields")
79
+
80
+ let body = lines.joined(separator: "\n")
81
+
82
+ let method: DeclSyntax = """
83
+ public \(raw: overrideKeyword)class func _recordFields(of instance: Self) -> [RecordFieldDescriptor] {
84
+ \(raw: body)
85
+ }
86
+ """
87
+
88
+ let staticMethod: DeclSyntax = """
89
+ public static func _recordFields(of instance: Self) -> [RecordFieldDescriptor] {
90
+ \(raw: body)
91
+ }
92
+ """
93
+
94
+ return [isClass ? method : staticMethod]
95
+ }
96
+
97
+ /**
98
+ Adds `Record` and `_RecordFieldsProvider` conformances. Each is only added when the type
99
+ doesn't already declare it — Swift rejects redundant conformances, so the macro must check
100
+ the inheritance clause syntactically.
101
+
102
+ For class subclasses (which inherit conformance from a `@Record`-annotated parent), no
103
+ extension is emitted at all. The user can write either `@Record struct Options { ... }`
104
+ or `@Record struct Options: Record { ... }`; both work.
105
+ */
106
+ public static func expansion(
107
+ of node: AttributeSyntax,
108
+ attachedTo declaration: some DeclGroupSyntax,
109
+ providingExtensionsOf type: some TypeSyntaxProtocol,
110
+ conformingTo protocols: [TypeSyntax],
111
+ in context: some MacroExpansionContext
112
+ ) throws -> [ExtensionDeclSyntax] {
113
+ if declaration.is(ClassDeclSyntax.self) && classHasInheritance(declaration) {
114
+ return []
115
+ }
116
+ guard declaration.is(StructDeclSyntax.self) || declaration.is(ClassDeclSyntax.self) else {
117
+ return []
118
+ }
119
+
120
+ var conformances: [String] = []
121
+ if !inheritsProtocol(named: "Record", in: declaration) {
122
+ conformances.append("Record")
123
+ }
124
+ if !inheritsProtocol(named: "_RecordFieldsProvider", in: declaration) {
125
+ conformances.append("_RecordFieldsProvider")
126
+ }
127
+ if conformances.isEmpty {
128
+ return []
129
+ }
130
+
131
+ let ext: DeclSyntax = """
132
+ extension \(type.trimmed): \(raw: conformances.joined(separator: ", ")) {}
133
+ """
134
+ guard let extDecl = ext.as(ExtensionDeclSyntax.self) else {
135
+ return []
136
+ }
137
+ return [extDecl]
138
+ }
139
+ }
140
+
141
+ /**
142
+ True if the type's inheritance clause already lists a protocol with the given name.
143
+ Matches either the bare identifier (`Record`) or a qualified member access ending in
144
+ the name (`ExpoModulesCore.Record`). Typealiases can't be resolved syntactically, so a
145
+ user who refers to `Record` via a typealias will get a redundant-conformance error from
146
+ the macro-emitted extension and should drop the alias for the inheritance clause.
147
+ */
148
+ private func inheritsProtocol(named name: String, in declaration: some DeclGroupSyntax) -> Bool {
149
+ let inheritanceClause: InheritanceClauseSyntax?
150
+ if let structDecl = declaration.as(StructDeclSyntax.self) {
151
+ inheritanceClause = structDecl.inheritanceClause
152
+ } else if let classDecl = declaration.as(ClassDeclSyntax.self) {
153
+ inheritanceClause = classDecl.inheritanceClause
154
+ } else {
155
+ return false
156
+ }
157
+ guard let inherited = inheritanceClause?.inheritedTypes else {
158
+ return false
159
+ }
160
+ for entry in inherited {
161
+ let typeSyntax = entry.type
162
+ if let identifier = typeSyntax.as(IdentifierTypeSyntax.self),
163
+ identifier.name.text == name {
164
+ return true
165
+ }
166
+ if let member = typeSyntax.as(MemberTypeSyntax.self),
167
+ member.name.text == name {
168
+ return true
169
+ }
170
+ }
171
+ return false
172
+ }
173
+
174
+ /**
175
+ True if the class declaration has any inheritance clause. Used as a heuristic for
176
+ whether the superclass also conforms to `Record` and provides `_recordFields(of:)`.
177
+ The macro emits an `override` in this case; if the superclass is not a `Record`,
178
+ the user gets a clear compiler error pointing at the missing override target.
179
+ */
180
+ private func classHasInheritance(_ declaration: some DeclGroupSyntax) -> Bool {
181
+ guard let classDecl = declaration.as(ClassDeclSyntax.self),
182
+ let inherited = classDecl.inheritanceClause?.inheritedTypes,
183
+ !inherited.isEmpty else {
184
+ return false
185
+ }
186
+ return true
187
+ }
188
+
189
+ /**
190
+ True if the `@Field` attribute has `.required` as one of its arguments. The check is purely
191
+ syntactic — it matches the literal member-access expression `.required`. Variants like
192
+ `FieldOption.required` or aliased identifiers won't be detected; the framework still falls
193
+ back to the field's runtime `isRequired` for correctness in those cases (see `Record.swift`).
194
+ */
195
+ private func hasRequiredArgument(of attribute: AttributeSyntax) -> Bool {
196
+ guard let args = attribute.arguments?.as(LabeledExprListSyntax.self) else {
197
+ return false
198
+ }
199
+ for arg in args {
200
+ if let member = arg.expression.as(MemberAccessExprSyntax.self),
201
+ member.declName.baseName.text == "required" {
202
+ return true
203
+ }
204
+ }
205
+ return false
206
+ }
207
+
208
+ /**
209
+ Extracts the dictionary key from a `@Field` attribute. Recognizes both forms:
210
+ a bare string literal (`@Field("custom_key")`, relying on
211
+ `FieldOption: ExpressibleByStringLiteral`) and the explicit factory call
212
+ (`@Field(.keyed("custom_key"))`). Returns nil if neither is present; other
213
+ `FieldOption` cases (`.required`, etc.) are ignored.
214
+ */
215
+ private func explicitKeyArgument(of attribute: AttributeSyntax) -> String? {
216
+ guard let args = attribute.arguments?.as(LabeledExprListSyntax.self) else {
217
+ return nil
218
+ }
219
+ for arg in args {
220
+ if let str = arg.expression.as(StringLiteralExprSyntax.self),
221
+ let segment = str.segments.first?.as(StringSegmentSyntax.self) {
222
+ return segment.content.text
223
+ }
224
+ if let call = arg.expression.as(FunctionCallExprSyntax.self),
225
+ let member = call.calledExpression.as(MemberAccessExprSyntax.self),
226
+ member.declName.baseName.text == "keyed",
227
+ let firstArg = call.arguments.first,
228
+ let str = firstArg.expression.as(StringLiteralExprSyntax.self),
229
+ let segment = str.segments.first?.as(StringSegmentSyntax.self) {
230
+ return segment.content.text
231
+ }
232
+ }
233
+ return nil
234
+ }
@@ -0,0 +1,191 @@
1
+ import SwiftSyntax
2
+ import SwiftSyntaxBuilder
3
+ import SwiftSyntaxMacros
4
+
5
+ /**
6
+ Member macro applied to a `SharedObject` subclass. Scans the class body for
7
+ declarations marked with `@JS` and synthesizes `_exposedClassDefinition()`,
8
+ returning a `ClassDefinition` ready to drop into a module's `Class { ... }` slot.
9
+
10
+ @SharedObject
11
+ final class Cache: SharedObject {
12
+ @JS
13
+ init(name: String) { self.name = name }
14
+
15
+ @JS
16
+ func get(_ key: String) -> String? { ... }
17
+
18
+ @JS
19
+ var size: Int { 42 }
20
+ }
21
+
22
+ The companion `@ExpoModule(classes: [Cache.self])` wires the resulting
23
+ definition into the module's exposed surface.
24
+ */
25
+ public struct SharedObjectMacro: MemberMacro {
26
+ public static func expansion(
27
+ of node: AttributeSyntax,
28
+ providingMembersOf declaration: some DeclGroupSyntax,
29
+ conformingTo protocols: [TypeSyntax],
30
+ in context: some MacroExpansionContext
31
+ ) throws -> [DeclSyntax] {
32
+ guard let classDecl = declaration.as(ClassDeclSyntax.self) else {
33
+ throw MacroExpansionErrorMessage("@SharedObject can only be applied to a class")
34
+ }
35
+
36
+ guard inheritsFromSharedObject(classDecl) else {
37
+ throw MacroExpansionErrorMessage(
38
+ "@SharedObject class must inherit from SharedObject. Add `: SharedObject` to the class declaration.")
39
+ }
40
+
41
+ let typeName = classDecl.name.text
42
+ let jsName = jsNameArgument(of: node) ?? typeName
43
+
44
+ var entries: [String] = []
45
+ var sawConstructor = false
46
+
47
+ for member in classDecl.memberBlock.members {
48
+ let decl = member.decl
49
+
50
+ if let initDecl = decl.as(InitializerDeclSyntax.self),
51
+ initDecl.attributes.firstAttribute(named: "JS") != nil {
52
+ if sawConstructor {
53
+ throw MacroExpansionErrorMessage(
54
+ "@SharedObject classes can have at most one @JS initializer; JavaScript classes have a single constructor.")
55
+ }
56
+ sawConstructor = true
57
+ entries.append(buildConstructorEntry(initDecl: initDecl, typeName: typeName))
58
+ continue
59
+ }
60
+
61
+ if let funcDecl = decl.as(FunctionDeclSyntax.self),
62
+ let attribute = funcDecl.attributes.firstAttribute(named: "JS") {
63
+ entries.append(
64
+ buildClassFunctionEntry(funcDecl: funcDecl, attribute: attribute, typeName: typeName))
65
+ continue
66
+ }
67
+
68
+ if let varDecl = decl.as(VariableDeclSyntax.self),
69
+ let attribute = varDecl.attributes.firstAttribute(named: "JS") {
70
+ entries.append(
71
+ contentsOf: buildClassPropertyEntries(
72
+ varDecl: varDecl, attribute: attribute, typeName: typeName))
73
+ }
74
+ }
75
+
76
+ let lines = entries.map { " \($0)" }.joined(separator: "\n")
77
+ let body = entries.isEmpty
78
+ ? " return Class(\"\(jsName)\", \(typeName).self) {\n }"
79
+ : " return Class(\"\(jsName)\", \(typeName).self) {\n\(lines)\n }"
80
+
81
+ let method: DeclSyntax = """
82
+ public static func _exposedClassDefinition() -> ClassDefinition {
83
+ \(raw: body)
84
+ }
85
+ """
86
+
87
+ return [method]
88
+ }
89
+ }
90
+
91
+ // MARK: - Inheritance check
92
+
93
+ private func inheritsFromSharedObject(_ classDecl: ClassDeclSyntax) -> Bool {
94
+ guard let inherited = classDecl.inheritanceClause?.inheritedTypes else {
95
+ return false
96
+ }
97
+ for entry in inherited {
98
+ if baseIdentifier(of: entry.type) == "SharedObject" {
99
+ return true
100
+ }
101
+ }
102
+ return false
103
+ }
104
+
105
+ private func baseIdentifier(of type: TypeSyntax) -> String? {
106
+ if let identifier = type.as(IdentifierTypeSyntax.self) {
107
+ return identifier.name.text
108
+ }
109
+ if let member = type.as(MemberTypeSyntax.self) {
110
+ return member.name.text
111
+ }
112
+ return nil
113
+ }
114
+
115
+ // MARK: - Class-scope entry builders
116
+
117
+ private func buildClassFunctionEntry(
118
+ funcDecl: FunctionDeclSyntax,
119
+ attribute: AttributeSyntax,
120
+ typeName: String
121
+ ) -> String {
122
+ let swiftName = funcDecl.name.text
123
+ let jsName = jsNameArgument(of: attribute) ?? swiftName
124
+ let effects = funcDecl.signature.effectSpecifiers
125
+ let isAsync = effects?.asyncSpecifier != nil
126
+ let isThrowing = effects?.throwsClause?.throwsSpecifier != nil
127
+ let dslEntry = isAsync ? "AsyncFunction" : "Function"
128
+
129
+ let params = funcDecl.signature.parameterClause.parameters
130
+ let closureParamList: String
131
+ let callArgList: String
132
+ if params.isEmpty {
133
+ closureParamList = "(this: \(typeName))"
134
+ callArgList = ""
135
+ } else {
136
+ let typedParams = params.enumerated().map { index, param in
137
+ "_ arg\(index): \(param.type.trimmedDescription)"
138
+ }.joined(separator: ", ")
139
+ closureParamList = "(this: \(typeName), \(typedParams))"
140
+
141
+ callArgList = params.enumerated().map { index, param in
142
+ let label = param.firstName.text
143
+ return label == "_" ? "arg\(index)" : "\(label): arg\(index)"
144
+ }.joined(separator: ", ")
145
+ }
146
+
147
+ let awaitKeyword = isAsync ? "await " : ""
148
+ let tryKeyword = (isAsync || isThrowing) ? "try " : ""
149
+ let callExpr = "\(tryKeyword)\(awaitKeyword)this.\(swiftName)(\(callArgList))"
150
+
151
+ return "\(dslEntry)(\"\(jsName)\") { \(closureParamList) in \(callExpr) }"
152
+ }
153
+
154
+ private func buildClassPropertyEntries(
155
+ varDecl: VariableDeclSyntax,
156
+ attribute: AttributeSyntax,
157
+ typeName: String
158
+ ) -> [String] {
159
+ let jsNameOverride = jsNameArgument(of: attribute)
160
+
161
+ return varDecl.bindings.compactMap { binding in
162
+ guard let ident = binding.pattern.as(IdentifierPatternSyntax.self) else {
163
+ return nil
164
+ }
165
+ let swiftName = ident.identifier.text
166
+ let jsName = jsNameOverride ?? swiftName
167
+ return "Property(\"\(jsName)\") { (this: \(typeName)) in this.\(swiftName) }"
168
+ }
169
+ }
170
+
171
+ private func buildConstructorEntry(
172
+ initDecl: InitializerDeclSyntax,
173
+ typeName: String
174
+ ) -> String {
175
+ let params = initDecl.signature.parameterClause.parameters
176
+
177
+ if params.isEmpty {
178
+ return "Constructor { \(typeName)() }"
179
+ }
180
+
181
+ let argList = params.enumerated().map { index, param in
182
+ "_ arg\(index): \(param.type.trimmedDescription)"
183
+ }.joined(separator: ", ")
184
+
185
+ let callArgs = params.enumerated().map { index, param in
186
+ let label = param.firstName.text
187
+ return label == "_" ? "arg\(index)" : "\(label): arg\(index)"
188
+ }.joined(separator: ", ")
189
+
190
+ return "Constructor { (\(argList)) in \(typeName)(\(callArgs)) }"
191
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@expo/expo-modules-macros-plugin",
3
- "version": "0.0.8",
3
+ "version": "0.0.9",
4
4
  "description": "Swift macro plugin for Expo modules",
5
5
  "license": "MIT",
6
6
  "author": "650 Industries, Inc.",