@expo/expo-modules-macros-plugin 0.4.0 → 0.5.0

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
@@ -1,17 +1,15 @@
1
1
  import SwiftSyntax
2
2
 
3
- /**
4
- A `@JS func` collected for **direct JSI binding**. Instead of describing the function with a
5
- `Function(...)` / `AsyncFunction(...)` DSL entry that the runtime interprets per call,
6
- `@ExpoModule` synthesizes a `_decorateModule` that binds each such function into the module's JS object
7
- via the closure-taking `JavaScriptObject.setProperty(_:)`, with the decode-call-encode body
8
- inlined into the closure. This omits the `[Any]`/`toTuple` dynamic-call path: every argument is
9
- decoded individually by its static type.
10
-
11
- The receiver is the module's real `self` (a module is a singleton instance), so the body calls
12
- `self.<name>(...)` directly and ignores the JS `this`. An `async` `@JS func` produces an `async`
13
- closure body and is installed through the async `setProperty(_:)` overload (so JS gets a promise).
14
- */
3
+ /// A `@JS func` collected for **direct JSI binding**. Instead of describing the function with a
4
+ /// `Function(...)` / `AsyncFunction(...)` DSL entry that the runtime interprets per call, the enclosing
5
+ /// macro synthesizes a decorator (`_decorateModule` / `_decorateSharedObject`) that binds each such
6
+ /// function into the JS object via the closure-taking `JavaScriptObject.setProperty(_:)`, with the
7
+ /// decode-call-encode body inlined into the closure. This omits the `[Any]`/`toTuple` dynamic-call path:
8
+ /// every argument is decoded individually by its static type.
9
+ ///
10
+ /// The receiver (see `Receiver`) is the module's `self` for a module binding, or the per-call `_self`
11
+ /// unwrapped from the JS `this` for a shared-object binding. An `async` `@JS func` produces an `async`
12
+ /// closure body and is installed through the async `setProperty(_:)` overload (so JS gets a promise).
15
13
  internal struct JSFunction {
16
14
  let swiftName: String
17
15
  let jsName: String
@@ -50,19 +48,23 @@ internal struct JSFunction {
50
48
  }
51
49
 
52
50
  /// The decode-call-encode statements that form the host-function body, indented with the given
53
- /// prefix. An arity guard (an exact check when every parameter is required, otherwise a range
54
- /// check) throwing `Exceptions.ArgumentsRangeMismatch`; then the decode of the always-present
55
- /// required prefix (primitives via a direct typed accessor like `asDouble()` on a zero-copy
56
- /// `arguments.unownedValue(at:)`, others via `getDynamicType().cast(...)`); then the call and
57
- /// result encode (primitives via `toJavaScriptValue(in:)`, others via `castToJS(...)`). When a
58
- /// trailing run of parameters is omittable the call branches on `arguments.count`, decoding only
59
- /// the slots that branch actually has — `arguments[i]` traps past `count`, so a slot the caller
60
- /// didn't pass is never indexed.
61
- private func bodyStatements(indent: String) -> String {
51
+ /// prefix. Receiver unwrap (shared objects only), then an arity guard (an exact check when every
52
+ /// parameter is required, otherwise a range check) throwing `Exceptions.ArgumentsRangeMismatch`;
53
+ /// then the decode of the always-present required prefix (primitives via a direct typed accessor
54
+ /// like `asDouble()` on a zero-copy `arguments.unownedValue(at:)`, others via
55
+ /// `getDynamicType().cast(...)`); then the call and result encode (primitives via
56
+ /// `toJavaScriptValue(in:)`, others via `castToJS(...)`). When a trailing run of parameters is
57
+ /// omittable the call branches on `arguments.count`, decoding only the slots that branch actually
58
+ /// has — `arguments[i]` traps past `count`, so a slot the caller didn't pass is never indexed.
59
+ private func bodyStatements(receiver: Receiver, indent: String) -> String {
62
60
  let required = requiredArgumentCount
63
61
  let maximum = parameters.count
64
62
  var lines: [String] = []
65
63
 
64
+ if let unwrap = receiver.unwrapStatement {
65
+ lines.append(unwrap)
66
+ }
67
+
66
68
  if required == maximum {
67
69
  lines.append(
68
70
  """
@@ -87,7 +89,7 @@ internal struct JSFunction {
87
89
 
88
90
  if required == maximum {
89
91
  // No omittable trailing run: a single flat call with every argument decoded.
90
- lines.append(contentsOf: callAndEncodeLines(arity: maximum, decodingFrom: required))
92
+ lines.append(contentsOf: callAndEncodeLines(receiver: receiver, arity: maximum, decodingFrom: required))
91
93
  } else {
92
94
  // One call shape per accepted arity. Each branch decodes only the trailing slots it has and
93
95
  // fills the rest (defaulted params drop their label so Swift applies the default; optional
@@ -105,7 +107,7 @@ internal struct JSFunction {
105
107
  for index in required..<arity {
106
108
  lines.append(" " + decodeStatement(at: index))
107
109
  }
108
- lines.append(" \(callExpression(arity: arity))")
110
+ lines.append(" \(callExpression(receiver: receiver, arity: arity))")
109
111
  }
110
112
  lines.append("}")
111
113
  lines.append(contentsOf: encodeResultLines())
@@ -127,13 +129,14 @@ internal struct JSFunction {
127
129
  if let accessor = fastDecodeAccessor(for: type) {
128
130
  return "let arg\(index) = try arguments.unownedValue(at: \(index)).\(accessor)()"
129
131
  }
130
- return "let arg\(index) = try \(type).getDynamicType().cast(jsValue: arguments[\(index)], appContext: appContext) as! \(type)"
132
+ let exprType = expressionType(type)
133
+ return "let arg\(index) = try \(exprType).getDynamicType().cast(jsValue: arguments[\(index)], appContext: appContext) as! \(exprType)"
131
134
  }
132
135
 
133
- /// The `self.<name>(...)` call for the given arity. Slots `0..<arity` are passed their decoded
136
+ /// The `<callee>.<name>(...)` call for the given arity. Slots `0..<arity` are passed their decoded
134
137
  /// `arg<i>`; a trailing optional-without-default slot that this arity omits is passed `nil`; a
135
138
  /// trailing defaulted slot that this arity omits is dropped entirely so Swift applies its default.
136
- private func callExpression(arity: Int) -> String {
139
+ private func callExpression(receiver: Receiver, arity: Int) -> String {
137
140
  var callArguments: [String] = []
138
141
  for (index, parameter) in parameters.enumerated() {
139
142
  let label = parameter.firstName.text
@@ -154,21 +157,22 @@ internal struct JSFunction {
154
157
  }
155
158
  let tryKeyword = (isThrowing || isAsync) ? "try " : ""
156
159
  let awaitKeyword = isAsync ? "await " : ""
157
- return "\(tryKeyword)\(awaitKeyword)self.\(swiftName)(\(callArguments.joined(separator: ", ")))"
160
+ return "\(tryKeyword)\(awaitKeyword)\(receiver.callee).\(swiftName)(\(callArguments.joined(separator: ", ")))"
158
161
  }
159
162
 
160
163
  /// The flat (single-arity) call-and-encode lines used when no trailing parameter is omittable:
161
- /// `let result = self.f(...)` then the return encode (or the no-return `self.f(...)` + `.undefined`).
162
- private func callAndEncodeLines(arity: Int, decodingFrom: Int) -> [String] {
164
+ /// `let result = <callee>.f(...)` then the return encode (or the no-return `<callee>.f(...)` +
165
+ /// `.undefined`).
166
+ private func callAndEncodeLines(receiver: Receiver, arity: Int, decodingFrom: Int) -> [String] {
163
167
  var lines: [String] = []
164
168
  for index in decodingFrom..<arity {
165
169
  lines.append(decodeStatement(at: index))
166
170
  }
167
171
  if returnType != nil {
168
- lines.append("let result = \(callExpression(arity: arity))")
172
+ lines.append("let result = \(callExpression(receiver: receiver, arity: arity))")
169
173
  lines.append(contentsOf: encodeResultLines())
170
174
  } else {
171
- lines.append(callExpression(arity: arity))
175
+ lines.append(callExpression(receiver: receiver, arity: arity))
172
176
  lines.append("return .undefined")
173
177
  }
174
178
  return lines
@@ -184,7 +188,7 @@ internal struct JSFunction {
184
188
  if fastDecodeAccessor(for: returnType) != nil {
185
189
  return ["return result.toJavaScriptValue(in: runtime)"]
186
190
  }
187
- return ["return try \(returnType).getDynamicType().castToJS(result, appContext: appContext, in: runtime)"]
191
+ return ["return try \(expressionType(returnType)).getDynamicType().castToJS(result, appContext: appContext, in: runtime)"]
188
192
  }
189
193
 
190
194
  /// The `setProperty` statement that installs this function on the JS object. The decode-call-encode
@@ -193,42 +197,44 @@ internal struct JSFunction {
193
197
  /// function the body `await`s the call, which selects the async `setProperty` overload (so JS
194
198
  /// receives a promise).
195
199
  ///
196
- /// Capture mirrors core's `SyncFunctionDefinition.build`: `self` (the module) is captured
197
- /// **strong** — the host-function closure is what keeps the native callable alive for as long as
198
- /// JS can invoke it; its lifetime is bounded by the JS VM's garbage collection of the object.
200
+ /// Capture mirrors core's `SyncFunctionDefinition.build`: a module captures its `self` **strong**
201
+ /// the host-function closure is what keeps the native callable alive for as long as JS can invoke
202
+ /// it; its lifetime is bounded by the JS VM's garbage collection of the object. A shared object
203
+ /// captures nothing of the instance: it recovers the typed receiver from the JS `this` per call.
199
204
  /// `appContext` is captured **weak** (and guarded) so it doesn't form a real retain cycle through
200
205
  /// the app context. When no argument or return value goes through the dynamic-type converter the
201
206
  /// body never references `appContext`, so the capture and guard are omitted to avoid the
202
207
  /// unused-capture warning.
203
- var decorateStatements: String {
204
- // Synchronous `@JS` functions never decode `this` (the receiver is the module's real `self`), so
205
- // they bind through the unowned-`this` `setProperty` overload, which hands `this` in as a borrowed
206
- // `JavaScriptUnownedValue` instead of allocating an owning `JavaScriptValue` and forming its
207
- // `weak`-runtime reference on every call. The first parameter is typed `borrowing
208
+ func decorateStatements(receiver: Receiver) -> String {
209
+ // Synchronous `@JS` bindings bind through the unowned-`this` `setProperty` overload, which hands
210
+ // `this` in as a borrowed `JavaScriptUnownedValue` instead of allocating an owning
211
+ // `JavaScriptValue` and forming its `weak`-runtime reference on every call. A module ignores
212
+ // `this`; a shared object unwraps it (still borrowed). The first parameter is typed `borrowing
208
213
  // JavaScriptUnownedValue` to select that (otherwise `@_disfavoredOverload`) overload — which
209
214
  // requires the *parenthesized, fully typed* parameter list, since Swift rejects a type annotation
210
215
  // on a shorthand `{ [capture] name, name in }` parameter. Async functions keep the untyped
211
216
  // shorthand and the owning-`this` overload: there is no unowned-`this` async variant and the buffer
212
217
  // escapes into the task anyway.
213
- let captures = usesAppContext ? "[weak appContext, self]" : "[self]"
218
+ let captures = receiver.captureClause(usesAppContext: usesAppContext)
214
219
  let parameters =
215
220
  isAsync
216
221
  ? "this, arguments"
217
222
  : "(this: borrowing JavaScriptUnownedValue, arguments: consuming JavaScriptValuesBuffer)"
218
223
 
224
+ let object = receiver.decoratedObject
219
225
  if usesAppContext {
220
226
  return """
221
- object.setProperty("\(jsName)") { \(captures) \(parameters) in
227
+ \(object).setProperty("\(jsName)") { \(captures)\(parameters) in
222
228
  guard let appContext else {
223
229
  throw Exceptions.AppContextLost()
224
230
  }
225
- \(bodyStatements(indent: " "))
231
+ \(bodyStatements(receiver: receiver, indent: " "))
226
232
  }
227
233
  """
228
234
  }
229
235
  return """
230
- object.setProperty("\(jsName)") { \(captures) \(parameters) in
231
- \(bodyStatements(indent: " "))
236
+ \(object).setProperty("\(jsName)") { \(captures)\(parameters) in
237
+ \(bodyStatements(receiver: receiver, indent: " "))
232
238
  }
233
239
  """
234
240
  }
@@ -248,17 +254,18 @@ internal struct JSFunction {
248
254
  }
249
255
 
250
256
  /// A `@JS var` collected for **direct JSI binding**. Instead of describing the property with a
251
- /// `Property(...)` DSL entry, `@ExpoModule` synthesizes a get/set accessor into the module's JS
252
- /// object inside `_decorateModule`: it builds a descriptor object (`enumerable` + `get`, and `set`
253
- /// when the property is settable) and installs it with `object.defineProperty(name, descriptor:)`,
254
- /// mirroring core's `PropertyDefinition.buildDescriptor`. The `get`/`set` host functions are
255
- /// installed the same way `@JS func`s are — the closure-taking `setProperty(_:)` overload, with the
256
- /// read/write body inlined into the closure.
257
+ /// `Property(...)` DSL entry, the enclosing macro synthesizes a get/set accessor into the JS object
258
+ /// inside its decorator (`_decorateModule` / `_decorateSharedObject`): it builds a descriptor object
259
+ /// (`enumerable` + `get`, and `set` when the property is settable) and installs it with
260
+ /// `object.defineProperty(name, descriptor:)`, mirroring core's `PropertyDefinition.buildDescriptor`.
261
+ /// The `get`/`set` host functions are installed the same way `@JS func`s are — the closure-taking
262
+ /// `setProperty(_:)` overload, with the read/write body inlined into the closure.
257
263
  ///
258
- /// The receiver is the module's real `self`, so the getter reads `self.<name>` and the setter writes
259
- /// `self.<name> = …` directly, ignoring the JS `this`. Decode/encode of the value reuse the same
260
- /// static-type fast path as functions (primitives through a direct typed accessor / `toJavaScriptValue`,
261
- /// other types through the `getDynamicType()` converter).
264
+ /// The receiver (see `Receiver`) is the module's `self` for a module binding, or the per-call `_self`
265
+ /// unwrapped from the JS `this` for a shared object. The getter reads `<callee>.<name>` and the setter
266
+ /// writes `<callee>.<name> = …`. Decode/encode of the value reuse the same static-type fast path as
267
+ /// functions (primitives through a direct typed accessor / `toJavaScriptValue`, other types through
268
+ /// the `getDynamicType()` converter).
262
269
  internal struct JSProperty {
263
270
  let swiftName: String
264
271
  let jsName: String
@@ -271,54 +278,66 @@ internal struct JSProperty {
271
278
  let isSettable: Bool
272
279
 
273
280
  /// The statements that install this property's accessor on the JS object, indented for the
274
- /// `_decorateModule` body. Builds a descriptor object (`enumerable` + `get`, and `set` when
275
- /// settable) via the closure-taking `setProperty(_:)` overload — with the read/write body inlined
276
- /// into each closure — and installs it with `object.defineProperty(name, descriptor:)`. Capture
277
- /// matches the function bindings: `self` strong, `appContext` weak + guarded and, like functions,
278
- /// the `appContext` capture + guard are omitted from an accessor whose body never references it (a
279
- /// primitive value, decoded/encoded without the dynamic converter), to avoid the unused-capture
280
- /// warning. Getter and setter are gated independently.
281
- var decorateStatements: String {
281
+ /// decorator body. Builds a descriptor object (`enumerable` + `get`, and `set` when settable) via
282
+ /// the closure-taking `setProperty(_:)` overload — with the read/write body inlined into each
283
+ /// closure — and installs it with `object.defineProperty(name, descriptor:)`. Capture matches the
284
+ /// function bindings: a module captures `self` strong, a shared object captures nothing of the
285
+ /// instance; `appContext` is weak + guarded and, like functions, the `appContext` capture + guard
286
+ /// are omitted from an accessor whose body never references it (a primitive value, decoded/encoded
287
+ /// without the dynamic converter), to avoid the unused-capture warning. Getter and setter are gated
288
+ /// independently.
289
+ func decorateStatements(receiver: Receiver) -> String {
282
290
  let descriptorName = "\(swiftName)Descriptor"
291
+ let callee = receiver.callee
292
+ let object = receiver.decoratedObject
283
293
  // A primitive value type encodes/decodes without `getDynamicType()`, so its accessor body never
284
294
  // references `appContext`. `nil` (untyped) goes through the dynamic-less `toJavaScriptValue`
285
295
  // getter, which also doesn't use it.
286
296
  let usesAppContext = valueType.map { fastDecodeAccessor(for: $0) == nil } ?? false
297
+ // A shared object's accessors unwrap the JS `this` into `_self` before reading/writing; a module
298
+ // reads `self` directly. The unwrap leads each accessor body.
299
+ let unwrap = receiver.unwrapStatement.map { "\($0)\n" } ?? ""
287
300
  var lines: [String] = []
288
301
 
289
302
  lines.append("let \(descriptorName) = runtime.createObject()")
290
303
  lines.append("\(descriptorName).setProperty(\"enumerable\", value: true)")
291
304
 
292
- // Getter: read `self.<name>` and encode the result back to JS.
305
+ // Getter: read `<callee>.<name>` and encode the result back to JS.
293
306
  let getEncode: String
294
307
  if let valueType, fastDecodeAccessor(for: valueType) != nil {
295
- getEncode = "return self.\(swiftName).toJavaScriptValue(in: runtime)"
308
+ getEncode = "return \(callee).\(swiftName).toJavaScriptValue(in: runtime)"
296
309
  } else if let valueType {
297
310
  getEncode =
298
- "return try \(valueType).getDynamicType().castToJS(self.\(swiftName), appContext: appContext, in: runtime)"
311
+ "return try \(expressionType(valueType)).getDynamicType().castToJS(\(callee).\(swiftName), appContext: appContext, in: runtime)"
299
312
  } else {
300
- // No known type: fall back to converting whatever `self.<name>` is. This only happens when the
301
- // declaration has neither an annotation nor a literal default, which is rare for a stored var.
302
- getEncode = "return self.\(swiftName).toJavaScriptValue(in: runtime)"
313
+ // No known type: fall back to converting whatever `<callee>.<name>` is. This only happens when
314
+ // the declaration has neither an annotation nor a literal default, which is rare for a stored
315
+ // var.
316
+ getEncode = "return \(callee).\(swiftName).toJavaScriptValue(in: runtime)"
303
317
  }
304
- lines.append(accessorClosure(descriptorName, "get", usesAppContext: usesAppContext, body: getEncode))
318
+ lines.append(
319
+ accessorClosure(
320
+ descriptorName, "get", receiver: receiver, usesAppContext: usesAppContext, body: "\(unwrap)\(getEncode)"))
305
321
 
306
- // Setter: decode argument 0 by the static type and write `self.<name>`. A typed setter needs a
307
- // known value type; when the type couldn't be inferred the property is bound getter-only (a
322
+ // Setter: decode argument 0 by the static type and write `<callee>.<name>`. A typed setter needs
323
+ // a known value type; when the type couldn't be inferred the property is bound getter-only (a
308
324
  // settable var with neither an annotation nor a literal default is rare and can't be decoded).
309
325
  if isSettable, let valueType {
310
326
  let setDecode: String
311
327
  if let accessor = fastDecodeAccessor(for: valueType) {
312
- setDecode = "self.\(swiftName) = try arguments.unownedValue(at: 0).\(accessor)()"
328
+ setDecode = "\(callee).\(swiftName) = try arguments.unownedValue(at: 0).\(accessor)()"
313
329
  } else {
330
+ let exprType = expressionType(valueType)
314
331
  setDecode =
315
- "self.\(swiftName) = try \(valueType).getDynamicType().cast(jsValue: arguments[0], appContext: appContext) as! \(valueType)"
332
+ "\(callee).\(swiftName) = try \(exprType).getDynamicType().cast(jsValue: arguments[0], appContext: appContext) as! \(exprType)"
316
333
  }
317
334
  lines.append(
318
- accessorClosure(descriptorName, "set", usesAppContext: usesAppContext, body: "\(setDecode)\nreturn .undefined"))
335
+ accessorClosure(
336
+ descriptorName, "set", receiver: receiver, usesAppContext: usesAppContext,
337
+ body: "\(unwrap)\(setDecode)\nreturn .undefined"))
319
338
  }
320
339
 
321
- lines.append("object.defineProperty(\"\(jsName)\", descriptor: \(descriptorName))")
340
+ lines.append("\(object).defineProperty(\"\(jsName)\", descriptor: \(descriptorName))")
322
341
 
323
342
  return lines
324
343
  .flatMap { $0.split(separator: "\n", omittingEmptySubsequences: false) }
@@ -326,26 +345,29 @@ internal struct JSProperty {
326
345
  .joined(separator: "\n")
327
346
  }
328
347
 
329
- /// One `descriptor.setProperty("get"/"set") { … }` accessor entry. Captures `self` strong and, when
330
- /// `usesAppContext`, `appContext` weak + guarded (matching the function bindings); otherwise the
331
- /// capture and guard are omitted so a primitive accessor doesn't warn on an unused capture.
348
+ /// One `descriptor.setProperty("get"/"set") { … }` accessor entry. The capture list follows the
349
+ /// receiver (a module captures `self` strong; a shared object captures nothing of the instance) and,
350
+ /// when `usesAppContext`, adds `appContext` weak + guarded (matching the function bindings);
351
+ /// otherwise the guard is omitted so a primitive accessor doesn't warn on an unused capture.
332
352
  private func accessorClosure(
333
- _ descriptorName: String, _ key: String, usesAppContext: Bool, body: String
353
+ _ descriptorName: String, _ key: String, receiver: Receiver, usesAppContext: Bool, body: String
334
354
  ) -> String {
355
+ let captures = receiver.captureClause(usesAppContext: usesAppContext)
335
356
  // Indent each line of a (possibly multi-line) body to sit one level inside the closure, aligned
336
357
  // with the `guard`; a bare `\(body)` interpolation would only indent the first line.
337
358
  let indentedBody = body
338
359
  .split(separator: "\n", omittingEmptySubsequences: false)
339
360
  .map { " \($0)" }
340
361
  .joined(separator: "\n")
341
- // Property `get`/`set` accessors are always synchronous and never decode `this`, so they bind
342
- // through the unowned-`this` `setProperty` overload like sync functions. The parameter list is
343
- // parenthesized and fully typed because Swift rejects a type annotation on a shorthand closure
344
- // parameter; the explicit `borrowing JavaScriptUnownedValue` selects the unowned-`this` overload.
362
+ // Property `get`/`set` accessors are always synchronous, so they bind through the unowned-`this`
363
+ // `setProperty` overload like sync functions. The parameter list is parenthesized and fully typed
364
+ // because Swift rejects a type annotation on a shorthand closure parameter; the explicit
365
+ // `borrowing JavaScriptUnownedValue` selects the unowned-`this` overload. A module ignores `this`;
366
+ // a shared object unwraps it in the body.
345
367
  let parameters = "(this: borrowing JavaScriptUnownedValue, arguments: consuming JavaScriptValuesBuffer)"
346
368
  if usesAppContext {
347
369
  return """
348
- \(descriptorName).setProperty("\(key)") { [weak appContext, self] \(parameters) in
370
+ \(descriptorName).setProperty("\(key)") { \(captures)\(parameters) in
349
371
  guard let appContext else {
350
372
  throw Exceptions.AppContextLost()
351
373
  }
@@ -354,24 +376,31 @@ internal struct JSProperty {
354
376
  """
355
377
  }
356
378
  return """
357
- \(descriptorName).setProperty("\(key)") { [self] \(parameters) in
379
+ \(descriptorName).setProperty("\(key)") { \(captures)\(parameters) in
358
380
  \(indentedBody)
359
381
  }
360
382
  """
361
383
  }
362
384
  }
363
385
 
386
+ /// The body shared by both decorators: every `@JS func` bound via an inlined `setProperty` closure
387
+ /// and every `@JS var` via a `defineProperty` accessor, joined for the function body. The `receiver`
388
+ /// selects how each binding reaches its Swift value (module `self` vs. shared-object `_self`).
389
+ private func decorateBody(functions: [JSFunction], properties: [JSProperty], receiver: Receiver) -> String {
390
+ let functionBody = functions.map { $0.decorateStatements(receiver: receiver) }
391
+ let propertyBody = properties.map { $0.decorateStatements(receiver: receiver) }
392
+ return (functionBody + propertyBody).joined(separator: "\n")
393
+ }
394
+
364
395
  /// The single generated function that decorates the module's JS object. Core supplies the object;
365
396
  /// this binds every `@JS func` (via an inlined `setProperty` closure) and every `@JS var` (via a
366
397
  /// `defineProperty` accessor) into it. Mirrors core's `ObjectDefinition.decorate(object:)`, including
367
398
  /// its `borrowing` object parameter (it mutates through the reference without reassigning or taking
368
399
  /// ownership). Named `_decorateModule` with the leading-underscore convention for synthesized members
369
400
  /// the **runtime calls by name**; the `ExpoModule` suffix names the `@ExpoModule` macro it came from (a
370
- /// shared object's counterpart is `_decorateSharedObject`).
401
+ /// shared object's counterpart is `_decorateSharedObject`). The bindings call into the module `self`.
371
402
  internal func buildDecorateJavaScriptObject(functions: [JSFunction], properties: [JSProperty]) -> DeclSyntax {
372
- let functionBody = functions.map { $0.decorateStatements }
373
- let propertyBody = properties.map { $0.decorateStatements }
374
- let body = (functionBody + propertyBody).joined(separator: "\n")
403
+ let body = decorateBody(functions: functions, properties: properties, receiver: .module)
375
404
  return """
376
405
  @JavaScriptActor
377
406
  public func _decorateModule(object: borrowing JavaScriptObject, in runtime: JavaScriptRuntime, appContext: AppContext) throws {
@@ -380,23 +409,24 @@ internal func buildDecorateJavaScriptObject(functions: [JSFunction], properties:
380
409
  """
381
410
  }
382
411
 
383
- /// The throwing `JavaScriptUnownedValue` accessor that decodes the given primitive type directly,
384
- /// bypassing the dynamic-type converter (`asDouble()` for `Double`, etc.). Returns `nil` for
385
- /// types without a dedicated accessor arrays, records, optionals, shared objects, other numeric
386
- /// widths which decode through `getDynamicType().cast(...)`.
387
- private func fastDecodeAccessor(for type: String) -> String? {
388
- switch type {
389
- case "Bool":
390
- return "asBool"
391
- case "Int":
392
- return "asInt"
393
- case "Double":
394
- return "asDouble"
395
- case "String":
396
- return "asString"
397
- default:
398
- return nil
399
- }
412
+ /// The shared-object counterpart of `_decorateModule`. Core supplies the class `prototype`; this binds
413
+ /// every `@JS func` and `@JS var` of the given shared-object type onto it. Because a shared object has a
414
+ /// distinct native instance behind each JS object, the bindings are **static** and recover the typed
415
+ /// receiver from the JS `this` per call (`try SharedObject.native(from: this.asObject(in: runtime), as: <Type>.self)`)
416
+ /// rather than capturing a singleton `self`. The first parameter is `prototype` (not `object` as on
417
+ /// `_decorateModule`) because it's the shared class prototype, not an instance. The constructor is
418
+ /// bound separately (see `JSConstructor.buildConstructor`). Only emitted when the type has at least one
419
+ /// `@JS func`/`var`.
420
+ internal func buildDecorateSharedObject(
421
+ functions: [JSFunction], properties: [JSProperty], typeName: String
422
+ ) -> DeclSyntax {
423
+ let body = decorateBody(functions: functions, properties: properties, receiver: .sharedObject(typeName: typeName))
424
+ return """
425
+ @JavaScriptActor
426
+ public static func _decorateSharedObject(prototype: borrowing JavaScriptObject, in runtime: JavaScriptRuntime, appContext: AppContext) throws {
427
+ \(raw: body)
428
+ }
429
+ """
400
430
  }
401
431
 
402
432
  /// True when a return clause is absent or written as `Void` / `()` — i.e. the function returns
@@ -244,55 +244,3 @@ private func hasAppContextInitializer(_ classDecl: ClassDeclSyntax) -> Bool {
244
244
  }
245
245
  return false
246
246
  }
247
-
248
- // MARK: - Member builders
249
-
250
- private func collectProperties(
251
- varDecl: VariableDeclSyntax,
252
- attribute: AttributeSyntax
253
- ) -> [JSProperty] {
254
- let jsNameOverride = jsNameArgument(of: attribute)
255
- // A `let` is never settable; only `var` bindings can carry a setter.
256
- let isVar = varDecl.bindingSpecifier.tokenKind == .keyword(.var)
257
-
258
- return varDecl.bindings.compactMap { binding in
259
- guard let ident = binding.pattern.as(IdentifierPatternSyntax.self) else {
260
- return nil
261
- }
262
- let swiftName = ident.identifier.text
263
- // Prefer the explicit annotation; recover the type from a literal default (`var x = false`)
264
- // when there's none. `nil` falls back to inference at the use site.
265
- let valueType = binding.typeAnnotation?.type.trimmedDescription
266
- ?? binding.initializer.flatMap { inferredLiteralType(of: $0.value) }
267
- return JSProperty(
268
- swiftName: swiftName,
269
- jsName: jsNameOverride ?? swiftName,
270
- valueType: valueType,
271
- isSettable: isVar && bindingIsSettable(binding)
272
- )
273
- }
274
- }
275
-
276
- /// Whether a `var` binding is settable from JS. A stored property (no accessor block) is settable;
277
- /// a computed property is settable only when it declares an explicit `set` accessor. A getter-only
278
- /// computed property (`{ get }` or a single getter body) stays read-only. `willSet`/`didSet`
279
- /// observers imply stored storage, which is also settable.
280
- private func bindingIsSettable(_ binding: PatternBindingSyntax) -> Bool {
281
- guard let accessorBlock = binding.accessorBlock else {
282
- return true
283
- }
284
- switch accessorBlock.accessors {
285
- case .accessors(let accessors):
286
- return accessors.contains { accessor in
287
- switch accessor.accessorSpecifier.tokenKind {
288
- case .keyword(.set), .keyword(.willSet), .keyword(.didSet):
289
- return true
290
- default:
291
- return false
292
- }
293
- }
294
- case .getter:
295
- return false
296
- }
297
- }
298
-
@@ -0,0 +1,60 @@
1
+ import SwiftSyntax
2
+
3
+ /// A `@JS init` collected for direct JSI binding. A shared-object type has at most one (JS classes
4
+ /// have a single constructor). Instead of a `Constructor { … }` DSL entry, the macro synthesizes a
5
+ /// static `_constructSharedObject(...)` that decodes the JS arguments and returns a fresh instance;
6
+ /// unlike the method/property bindings it produces the native instance rather than recovering one.
7
+ internal struct JSConstructor {
8
+ let parameters: [FunctionParameterSyntax]
9
+
10
+ init(initDecl: InitializerDeclSyntax) {
11
+ self.parameters = Array(initDecl.signature.parameterClause.parameters)
12
+ }
13
+
14
+ /// The body statements, indented with `indent`: arity guard, per-argument decode (primitives via a
15
+ /// typed accessor, others via the dynamic converter), then `return <Type>(label: arg0, …)`.
16
+ private func bodyStatements(typeName: String, indent: String) -> String {
17
+ var lines: [String] = []
18
+
19
+ lines.append(
20
+ """
21
+ guard arguments.count == \(parameters.count) else {
22
+ throw Exceptions.ArgumentsRangeMismatch((functionName: "\(typeName)", received: arguments.count, required: \(parameters.count), maximum: \(parameters.count)))
23
+ }
24
+ """)
25
+
26
+ var callArguments: [String] = []
27
+ for (index, parameter) in parameters.enumerated() {
28
+ let type = parameter.type.trimmedDescription
29
+
30
+ if let accessor = fastDecodeAccessor(for: type) {
31
+ lines.append("let arg\(index) = try arguments.unownedValue(at: \(index)).\(accessor)()")
32
+ } else {
33
+ let exprType = expressionType(type)
34
+ lines.append(
35
+ "let arg\(index) = try \(exprType).getDynamicType().cast(jsValue: arguments[\(index)], appContext: appContext) as! \(exprType)")
36
+ }
37
+
38
+ let label = parameter.firstName.text
39
+ callArguments.append(label == "_" ? "arg\(index)" : "\(label): arg\(index)")
40
+ }
41
+
42
+ lines.append("return \(typeName)(\(callArguments.joined(separator: ", ")))")
43
+
44
+ return lines
45
+ .flatMap { $0.split(separator: "\n", omittingEmptySubsequences: false) }
46
+ .map { indent + $0 }
47
+ .joined(separator: "\n")
48
+ }
49
+
50
+ /// The static `_constructSharedObject` entry point the runtime calls to build an instance from JS
51
+ /// arguments, returning the concrete type. `this`/`appContext` may go unreferenced, which is harmless.
52
+ func buildConstructor(typeName: String) -> DeclSyntax {
53
+ return """
54
+ @JavaScriptActor
55
+ public static func _constructSharedObject(this: JavaScriptValue, arguments: borrowing JavaScriptValuesBuffer, in runtime: JavaScriptRuntime, appContext: AppContext) throws -> \(raw: typeName) {
56
+ \(raw: bodyStatements(typeName: typeName, indent: " "))
57
+ }
58
+ """
59
+ }
60
+ }
@@ -245,3 +245,87 @@ extension AttributeListSyntax {
245
245
  return nil
246
246
  }
247
247
  }
248
+
249
+ /// A type spelled so it's valid in expression position (before `.getDynamicType()` or after `as!`).
250
+ /// Implicitly-unwrapped optionals (`T!`) are only allowed in type-annotation position, so a trailing
251
+ /// `!` is rewritten to `?` (`T!` and `T?` are both `Optional<T>`, which the dynamic-type / cast layer
252
+ /// treats identically). Other type spellings pass through unchanged.
253
+ internal func expressionType(_ type: String) -> String {
254
+ guard type.hasSuffix("!") else {
255
+ return type
256
+ }
257
+ return type.dropLast() + "?"
258
+ }
259
+
260
+ // MARK: - @JS property collection
261
+
262
+ /// Collects the `@JS var` bindings of a declaration into `JSProperty` values for direct JSI binding.
263
+ /// Shared between `@ExpoModule` and `@SharedObject` — the resulting properties are receiver-agnostic;
264
+ /// the decorator that emits them picks the receiver (module `self` vs. shared-object `_self`).
265
+ internal func collectProperties(
266
+ varDecl: VariableDeclSyntax,
267
+ attribute: AttributeSyntax
268
+ ) -> [JSProperty] {
269
+ let jsNameOverride = jsNameArgument(of: attribute)
270
+ // A `let` is never settable; only `var` bindings can carry a setter.
271
+ let isVar = varDecl.bindingSpecifier.tokenKind == .keyword(.var)
272
+
273
+ return varDecl.bindings.compactMap { binding in
274
+ guard let ident = binding.pattern.as(IdentifierPatternSyntax.self) else {
275
+ return nil
276
+ }
277
+ let swiftName = ident.identifier.text
278
+ // Prefer the explicit annotation; recover the type from a literal default (`var x = false`)
279
+ // when there's none. `nil` falls back to inference at the use site.
280
+ let valueType = binding.typeAnnotation?.type.trimmedDescription
281
+ ?? binding.initializer.flatMap { inferredLiteralType(of: $0.value) }
282
+ return JSProperty(
283
+ swiftName: swiftName,
284
+ jsName: jsNameOverride ?? swiftName,
285
+ valueType: valueType,
286
+ isSettable: isVar && bindingIsSettable(binding)
287
+ )
288
+ }
289
+ }
290
+
291
+ /// Whether a `var` binding is settable from JS. A stored property (no accessor block) is settable;
292
+ /// a computed property is settable only when it declares an explicit `set` accessor. A getter-only
293
+ /// computed property (`{ get }` or a single getter body) stays read-only. `willSet`/`didSet`
294
+ /// observers imply stored storage, which is also settable.
295
+ private func bindingIsSettable(_ binding: PatternBindingSyntax) -> Bool {
296
+ guard let accessorBlock = binding.accessorBlock else {
297
+ return true
298
+ }
299
+ switch accessorBlock.accessors {
300
+ case .accessors(let accessors):
301
+ return accessors.contains { accessor in
302
+ switch accessor.accessorSpecifier.tokenKind {
303
+ case .keyword(.set), .keyword(.willSet), .keyword(.didSet):
304
+ return true
305
+ default:
306
+ return false
307
+ }
308
+ }
309
+ case .getter:
310
+ return false
311
+ }
312
+ }
313
+
314
+ /// The throwing `JavaScriptUnownedValue` accessor that decodes the given primitive type directly
315
+ /// (`asDouble()` for `Double`, etc.), bypassing the dynamic-type converter. Returns `nil` for types
316
+ /// without a dedicated accessor (arrays, records, optionals, shared objects, other numeric widths),
317
+ /// which decode through `getDynamicType().cast(...)`.
318
+ func fastDecodeAccessor(for type: String) -> String? {
319
+ switch type {
320
+ case "Bool":
321
+ return "asBool"
322
+ case "Int":
323
+ return "asInt"
324
+ case "Double":
325
+ return "asDouble"
326
+ case "String":
327
+ return "asString"
328
+ default:
329
+ return nil
330
+ }
331
+ }
@@ -0,0 +1,58 @@
1
+ import SwiftSyntax
2
+
3
+ /// Where a directly-bound closure gets the Swift value it calls into. A module is a singleton, so its
4
+ /// bindings call `self` and ignore the JS `this`; a shared object has a distinct native instance per JS
5
+ /// object, so its bindings recover the typed receiver from `this`.
6
+ internal enum Receiver {
7
+ /// The module singleton; the closure captures `self` strong.
8
+ case module
9
+ /// A shared object of the given concrete type; the closure captures nothing and recovers the receiver
10
+ /// from `this` per call.
11
+ case sharedObject(typeName: String)
12
+
13
+ /// The expression the body calls members on: `self` for a module, `_self` (bound by `unwrapStatement`)
14
+ /// for a shared object. The leading underscore avoids colliding with a user member like `var owner`.
15
+ var callee: String {
16
+ switch self {
17
+ case .module:
18
+ return "self"
19
+ case .sharedObject:
20
+ return "_self"
21
+ }
22
+ }
23
+
24
+ /// The JS object the decorator binds members onto, matching its first parameter: `object` for a
25
+ /// module (its own JS object), `prototype` for a shared object (the shared class prototype).
26
+ var decoratedObject: String {
27
+ switch self {
28
+ case .module:
29
+ return "object"
30
+ case .sharedObject:
31
+ return "prototype"
32
+ }
33
+ }
34
+
35
+ /// The leading body line binding the receiver, or `nil` for a module (it reads `self` directly). For a
36
+ /// shared object, `native(from:as:)` recovers the typed instance from the borrowed `this`, throwing on
37
+ /// a foreign object or a type mismatch.
38
+ var unwrapStatement: String? {
39
+ switch self {
40
+ case .module:
41
+ return nil
42
+ case .sharedObject(let typeName):
43
+ return "let _self = try SharedObject.native(from: this.asObject(in: runtime), as: \(typeName).self)"
44
+ }
45
+ }
46
+
47
+ /// The capture-clause fragment (with a trailing space, or empty when nothing is captured). A module
48
+ /// captures `self` strong; a shared object captures nothing of the instance. `appContext`, when used,
49
+ /// is captured weak in both cases.
50
+ func captureClause(usesAppContext: Bool) -> String {
51
+ switch self {
52
+ case .module:
53
+ return usesAppContext ? "[weak appContext, self] " : "[self] "
54
+ case .sharedObject:
55
+ return usesAppContext ? "[weak appContext] " : ""
56
+ }
57
+ }
58
+ }
@@ -316,7 +316,8 @@ private func jsObjectReadLines(properties: [RecordProperty]) -> [String] {
316
316
  var lines: [String] = []
317
317
  for property in properties {
318
318
  let valueVar = "\(property.name)JSValue"
319
- let cast = "try \(property.type).getDynamicType().cast(jsValue: \(valueVar), appContext: appContext) as! \(property.type)"
319
+ let exprType = expressionType(property.type)
320
+ let cast = "try \(exprType).getDynamicType().cast(jsValue: \(valueVar), appContext: appContext) as! \(exprType)"
320
321
  lines.append(" let \(valueVar) = object.getProperty(\"\(property.name)\")")
321
322
  if property.isRequired {
322
323
  lines.append(" guard !\(valueVar).isUndefined() else {")
@@ -337,7 +338,8 @@ private func dictionaryReadLines(properties: [RecordProperty]) -> [String] {
337
338
  var lines: [String] = []
338
339
  for property in properties {
339
340
  let valueVar = "\(property.name)Value"
340
- let cast = "try \(property.type).getDynamicType().cast(\(valueVar), appContext: appContext) as! \(property.type)"
341
+ let exprType = expressionType(property.type)
342
+ let cast = "try \(exprType).getDynamicType().cast(\(valueVar), appContext: appContext) as! \(exprType)"
341
343
  lines.append(" let \(valueVar) = dictionary[\"\(property.name)\"]")
342
344
  if property.isRequired {
343
345
  lines.append(" guard let \(valueVar) else {")
@@ -399,7 +401,7 @@ private func toObjectMethod(properties: [RecordProperty], inheritsRecord: Bool)
399
401
  lines.append(" let object = try appContext.runtime.createObject()")
400
402
  }
401
403
  for property in properties {
402
- lines.append(" object.setProperty(\"\(property.name)\", value: try \(property.type).getDynamicType().convertToJS(self.\(property.name), appContext: appContext))")
404
+ lines.append(" object.setProperty(\"\(property.name)\", value: try \(expressionType(property.type)).getDynamicType().convertToJS(self.\(property.name), appContext: appContext))")
403
405
  }
404
406
  lines.append(" return object")
405
407
  let body = lines.joined(separator: "\n")
@@ -41,35 +41,38 @@ public struct SharedObjectMacro: MemberMacro {
41
41
  let typeName = classDecl.name.text
42
42
  let jsName = jsNameArgument(of: node) ?? typeName
43
43
 
44
- var entries: [String] = []
45
- var sawConstructor = false
44
+ // `@JS func`s/`var`s and the `@JS init` are bound directly into the shared object's JS object by
45
+ // the synthesized `_decorateSharedObject` / `_constructSharedObject` rather than described with a
46
+ // `Function(...)` / `Property(...)` / `Constructor { … }` DSL entry, so they're collected here
47
+ // instead of appended to the `Class` block. The block keeps only non-`@JS` definitions (none are
48
+ // collected today), so it's empty when every member is `@JS`.
49
+ let entries: [String] = []
50
+ var functions: [JSFunction] = []
51
+ var properties: [JSProperty] = []
52
+ var constructor: JSConstructor?
46
53
 
47
54
  for member in classDecl.memberBlock.members {
48
55
  let decl = member.decl
49
56
 
50
57
  if let initDecl = decl.as(InitializerDeclSyntax.self),
51
58
  initDecl.attributes.firstAttribute(named: "JS") != nil {
52
- if sawConstructor {
59
+ if constructor != nil {
53
60
  throw MacroExpansionErrorMessage(
54
61
  "@SharedObject classes can have at most one @JS initializer; JavaScript classes have a single constructor.")
55
62
  }
56
- sawConstructor = true
57
- entries.append(buildConstructorEntry(initDecl: initDecl, typeName: typeName))
63
+ constructor = JSConstructor(initDecl: initDecl)
58
64
  continue
59
65
  }
60
66
 
61
67
  if let funcDecl = decl.as(FunctionDeclSyntax.self),
62
68
  let attribute = funcDecl.attributes.firstAttribute(named: "JS") {
63
- entries.append(
64
- buildClassFunctionEntry(funcDecl: funcDecl, attribute: attribute, typeName: typeName))
69
+ functions.append(JSFunction(funcDecl: funcDecl, attribute: attribute))
65
70
  continue
66
71
  }
67
72
 
68
73
  if let varDecl = decl.as(VariableDeclSyntax.self),
69
74
  let attribute = varDecl.attributes.firstAttribute(named: "JS") {
70
- entries.append(
71
- contentsOf: buildClassPropertyEntries(
72
- varDecl: varDecl, attribute: attribute, typeName: typeName))
75
+ properties.append(contentsOf: collectProperties(varDecl: varDecl, attribute: attribute))
73
76
  }
74
77
  }
75
78
 
@@ -78,13 +81,27 @@ public struct SharedObjectMacro: MemberMacro {
78
81
  ? " return Class(\"\(jsName)\", \(typeName).self) {\n }"
79
82
  : " return Class(\"\(jsName)\", \(typeName).self) {\n\(lines)\n }"
80
83
 
81
- let method: DeclSyntax = """
84
+ var emitted: [DeclSyntax] = [
85
+ """
82
86
  public static func _synthesizedClassDefinition() -> ClassDefinition {
83
87
  \(raw: body)
84
88
  }
85
89
  """
90
+ ]
91
+
92
+ // Direct JSI binding: one `_decorateSharedObject` that binds each `@JS func`/`var` onto the JS
93
+ // object (unwrapping the per-call receiver from `this`), and a `_constructSharedObject` that
94
+ // builds an instance from the `@JS init` arguments. Each is emitted only when it has something
95
+ // to do.
96
+ if !functions.isEmpty || !properties.isEmpty {
97
+ emitted.append(
98
+ buildDecorateSharedObject(functions: functions, properties: properties, typeName: typeName))
99
+ }
100
+ if let constructor {
101
+ emitted.append(constructor.buildConstructor(typeName: typeName))
102
+ }
86
103
 
87
- return [method]
104
+ return emitted
88
105
  }
89
106
  }
90
107
 
@@ -111,81 +128,3 @@ extension SharedObjectMacro: MemberAttributeMacro {
111
128
  private func inheritsFromSharedObject(_ classDecl: ClassDeclSyntax) -> Bool {
112
129
  return inheritsFromAny(classDecl, names: ["SharedObject"])
113
130
  }
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.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Swift macro plugin for Expo modules",
5
5
  "license": "MIT",
6
6
  "author": "650 Industries, Inc.",