expo-modules-macros 0.31.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
+ }
@@ -349,6 +349,19 @@ internal func memberHasJSAttribute(_ decl: DeclSyntaxProtocol) -> Bool {
349
349
  return false
350
350
  }
351
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
+
352
365
  /// Decides whether the macro should stamp `@JavaScriptActor` on a `@JS`-marked member.
353
366
  /// The macro defers to the user when they've already chosen an isolation:
354
367
  /// - the member opted out with `@JS(.concurrent)`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-modules-macros",
3
- "version": "0.31.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.31.0",
30
- "@expo/modules-macros-linux-x64": "0.31.0",
31
- "@expo/modules-macros-win32-arm64": "0.31.0",
32
- "@expo/modules-macros-win32-x64": "0.31.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
  }