expo-modules-macros 0.30.0 → 0.32.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/.github/workflows/{swift.yml → macos.yml} +12 -3
- package/AGENTS.md +2 -1
- package/README.md +9 -7
- package/apple/ExpoModulesMacros +0 -0
- package/apple/Sources/ExpoModulesCLI/Commands/Expand/ExpandReport.swift +3 -2
- package/apple/Sources/ExpoModulesCLI/Commands/Expand/MacroExpander.swift +87 -38
- package/apple/Sources/ExpoModulesCLI/Commands/Expand/RunExpand.swift +27 -9
- package/apple/Sources/ExpoModulesCLI/Commands/Fix/RunFix.swift +88 -26
- package/apple/Sources/ExpoModulesCLI/Commands/ScanExports/ExportedSurface.swift +14 -7
- package/apple/Sources/ExpoModulesCLI/Commands/ScanExports/SurfaceVisitor.swift +6 -4
- package/apple/Sources/ExpoModulesMacrosCore/Macros/EventMacro.swift +104 -17
- package/apple/Sources/ExpoModulesMacrosCore/Macros/ExpoModuleMacro.swift +13 -4
- package/apple/Sources/ExpoModulesMacrosCore/Macros/JSMacro.swift +124 -0
- package/apple/Sources/ExpoModulesMacrosCore/Macros/RecordMacro.swift +68 -0
- package/apple/Sources/ExpoModulesMacrosCore/Macros/SharedObjectMacro.swift +80 -5
- package/apple/Sources/ExpoModulesMacrosCore/Macros/UnionMacro.swift +101 -5
- package/apple/Sources/ExpoModulesMacrosCore/Macros/ViewPropsMacro.swift +609 -33
- package/apple/Sources/ExpoModulesMacrosCore/Support/JSNameCollisions.swift +111 -2
- package/apple/Sources/ExpoModulesMacrosCore/Support/MacroHelpers.swift +15 -16
- package/build/types.d.ts +3 -3
- package/build/types.js +1 -1
- package/package.json +5 -5
|
@@ -40,6 +40,94 @@ internal func diagnoseJSNameCollisions(
|
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
/// The JavaScript object that a set of `@JS` members binds onto, for `diagnoseReservedJSNames`.
|
|
44
|
+
internal enum JSBindingTarget {
|
|
45
|
+
/// The module object of an `@ExpoModule` class.
|
|
46
|
+
case module
|
|
47
|
+
/// The prototype of a shared object class, which holds the instance members.
|
|
48
|
+
case prototype
|
|
49
|
+
/// The constructor of a shared object class, which holds the static members.
|
|
50
|
+
case constructor
|
|
51
|
+
|
|
52
|
+
/// The names that JavaScript already gives a meaning on this object, with what binding a member
|
|
53
|
+
/// under one would break.
|
|
54
|
+
var reservedNames: [String: String] {
|
|
55
|
+
let proto = "setting '__proto__' on an object replaces its prototype instead of adding a member"
|
|
56
|
+
switch self {
|
|
57
|
+
case .module:
|
|
58
|
+
return ["__proto__": proto]
|
|
59
|
+
case .prototype:
|
|
60
|
+
return [
|
|
61
|
+
"__proto__": proto,
|
|
62
|
+
"constructor": "a member named 'constructor' replaces the prototype's reference to the class",
|
|
63
|
+
]
|
|
64
|
+
case .constructor:
|
|
65
|
+
return [
|
|
66
|
+
"__proto__": proto,
|
|
67
|
+
"prototype": "a static member named 'prototype' replaces the prototype that holds the instance members",
|
|
68
|
+
]
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/// Reports each `@JS` member whose JavaScript name JavaScript already gives a meaning on the object that
|
|
74
|
+
/// the member binds onto, such as `constructor` on a shared object's prototype.
|
|
75
|
+
internal func diagnoseReservedJSNames(
|
|
76
|
+
functions: [JSFunction],
|
|
77
|
+
properties: [JSProperty],
|
|
78
|
+
on target: JSBindingTarget,
|
|
79
|
+
in context: some MacroExpansionContext
|
|
80
|
+
) {
|
|
81
|
+
let members = functions.map(BoundMember.init(function:)) + properties.map(BoundMember.init(property:))
|
|
82
|
+
for member in members {
|
|
83
|
+
guard let reason = target.reservedNames[member.jsName] else {
|
|
84
|
+
continue
|
|
85
|
+
}
|
|
86
|
+
context.diagnose(
|
|
87
|
+
Diagnostic(
|
|
88
|
+
node: member.nameToken,
|
|
89
|
+
message: ReservedJSNameMessage(
|
|
90
|
+
"The JavaScript name '\(member.jsName)' can't be bound: \(reason). Give the \(member.kind) another name with \(member.renaming)."
|
|
91
|
+
)
|
|
92
|
+
)
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/// Reports each `@Event` property of a class body whose JavaScript event name an earlier `@Event`
|
|
98
|
+
/// property already uses. Both properties emit under that name, so a listener can't tell which one fired.
|
|
99
|
+
internal func diagnoseEventNameCollisions(in memberBlock: MemberBlockSyntax, in context: some MacroExpansionContext) {
|
|
100
|
+
var firstEvents: [String: TokenSyntax] = [:]
|
|
101
|
+
|
|
102
|
+
for member in memberBlock.members {
|
|
103
|
+
guard let varDecl = member.decl.as(VariableDeclSyntax.self),
|
|
104
|
+
let attribute = varDecl.attributes.firstAttribute(named: "Event"),
|
|
105
|
+
let jsName = eventJSName(of: varDecl, attribute: attribute),
|
|
106
|
+
let nameToken = varDecl.bindings.first?.pattern.as(IdentifierPatternSyntax.self)?.identifier
|
|
107
|
+
else {
|
|
108
|
+
continue
|
|
109
|
+
}
|
|
110
|
+
guard let first = firstEvents[jsName] else {
|
|
111
|
+
firstEvents[jsName] = nameToken
|
|
112
|
+
continue
|
|
113
|
+
}
|
|
114
|
+
context.diagnose(
|
|
115
|
+
Diagnostic(
|
|
116
|
+
node: nameToken,
|
|
117
|
+
message: EventNameCollisionMessage(
|
|
118
|
+
"The event name '\(jsName)' is already used by the earlier @Event '\(first.text)'. Both properties emit the same event, so a JavaScript listener can't tell them apart. Give one of them another name with @Event(\"...\")."
|
|
119
|
+
),
|
|
120
|
+
notes: [
|
|
121
|
+
Note(
|
|
122
|
+
node: Syntax(first),
|
|
123
|
+
message: JSNameCollisionNote("'\(jsName)' is first used here", id: "event-name-collision-first")
|
|
124
|
+
)
|
|
125
|
+
]
|
|
126
|
+
)
|
|
127
|
+
)
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
43
131
|
private struct BoundMember {
|
|
44
132
|
let jsName: String
|
|
45
133
|
/// How the message names the member, such as `@JS function`.
|
|
@@ -74,11 +162,32 @@ private struct JSNameCollisionMessage: DiagnosticMessage {
|
|
|
74
162
|
}
|
|
75
163
|
}
|
|
76
164
|
|
|
77
|
-
private struct
|
|
165
|
+
private struct ReservedJSNameMessage: DiagnosticMessage {
|
|
166
|
+
let message: String
|
|
167
|
+
let diagnosticID = MessageID(domain: "ExpoModulesMacros", id: "js-reserved-name")
|
|
168
|
+
let severity: DiagnosticSeverity = .error
|
|
169
|
+
|
|
170
|
+
init(_ message: String) {
|
|
171
|
+
self.message = message
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
private struct EventNameCollisionMessage: DiagnosticMessage {
|
|
78
176
|
let message: String
|
|
79
|
-
let
|
|
177
|
+
let diagnosticID = MessageID(domain: "ExpoModulesMacros", id: "event-name-collision")
|
|
178
|
+
let severity: DiagnosticSeverity = .warning
|
|
80
179
|
|
|
81
180
|
init(_ message: String) {
|
|
82
181
|
self.message = message
|
|
83
182
|
}
|
|
84
183
|
}
|
|
184
|
+
|
|
185
|
+
private struct JSNameCollisionNote: NoteMessage {
|
|
186
|
+
let message: String
|
|
187
|
+
let noteID: MessageID
|
|
188
|
+
|
|
189
|
+
init(_ message: String, id: String = "js-name-collision-first") {
|
|
190
|
+
self.message = message
|
|
191
|
+
self.noteID = MessageID(domain: "ExpoModulesMacros", id: id)
|
|
192
|
+
}
|
|
193
|
+
}
|
|
@@ -13,8 +13,8 @@ internal func jsNameArgument(of attribute: AttributeSyntax) -> String? {
|
|
|
13
13
|
return segment.content.text
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
/// True if the attribute lists the given option member, e.g. `@JS(.concurrent)`, `@Event(.
|
|
17
|
-
/// `@Event("name", EventOptions.
|
|
16
|
+
/// True if the attribute lists the given option member, e.g. `@JS(.concurrent)`, `@Event(.inline)` or
|
|
17
|
+
/// `@Event("name", EventOptions.inline)` -> true for the matching name. Options are written as
|
|
18
18
|
/// member-access expressions, one per variadic argument. Array literals are scanned too, though the
|
|
19
19
|
/// variadic declarations in `expo-modules-core` don't accept them. The leading string literal, when
|
|
20
20
|
/// present, is the JS name and is skipped.
|
|
@@ -276,20 +276,6 @@ internal func hasDefaultValue(_ parameter: FunctionParameterSyntax) -> Bool {
|
|
|
276
276
|
return parameter.defaultValue != nil
|
|
277
277
|
}
|
|
278
278
|
|
|
279
|
-
/// True if the declaration is a `@Event(.sync)` property. A sync event dispatches inline on the
|
|
280
|
-
/// JS thread instead of scheduling, so `@ExpoModule`/`@SharedObject` stamp it with `@JavaScriptActor`,
|
|
281
|
-
/// making "must be called on the JS thread" a compile-time guarantee at the call site. Async events
|
|
282
|
-
/// (the default) are deliberately not stamped: their `emit` schedules onto the JS thread itself, so
|
|
283
|
-
/// they stay callable from any thread.
|
|
284
|
-
internal func isSyncEventMember(_ decl: DeclSyntaxProtocol) -> Bool {
|
|
285
|
-
guard let varDecl = decl.as(VariableDeclSyntax.self),
|
|
286
|
-
let attribute = varDecl.attributes.firstAttribute(named: "Event")
|
|
287
|
-
else {
|
|
288
|
-
return false
|
|
289
|
-
}
|
|
290
|
-
return hasOption(attribute, named: "sync")
|
|
291
|
-
}
|
|
292
|
-
|
|
293
279
|
/// Reads a labeled array-literal argument of an attribute, e.g. `@ExpoModule(classes: [Foo.self, Bar.self])`,
|
|
294
280
|
/// and returns the type names referenced (e.g. `["Foo", "Bar"]`). Each element must be a
|
|
295
281
|
/// `<TypeName>.self` member-access expression; non-conforming elements are skipped silently.
|
|
@@ -363,6 +349,19 @@ internal func memberHasJSAttribute(_ decl: DeclSyntaxProtocol) -> Bool {
|
|
|
363
349
|
return false
|
|
364
350
|
}
|
|
365
351
|
|
|
352
|
+
/// The generic parameter clause of a `@JS` function or initializer, or `nil` when it has none. The
|
|
353
|
+
/// binding decodes each argument by its static type, so a generic member can't be bound: `@JS` reports
|
|
354
|
+
/// it, and `@ExpoModule` and `@SharedObject` leave it out.
|
|
355
|
+
internal func jsGenericParameterClause(of decl: DeclSyntaxProtocol) -> GenericParameterClauseSyntax? {
|
|
356
|
+
if let funcDecl = decl.as(FunctionDeclSyntax.self) {
|
|
357
|
+
return funcDecl.genericParameterClause
|
|
358
|
+
}
|
|
359
|
+
if let initDecl = decl.as(InitializerDeclSyntax.self) {
|
|
360
|
+
return initDecl.genericParameterClause
|
|
361
|
+
}
|
|
362
|
+
return nil
|
|
363
|
+
}
|
|
364
|
+
|
|
366
365
|
/// Decides whether the macro should stamp `@JavaScriptActor` on a `@JS`-marked member.
|
|
367
366
|
/// The macro defers to the user when they've already chosen an isolation:
|
|
368
367
|
/// - the member opted out with `@JS(.concurrent)`
|
package/build/types.d.ts
CHANGED
|
@@ -260,8 +260,8 @@ export interface ExportedEvent extends ExportedAccess, ExportedAvailability {
|
|
|
260
260
|
jsName: string;
|
|
261
261
|
/** The payload type, absent for a no-payload `() -> Void` event. */
|
|
262
262
|
payload?: TypeNode;
|
|
263
|
-
/**
|
|
264
|
-
|
|
263
|
+
/** How the event is dispatched: the default `scheduled`, or the `.inline` or `.blocking` option. */
|
|
264
|
+
dispatch: 'scheduled' | 'inline' | 'blocking';
|
|
265
265
|
}
|
|
266
266
|
/** The `@JS init` of a shared object: the constructor JS calls with `new`. */
|
|
267
267
|
export interface ExportedInitializer extends ExportedAccess, ExportedAvailability {
|
|
@@ -480,5 +480,5 @@ export interface ExpandResult {
|
|
|
480
480
|
* as a clear error instead of silently misread fields.
|
|
481
481
|
*/
|
|
482
482
|
export declare const SUPPORTED_SCAN_MODULES_SCHEMA_VERSION = 2;
|
|
483
|
-
export declare const SUPPORTED_SCAN_EXPORTS_SCHEMA_VERSION =
|
|
483
|
+
export declare const SUPPORTED_SCAN_EXPORTS_SCHEMA_VERSION = 11;
|
|
484
484
|
export declare const SUPPORTED_EXPAND_SCHEMA_VERSION = 1;
|
package/build/types.js
CHANGED
|
@@ -12,5 +12,5 @@ exports.SUPPORTED_EXPAND_SCHEMA_VERSION = exports.SUPPORTED_SCAN_EXPORTS_SCHEMA_
|
|
|
12
12
|
* as a clear error instead of silently misread fields.
|
|
13
13
|
*/
|
|
14
14
|
exports.SUPPORTED_SCAN_MODULES_SCHEMA_VERSION = 2;
|
|
15
|
-
exports.SUPPORTED_SCAN_EXPORTS_SCHEMA_VERSION =
|
|
15
|
+
exports.SUPPORTED_SCAN_EXPORTS_SCHEMA_VERSION = 11;
|
|
16
16
|
exports.SUPPORTED_EXPAND_SCHEMA_VERSION = 1;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "expo-modules-macros",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.32.0",
|
|
4
4
|
"description": "Swift macro plugin for Expo modules",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -26,9 +26,9 @@
|
|
|
26
26
|
"typescript": "^5.4.0"
|
|
27
27
|
},
|
|
28
28
|
"optionalDependencies": {
|
|
29
|
-
"@expo/modules-macros-linux-arm64": "0.
|
|
30
|
-
"@expo/modules-macros-linux-x64": "0.
|
|
31
|
-
"@expo/modules-macros-win32-arm64": "0.
|
|
32
|
-
"@expo/modules-macros-win32-x64": "0.
|
|
29
|
+
"@expo/modules-macros-linux-arm64": "0.32.0",
|
|
30
|
+
"@expo/modules-macros-linux-x64": "0.32.0",
|
|
31
|
+
"@expo/modules-macros-win32-arm64": "0.32.0",
|
|
32
|
+
"@expo/modules-macros-win32-x64": "0.32.0"
|
|
33
33
|
}
|
|
34
34
|
}
|