Skip to content

Commit c58bc3d

Browse files
BridgeJS: Fix macro unit tests and macro implementations to pass tests
1 parent 7bce3c7 commit c58bc3d

File tree

8 files changed

+104
-27
lines changed

8 files changed

+104
-27
lines changed

Plugins/BridgeJS/Sources/BridgeJSMacros/JSClassMacro.swift

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,16 @@ extension JSClassMacro: ExtensionMacro {
6666
conformingTo protocols: [TypeSyntax],
6767
in context: some MacroExpansionContext
6868
) throws -> [ExtensionDeclSyntax] {
69-
guard declaration.is(StructDeclSyntax.self) else { return [] }
69+
guard let structDecl = declaration.as(StructDeclSyntax.self) else { return [] }
7070
guard !protocols.isEmpty else { return [] }
7171

72+
// Do not add extension if the struct already conforms to _JSBridgedClass
73+
if let clause = structDecl.inheritanceClause,
74+
clause.inheritedTypes.contains(where: { $0.type.trimmed.description == "_JSBridgedClass" })
75+
{
76+
return []
77+
}
78+
7279
let conformanceList = protocols.map { $0.trimmed.description }.joined(separator: ", ")
7380
return [
7481
try ExtensionDeclSyntax("extension \(type.trimmed): \(raw: conformanceList) {}")

Plugins/BridgeJS/Sources/BridgeJSMacros/JSFunctionMacro.swift

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ extension JSFunctionMacro: BodyMacro {
4747
context.diagnose(
4848
Diagnostic(node: Syntax(declaration), message: JSMacroMessage.unsupportedDeclaration)
4949
)
50-
return []
50+
return [CodeBlockItemSyntax(stringLiteral: "fatalError(\"@JSFunction init must be inside a type\")")]
5151
}
5252

5353
let glueName = JSMacroHelper.glueName(baseName: "init", enclosingTypeName: enclosingTypeName)
@@ -70,3 +70,19 @@ extension JSFunctionMacro: BodyMacro {
7070
return []
7171
}
7272
}
73+
74+
extension JSFunctionMacro: PeerMacro {
75+
/// Emits a diagnostic when @JSFunction is applied to a declaration that is not a function or initializer.
76+
/// BodyMacro is only invoked for declarations with optional code blocks (e.g. functions, initializers),
77+
/// so for vars and other decls we need PeerMacro to run and diagnose.
78+
public static func expansion(
79+
of node: AttributeSyntax,
80+
providingPeersOf declaration: some DeclSyntaxProtocol,
81+
in context: some MacroExpansionContext
82+
) throws -> [DeclSyntax] {
83+
if declaration.is(FunctionDeclSyntax.self) { return [] }
84+
if declaration.is(InitializerDeclSyntax.self) { return [] }
85+
context.diagnose(Diagnostic(node: Syntax(declaration), message: JSMacroMessage.unsupportedDeclaration))
86+
return []
87+
}
88+
}

Plugins/BridgeJS/Sources/BridgeJSMacros/JSGetterMacro.swift

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,20 @@ extension JSGetterMacro: AccessorMacro {
6060
]
6161
}
6262
}
63+
64+
extension JSGetterMacro: PeerMacro {
65+
/// Emits a diagnostic when @JSGetter is applied to a declaration that is not a variable (e.g. a function).
66+
/// AccessorMacro may not be invoked for non-property declarations. For variables with multiple
67+
/// bindings, the compiler emits its own diagnostic; we only diagnose non-variable decls here.
68+
public static func expansion(
69+
of node: AttributeSyntax,
70+
providingPeersOf declaration: some DeclSyntaxProtocol,
71+
in context: some MacroExpansionContext
72+
) throws -> [DeclSyntax] {
73+
guard declaration.is(VariableDeclSyntax.self) else {
74+
context.diagnose(Diagnostic(node: Syntax(declaration), message: JSMacroMessage.unsupportedVariable))
75+
return []
76+
}
77+
return []
78+
}
79+
}

