expo-modules-macros 0.17.0 → 0.18.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.
- package/apple/ExpoModulesMacros +0 -0
- package/apple/Sources/ExpoModulesMacros/ClosureArguments.swift +8 -15
- package/apple/Sources/ExpoModulesMacros/DecorateModuleBuilder.swift +2 -2
- package/apple/Sources/ExpoModulesMacros/EventMacro.swift +3 -2
- package/apple/Sources/ExpoModulesMacros/ExpoModuleMacro.swift +1 -1
- package/apple/Sources/ExpoModulesMacros/JSMacro.swift +45 -4
- package/apple/Sources/ExpoModulesMacros/MacroHelpers.swift +63 -1
- package/apple/Sources/ExpoModulesMacros/RecordMacro.swift +19 -12
- package/apple/Sources/ExpoModulesMacros/SharedObjectMacro.swift +1 -1
- package/apple/Sources/ExpoModulesMacros/TypeConformanceAssertion.swift +2 -1
- package/apple/Sources/ExpoModulesMacros/ViewPropsMacro.swift +0 -12
- package/apple/Sources/ExpoModulesScanner/Exports/SurfaceVisitor.swift +22 -13
- package/apple/Sources/ExpoModulesScanner/Exports/TypeNode.swift +1 -1
- package/package.json +3 -3
package/apple/ExpoModulesMacros
CHANGED
|
Binary file
|
|
@@ -68,29 +68,20 @@ internal struct JSClosureType {
|
|
|
68
68
|
let isVariadic: Bool
|
|
69
69
|
|
|
70
70
|
init(_ element: TupleTypeElementSyntax) {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
attributed.attributes.isEmpty
|
|
75
|
-
? attributed.baseType : TypeSyntax(attributed.with(\.specifiers, []))
|
|
76
|
-
} else {
|
|
77
|
-
self.specifiers = []
|
|
78
|
-
self.type = element.type
|
|
79
|
-
}
|
|
71
|
+
let specified = SpecifiedType(element.type)
|
|
72
|
+
self.specifiers = specified.specifiers
|
|
73
|
+
self.type = specified.type
|
|
80
74
|
self.isVariadic = element.ellipsis != nil
|
|
81
75
|
}
|
|
82
76
|
|
|
83
|
-
/// The first specifier the wrapper can't support, such as `inout` or `isolated`.
|
|
77
|
+
/// The first specifier the wrapper can't support, such as `inout` or `isolated`. The wrapper passes
|
|
78
|
+
/// each parameter on to the callback, so an ownership convention doesn't change what it does.
|
|
84
79
|
var unsupportedSpecifier: String? {
|
|
85
|
-
return specifiers.first { !
|
|
80
|
+
return specifiers.first { !ownershipSpecifiers.contains($0) }
|
|
86
81
|
}
|
|
87
82
|
}
|
|
88
83
|
}
|
|
89
84
|
|
|
90
|
-
/// The ownership specifiers a closure parameter may carry. The wrapper passes each parameter on to the
|
|
91
|
-
/// callback, so the ownership convention doesn't change what it does.
|
|
92
|
-
private let supportedParameterSpecifiers: Set<String> = ["borrowing", "consuming", "sending", "__shared", "__owned"]
|
|
93
|
-
|
|
94
85
|
/// The statements that bind `name` to the native value of a `@JS` argument read from
|
|
95
86
|
/// `valueExpression` (a `JavaScriptUnownedValue`). A closure gets a wrapper around the JS function,
|
|
96
87
|
/// and every other type decodes through `decodeCall`.
|
|
@@ -99,6 +90,8 @@ internal func argumentDecodeStatements(
|
|
|
99
90
|
into name: String,
|
|
100
91
|
from valueExpression: String
|
|
101
92
|
) -> [String] {
|
|
93
|
+
// A specifier such as `borrowing` belongs to the parameter, not to the type that decodes.
|
|
94
|
+
let type = SpecifiedType(type).type
|
|
102
95
|
guard let closure = JSClosureType(type) else {
|
|
103
96
|
return ["let \(name) = try \(decodeCall(type.trimmedDescription, from: valueExpression))"]
|
|
104
97
|
}
|
|
@@ -24,7 +24,7 @@ internal struct JSFunction {
|
|
|
24
24
|
|
|
25
25
|
init(funcDecl: FunctionDeclSyntax, attribute: AttributeSyntax) {
|
|
26
26
|
self.swiftName = funcDecl.name.text
|
|
27
|
-
self.jsName = jsNameArgument(of: attribute) ?? funcDecl.name.text
|
|
27
|
+
self.jsName = jsNameArgument(of: attribute) ?? withoutBackticks(funcDecl.name.text)
|
|
28
28
|
self.parameters = Array(funcDecl.signature.parameterClause.parameters)
|
|
29
29
|
|
|
30
30
|
let declaredReturnType = funcDecl.signature.returnClause?.type
|
|
@@ -309,7 +309,7 @@ internal struct JSProperty {
|
|
|
309
309
|
/// function bindings: a module captures `self` strong, a shared object captures nothing of the
|
|
310
310
|
/// instance. Getter and setter are gated independently.
|
|
311
311
|
func decorateStatements(object: String, receiver: Receiver) -> String {
|
|
312
|
-
let descriptorName =
|
|
312
|
+
let descriptorName = derivedIdentifier(from: swiftName, suffix: "Descriptor")
|
|
313
313
|
let callee = receiver.callee
|
|
314
314
|
// A shared object's accessors unwrap the JS `this` into `_self` before reading/writing; a module
|
|
315
315
|
// reads `self` directly. The unwrap leads each accessor body. Property accessors are synchronous, so
|
|
@@ -91,9 +91,10 @@ extension EventMacro: PeerMacro {
|
|
|
91
91
|
? "func \(name)<P: \(jsConvertibleProtocolName), E: \(eventEmitterProtocolName)>(_: P.Type, _: E.Type) {}"
|
|
92
92
|
: "func \(name)<E: \(eventEmitterProtocolName)>(_: E.Type) {}"
|
|
93
93
|
let call = payload.map { "\(name)(\($0).self, \(owner).self)" } ?? "\(name)(\(owner).self)"
|
|
94
|
+
let peerName = derivedIdentifier(from: name, prefix: "_assertTypesConformance_")
|
|
94
95
|
return [
|
|
95
96
|
"""
|
|
96
|
-
private func
|
|
97
|
+
private func \(raw: peerName)() {
|
|
97
98
|
\(raw: helper)
|
|
98
99
|
\(raw: call)
|
|
99
100
|
}
|
|
@@ -172,7 +173,7 @@ private func validatedEvent(
|
|
|
172
173
|
let swiftName = identifier.identifier.text
|
|
173
174
|
return EventMember(
|
|
174
175
|
swiftName: swiftName,
|
|
175
|
-
jsName: jsNameArgument(of: node) ?? defaultEventName(for: swiftName),
|
|
176
|
+
jsName: jsNameArgument(of: node) ?? defaultEventName(for: withoutBackticks(swiftName)),
|
|
176
177
|
payloadType: functionType.parameters.first?.type.trimmedDescription,
|
|
177
178
|
isSync: boolArgument(of: node, label: "sync") == true
|
|
178
179
|
)
|
|
@@ -45,7 +45,7 @@ public struct ExpoModuleMacro: MemberMacro {
|
|
|
45
45
|
// class name) and emitted as a non-optional `_jsName` static below — so the
|
|
46
46
|
// macro no longer emits a `Name(…)` DSL entry. Core reads the static for the module name,
|
|
47
47
|
// ahead of its type-name fallback (which then only applies to non-macro DSL modules).
|
|
48
|
-
let moduleName = jsNameArgument(of: node) ?? classDecl.name.text
|
|
48
|
+
let moduleName = jsNameArgument(of: node) ?? withoutBackticks(classDecl.name.text)
|
|
49
49
|
var entries: [String] = []
|
|
50
50
|
|
|
51
51
|
// `@JS func`s (sync and async) and `@JS var`s are bound directly into the JS object by the
|
|
@@ -31,6 +31,7 @@ public struct JSMacro: PeerMacro {
|
|
|
31
31
|
providingPeersOf declaration: some DeclSyntaxProtocol,
|
|
32
32
|
in context: some MacroExpansionContext
|
|
33
33
|
) throws -> [DeclSyntax] {
|
|
34
|
+
diagnoseParameterSpecifiers(in: declaration, in: context)
|
|
34
35
|
diagnoseFreeFormTypes(in: declaration, in: context)
|
|
35
36
|
diagnoseClosureTypes(in: declaration, in: context)
|
|
36
37
|
diagnoseConcurrentOption(of: node, on: declaration, in: context)
|
|
@@ -114,6 +115,39 @@ private func insertAsyncFixIt(for funcDecl: FunctionDeclSyntax) -> FixIt {
|
|
|
114
115
|
)
|
|
115
116
|
}
|
|
116
117
|
|
|
118
|
+
/// Reports a function or constructor parameter whose specifier a decoded argument can't honor: an
|
|
119
|
+
/// `inout` parameter expects its writes to reach the caller, and an `isolated` one expects an actor,
|
|
120
|
+
/// while a `@JS` argument is a fresh value decoded from JavaScript. Ownership specifiers (`borrowing`,
|
|
121
|
+
/// `consuming`, `sending`) are fine: the binding passes the decoded value under whichever convention
|
|
122
|
+
/// the parameter asks for. The specifier is left off the decode and the assertion (see
|
|
123
|
+
/// `SpecifiedType`); a parameter reported here is also left out of the assertion.
|
|
124
|
+
private func diagnoseParameterSpecifiers(
|
|
125
|
+
in declaration: some DeclSyntaxProtocol,
|
|
126
|
+
in context: some MacroExpansionContext
|
|
127
|
+
) {
|
|
128
|
+
let parameters: FunctionParameterListSyntax
|
|
129
|
+
if let funcDecl = declaration.as(FunctionDeclSyntax.self) {
|
|
130
|
+
parameters = funcDecl.signature.parameterClause.parameters
|
|
131
|
+
} else if let initDecl = declaration.as(InitializerDeclSyntax.self) {
|
|
132
|
+
parameters = initDecl.signature.parameterClause.parameters
|
|
133
|
+
} else {
|
|
134
|
+
return
|
|
135
|
+
}
|
|
136
|
+
for parameter in parameters {
|
|
137
|
+
guard let specifier = SpecifiedType(parameter.type).unsupportedSpecifier else {
|
|
138
|
+
continue
|
|
139
|
+
}
|
|
140
|
+
context.diagnose(
|
|
141
|
+
Diagnostic(
|
|
142
|
+
node: parameter.type,
|
|
143
|
+
message: JSDiagnosticMessage(
|
|
144
|
+
"A @JS parameter can't be '\(specifier)': its argument is a value decoded from JavaScript, which can't be written back or carry an isolation.",
|
|
145
|
+
id: "js-parameter-specifier",
|
|
146
|
+
severity: .error
|
|
147
|
+
)))
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
117
151
|
/// Emits the free-form (`Any` / `[Any]` / `[String: Any]`) diagnostics for a `@JS` declaration.
|
|
118
152
|
///
|
|
119
153
|
/// A free-form type is only supported crossing the boundary as an **argument**, decoded through
|
|
@@ -172,7 +206,8 @@ private func warnFreeFormArguments(
|
|
|
172
206
|
in context: some MacroExpansionContext
|
|
173
207
|
) {
|
|
174
208
|
for parameter in parameters {
|
|
175
|
-
|
|
209
|
+
// Matched on the bare type: `borrowing [String: Any]` decodes as free-form too.
|
|
210
|
+
let type = SpecifiedType(parameter.type).type.trimmedDescription
|
|
176
211
|
guard let suggested = typedFreeFormReplacement(for: type) else {
|
|
177
212
|
continue
|
|
178
213
|
}
|
|
@@ -265,9 +300,15 @@ private func boundaryMember(of declaration: some DeclSyntaxProtocol) -> Boundary
|
|
|
265
300
|
var decodableTypes: [String] = []
|
|
266
301
|
var callbackArgumentTypes: [String] = []
|
|
267
302
|
for parameter in funcDecl.signature.parameterClause.parameters {
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
303
|
+
// The specifier isn't part of the asserted type, and a parameter with one the binding can't honor
|
|
304
|
+
// is reported by `diagnoseParameterSpecifiers` and not asserted.
|
|
305
|
+
let specified = SpecifiedType(parameter.type)
|
|
306
|
+
guard specified.unsupportedSpecifier == nil else {
|
|
307
|
+
continue
|
|
308
|
+
}
|
|
309
|
+
guard let closure = JSClosureType(specified.type) else {
|
|
310
|
+
if !containsFunctionType(specified.type) {
|
|
311
|
+
decodableTypes.append(specified.type.trimmedDescription)
|
|
271
312
|
}
|
|
272
313
|
continue
|
|
273
314
|
}
|
|
@@ -72,6 +72,9 @@ internal func isConcurrentJSMember(_ decl: DeclSyntaxProtocol) -> Bool {
|
|
|
72
72
|
/// True if the type is written as an optional: `T?`, `T!`, or the explicit `Optional<T>`. Used to
|
|
73
73
|
/// decide argument requiredness (an optional parameter may be omitted) and record-field nullability.
|
|
74
74
|
internal func isOptionalType(_ type: TypeSyntax) -> Bool {
|
|
75
|
+
if let attributed = type.as(AttributedTypeSyntax.self) {
|
|
76
|
+
return isOptionalType(attributed.baseType)
|
|
77
|
+
}
|
|
75
78
|
if type.is(OptionalTypeSyntax.self) || type.is(ImplicitlyUnwrappedOptionalTypeSyntax.self) {
|
|
76
79
|
return true
|
|
77
80
|
}
|
|
@@ -81,6 +84,39 @@ internal func isOptionalType(_ type: TypeSyntax) -> Bool {
|
|
|
81
84
|
return false
|
|
82
85
|
}
|
|
83
86
|
|
|
87
|
+
/// A parameter's type split from the specifiers written in front of it (`borrowing`, `consuming`,
|
|
88
|
+
/// `sending`, `inout`, `isolated`, ...). The specifiers aren't part of the type: the decode call and
|
|
89
|
+
/// the conformance assertion need the bare type (`borrowing Point.decode(...)` isn't valid Swift), and
|
|
90
|
+
/// a decoded argument is a fresh owned value, so it can be passed under any ownership convention.
|
|
91
|
+
internal struct SpecifiedType {
|
|
92
|
+
/// The type without its specifiers.
|
|
93
|
+
let type: TypeSyntax
|
|
94
|
+
/// The specifiers written before the type, in source order.
|
|
95
|
+
let specifiers: [String]
|
|
96
|
+
|
|
97
|
+
init(_ type: TypeSyntax) {
|
|
98
|
+
if let attributed = type.as(AttributedTypeSyntax.self), !attributed.specifiers.isEmpty {
|
|
99
|
+
self.specifiers = attributed.specifiers.map(\.trimmedDescription)
|
|
100
|
+
self.type =
|
|
101
|
+
attributed.attributes.isEmpty
|
|
102
|
+
? attributed.baseType : TypeSyntax(attributed.with(\.specifiers, []))
|
|
103
|
+
} else {
|
|
104
|
+
self.specifiers = []
|
|
105
|
+
self.type = type
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/// The first specifier a decoded argument can't honor: `inout` (JavaScript can't observe a write
|
|
110
|
+
/// back) or `isolated` (the argument doesn't carry an isolation).
|
|
111
|
+
var unsupportedSpecifier: String? {
|
|
112
|
+
return specifiers.first { !ownershipSpecifiers.contains($0) }
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/// The ownership specifiers a parameter may carry. They only change how the argument is passed, which
|
|
117
|
+
/// a fresh decoded value satisfies either way.
|
|
118
|
+
internal let ownershipSpecifiers: Set<String> = ["borrowing", "consuming", "sending", "__shared", "__owned"]
|
|
119
|
+
|
|
84
120
|
/// The type wrapped by one optional layer (`T?`, `T!` or `Optional<T>`), or `nil` when the type isn't
|
|
85
121
|
/// optional.
|
|
86
122
|
internal func optionalWrappedType(_ type: TypeSyntax) -> TypeSyntax? {
|
|
@@ -341,6 +377,32 @@ internal func decodeCall(_ type: String, from valueExpression: String) -> String
|
|
|
341
377
|
return "\(expressionType(type)).decode(\(valueExpression), in: runtime)"
|
|
342
378
|
}
|
|
343
379
|
|
|
380
|
+
// MARK: - Escaped names
|
|
381
|
+
|
|
382
|
+
/// `name` without the backticks that escape it: `` `default` `` becomes `default`. Swift needs the
|
|
383
|
+
/// backticks in every identifier position, but a name JavaScript sees (a JS member name, an event
|
|
384
|
+
/// name, a record key) is the bare name.
|
|
385
|
+
internal func withoutBackticks(_ name: String) -> String {
|
|
386
|
+
var name = Substring(name)
|
|
387
|
+
while name.first == "`" {
|
|
388
|
+
name.removeFirst()
|
|
389
|
+
}
|
|
390
|
+
while name.last == "`" {
|
|
391
|
+
name.removeLast()
|
|
392
|
+
}
|
|
393
|
+
return String(name)
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/// A generated identifier built from a member's name, such as `countDescriptor` from `count`. The
|
|
397
|
+
/// affixes join the bare name, and an escaped name keeps the backticks around the whole result
|
|
398
|
+
/// (`` `class` `` gives `` `classDescriptor` ``), since the bare name may not be an identifier on
|
|
399
|
+
/// its own.
|
|
400
|
+
internal func derivedIdentifier(from name: String, prefix: String = "", suffix: String = "") -> String {
|
|
401
|
+
let bareName = withoutBackticks(name)
|
|
402
|
+
let identifier = prefix + bareName + suffix
|
|
403
|
+
return bareName == name ? identifier : "`\(identifier)`"
|
|
404
|
+
}
|
|
405
|
+
|
|
344
406
|
// MARK: - @JS property collection
|
|
345
407
|
|
|
346
408
|
/// Collects the `@JS var` bindings of a declaration into `JSProperty` values for direct JSI binding.
|
|
@@ -365,7 +427,7 @@ internal func collectProperties(
|
|
|
365
427
|
?? binding.initializer.flatMap { inferredLiteralType(of: $0.value) }
|
|
366
428
|
return JSProperty(
|
|
367
429
|
swiftName: swiftName,
|
|
368
|
-
jsName: jsNameOverride ?? swiftName,
|
|
430
|
+
jsName: jsNameOverride ?? withoutBackticks(swiftName),
|
|
369
431
|
valueType: valueType,
|
|
370
432
|
isSettable: isVar && bindingIsSettable(binding)
|
|
371
433
|
)
|
|
@@ -95,7 +95,7 @@ public struct RecordMacro: MemberMacro, ExtensionMacro {
|
|
|
95
95
|
members.append(defaultInit)
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
|
-
if !existingInitLabels.contains(properties.map { $0.
|
|
98
|
+
if !existingInitLabels.contains(properties.map { $0.key }) {
|
|
99
99
|
members.append(memberwiseInit(properties: properties))
|
|
100
100
|
}
|
|
101
101
|
members.append(fromJSObjectFactory(properties: properties))
|
|
@@ -195,10 +195,11 @@ private enum FreeFormConversion {
|
|
|
195
195
|
|
|
196
196
|
/**
|
|
197
197
|
A single record property discovered on the type, paired with everything the synthesized
|
|
198
|
-
conversions need: its property name
|
|
199
|
-
source may omit it.
|
|
198
|
+
conversions need: its property name, its written type, and how the source may omit it.
|
|
200
199
|
*/
|
|
201
200
|
private struct RecordProperty {
|
|
201
|
+
/// The property name as written, backticks included for an escaped name (`` `default` ``), since
|
|
202
|
+
/// every identifier position in the generated code needs them.
|
|
202
203
|
let name: String
|
|
203
204
|
/// The property's declared type, verbatim (e.g. `String`, `Int`, `String?`). Used to build the
|
|
204
205
|
/// memberwise-init parameter and as the receiver of the per-property `decode`/`encode` (and, on the
|
|
@@ -222,6 +223,12 @@ private struct RecordProperty {
|
|
|
222
223
|
var isRequired: Bool {
|
|
223
224
|
return !hasDefault && !isOptional
|
|
224
225
|
}
|
|
226
|
+
|
|
227
|
+
/// The key the property reads from and writes to, in both the JS object and the dictionary: the
|
|
228
|
+
/// name without escaping backticks.
|
|
229
|
+
var key: String {
|
|
230
|
+
return withoutBackticks(name)
|
|
231
|
+
}
|
|
225
232
|
}
|
|
226
233
|
|
|
227
234
|
/**
|
|
@@ -414,7 +421,7 @@ private func factoryBody(properties: [RecordProperty], readLines: [String]) -> S
|
|
|
414
421
|
private func jsObjectReadLines(properties: [RecordProperty]) -> [String] {
|
|
415
422
|
var lines: [String] = []
|
|
416
423
|
for property in properties {
|
|
417
|
-
let valueVar =
|
|
424
|
+
let valueVar = derivedIdentifier(from: property.name, suffix: "JSValue")
|
|
418
425
|
let exprType = expressionType(property.type)
|
|
419
426
|
let decode: String
|
|
420
427
|
switch property.freeForm {
|
|
@@ -426,10 +433,10 @@ private func jsObjectReadLines(properties: [RecordProperty]) -> [String] {
|
|
|
426
433
|
case nil:
|
|
427
434
|
decode = "try \(exprType).decode(\(valueVar), in: runtime)"
|
|
428
435
|
}
|
|
429
|
-
lines.append(" let \(valueVar) = object.getProperty(\"\(property.
|
|
436
|
+
lines.append(" let \(valueVar) = object.getProperty(\"\(property.key)\")")
|
|
430
437
|
if property.isRequired {
|
|
431
438
|
lines.append(" guard !\(valueVar).isUndefined() else {")
|
|
432
|
-
lines.append(" throw RecordPropertyRequiredException(\"\(property.
|
|
439
|
+
lines.append(" throw RecordPropertyRequiredException(\"\(property.key)\")")
|
|
433
440
|
lines.append(" }")
|
|
434
441
|
lines.append(" let \(property.name) = \(decode)")
|
|
435
442
|
} else if property.isOptional {
|
|
@@ -449,16 +456,16 @@ private func jsObjectReadLines(properties: [RecordProperty]) -> [String] {
|
|
|
449
456
|
private func dictionaryReadLines(properties: [RecordProperty]) -> [String] {
|
|
450
457
|
var lines: [String] = []
|
|
451
458
|
for property in properties {
|
|
452
|
-
let valueVar =
|
|
459
|
+
let valueVar = derivedIdentifier(from: property.name, suffix: "Value")
|
|
453
460
|
let exprType = expressionType(property.type)
|
|
454
461
|
let isBareAny = property.freeForm?.isBareAny == true
|
|
455
462
|
let cast = isBareAny
|
|
456
463
|
? valueVar
|
|
457
464
|
: "try \(exprType).getDynamicType().cast(\(valueVar), appContext: appContext) as! \(exprType)"
|
|
458
|
-
lines.append(" let \(valueVar) = dictionary[\"\(property.
|
|
465
|
+
lines.append(" let \(valueVar) = dictionary[\"\(property.key)\"]")
|
|
459
466
|
if property.isRequired {
|
|
460
467
|
lines.append(" guard let \(valueVar) else {")
|
|
461
|
-
lines.append(" throw RecordPropertyRequiredException(\"\(property.
|
|
468
|
+
lines.append(" throw RecordPropertyRequiredException(\"\(property.key)\")")
|
|
462
469
|
lines.append(" }")
|
|
463
470
|
lines.append(" let \(property.name) = \(cast)")
|
|
464
471
|
} else if property.isOptional {
|
|
@@ -483,7 +490,7 @@ private func toDictionaryMethod(properties: [RecordProperty], inheritsRecord: Bo
|
|
|
483
490
|
lines.append(" var dictionary: [String: Any] = [:]")
|
|
484
491
|
}
|
|
485
492
|
for property in properties {
|
|
486
|
-
lines.append(" dictionary[\"\(property.
|
|
493
|
+
lines.append(" dictionary[\"\(property.key)\"] = self.\(property.name)")
|
|
487
494
|
}
|
|
488
495
|
lines.append(" return dictionary")
|
|
489
496
|
let body = lines.joined(separator: "\n")
|
|
@@ -537,7 +544,7 @@ private func toObjectMethod(properties: [RecordProperty], inheritsRecord: Bool)
|
|
|
537
544
|
case nil:
|
|
538
545
|
encode = "try \(exprType).encode(self.\(property.name), in: runtime)"
|
|
539
546
|
}
|
|
540
|
-
lines.append(" object.setProperty(\"\(property.
|
|
547
|
+
lines.append(" object.setProperty(\"\(property.key)\", value: \(encode))")
|
|
541
548
|
}
|
|
542
549
|
lines.append(" return object")
|
|
543
550
|
let body = lines.joined(separator: "\n")
|
|
@@ -567,7 +574,7 @@ private func initializerParameterLabels(of declaration: some DeclGroupSyntax) ->
|
|
|
567
574
|
}
|
|
568
575
|
let labels = initDecl.signature.parameterClause.parameters.map { parameter in
|
|
569
576
|
// `firstName` is the external label (or `_`); fall back to the internal name when omitted.
|
|
570
|
-
let label = parameter.firstName.text
|
|
577
|
+
let label = withoutBackticks(parameter.firstName.text)
|
|
571
578
|
return label == "_" ? "" : label
|
|
572
579
|
}
|
|
573
580
|
signatures.append(labels)
|
|
@@ -39,7 +39,7 @@ public struct SharedObjectMacro: MemberMacro {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
let typeName = classDecl.name.text
|
|
42
|
-
let jsName = jsNameArgument(of: node) ?? typeName
|
|
42
|
+
let jsName = jsNameArgument(of: node) ?? withoutBackticks(typeName)
|
|
43
43
|
|
|
44
44
|
// `@JS func`s/`var`s and the `@JS init` are bound directly into the shared object's JS object by
|
|
45
45
|
// the synthesized `_decorateSharedObject(prototype:)` / `_decorateSharedObject(constructor:)` / `_constructSharedObject`
|
|
@@ -111,8 +111,9 @@ internal func directionalConformanceAssertion(
|
|
|
111
111
|
let helper = "func \(name)<\(parameters.joined(separator: ", "))>(\(typeParameters.joined(separator: ", "))) {}"
|
|
112
112
|
let call = "\(name)(\(arguments.joined(separator: ", ")))"
|
|
113
113
|
let staticKeyword = isStatic ? "static " : ""
|
|
114
|
+
let peerName = derivedIdentifier(from: name, prefix: "_assertTypesConformance_")
|
|
114
115
|
return """
|
|
115
|
-
private \(raw: staticKeyword)func
|
|
116
|
+
private \(raw: staticKeyword)func \(raw: peerName)() {
|
|
116
117
|
\(raw: helper)
|
|
117
118
|
\(raw: call)
|
|
118
119
|
}
|
|
@@ -251,18 +251,6 @@ private struct ViewProp {
|
|
|
251
251
|
}
|
|
252
252
|
}
|
|
253
253
|
|
|
254
|
-
/// `name` without the backticks that escape it: `` `default` `` becomes `default`.
|
|
255
|
-
private func withoutBackticks(_ name: String) -> String {
|
|
256
|
-
var name = Substring(name)
|
|
257
|
-
while name.first == "`" {
|
|
258
|
-
name.removeFirst()
|
|
259
|
-
}
|
|
260
|
-
while name.last == "`" {
|
|
261
|
-
name.removeLast()
|
|
262
|
-
}
|
|
263
|
-
return String(name)
|
|
264
|
-
}
|
|
265
|
-
|
|
266
254
|
private struct ViewPropsModel {
|
|
267
255
|
/// Value props, in declaration order. The order is the bit order, so it is part of the ABI
|
|
268
256
|
/// between a compiled view and the runtime that fills the mask.
|
|
@@ -20,7 +20,7 @@ final class SurfaceVisitor: SyntaxVisitor {
|
|
|
20
20
|
|
|
21
21
|
override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind {
|
|
22
22
|
if isTopLevel(node) {
|
|
23
|
-
classify(name: node.name.
|
|
23
|
+
classify(name: node.name.identifierName, attributes: node.attributes, members: node.memberBlock.members)
|
|
24
24
|
}
|
|
25
25
|
// The member walk reads the body itself; nested types aren't part of this surface (matching
|
|
26
26
|
// `DetectionVisitor`'s top-level-only scope), so there's no reason to descend.
|
|
@@ -29,7 +29,7 @@ final class SurfaceVisitor: SyntaxVisitor {
|
|
|
29
29
|
|
|
30
30
|
override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind {
|
|
31
31
|
if isTopLevel(node) {
|
|
32
|
-
classify(name: node.name.
|
|
32
|
+
classify(name: node.name.identifierName, attributes: node.attributes, members: node.memberBlock.members)
|
|
33
33
|
}
|
|
34
34
|
return .skipChildren
|
|
35
35
|
}
|
|
@@ -48,7 +48,7 @@ final class SurfaceVisitor: SyntaxVisitor {
|
|
|
48
48
|
if node.attributes.firstAttribute(named: DetectedMacro.union.rawValue) != nil {
|
|
49
49
|
unions.append(
|
|
50
50
|
ExportedUnion(
|
|
51
|
-
name: node.name.
|
|
51
|
+
name: node.name.identifierName,
|
|
52
52
|
members: collectUnionMembers(node.memberBlock.members),
|
|
53
53
|
file: file
|
|
54
54
|
))
|
|
@@ -56,7 +56,7 @@ final class SurfaceVisitor: SyntaxVisitor {
|
|
|
56
56
|
let rawType = rawValueType(of: node.inheritanceClause)
|
|
57
57
|
enums.append(
|
|
58
58
|
ExportedEnum(
|
|
59
|
-
name: node.name.
|
|
59
|
+
name: node.name.identifierName,
|
|
60
60
|
rawType: rawType,
|
|
61
61
|
cases: collectEnumCases(node.memberBlock.members, rawType: rawType),
|
|
62
62
|
file: file
|
|
@@ -178,7 +178,7 @@ final class SurfaceVisitor: SyntaxVisitor {
|
|
|
178
178
|
functionType.parameters.count <= 1 else {
|
|
179
179
|
continue
|
|
180
180
|
}
|
|
181
|
-
let name = ident.identifier.
|
|
181
|
+
let name = ident.identifier.identifierName
|
|
182
182
|
result.append(
|
|
183
183
|
ExportedEvent(
|
|
184
184
|
name: name,
|
|
@@ -196,8 +196,8 @@ final class SurfaceVisitor: SyntaxVisitor {
|
|
|
196
196
|
let effects = funcDecl.signature.effectSpecifiers
|
|
197
197
|
let returnType = funcDecl.signature.returnClause?.type
|
|
198
198
|
return ExportedFunction(
|
|
199
|
-
name: funcDecl.name.
|
|
200
|
-
jsName: stringArgument(of: attribute) ?? funcDecl.name.
|
|
199
|
+
name: funcDecl.name.identifierName,
|
|
200
|
+
jsName: stringArgument(of: attribute) ?? funcDecl.name.identifierName,
|
|
201
201
|
parameters: parameters(of: funcDecl.signature.parameterClause),
|
|
202
202
|
returns: isVoidType(returnType) ? nil : returnType.map { typeNode(from: $0) },
|
|
203
203
|
isAsync: effects?.asyncSpecifier != nil,
|
|
@@ -219,7 +219,7 @@ final class SurfaceVisitor: SyntaxVisitor {
|
|
|
219
219
|
guard let ident = binding.pattern.as(IdentifierPatternSyntax.self) else {
|
|
220
220
|
continue
|
|
221
221
|
}
|
|
222
|
-
let name = ident.identifier.
|
|
222
|
+
let name = ident.identifier.identifierName
|
|
223
223
|
let type = valueTypeNode(annotation: binding.typeAnnotation?.type, initializer: binding.initializer?.value)
|
|
224
224
|
result.append(
|
|
225
225
|
ExportedProperty(
|
|
@@ -284,7 +284,7 @@ final class SurfaceVisitor: SyntaxVisitor {
|
|
|
284
284
|
}
|
|
285
285
|
properties.append(
|
|
286
286
|
ExportedRecordProperty(
|
|
287
|
-
name: ident.identifier.
|
|
287
|
+
name: ident.identifier.identifierName,
|
|
288
288
|
type: type,
|
|
289
289
|
isOptional: annotation.map { isOptionalType($0) } ?? false,
|
|
290
290
|
hasDefault: binding.initializer != nil
|
|
@@ -312,7 +312,7 @@ final class SurfaceVisitor: SyntaxVisitor {
|
|
|
312
312
|
continue
|
|
313
313
|
}
|
|
314
314
|
for element in caseDecl.elements where element.parameterClause == nil {
|
|
315
|
-
let name = element.name.
|
|
315
|
+
let name = element.name.identifierName
|
|
316
316
|
cases.append(
|
|
317
317
|
ExportedEnumCase(
|
|
318
318
|
name: name,
|
|
@@ -343,7 +343,7 @@ final class SurfaceVisitor: SyntaxVisitor {
|
|
|
343
343
|
continue
|
|
344
344
|
}
|
|
345
345
|
result.append(
|
|
346
|
-
ExportedUnionMember(name: element.name.
|
|
346
|
+
ExportedUnionMember(name: element.name.identifierName, type: typeNode(from: parameter.type)))
|
|
347
347
|
}
|
|
348
348
|
}
|
|
349
349
|
return result
|
|
@@ -353,8 +353,8 @@ final class SurfaceVisitor: SyntaxVisitor {
|
|
|
353
353
|
/// first), and `optional` when it has a default value or an optional type.
|
|
354
354
|
private func parameters(of clause: FunctionParameterClauseSyntax) -> [ExportedParameter] {
|
|
355
355
|
clause.parameters.map { parameter in
|
|
356
|
-
let label = parameter.firstName.
|
|
357
|
-
let name = parameter.secondName?.
|
|
356
|
+
let label = parameter.firstName.identifierName
|
|
357
|
+
let name = parameter.secondName?.identifierName ?? label
|
|
358
358
|
return ExportedParameter(
|
|
359
359
|
label: label,
|
|
360
360
|
name: name,
|
|
@@ -376,6 +376,15 @@ final class SurfaceVisitor: SyntaxVisitor {
|
|
|
376
376
|
|
|
377
377
|
// MARK: - Syntactic helpers (shared spelling with the macros)
|
|
378
378
|
|
|
379
|
+
extension TokenSyntax {
|
|
380
|
+
/// The name a token spells, without the backticks Swift requires around a reserved word used as
|
|
381
|
+
/// a name: `` `default` `` reads as `default`. Tokens that aren't identifiers, such as the `_`
|
|
382
|
+
/// wildcard label, read as written.
|
|
383
|
+
var identifierName: String {
|
|
384
|
+
identifier?.name ?? text
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
379
388
|
/// True when the modifiers make a member type-level (`static` or `class`).
|
|
380
389
|
// MARK: - `@Event` helpers
|
|
381
390
|
|
|
@@ -204,7 +204,7 @@ func typeNode(from type: TypeSyntax) -> TypeNode {
|
|
|
204
204
|
}
|
|
205
205
|
// A nominal type, possibly generic: `Int`, `Point`, `Optional<T>`, `Array<T>`, `Promise<T>`.
|
|
206
206
|
if let identifier = type.as(IdentifierTypeSyntax.self) {
|
|
207
|
-
return nominalNode(name: identifier.name.
|
|
207
|
+
return nominalNode(name: identifier.name.identifierName, generics: identifier.genericArgumentClause)
|
|
208
208
|
}
|
|
209
209
|
// A qualified nominal type: `Foo.Bar`. Kept as a ref under its full spelling.
|
|
210
210
|
if let member = type.as(MemberTypeSyntax.self) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "expo-modules-macros",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"description": "Swift macro plugin for Expo modules",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"typescript": "^5.4.0"
|
|
24
24
|
},
|
|
25
25
|
"optionalDependencies": {
|
|
26
|
-
"@expo/modules-macros-win32-arm64": "0.
|
|
27
|
-
"@expo/modules-macros-win32-x64": "0.
|
|
26
|
+
"@expo/modules-macros-win32-arm64": "0.18.0",
|
|
27
|
+
"@expo/modules-macros-win32-x64": "0.18.0"
|
|
28
28
|
}
|
|
29
29
|
}
|