expo-modules-macros 0.20.0 → 0.22.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.
Files changed (39) hide show
  1. package/.github/workflows/linux.yml +132 -0
  2. package/.github/workflows/publish.yml +160 -11
  3. package/.github/workflows/swift-format.yml +76 -0
  4. package/.github/workflows/windows.yml +3 -28
  5. package/AGENTS.md +19 -9
  6. package/README.md +27 -8
  7. package/apple/ExpoModulesMacros +0 -0
  8. package/apple/Package.resolved +3 -3
  9. package/apple/Package.swift +1 -2
  10. package/apple/Sources/ExpoModulesMacros/ClosureArguments.swift +39 -14
  11. package/apple/Sources/ExpoModulesMacros/DecorateModuleBuilder.swift +117 -25
  12. package/apple/Sources/ExpoModulesMacros/EventMacro.swift +29 -15
  13. package/apple/Sources/ExpoModulesMacros/ExpoModuleMacro.swift +55 -45
  14. package/apple/Sources/ExpoModulesMacros/ExpoModulesOptimizedMacro.swift +2 -1
  15. package/apple/Sources/ExpoModulesMacros/FreeFormTypes.swift +2 -61
  16. package/apple/Sources/ExpoModulesMacros/JSConstructor.swift +6 -3
  17. package/apple/Sources/ExpoModulesMacros/JSMacro.swift +180 -44
  18. package/apple/Sources/ExpoModulesMacros/JSNameCollisions.swift +84 -0
  19. package/apple/Sources/ExpoModulesMacros/MacroHelpers.swift +165 -43
  20. package/apple/Sources/ExpoModulesMacros/RecordMacro.swift +358 -234
  21. package/apple/Sources/ExpoModulesMacros/SharedObjectMacro.swift +48 -32
  22. package/apple/Sources/ExpoModulesMacros/TypeConformanceAssertion.swift +57 -27
  23. package/apple/Sources/ExpoModulesMacros/UnionMacro.swift +23 -9
  24. package/apple/Sources/ExpoModulesMacros/ViewPropsMacro.swift +10 -5
  25. package/apple/Sources/ExpoModulesScanner/CLI.swift +3 -1
  26. package/apple/Sources/ExpoModulesScanner/Core/DetectionVisitor.swift +11 -7
  27. package/apple/Sources/ExpoModulesScanner/Core/ScanBuildConfiguration.swift +2 -1
  28. package/apple/Sources/ExpoModulesScanner/Core/SourceScan.swift +10 -7
  29. package/apple/Sources/ExpoModulesScanner/Exports/Availability.swift +6 -3
  30. package/apple/Sources/ExpoModulesScanner/Exports/ExportedSurface.swift +3 -2
  31. package/apple/Sources/ExpoModulesScanner/Exports/ResolveRefs.swift +15 -5
  32. package/apple/Sources/ExpoModulesScanner/Exports/ScanExports.swift +8 -3
  33. package/apple/Sources/ExpoModulesScanner/Exports/SurfaceVisitor.swift +127 -45
  34. package/apple/Sources/ExpoModulesScanner/Exports/TypeNode.swift +2 -1
  35. package/apple/Sources/ExpoModulesScanner/Modules/ScanModules.swift +8 -2
  36. package/build/index.d.ts +4 -3
  37. package/build/index.js +33 -18
  38. package/build/types.d.ts +7 -1
  39. package/package.json +7 -3
@@ -100,7 +100,8 @@ final class SurfaceVisitor: SyntaxVisitor {
100
100
  accessLevel: spelledAccessLevel(of: node.modifiers),
101
101
  platforms: platforms,
102
102
  file: file
103
- ))
103
+ )
104
+ )
104
105
  } else if inherits(from: enumerableConformanceName, in: node.inheritanceClause) {
105
106
  let rawType = rawValueType(of: node.inheritanceClause)
106
107
  enums.append(
@@ -111,7 +112,8 @@ final class SurfaceVisitor: SyntaxVisitor {
111
112
  accessLevel: spelledAccessLevel(of: node.modifiers),
112
113
  platforms: platforms,
113
114
  file: file
114
- ))
115
+ )
116
+ )
115
117
  }