Plugins/BridgeJS/Sources/BridgeJSMacros/JSSetterMacro.swift

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,13 @@ extension JSSetterMacro: BodyMacro {
2525
let rawFunctionName = JSMacroHelper.stripBackticks(functionName)
2626
guard rawFunctionName.hasPrefix("set"), rawFunctionName.count > 3 else {
2727
context.diagnose(Diagnostic(node: Syntax(declaration), message: JSMacroMessage.invalidSetterName))
28-
return []
28+
return [CodeBlockItemSyntax(stringLiteral: "fatalError(\"@JSSetter function name must start with 'set' followed by a property name\")")]
2929
}
3030

3131
let propertyName = String(rawFunctionName.dropFirst(3))
3232
guard !propertyName.isEmpty else {
3333
context.diagnose(Diagnostic(node: Syntax(declaration), message: JSMacroMessage.invalidSetterName))
34-
return []
34+
return [CodeBlockItemSyntax(stringLiteral: "fatalError(\"@JSSetter function name must start with 'set' followed by a property name\")")]
3535
}
3636

3737
// Convert first character to lowercase (e.g., "Foo" -> "foo")
@@ -56,7 +56,7 @@ extension JSSetterMacro: BodyMacro {
5656
let parameters = functionDecl.signature.parameterClause.parameters
5757
guard let firstParam = parameters.first else {
5858
context.diagnose(Diagnostic(node: Syntax(declaration), message: JSMacroMessage.setterRequiresParameter))
59-
return []
59+
return [CodeBlockItemSyntax(stringLiteral: "fatalError(\"@JSSetter function must have at least one parameter\")")]
6060
}
6161

6262
let paramName = firstParam.secondName ?? firstParam.firstName
@@ -69,3 +69,21 @@ extension JSSetterMacro: BodyMacro {
6969
return [CodeBlockItemSyntax(stringLiteral: "try \(call)")]
7070
}
7171
}
72+
73+
extension JSSetterMacro: PeerMacro {
74+
/// Emits a diagnostic when @JSSetter is applied to a declaration that is not a function.
75+
/// BodyMacro is only invoked for declarations with optional code blocks (e.g. functions).
76+
public static func expansion(
77+
of node: AttributeSyntax,
78+
providingPeersOf declaration: some DeclSyntaxProtocol,
79+
in context: some MacroExpansionContext
80+
) throws -> [DeclSyntax] {
81+
guard declaration.is(FunctionDeclSyntax.self) else {
82+
context.diagnose(
83+
Diagnostic(node: Syntax(declaration), message: JSMacroMessage.unsupportedSetterDeclaration)
84+
)
85+
return []
86+
}
87+
return []
88+
}
89+
}

Plugins/BridgeJS/Tests/BridgeJSMacrosTests/JSClassMacroTests.swift

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -73,11 +73,11 @@ import BridgeJSMacros
7373
""",
7474
expandedSource: """
7575
struct MyClass {
76-
let jsObject: JSObject
77-
7876
init(unsafelyWrapping jsObject: JSObject) {
7977
self.jsObject = jsObject
8078
}
79+
80+
let jsObject: JSObject
8181
}
8282
8383
extension MyClass: _JSBridgedClass {
@@ -127,10 +127,10 @@ import BridgeJSMacros
127127
""",
128128
expandedSource: """
129129
struct MyClass {
130-
let jsObject: JSObject
131-
132130
var name: String
133131
132+
let jsObject: JSObject
133+
134134
init(unsafelyWrapping jsObject: JSObject) {
135135
self.jsObject = jsObject
136136
}
@@ -223,10 +223,10 @@ import BridgeJSMacros
223223
""",
224224
expandedSource: """
225225
struct MyClass {
226-
let jsObject: JSObject
227-
228226
var otherProperty: String
229227
228+
let jsObject: JSObject
229+
230230
init(unsafelyWrapping jsObject: JSObject) {
231231
self.jsObject = jsObject
232232
}
@@ -251,11 +251,11 @@ import BridgeJSMacros
251251
""",
252252
expandedSource: """
253253
struct MyClass {
254-
let jsObject: JSObject
255-
256254
init(name: String) {
257255
}
258256
257+
let jsObject: JSObject
258+
259259
init(unsafelyWrapping jsObject: JSObject) {
260260
self.jsObject = jsObject
261261
}
@@ -280,11 +280,11 @@ import BridgeJSMacros
280280
""",
281281
expandedSource: """
282282
struct MyClass {
283-
let jsObject: JSObject
284-
285283
var name: String
286284
var age: Int
287285
286+
let jsObject: JSObject
287+
288288
init(unsafelyWrapping jsObject: JSObject) {
289289
self.jsObject = jsObject
290290
}
@@ -309,6 +309,7 @@ import BridgeJSMacros
309309
expandedSource: """
310310
/// Documentation comment
311311
struct MyClass {
312+
312313
let jsObject: JSObject
313314
314315
init(unsafelyWrapping jsObject: JSObject) {
@@ -333,6 +334,7 @@ import BridgeJSMacros
333334
""",
334335
expandedSource: """
335336
struct MyClass: _JSBridgedClass {
337+
336338
let jsObject: JSObject
337339
338340
init(unsafelyWrapping jsObject: JSObject) {

Plugins/BridgeJS/Tests/BridgeJSMacrosTests/JSFunctionMacroTests.swift

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ import BridgeJSMacros
186186
expandedSource: """
187187
struct MyClass {
188188
static func create() -> MyClass {
189-
return _$create()
189+
return _$MyClass_create()
190190
}
191191
}
192192
""",
@@ -206,7 +206,7 @@ import BridgeJSMacros
206206
expandedSource: """
207207
class MyClass {
208208
class func create() -> MyClass {
209-
return _$create()
209+
return _$MyClass_create()
210210
}
211211
}
212212
""",
@@ -285,7 +285,9 @@ import BridgeJSMacros
285285
init()
286286
""",
287287
expandedSource: """
288-
init()
288+
init() {
289+
fatalError("@JSFunction init must be inside a type")
290+
}
289291
""",
290292
diagnostics: [
291293
DiagnosticSpec(

Plugins/BridgeJS/Tests/BridgeJSMacrosTests/JSGetterMacroTests.swift

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ import BridgeJSMacros
103103
struct MyClass {
104104
static var version: String {
105105
get throws(JSException) {
106-
return try _$version_get()
106+
return try _$MyClass_version_get()
107107
}
108108
}
109109
}
@@ -125,7 +125,7 @@ import BridgeJSMacros
125125
class MyClass {
126126
class var version: String {
127127
get throws(JSException) {
128-
return try _$version_get()
128+
return try _$MyClass_version_get()
129129
}
130130
}
131131
}
@@ -231,7 +231,13 @@ import BridgeJSMacros
231231
""",
232232
diagnostics: [
233233
DiagnosticSpec(
234-
message: "@JSGetter can only be applied to single-variable declarations.",
234+
message: "accessor macro can only be applied to a single variable",
235+
line: 1,
236+
column: 1,
237+
severity: .error
238+
),
239+
DiagnosticSpec(
240+
message: "peer macro can only be applied to a single variable",
235241
line: 1,
236242
column: 1,
237243
severity: .error
@@ -264,6 +270,8 @@ import BridgeJSMacros
264270
)
265271
}
266272

273+
#if canImport(SwiftSyntax601)
274+
// https://github.com/swiftlang/swift-syntax/pull/2722
267275
@Test func variableWithTrailingComment() {
268276
TestSupport.assertMacroExpansion(
269277
"""
@@ -281,6 +289,7 @@ import BridgeJSMacros
281289
indentationWidth: indentationWidth,
282290
)
283291
}
292+
#endif
284293

285294
@Test func variableWithUnderscoreName() {
286295
TestSupport.assertMacroExpansion(
@@ -291,7 +300,7 @@ import BridgeJSMacros
291300
expandedSource: """
292301
var _internal: String {
293302
get throws(JSException) {
294-
return try _$internal_get()
303+
return try _$_internal_get()
295304
}
296305
}
297306
""",

Plugins/BridgeJS/Tests/BridgeJSMacrosTests/JSSetterMacroTests.swift

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ import BridgeJSMacros
7474
expandedSource: """
7575
struct MyClass {
7676
static func setVersion(_ version: String) throws(JSException) {
77-
try _$version_set(version)
77+
try _$MyClass_version_set(version)
7878
}
7979
}
8080
""",
@@ -94,7 +94,7 @@ import BridgeJSMacros
9494
expandedSource: """
9595
class MyClass {
9696
class func setConfig(_ config: Config) throws(JSException) {
97-
try _$config_set(config)
97+
try _$MyClass_config_set(config)
9898
}
9999
}
100100
""",
@@ -168,7 +168,9 @@ import BridgeJSMacros
168168
func updateFoo(_ value: Foo) throws(JSException)
169169
""",
170170
expandedSource: """
171-
func updateFoo(_ value: Foo) throws(JSException)
171+
func updateFoo(_ value: Foo) throws(JSException) {
172+
fatalError("@JSSetter function name must start with 'set' followed by a property name")
173+
}
172174
""",
173175
diagnostics: [
174176
DiagnosticSpec(
@@ -190,7 +192,9 @@ import BridgeJSMacros
190192
func set(_ value: Foo) throws(JSException)
191193
""",
192194
expandedSource: """
193-
func set(_ value: Foo) throws(JSException)
195+
func set(_ value: Foo) throws(JSException) {
196+
fatalError("@JSSetter function name must start with 'set' followed by a property name")
197+
}
194198
""",
195199
diagnostics: [
196200
DiagnosticSpec(
@@ -212,7 +216,9 @@ import BridgeJSMacros
212216
func setFoo() throws(JSException)
213217
""",
214218
expandedSource: """
215-
func setFoo() throws(JSException)
219+
func setFoo() throws(JSException) {
220+
fatalError("@JSSetter function must have at least one parameter")
221+
}
216222
""",
217223
diagnostics: [
218224
DiagnosticSpec(

0 commit comments

Comments
 (0)