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.
@@ -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 JSNameCollisionNote: NoteMessage {
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 noteID = MessageID(domain: "ExpoModulesMacros", id: "js-name-collision-first")
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(.sync)` or
17
- /// `@Event("name", EventOptions.sync)` -> true for the matching name. Options are written as
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
- /** `@Event(.sync)`, dispatching inline on the JS thread instead of scheduling. */
264
- sync: boolean;
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 = 10;
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 = 10;
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.30.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.0",
30
- "@expo/modules-macros-linux-x64": "0.30.0",
31
- "@expo/modules-macros-win32-arm64": "0.30.0",
32
- "@expo/modules-macros-win32-x64": "0.30.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
  }