116
118
  // An enum is often a namespace for other types, so its body is searched too.
117
119
  return .visitChildren
@@ -156,7 +158,8 @@ final class SurfaceVisitor: SyntaxVisitor {
156
158
  accessLevel: accessLevel,
157
159
  platforms: platforms,
158
160
  file: file
159
- ))
161
+ )
162
+ )
160
163
  return
161
164
  }
162
165
 
@@ -173,7 +176,8 @@ final class SurfaceVisitor: SyntaxVisitor {
173
176
  accessLevel: accessLevel,
174
177
  platforms: platforms,
175
178
  file: file
176
- ))
179
+ )
180
+ )
177
181
  return
178
182
  }
179
183
 
@@ -185,7 +189,8 @@ final class SurfaceVisitor: SyntaxVisitor {
185
189
  accessLevel: accessLevel,
186
190
  platforms: platforms,
187
191
  file: file
188
- ))
192
+ )
193
+ )
189
194
  }
190
195
  }
191
196
 
@@ -214,7 +219,8 @@ final class SurfaceVisitor: SyntaxVisitor {
214
219
 
215
220
  if let varDecl = decl.as(VariableDeclSyntax.self),
216
221
  varDecl.attributes.firstAttribute(named: DetectedMacro.js.rawValue) == nil,
217
- let attribute = varDecl.attributes.firstAttribute(named: DetectedMacro.event.rawValue) {
222
+ let attribute = varDecl.attributes.firstAttribute(named: DetectedMacro.event.rawValue)
223
+ {
218
224
  events.append(contentsOf: makeEvents(varDecl: varDecl, attribute: attribute, platforms: platforms))
219
225
  continue
220
226
  }
@@ -224,7 +230,8 @@ final class SurfaceVisitor: SyntaxVisitor {
224
230
  }
225
231
 
226
232
  if let initDecl = decl.as(InitializerDeclSyntax.self),
227
- initDecl.attributes.firstAttribute(named: DetectedMacro.js.rawValue) != nil {
233
+ initDecl.attributes.firstAttribute(named: DetectedMacro.js.rawValue) != nil
234
+ {
228
235
  // At most one `@JS init`; keep the first if a malformed source has more (the macro errors).
229
236
  if initializer == nil {
230
237
  initializer = ExportedInitializer(
@@ -237,13 +244,15 @@ final class SurfaceVisitor: SyntaxVisitor {
237
244
  }
238
245
 
239
246
  if let funcDecl = decl.as(FunctionDeclSyntax.self),
240
- let attribute = funcDecl.attributes.firstAttribute(named: DetectedMacro.js.rawValue) {
241
- functions.append(makeFunction(funcDecl: funcDecl, attribute: attribute, platforms: platforms))
247
+ let attribute = funcDecl.attributes.firstAttribute(named: DetectedMacro.js.rawValue)
248
+ {
249
+ functions.append(contentsOf: makeFunctions(funcDecl: funcDecl, attribute: attribute, platforms: platforms))
242
250
  continue
243
251
  }
244
252
 
245
253
  if let varDecl = decl.as(VariableDeclSyntax.self),
246
- let attribute = varDecl.attributes.firstAttribute(named: DetectedMacro.js.rawValue) {
254
+ let attribute = varDecl.attributes.firstAttribute(named: DetectedMacro.js.rawValue)
255
+ {
247
256
  properties.append(contentsOf: makeProperties(varDecl: varDecl, attribute: attribute, platforms: platforms))
248
257
  }
249
258
  }
@@ -256,10 +265,13 @@ final class SurfaceVisitor: SyntaxVisitor {
256
265
  /// reporting one would describe a surface that does not exist. `@JS` on the same property is
257
266
  /// caught by the caller, which reaches the `@JS` branch first.
258
267
  private func makeEvents(
259
- varDecl: VariableDeclSyntax, attribute: AttributeSyntax, platforms: [ExportedPlatform]
268
+ varDecl: VariableDeclSyntax,
269
+ attribute: AttributeSyntax,
270
+ platforms: [ExportedPlatform]
260
271
  ) -> [ExportedEvent] {
261
272
  guard varDecl.bindingSpecifier.tokenKind != .keyword(.let),
262
- !isTypeLevel(varDecl.modifiers) else {
273
+ !isTypeLevel(varDecl.modifiers)
274
+ else {
263
275
  return []
264
276
  }
265
277
  let override = stringArgument(of: attribute)
@@ -274,7 +286,8 @@ final class SurfaceVisitor: SyntaxVisitor {
274
286
  binding.accessorBlock == nil,
275
287
  let functionType = underlyingFunctionType(of: binding.typeAnnotation?.type),
276
288
  isVoidEventReturn(functionType.returnClause.type),
277
- functionType.parameters.count <= 1 else {
289
+ functionType.parameters.count <= 1
290
+ else {
278
291
  continue
279
292
  }
280
293
  let name = ident.identifier.identifierName
@@ -286,36 +299,53 @@ final class SurfaceVisitor: SyntaxVisitor {
286
299
  isSync: isSync,
287
300
  accessLevel: spelledAccessLevel(of: varDecl.modifiers),
288
301
  platforms: platforms
289
- ))
302
+ )
303
+ )
290
304
  }
291
305
  return result
292
306
  }
293
307
 
294
- /// Builds an `ExportedFunction` from a `@JS func`: JS-name fallback, parameters, a `Void` return as
295
- /// `nil`, and the effect/static flags.
296
- private func makeFunction(
297
- funcDecl: FunctionDeclSyntax, attribute: AttributeSyntax, platforms: [ExportedPlatform]
298
- ) -> ExportedFunction {
308
+ /// Builds the `ExportedFunction` entries for a `@JS func`: JS-name fallback, parameters, a `Void`
309
+ /// return as `nil`, and the effect/static flags. A synchronous function with `.withAsync("name")`
310
+ /// gets a second, `async` entry under that name, for the promise-returning variant the macro binds.
311
+ private func makeFunctions(
312
+ funcDecl: FunctionDeclSyntax,
313
+ attribute: AttributeSyntax,
314
+ platforms: [ExportedPlatform]
315
+ ) -> [ExportedFunction] {
299
316
  let effects = funcDecl.signature.effectSpecifiers
300
317
  let returnType = funcDecl.signature.returnClause?.type
301
- return ExportedFunction(
302
- name: funcDecl.name.identifierName,
303
- jsName: stringArgument(of: attribute) ?? funcDecl.name.identifierName,
304
- parameters: parameters(of: funcDecl.signature.parameterClause),
305
- returns: isVoidType(returnType) ? nil : returnType.map { typeNode(from: $0) },
306
- isAsync: effects?.asyncSpecifier != nil,
307
- isThrowing: effects?.throwsClause?.throwsSpecifier != nil,
308
- isStatic: isTypeLevel(funcDecl.modifiers),
309
- accessLevel: spelledAccessLevel(of: funcDecl.modifiers),
310
- platforms: platforms
311
- )
318
+ let isAsync = effects?.asyncSpecifier != nil
319
+ let exportedParameters = parameters(of: funcDecl.signature.parameterClause)
320
+ let returns = isVoidType(returnType) ? nil : returnType.map { typeNode(from: $0) }
321
+ let makeFunction = { (jsName: String, isAsync: Bool) in
322
+ ExportedFunction(
323
+ name: funcDecl.name.identifierName,
324
+ jsName: jsName,
325
+ parameters: exportedParameters,
326
+ returns: returns,
327
+ isAsync: isAsync,
328
+ isThrowing: effects?.throwsClause?.throwsSpecifier != nil,
329
+ isStatic: isTypeLevel(funcDecl.modifiers),
330
+ accessLevel: spelledAccessLevel(of: funcDecl.modifiers),
331
+ platforms: platforms
332
+ )
333
+ }
334
+ let function = makeFunction(stringArgument(of: attribute) ?? funcDecl.name.identifierName, isAsync)
335
+ // The macro binds no variant for an `async` function: it reports the option there instead.
336
+ guard !isAsync, let variantName = asyncVariantName(of: attribute) else {
337
+ return [function]
338
+ }
339
+ return [function, makeFunction(variantName, true)]
312
340
  }
313
341
 
314
342
  /// Builds the `ExportedProperty` entries for a `@JS var`/`let`. One declaration can introduce several
315
343
  /// bindings (`var a, b: Int`), so this returns an array. The value type is the annotation, else the
316
344
  /// literal default's inferred type, else `nil`.
317
345
  private func makeProperties(
318
- varDecl: VariableDeclSyntax, attribute: AttributeSyntax, platforms: [ExportedPlatform]
346
+ varDecl: VariableDeclSyntax,
347
+ attribute: AttributeSyntax,
348
+ platforms: [ExportedPlatform]
319
349
  ) -> [ExportedProperty] {
320
350
  let isLet = varDecl.bindingSpecifier.tokenKind == .keyword(.let)
321
351
  let isStatic = isTypeLevel(varDecl.modifiers)
@@ -339,7 +369,8 @@ final class SurfaceVisitor: SyntaxVisitor {
339
369
  defaultValue: binding.initializer?.value.trimmedDescription,
340
370
  accessLevel: spelledAccessLevel(of: varDecl.modifiers),
341
371
  platforms: platforms
342
- ))
372
+ )
373
+ )
343
374
  }
344
375
  return result
345
376
  }
@@ -380,7 +411,8 @@ final class SurfaceVisitor: SyntaxVisitor {
380
411
  // `@Record` reads only the properties written directly in the body, so `#if` is not looked into.
381
412
  for member in members {
382
413
  guard let varDecl = member.decl.as(VariableDeclSyntax.self),
383
- !isExcludedRecordModifier(varDecl.modifiers) else {
414
+ !isExcludedRecordModifier(varDecl.modifiers)
415
+ else {
384
416
  continue
385
417
  }
386
418
  let platforms = platformResolver.platforms(of: varDecl)
@@ -406,7 +438,8 @@ final class SurfaceVisitor: SyntaxVisitor {
406
438
  defaultValue: binding.initializer?.value.trimmedDescription,
407
439
  accessLevel: spelledAccessLevel(of: varDecl.modifiers),
408
440
  platforms: platforms
409
- ))
441
+ )
442
+ )
410
443
  }
411
444
  }
412
445
  return properties
@@ -440,7 +473,8 @@ final class SurfaceVisitor: SyntaxVisitor {
440
473
  name: name,
441
474
  rawValue: writtenRawValue(of: element) ?? derivedStringRawValue(for: name, rawType: rawType),
442
475
  platforms: platforms
443
- ))
476
+ )
477
+ )
444
478
  }
445
479
  }
446
480
  return cases
@@ -467,12 +501,17 @@ final class SurfaceVisitor: SyntaxVisitor {
467
501
  for element in caseDecl.elements {
468
502
  guard let parameters = element.parameterClause?.parameters,
469
503
  parameters.count == 1, let parameter = parameters.first,
470
- parameter.defaultValue == nil else {
504
+ parameter.defaultValue == nil
505
+ else {
471
506
  continue
472
507
  }
473
508
  result.append(
474
509
  ExportedUnionMember(
475
- name: element.name.identifierName, type: typeNode(from: parameter.type), platforms: platforms))
510
+ name: element.name.identifierName,
511
+ type: typeNode(from: parameter.type),
512
+ platforms: platforms
513
+ )
514
+ )
476
515
  }
477
516
  }
478
517
  return result
@@ -508,7 +547,8 @@ final class SurfaceVisitor: SyntaxVisitor {
508
547
  }
509
548
  guard let item = node.parent?.as(MemberBlockItemSyntax.self),
510
549
  let parent = declaration(owning: item),
511
- let scope = enclosingTypes(of: parent) else {
550
+ let scope = enclosingTypes(of: parent)
551
+ else {
512
552
  return nil
513
553
  }
514
554
  if let decl = parent.as(ExtensionDeclSyntax.self) {
@@ -633,7 +673,8 @@ private func underlyingFunctionType(of type: TypeSyntax?) -> FunctionTypeSyntax?
633
673
  return underlyingFunctionType(of: attributed.baseType)
634
674
  }
635
675
  if let tuple = type.as(TupleTypeSyntax.self),
636
- tuple.elements.count == 1, let element = tuple.elements.first, element.firstName == nil {
676
+ tuple.elements.count == 1, let element = tuple.elements.first, element.firstName == nil
677
+ {
637
678
  return underlyingFunctionType(of: element.type)
638
679
  }
639
680
  return type.as(FunctionTypeSyntax.self)
@@ -643,7 +684,8 @@ private func underlyingFunctionType(of type: TypeSyntax?) -> FunctionTypeSyntax?
643
684
  /// accepts neither `Swift.Void` nor a parenthesized `(Void)`, so it would drop valid events.
644
685
  private func isVoidEventReturn(_ type: TypeSyntax) -> Bool {
645
686
  if let tuple = type.as(TupleTypeSyntax.self), tuple.elements.count == 1,
646
- let element = tuple.elements.first, element.firstName == nil {
687
+ let element = tuple.elements.first, element.firstName == nil
688
+ {
647
689
  return isVoidEventReturn(element.type)
648
690
  }
649
691
  let text = type.trimmedDescription
@@ -664,6 +706,41 @@ private func boolArgument(of attribute: AttributeSyntax, label: String) -> Bool?
664
706
  return nil
665
707
  }
666
708
 
709
+ /// The name in a `@JS` attribute's `.withAsync("name")` option, in the arguments or inside an array
710
+ /// literal (`@JS("load", [.withAsync("loadAsync")])`), or `nil` when the option is absent or its
711
+ /// argument isn't a plain string literal. Mirrors `asyncVariantOption` and `asyncVariantName` in the
712
+ /// macros.
713
+ private func asyncVariantName(of attribute: AttributeSyntax) -> String? {
714
+ guard let arguments = attribute.arguments?.as(LabeledExprListSyntax.self) else {
715
+ return nil
716
+ }
717
+ let expressions = arguments.flatMap { argument -> [ExprSyntax] in
718
+ if let array = argument.expression.as(ArrayExprSyntax.self) {
719
+ return array.elements.map(\.expression)
720
+ }
721
+ return [argument.expression]
722
+ }
723
+ for expression in expressions {
724
+ guard let call = expression.as(FunctionCallExprSyntax.self),
725
+ let member = call.calledExpression.as(MemberAccessExprSyntax.self),
726
+ member.declName.baseName.text == "withAsync"
727
+ else {
728
+ continue
729
+ }
730
+ guard call.arguments.count == 1,
731
+ let argument = call.arguments.first,
732
+ argument.label == nil,
733
+ let literal = argument.expression.as(StringLiteralExprSyntax.self),
734
+ literal.segments.count == 1,
735
+ let segment = literal.segments.first?.as(StringSegmentSyntax.self)
736
+ else {
737
+ return nil
738
+ }
739
+ return segment.content.text
740
+ }
741
+ return nil
742
+ }
743
+
667
744
  private func isTypeLevel(_ modifiers: DeclModifierListSyntax) -> Bool {
668
745
  modifiers.contains {
669
746
  $0.name.tokenKind == .keyword(.static) || $0.name.tokenKind == .keyword(.class)
@@ -721,7 +798,8 @@ private func writtenRawValue(of element: EnumCaseElementSyntax) -> String? {
721
798
  return value.trimmedDescription
722
799
  }
723
800
  guard literal.segments.count == 1,
724
- let segment = literal.segments.first?.as(StringSegmentSyntax.self) else {
801
+ let segment = literal.segments.first?.as(StringSegmentSyntax.self)
802
+ else {
725
803
  return nil
726
804
  }
727
805
  // The segment's text is the literal's content with its delimiters already stripped, so a plain
@@ -777,7 +855,8 @@ private let knownNonRawValueProtocols: Set<String> = [
777
855
  func rawValueType(of clause: InheritanceClauseSyntax?) -> TypeNode? {
778
856
  guard let first = clause?.inheritedTypes.first?.type,
779
857
  let trailing = first.trimmedDescription.split(separator: ".").last.map(String.init),
780
- !knownNonRawValueProtocols.contains(trailing) else {
858
+ !knownNonRawValueProtocols.contains(trailing)
859
+ else {
781
860
  return nil
782
861
  }
783
862
  return typeNode(from: first)
@@ -801,7 +880,8 @@ extension AttributeListSyntax {
801
880
  fileprivate func firstAttribute(named name: String) -> AttributeSyntax? {
802
881
  for element in self {
803
882
  if let attribute = element.as(AttributeSyntax.self),
804
- attribute.attributeName.trimmedDescription == name {
883
+ attribute.attributeName.trimmedDescription == name
884
+ {
805
885
  return attribute
806
886
  }
807
887
  }
@@ -814,14 +894,16 @@ extension AttributeListSyntax {
814
894
  /// an array literal. A local copy of the macros' helper (the macro target can't be imported here).
815
895
  private func classListArgument(of attribute: AttributeSyntax, label: String) -> [String] {
816
896
  guard let args = attribute.arguments?.as(LabeledExprListSyntax.self),
817
- let array = args.first(where: { $0.label?.text == label })?.expression.as(ArrayExprSyntax.self) else {
897
+ let array = args.first(where: { $0.label?.text == label })?.expression.as(ArrayExprSyntax.self)
898
+ else {
818
899
  return []
819
900
  }
820
901
  return array.elements.compactMap { element in
821
902
  guard let memberAccess = element.expression.as(MemberAccessExprSyntax.self),
822
903
  memberAccess.declName.baseName.text == "self",
823
904
  let base = memberAccess.base,
824
- base.is(DeclReferenceExprSyntax.self) || base.is(MemberAccessExprSyntax.self) else {
905
+ base.is(DeclReferenceExprSyntax.self) || base.is(MemberAccessExprSyntax.self)
906
+ else {
825
907
  return nil
826
908
  }
827
909
  return base.trimmedDescription
@@ -201,7 +201,8 @@ func typeNode(from type: TypeSyntax) -> TypeNode {
201
201
  }
202
202
  // `(T)`: one unlabeled element in parentheses is the same type as `T`, as in `((Int) -> Void)?`.
203
203
  if let tuple = type.as(TupleTypeSyntax.self), tuple.elements.count == 1,
204
- let element = tuple.elements.first, element.firstName == nil {
204
+ let element = tuple.elements.first, element.firstName == nil
205
+ {
205
206
  return typeNode(from: element.type)
206
207
  }
207
208
  // A nominal type, possibly generic: `Int`, `Point`, `Optional<T>`, `Array<T>`, `Promise<T>`.
@@ -1,9 +1,10 @@
1
+ import SwiftParser
2
+
1
3
  #if canImport(FoundationEssentials)
2
4
  import FoundationEssentials
3
5
  #else
4
6
  import Foundation
5
7
  #endif
6
- import SwiftParser
7
8
 
8
9
  /// The scanner's public entry point. Argument parsing, subcommand dispatch, and usage text live in
9
10
  /// the CLI target; this just runs a command and writes its JSON report to stdout.
@@ -122,7 +123,12 @@ func scanModules(paths: [String], defines: Set<String> = []) -> ScanModulesResul
122
123
  var platformsByDetection: [String: [String]] = [:]
123
124
  for platform in platformUniverse {
124
125
  let configuration = ScanBuildConfiguration(platform: platform.condition, defines: defines)
125
- let visitor = DetectionVisitor(file: file, tree: tree, detectedMacros: [.expoModule], configuration: configuration)
126
+ let visitor = DetectionVisitor(
127
+ file: file,
128
+ tree: tree,
129
+ detectedMacros: [.expoModule],
130
+ configuration: configuration
131
+ )
126
132
  visitor.walk(tree)
127
133
  for detection in visitor.detections {
128
134
  platformsByDetection[detectionKey(detection), default: []].append(platform.reported)
package/build/index.d.ts CHANGED
@@ -41,9 +41,10 @@ export declare class ScannerSchemaVersionError extends Error {
41
41
  *
42
42
  * - macOS: the universal binary shipped in this package, next to the Swift package rather than in a
43
43
  * `bin` directory.
44
- * - Windows: `ExpoModulesMacros.exe` from the package for the current architecture (see
45
- * `windowsBinaryPackageName`). When that package isn't installed, for example in this repository,
46
- * it falls back to the executable that `npm run build` writes to `platforms/win32-<arch>`.
44
+ * - Windows and Linux: `ExpoModulesMacros.exe` or `ExpoModulesMacros` from the package for the
45
+ * current platform and architecture (see `platformBinaryPackageName`). When that package isn't
46
+ * installed, for example in this repository, it falls back to the executable that `npm run build`
47
+ * writes to `platforms/<platform>-<arch>`.
47
48
  *
48
49
  * The path isn't checked here; running a missing binary fails with a `ScannerError` that says how to
49
50
  * get it.
package/build/index.js CHANGED
@@ -51,12 +51,21 @@ class ScannerSchemaVersionError extends Error {
51
51
  }
52
52
  exports.ScannerSchemaVersionError = ScannerSchemaVersionError;
53
53
  /**
54
- * The package with the Windows binary for the current architecture, for example
55
- * `@expo/modules-macros-win32-x64`. It is an optional dependency of this package, and its `os` and
56
- * `cpu` fields make package managers install it only on a matching Windows machine.
54
+ * The platforms whose binaries ship in packages of their own, one per architecture, with the
55
+ * machines that those packages install on.
57
56
  */
58
- function windowsBinaryPackageName() {
59
- return `@expo/modules-macros-win32-${process.arch}`;
57
+ const platformPackageMachines = {
58
+ win32: 'Windows x64 and arm64',
59
+ linux: 'Linux x64 and arm64 with glibc',
60
+ };
61
+ /**
62
+ * The package with the binary for the current platform and architecture, for example
63
+ * `@expo/modules-macros-win32-x64` or `@expo/modules-macros-linux-arm64`. It is an optional
64
+ * dependency of this package, and its `os`, `cpu` and (on Linux) `libc` fields make package managers
65
+ * install it only on a matching machine.
66
+ */
67
+ function platformBinaryPackageName() {
68
+ return `@expo/modules-macros-${process.platform}-${process.arch}`;
60
69
  }
61
70
  /**
62
71
  * Absolute path to the scanner binary for the current platform. It doubles as the macro plugin
@@ -64,34 +73,40 @@ function windowsBinaryPackageName() {
64
73
  *
65
74
  * - macOS: the universal binary shipped in this package, next to the Swift package rather than in a
66
75
  * `bin` directory.
67
- * - Windows: `ExpoModulesMacros.exe` from the package for the current architecture (see
68
- * `windowsBinaryPackageName`). When that package isn't installed, for example in this repository,
69
- * it falls back to the executable that `npm run build` writes to `platforms/win32-<arch>`.
76
+ * - Windows and Linux: `ExpoModulesMacros.exe` or `ExpoModulesMacros` from the package for the
77
+ * current platform and architecture (see `platformBinaryPackageName`). When that package isn't
78
+ * installed, for example in this repository, it falls back to the executable that `npm run build`
79
+ * writes to `platforms/<platform>-<arch>`.
70
80
  *
71
81
  * The path isn't checked here; running a missing binary fails with a `ScannerError` that says how to
72
82
  * get it.
73
83
  */
74
84
  function getScannerBinaryPath() {
75
- if (process.platform !== 'win32') {
85
+ if (process.platform === 'darwin') {
76
86
  return node_path_1.default.join(__dirname, '..', 'apple', 'ExpoModulesMacros');
77
87
  }
88
+ const fileName = process.platform === 'win32' ? 'ExpoModulesMacros.exe' : 'ExpoModulesMacros';
78
89
  try {
79
- const packageJson = require.resolve(`${windowsBinaryPackageName()}/package.json`);
80
- return node_path_1.default.join(node_path_1.default.dirname(packageJson), 'ExpoModulesMacros.exe');
90
+ const packageJson = require.resolve(`${platformBinaryPackageName()}/package.json`);
91
+ return node_path_1.default.join(node_path_1.default.dirname(packageJson), fileName);
81
92
  }
82
93
  catch {
83
- return node_path_1.default.join(__dirname, '..', 'platforms', `win32-${process.arch}`, 'ExpoModulesMacros.exe');
94
+ return node_path_1.default.join(__dirname, '..', 'platforms', `${process.platform}-${process.arch}`, fileName);
84
95
  }
85
96
  }
86
97
  /** The message for a scanner binary that doesn't exist at `binaryPath`. */
87
98
  function missingBinaryMessage(binaryPath) {
88
- if (process.platform === 'win32') {
89
- return (`The scanner binary is missing at ${binaryPath}. On Windows it comes from the optional package ` +
90
- `${windowsBinaryPackageName()}, which your package manager installs only on Windows x64 and arm64. ` +
91
- 'Reinstall dependencies without skipping optional ones (for example without `--omit=optional`), ' +
92
- 'or run `npm run build` in this package to build it locally.');
99
+ if (process.platform === 'darwin') {
100
+ return `The scanner binary is missing at ${binaryPath}. Run \`npm run build\` in this package to build it.`;
101
+ }
102
+ const machines = platformPackageMachines[process.platform];
103
+ if (machines === undefined) {
104
+ return `The scanner binary is missing at ${binaryPath}. No binary is built for ${process.platform}.`;
93
105
  }
94
- return `The scanner binary is missing at ${binaryPath}. Run \`npm run build\` in this package to build it.`;
106
+ return (`The scanner binary is missing at ${binaryPath}. It comes from the optional package ` +
107
+ `${platformBinaryPackageName()}, which your package manager installs only on ${machines}. ` +
108
+ 'Reinstall dependencies without skipping optional ones (for example without `--omit=optional`), ' +
109
+ 'or run `npm run build` in this package to build it locally.');
95
110
  }
96
111
  /**
97
112
  * Runs a scanner subcommand and parses its JSON report.
package/build/types.d.ts CHANGED
@@ -139,8 +139,14 @@ export interface ExportedParameter {
139
139
  }
140
140
  /** One `@JS func` on a module or shared object. */
141
141
  export interface ExportedFunction extends ExportedAccess, ExportedAvailability {
142
+ /**
143
+ * The Swift declaration name. A function with `.withAsync("x")` has two entries with this name.
144
+ */
142
145
  name: string;
143
- /** The JS name it binds under: the `@JS("x")` override, else `name`. */
146
+ /**
147
+ * The JS name it binds under: the `@JS("x")` override, else `name`. The second entry of a function
148
+ * with `.withAsync("x")` binds under `x`.
149
+ */
144
150
  jsName: string;
145
151
  parameters: ExportedParameter[];
146
152
  /** Absent for a `Void` return. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-modules-macros",
3
- "version": "0.20.0",
3
+ "version": "0.22.0",
4
4
  "description": "Swift macro plugin for Expo modules",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -16,6 +16,8 @@
16
16
  "build:ts": "tsc -p tsconfig.json",
17
17
  "clean": "rm -rf build",
18
18
  "typecheck": "tsc -p tsconfig.json --noEmit",
19
+ "swift:format": "node scripts/swift-format.js",
20
+ "swift:lint": "node scripts/swift-format.js --lint",
19
21
  "prepublishOnly": "npm run build:ts"
20
22
  },
21
23
  "devDependencies": {
@@ -23,7 +25,9 @@
23
25
  "typescript": "^5.4.0"
24
26
  },
25
27
  "optionalDependencies": {
26
- "@expo/modules-macros-win32-arm64": "0.20.0",
27
- "@expo/modules-macros-win32-x64": "0.20.0"
28
+ "@expo/modules-macros-linux-arm64": "0.22.0",
29
+ "@expo/modules-macros-linux-x64": "0.22.0",
30
+ "@expo/modules-macros-win32-arm64": "0.22.0",
31
+ "@expo/modules-macros-win32-x64": "0.22.0"
28
32
  }
29
33
  }