From 7bce3c7c66cdb767079cb1687c512db498dbc652 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Tue, 3 Feb 2026 11:00:15 +0900 Subject: [PATCH 1/2] BridgeJS: Fix macro test suites silently ignoring failures --- Plugins/BridgeJS/Package.swift | 2 +- .../JSClassMacroTests.swift | 32 ++--- .../JSFunctionMacroTests.swift | 127 +++++++++--------- .../JSGetterMacroTests.swift | 91 +++++++------ .../JSSetterMacroTests.swift | 91 +++++++------ .../BridgeJSMacrosTests/TestSupport.swift | 52 +++++++ 6 files changed, 228 insertions(+), 167 deletions(-) create mode 100644 Plugins/BridgeJS/Tests/BridgeJSMacrosTests/TestSupport.swift diff --git a/Plugins/BridgeJS/Package.swift b/Plugins/BridgeJS/Package.swift index 01a3c6d41..a03172f17 100644 --- a/Plugins/BridgeJS/Package.swift +++ b/Plugins/BridgeJS/Package.swift @@ -72,7 +72,7 @@ let package = Package( name: "BridgeJSMacrosTests", dependencies: [ "BridgeJSMacros", - .product(name: "SwiftSyntaxMacrosTestSupport", package: "swift-syntax"), + .product(name: "SwiftSyntaxMacrosGenericTestSupport", package: "swift-syntax"), ] ), diff --git a/Plugins/BridgeJS/Tests/BridgeJSMacrosTests/JSClassMacroTests.swift b/Plugins/BridgeJS/Tests/BridgeJSMacrosTests/JSClassMacroTests.swift index b27b93941..5d98a38af 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSMacrosTests/JSClassMacroTests.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSMacrosTests/JSClassMacroTests.swift @@ -1,8 +1,7 @@ import SwiftDiagnostics import SwiftSyntax import SwiftSyntaxMacroExpansion -import SwiftSyntaxMacros -import SwiftSyntaxMacrosTestSupport +import SwiftSyntaxMacrosGenericTestSupport import Testing import BridgeJSMacros @@ -13,7 +12,7 @@ import BridgeJSMacros ] @Test func emptyStruct() { - assertMacroExpansion( + TestSupport.assertMacroExpansion( """ @JSClass struct MyClass { @@ -21,6 +20,7 @@ import BridgeJSMacros """, expandedSource: """ struct MyClass { + let jsObject: JSObject init(unsafelyWrapping jsObject: JSObject) { @@ -32,12 +32,12 @@ import BridgeJSMacros } """, macroSpecs: macroSpecs, - indentationWidth: indentationWidth + indentationWidth: indentationWidth, ) } @Test func structWithExistingJSObject() { - assertMacroExpansion( + TestSupport.assertMacroExpansion( """ @JSClass struct MyClass { @@ -62,7 +62,7 @@ import BridgeJSMacros } @Test func structWithExistingInit() { - assertMacroExpansion( + TestSupport.assertMacroExpansion( """ @JSClass struct MyClass { @@ -89,7 +89,7 @@ import BridgeJSMacros } @Test func structWithBothExisting() { - assertMacroExpansion( + TestSupport.assertMacroExpansion( """ @JSClass struct MyClass { @@ -118,7 +118,7 @@ import BridgeJSMacros } @Test func structWithMembers() { - assertMacroExpansion( + TestSupport.assertMacroExpansion( """ @JSClass struct MyClass { @@ -145,7 +145,7 @@ import BridgeJSMacros } @Test func _class() { - assertMacroExpansion( + TestSupport.assertMacroExpansion( """ @JSClass class MyClass { @@ -168,7 +168,7 @@ import BridgeJSMacros } @Test func _enum() { - assertMacroExpansion( + TestSupport.assertMacroExpansion( """ @JSClass enum MyEnum { @@ -191,7 +191,7 @@ import BridgeJSMacros } @Test func _actor() { - assertMacroExpansion( + TestSupport.assertMacroExpansion( """ @JSClass actor MyActor { @@ -214,7 +214,7 @@ import BridgeJSMacros } @Test func structWithDifferentJSObjectName() { - assertMacroExpansion( + TestSupport.assertMacroExpansion( """ @JSClass struct MyClass { @@ -241,7 +241,7 @@ import BridgeJSMacros } @Test func structWithDifferentInit() { - assertMacroExpansion( + TestSupport.assertMacroExpansion( """ @JSClass struct MyClass { @@ -270,7 +270,7 @@ import BridgeJSMacros } @Test func structWithMultipleMembers() { - assertMacroExpansion( + TestSupport.assertMacroExpansion( """ @JSClass struct MyClass { @@ -299,7 +299,7 @@ import BridgeJSMacros } @Test func structWithComment() { - assertMacroExpansion( + TestSupport.assertMacroExpansion( """ /// Documentation comment @JSClass @@ -325,7 +325,7 @@ import BridgeJSMacros } @Test func structAlreadyConforms() { - assertMacroExpansion( + TestSupport.assertMacroExpansion( """ @JSClass struct MyClass: _JSBridgedClass { diff --git a/Plugins/BridgeJS/Tests/BridgeJSMacrosTests/JSFunctionMacroTests.swift b/Plugins/BridgeJS/Tests/BridgeJSMacrosTests/JSFunctionMacroTests.swift index 8cfdd66e0..22c6ced59 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSMacrosTests/JSFunctionMacroTests.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSMacrosTests/JSFunctionMacroTests.swift @@ -1,15 +1,18 @@ import SwiftDiagnostics import SwiftSyntax -import SwiftSyntaxMacros -import SwiftSyntaxMacrosTestSupport +import SwiftSyntaxMacroExpansion +import SwiftSyntaxMacrosGenericTestSupport import Testing import BridgeJSMacros @Suite struct JSFunctionMacroTests { private let indentationWidth: Trivia = .spaces(4) + private let macroSpecs: [String: MacroSpec] = [ + "JSFunction": MacroSpec(type: JSFunctionMacro.self) + ] @Test func topLevelFunction() { - assertMacroExpansion( + TestSupport.assertMacroExpansion( """ @JSFunction func greet(name: String) -> String @@ -19,13 +22,13 @@ import BridgeJSMacros return _$greet(name) } """, - macros: ["JSFunction": JSFunctionMacro.self], - indentationWidth: indentationWidth + macroSpecs: macroSpecs, + indentationWidth: indentationWidth, ) } @Test func topLevelFunctionVoidReturn() { - assertMacroExpansion( + TestSupport.assertMacroExpansion( """ @JSFunction func log(message: String) @@ -35,13 +38,13 @@ import BridgeJSMacros _$log(message) } """, - macros: ["JSFunction": JSFunctionMacro.self], - indentationWidth: indentationWidth + macroSpecs: macroSpecs, + indentationWidth: indentationWidth, ) } @Test func topLevelFunctionWithExplicitVoidReturn() { - assertMacroExpansion( + TestSupport.assertMacroExpansion( """ @JSFunction func log(message: String) -> Void @@ -51,13 +54,13 @@ import BridgeJSMacros _$log(message) } """, - macros: ["JSFunction": JSFunctionMacro.self], - indentationWidth: indentationWidth + macroSpecs: macroSpecs, + indentationWidth: indentationWidth, ) } @Test func topLevelFunctionWithEmptyTupleReturn() { - assertMacroExpansion( + TestSupport.assertMacroExpansion( """ @JSFunction func log(message: String) -> () @@ -67,13 +70,13 @@ import BridgeJSMacros _$log(message) } """, - macros: ["JSFunction": JSFunctionMacro.self], - indentationWidth: indentationWidth + macroSpecs: macroSpecs, + indentationWidth: indentationWidth, ) } @Test func topLevelFunctionThrows() { - assertMacroExpansion( + TestSupport.assertMacroExpansion( """ @JSFunction func parse(json: String) throws -> [String: Any] @@ -83,13 +86,13 @@ import BridgeJSMacros return try _$parse(json) } """, - macros: ["JSFunction": JSFunctionMacro.self], - indentationWidth: indentationWidth + macroSpecs: macroSpecs, + indentationWidth: indentationWidth, ) } @Test func topLevelFunctionAsync() { - assertMacroExpansion( + TestSupport.assertMacroExpansion( """ @JSFunction func fetch(url: String) async -> String @@ -99,13 +102,13 @@ import BridgeJSMacros return await _$fetch(url) } """, - macros: ["JSFunction": JSFunctionMacro.self], - indentationWidth: indentationWidth + macroSpecs: macroSpecs, + indentationWidth: indentationWidth, ) } @Test func topLevelFunctionAsyncThrows() { - assertMacroExpansion( + TestSupport.assertMacroExpansion( """ @JSFunction func fetch(url: String) async throws -> String @@ -115,13 +118,13 @@ import BridgeJSMacros return try await _$fetch(url) } """, - macros: ["JSFunction": JSFunctionMacro.self], - indentationWidth: indentationWidth + macroSpecs: macroSpecs, + indentationWidth: indentationWidth, ) } @Test func topLevelFunctionWithUnderscoreParameter() { - assertMacroExpansion( + TestSupport.assertMacroExpansion( """ @JSFunction func process(_ value: Int) -> Int @@ -131,13 +134,13 @@ import BridgeJSMacros return _$process(value) } """, - macros: ["JSFunction": JSFunctionMacro.self], - indentationWidth: indentationWidth + macroSpecs: macroSpecs, + indentationWidth: indentationWidth, ) } @Test func topLevelFunctionWithMultipleParameters() { - assertMacroExpansion( + TestSupport.assertMacroExpansion( """ @JSFunction func add(a: Int, b: Int) -> Int @@ -147,13 +150,13 @@ import BridgeJSMacros return _$add(a, b) } """, - macros: ["JSFunction": JSFunctionMacro.self], - indentationWidth: indentationWidth + macroSpecs: macroSpecs, + indentationWidth: indentationWidth, ) } @Test func instanceMethod() { - assertMacroExpansion( + TestSupport.assertMacroExpansion( """ struct MyClass { @JSFunction @@ -167,13 +170,13 @@ import BridgeJSMacros } } """, - macros: ["JSFunction": JSFunctionMacro.self], - indentationWidth: indentationWidth + macroSpecs: macroSpecs, + indentationWidth: indentationWidth, ) } @Test func staticMethod() { - assertMacroExpansion( + TestSupport.assertMacroExpansion( """ struct MyClass { @JSFunction @@ -187,13 +190,13 @@ import BridgeJSMacros } } """, - macros: ["JSFunction": JSFunctionMacro.self], - indentationWidth: indentationWidth + macroSpecs: macroSpecs, + indentationWidth: indentationWidth, ) } @Test func classMethod() { - assertMacroExpansion( + TestSupport.assertMacroExpansion( """ class MyClass { @JSFunction @@ -207,13 +210,13 @@ import BridgeJSMacros } } """, - macros: ["JSFunction": JSFunctionMacro.self], - indentationWidth: indentationWidth + macroSpecs: macroSpecs, + indentationWidth: indentationWidth, ) } @Test func initializer() { - assertMacroExpansion( + TestSupport.assertMacroExpansion( """ struct MyClass { @JSFunction @@ -228,13 +231,13 @@ import BridgeJSMacros } } """, - macros: ["JSFunction": JSFunctionMacro.self], - indentationWidth: indentationWidth + macroSpecs: macroSpecs, + indentationWidth: indentationWidth, ) } @Test func initializerThrows() { - assertMacroExpansion( + TestSupport.assertMacroExpansion( """ struct MyClass { @JSFunction @@ -249,13 +252,13 @@ import BridgeJSMacros } } """, - macros: ["JSFunction": JSFunctionMacro.self], - indentationWidth: indentationWidth + macroSpecs: macroSpecs, + indentationWidth: indentationWidth, ) } @Test func initializerAsyncThrows() { - assertMacroExpansion( + TestSupport.assertMacroExpansion( """ struct MyClass { @JSFunction @@ -270,13 +273,13 @@ import BridgeJSMacros } } """, - macros: ["JSFunction": JSFunctionMacro.self], - indentationWidth: indentationWidth + macroSpecs: macroSpecs, + indentationWidth: indentationWidth, ) } @Test func initializerWithoutEnclosingType() { - assertMacroExpansion( + TestSupport.assertMacroExpansion( """ @JSFunction init() @@ -291,13 +294,13 @@ import BridgeJSMacros column: 1 ) ], - macros: ["JSFunction": JSFunctionMacro.self], - indentationWidth: indentationWidth + macroSpecs: macroSpecs, + indentationWidth: indentationWidth, ) } @Test func unsupportedDeclaration() { - assertMacroExpansion( + TestSupport.assertMacroExpansion( """ @JSFunction var property: String @@ -312,13 +315,13 @@ import BridgeJSMacros column: 1 ) ], - macros: ["JSFunction": JSFunctionMacro.self], - indentationWidth: indentationWidth + macroSpecs: macroSpecs, + indentationWidth: indentationWidth, ) } @Test func enumInstanceMethod() { - assertMacroExpansion( + TestSupport.assertMacroExpansion( """ enum MyEnum { @JSFunction @@ -332,13 +335,13 @@ import BridgeJSMacros } } """, - macros: ["JSFunction": JSFunctionMacro.self], - indentationWidth: indentationWidth + macroSpecs: macroSpecs, + indentationWidth: indentationWidth, ) } @Test func actorInstanceMethod() { - assertMacroExpansion( + TestSupport.assertMacroExpansion( """ actor MyActor { @JSFunction @@ -352,13 +355,13 @@ import BridgeJSMacros } } """, - macros: ["JSFunction": JSFunctionMacro.self], - indentationWidth: indentationWidth + macroSpecs: macroSpecs, + indentationWidth: indentationWidth, ) } @Test func functionWithExistingBody() { - assertMacroExpansion( + TestSupport.assertMacroExpansion( """ @JSFunction func greet(name: String) -> String { @@ -370,8 +373,8 @@ import BridgeJSMacros return _$greet(name) } """, - macros: ["JSFunction": JSFunctionMacro.self], - indentationWidth: indentationWidth + macroSpecs: macroSpecs, + indentationWidth: indentationWidth, ) } } diff --git a/Plugins/BridgeJS/Tests/BridgeJSMacrosTests/JSGetterMacroTests.swift b/Plugins/BridgeJS/Tests/BridgeJSMacrosTests/JSGetterMacroTests.swift index 8c7f3c119..35b79d255 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSMacrosTests/JSGetterMacroTests.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSMacrosTests/JSGetterMacroTests.swift @@ -1,15 +1,18 @@ import SwiftDiagnostics import SwiftSyntax -import SwiftSyntaxMacros -import SwiftSyntaxMacrosTestSupport +import SwiftSyntaxMacroExpansion +import SwiftSyntaxMacrosGenericTestSupport import Testing import BridgeJSMacros @Suite struct JSGetterMacroTests { private let indentationWidth: Trivia = .spaces(4) + private let macroSpecs: [String: MacroSpec] = [ + "JSGetter": MacroSpec(type: JSGetterMacro.self) + ] @Test func topLevelVariable() { - assertMacroExpansion( + TestSupport.assertMacroExpansion( """ @JSGetter var count: Int @@ -21,13 +24,13 @@ import BridgeJSMacros } } """, - macros: ["JSGetter": JSGetterMacro.self], - indentationWidth: indentationWidth + macroSpecs: macroSpecs, + indentationWidth: indentationWidth, ) } @Test func topLevelLet() { - assertMacroExpansion( + TestSupport.assertMacroExpansion( """ @JSGetter let constant: String @@ -39,13 +42,13 @@ import BridgeJSMacros } } """, - macros: ["JSGetter": JSGetterMacro.self], - indentationWidth: indentationWidth + macroSpecs: macroSpecs, + indentationWidth: indentationWidth, ) } @Test func instanceProperty() { - assertMacroExpansion( + TestSupport.assertMacroExpansion( """ struct MyClass { @JSGetter @@ -61,13 +64,13 @@ import BridgeJSMacros } } """, - macros: ["JSGetter": JSGetterMacro.self], - indentationWidth: indentationWidth + macroSpecs: macroSpecs, + indentationWidth: indentationWidth, ) } @Test func instanceLetProperty() { - assertMacroExpansion( + TestSupport.assertMacroExpansion( """ struct MyClass { @JSGetter @@ -83,13 +86,13 @@ import BridgeJSMacros } } """, - macros: ["JSGetter": JSGetterMacro.self], - indentationWidth: indentationWidth + macroSpecs: macroSpecs, + indentationWidth: indentationWidth, ) } @Test func staticProperty() { - assertMacroExpansion( + TestSupport.assertMacroExpansion( """ struct MyClass { @JSGetter @@ -105,13 +108,13 @@ import BridgeJSMacros } } """, - macros: ["JSGetter": JSGetterMacro.self], - indentationWidth: indentationWidth + macroSpecs: macroSpecs, + indentationWidth: indentationWidth, ) } @Test func classProperty() { - assertMacroExpansion( + TestSupport.assertMacroExpansion( """ class MyClass { @JSGetter @@ -127,13 +130,13 @@ import BridgeJSMacros } } """, - macros: ["JSGetter": JSGetterMacro.self], - indentationWidth: indentationWidth + macroSpecs: macroSpecs, + indentationWidth: indentationWidth, ) } @Test func enumProperty() { - assertMacroExpansion( + TestSupport.assertMacroExpansion( """ enum MyEnum { @JSGetter @@ -149,13 +152,13 @@ import BridgeJSMacros } } """, - macros: ["JSGetter": JSGetterMacro.self], - indentationWidth: indentationWidth + macroSpecs: macroSpecs, + indentationWidth: indentationWidth, ) } @Test func actorProperty() { - assertMacroExpansion( + TestSupport.assertMacroExpansion( """ actor MyActor { @JSGetter @@ -171,13 +174,13 @@ import BridgeJSMacros } } """, - macros: ["JSGetter": JSGetterMacro.self], - indentationWidth: indentationWidth + macroSpecs: macroSpecs, + indentationWidth: indentationWidth, ) } @Test func variableWithExistingAccessor() { - assertMacroExpansion( + TestSupport.assertMacroExpansion( """ @JSGetter var count: Int { @@ -194,13 +197,13 @@ import BridgeJSMacros } } """, - macros: ["JSGetter": JSGetterMacro.self], - indentationWidth: indentationWidth + macroSpecs: macroSpecs, + indentationWidth: indentationWidth, ) } @Test func variableWithInitializer() { - assertMacroExpansion( + TestSupport.assertMacroExpansion( """ @JSGetter var count: Int = 0 @@ -212,13 +215,13 @@ import BridgeJSMacros } } """, - macros: ["JSGetter": JSGetterMacro.self], - indentationWidth: indentationWidth + macroSpecs: macroSpecs, + indentationWidth: indentationWidth, ) } @Test func multipleBindings() { - assertMacroExpansion( + TestSupport.assertMacroExpansion( """ @JSGetter var x: Int, y: Int @@ -234,13 +237,13 @@ import BridgeJSMacros severity: .error ) ], - macros: ["JSGetter": JSGetterMacro.self], - indentationWidth: indentationWidth + macroSpecs: macroSpecs, + indentationWidth: indentationWidth, ) } @Test func unsupportedDeclaration() { - assertMacroExpansion( + TestSupport.assertMacroExpansion( """ @JSGetter func test() {} @@ -256,13 +259,13 @@ import BridgeJSMacros severity: .error ) ], - macros: ["JSGetter": JSGetterMacro.self], - indentationWidth: indentationWidth + macroSpecs: macroSpecs, + indentationWidth: indentationWidth, ) } @Test func variableWithTrailingComment() { - assertMacroExpansion( + TestSupport.assertMacroExpansion( """ @JSGetter var count: Int // comment @@ -274,13 +277,13 @@ import BridgeJSMacros } } """, - macros: ["JSGetter": JSGetterMacro.self], - indentationWidth: indentationWidth + macroSpecs: macroSpecs, + indentationWidth: indentationWidth, ) } @Test func variableWithUnderscoreName() { - assertMacroExpansion( + TestSupport.assertMacroExpansion( """ @JSGetter var _internal: String @@ -292,8 +295,8 @@ import BridgeJSMacros } } """, - macros: ["JSGetter": JSGetterMacro.self], - indentationWidth: indentationWidth + macroSpecs: macroSpecs, + indentationWidth: indentationWidth, ) } } diff --git a/Plugins/BridgeJS/Tests/BridgeJSMacrosTests/JSSetterMacroTests.swift b/Plugins/BridgeJS/Tests/BridgeJSMacrosTests/JSSetterMacroTests.swift index 83289e092..7e115bfac 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSMacrosTests/JSSetterMacroTests.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSMacrosTests/JSSetterMacroTests.swift @@ -1,15 +1,18 @@ import SwiftDiagnostics import SwiftSyntax -import SwiftSyntaxMacros -import SwiftSyntaxMacrosTestSupport +import SwiftSyntaxMacroExpansion +import SwiftSyntaxMacrosGenericTestSupport import Testing import BridgeJSMacros @Suite struct JSSetterMacroTests { private let indentationWidth: Trivia = .spaces(4) + private let macroSpecs: [String: MacroSpec] = [ + "JSSetter": MacroSpec(type: JSSetterMacro.self) + ] @Test func topLevelSetter() { - assertMacroExpansion( + TestSupport.assertMacroExpansion( """ @JSSetter func setFoo(_ value: Foo) throws(JSException) @@ -19,13 +22,13 @@ import BridgeJSMacros try _$foo_set(value) } """, - macros: ["JSSetter": JSSetterMacro.self], - indentationWidth: indentationWidth + macroSpecs: macroSpecs, + indentationWidth: indentationWidth, ) } @Test func topLevelSetterWithNamedParameter() { - assertMacroExpansion( + TestSupport.assertMacroExpansion( """ @JSSetter func setCount(count: Int) throws(JSException) @@ -35,13 +38,13 @@ import BridgeJSMacros try _$count_set(count) } """, - macros: ["JSSetter": JSSetterMacro.self], - indentationWidth: indentationWidth + macroSpecs: macroSpecs, + indentationWidth: indentationWidth, ) } @Test func instanceSetter() { - assertMacroExpansion( + TestSupport.assertMacroExpansion( """ struct MyClass { @JSSetter @@ -55,13 +58,13 @@ import BridgeJSMacros } } """, - macros: ["JSSetter": JSSetterMacro.self], - indentationWidth: indentationWidth + macroSpecs: macroSpecs, + indentationWidth: indentationWidth, ) } @Test func staticSetter() { - assertMacroExpansion( + TestSupport.assertMacroExpansion( """ struct MyClass { @JSSetter @@ -75,13 +78,13 @@ import BridgeJSMacros } } """, - macros: ["JSSetter": JSSetterMacro.self], - indentationWidth: indentationWidth + macroSpecs: macroSpecs, + indentationWidth: indentationWidth, ) } @Test func classSetter() { - assertMacroExpansion( + TestSupport.assertMacroExpansion( """ class MyClass { @JSSetter @@ -95,13 +98,13 @@ import BridgeJSMacros } } """, - macros: ["JSSetter": JSSetterMacro.self], - indentationWidth: indentationWidth + macroSpecs: macroSpecs, + indentationWidth: indentationWidth, ) } @Test func enumSetter() { - assertMacroExpansion( + TestSupport.assertMacroExpansion( """ enum MyEnum { @JSSetter @@ -115,13 +118,13 @@ import BridgeJSMacros } } """, - macros: ["JSSetter": JSSetterMacro.self], - indentationWidth: indentationWidth + macroSpecs: macroSpecs, + indentationWidth: indentationWidth, ) } @Test func actorSetter() { - assertMacroExpansion( + TestSupport.assertMacroExpansion( """ actor MyActor { @JSSetter @@ -135,13 +138,13 @@ import BridgeJSMacros } } """, - macros: ["JSSetter": JSSetterMacro.self], - indentationWidth: indentationWidth + macroSpecs: macroSpecs, + indentationWidth: indentationWidth, ) } @Test func setterWithExistingBody() { - assertMacroExpansion( + TestSupport.assertMacroExpansion( """ @JSSetter func setFoo(_ value: Foo) throws(JSException) { @@ -153,13 +156,13 @@ import BridgeJSMacros try _$foo_set(value) } """, - macros: ["JSSetter": JSSetterMacro.self], - indentationWidth: indentationWidth + macroSpecs: macroSpecs, + indentationWidth: indentationWidth, ) } @Test func invalidSetterName() { - assertMacroExpansion( + TestSupport.assertMacroExpansion( """ @JSSetter func updateFoo(_ value: Foo) throws(JSException) @@ -175,13 +178,13 @@ import BridgeJSMacros column: 1 ) ], - macros: ["JSSetter": JSSetterMacro.self], - indentationWidth: indentationWidth + macroSpecs: macroSpecs, + indentationWidth: indentationWidth, ) } @Test func setterNameTooShort() { - assertMacroExpansion( + TestSupport.assertMacroExpansion( """ @JSSetter func set(_ value: Foo) throws(JSException) @@ -197,13 +200,13 @@ import BridgeJSMacros column: 1 ) ], - macros: ["JSSetter": JSSetterMacro.self], - indentationWidth: indentationWidth + macroSpecs: macroSpecs, + indentationWidth: indentationWidth, ) } @Test func setterWithoutParameter() { - assertMacroExpansion( + TestSupport.assertMacroExpansion( """ @JSSetter func setFoo() throws(JSException) @@ -218,13 +221,13 @@ import BridgeJSMacros column: 1 ) ], - macros: ["JSSetter": JSSetterMacro.self], - indentationWidth: indentationWidth + macroSpecs: macroSpecs, + indentationWidth: indentationWidth, ) } @Test func unsupportedDeclaration() { - assertMacroExpansion( + TestSupport.assertMacroExpansion( """ @JSSetter var property: String @@ -239,13 +242,13 @@ import BridgeJSMacros column: 1 ) ], - macros: ["JSSetter": JSSetterMacro.self], - indentationWidth: indentationWidth + macroSpecs: macroSpecs, + indentationWidth: indentationWidth, ) } @Test func setterWithMultipleWords() { - assertMacroExpansion( + TestSupport.assertMacroExpansion( """ @JSSetter func setConnectionTimeout(_ timeout: Int) throws(JSException) @@ -255,13 +258,13 @@ import BridgeJSMacros try _$connectionTimeout_set(timeout) } """, - macros: ["JSSetter": JSSetterMacro.self], - indentationWidth: indentationWidth + macroSpecs: macroSpecs, + indentationWidth: indentationWidth, ) } @Test func setterWithSingleLetterProperty() { - assertMacroExpansion( + TestSupport.assertMacroExpansion( """ @JSSetter func setX(_ x: Int) throws(JSException) @@ -271,8 +274,8 @@ import BridgeJSMacros try _$x_set(x) } """, - macros: ["JSSetter": JSSetterMacro.self], - indentationWidth: indentationWidth + macroSpecs: macroSpecs, + indentationWidth: indentationWidth, ) } } diff --git a/Plugins/BridgeJS/Tests/BridgeJSMacrosTests/TestSupport.swift b/Plugins/BridgeJS/Tests/BridgeJSMacrosTests/TestSupport.swift new file mode 100644 index 000000000..84e343281 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSMacrosTests/TestSupport.swift @@ -0,0 +1,52 @@ +import SwiftSyntaxMacrosGenericTestSupport +import Testing +import SwiftSyntax +import SwiftDiagnostics +import SwiftSyntaxMacroExpansion + +enum TestSupport { + static func failureHandler(spec: TestFailureSpec) { + Issue.record( + Comment(rawValue: spec.message), + sourceLocation: .init( + fileID: spec.location.fileID, + filePath: spec.location.filePath, + line: spec.location.line, + column: spec.location.column + ) + ) + } + + static func assertMacroExpansion( + _ originalSource: String, + expandedSource expectedExpandedSource: String, + diagnostics: [DiagnosticSpec] = [], + macroSpecs: [String: MacroSpec], + applyFixIts: [String]? = nil, + fixedSource expectedFixedSource: String? = nil, + testModuleName: String = "TestModule", + testFileName: String = "test.swift", + indentationWidth: Trivia = .spaces(4), + fileID: StaticString = #fileID, + filePath: StaticString = #filePath, + line: UInt = #line, + column: UInt = #column + ) { + SwiftSyntaxMacrosGenericTestSupport.assertMacroExpansion( + originalSource, + expandedSource: expectedExpandedSource, + diagnostics: diagnostics, + macroSpecs: macroSpecs, + applyFixIts: applyFixIts, + fixedSource: expectedFixedSource, + testModuleName: testModuleName, + testFileName: testFileName, + indentationWidth: indentationWidth, + failureHandler: TestSupport.failureHandler, + fileID: fileID, + filePath: filePath, + line: line, + column: column + ) + } +} From e848be6173b83507e7a55063c89f85afb1a5a860 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Tue, 3 Feb 2026 11:19:28 +0900 Subject: [PATCH 2/2] BridgeJS: Fix macro unit tests and macro implementations to pass tests --- .../Sources/BridgeJSMacros/JSClassMacro.swift | 9 ++++- .../BridgeJSMacros/JSFunctionMacro.swift | 18 ++++++++- .../BridgeJSMacros/JSGetterMacro.swift | 17 +++++++++ .../BridgeJSMacros/JSSetterMacro.swift | 38 +++++++++++++++++-- .../JSClassMacroTests.swift | 22 ++++++----- .../JSFunctionMacroTests.swift | 8 ++-- .../JSGetterMacroTests.swift | 19 +++++++--- .../JSSetterMacroTests.swift | 16 +++++--- 8 files changed, 119 insertions(+), 28 deletions(-) diff --git a/Plugins/BridgeJS/Sources/BridgeJSMacros/JSClassMacro.swift b/Plugins/BridgeJS/Sources/BridgeJSMacros/JSClassMacro.swift index 8a3f83f05..2641df4bf 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSMacros/JSClassMacro.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSMacros/JSClassMacro.swift @@ -66,9 +66,16 @@ extension JSClassMacro: ExtensionMacro { conformingTo protocols: [TypeSyntax], in context: some MacroExpansionContext ) throws -> [ExtensionDeclSyntax] { - guard declaration.is(StructDeclSyntax.self) else { return [] } + guard let structDecl = declaration.as(StructDeclSyntax.self) else { return [] } guard !protocols.isEmpty else { return [] } + // Do not add extension if the struct already conforms to _JSBridgedClass + if let clause = structDecl.inheritanceClause, + clause.inheritedTypes.contains(where: { $0.type.trimmed.description == "_JSBridgedClass" }) + { + return [] + } + let conformanceList = protocols.map { $0.trimmed.description }.joined(separator: ", ") return [ try ExtensionDeclSyntax("extension \(type.trimmed): \(raw: conformanceList) {}") diff --git a/Plugins/BridgeJS/Sources/BridgeJSMacros/JSFunctionMacro.swift b/Plugins/BridgeJS/Sources/BridgeJSMacros/JSFunctionMacro.swift index 7dfd635f9..a51bf10c5 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSMacros/JSFunctionMacro.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSMacros/JSFunctionMacro.swift @@ -47,7 +47,7 @@ extension JSFunctionMacro: BodyMacro { context.diagnose( Diagnostic(node: Syntax(declaration), message: JSMacroMessage.unsupportedDeclaration) ) - return [] + return [CodeBlockItemSyntax(stringLiteral: "fatalError(\"@JSFunction init must be inside a type\")")] } let glueName = JSMacroHelper.glueName(baseName: "init", enclosingTypeName: enclosingTypeName) @@ -70,3 +70,19 @@ extension JSFunctionMacro: BodyMacro { return [] } } + +extension JSFunctionMacro: PeerMacro { + /// Emits a diagnostic when @JSFunction is applied to a declaration that is not a function or initializer. + /// BodyMacro is only invoked for declarations with optional code blocks (e.g. functions, initializers), + /// so for vars and other decls we need PeerMacro to run and diagnose. + public static func expansion( + of node: AttributeSyntax, + providingPeersOf declaration: some DeclSyntaxProtocol, + in context: some MacroExpansionContext + ) throws -> [DeclSyntax] { + if declaration.is(FunctionDeclSyntax.self) { return [] } + if declaration.is(InitializerDeclSyntax.self) { return [] } + context.diagnose(Diagnostic(node: Syntax(declaration), message: JSMacroMessage.unsupportedDeclaration)) + return [] + } +} diff --git a/Plugins/BridgeJS/Sources/BridgeJSMacros/JSGetterMacro.swift b/Plugins/BridgeJS/Sources/BridgeJSMacros/JSGetterMacro.swift index b996facfa..44c3620cf 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSMacros/JSGetterMacro.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSMacros/JSGetterMacro.swift @@ -60,3 +60,20 @@ extension JSGetterMacro: AccessorMacro { ] } } + +extension JSGetterMacro: PeerMacro { + /// Emits a diagnostic when @JSGetter is applied to a declaration that is not a variable (e.g. a function). + /// AccessorMacro may not be invoked for non-property declarations. For variables with multiple + /// bindings, the compiler emits its own diagnostic; we only diagnose non-variable decls here. + public static func expansion( + of node: AttributeSyntax, + providingPeersOf declaration: some DeclSyntaxProtocol, + in context: some MacroExpansionContext + ) throws -> [DeclSyntax] { + guard declaration.is(VariableDeclSyntax.self) else { + context.diagnose(Diagnostic(node: Syntax(declaration), message: JSMacroMessage.unsupportedVariable)) + return [] + } + return [] + } +} diff --git a/Plugins/BridgeJS/Sources/BridgeJSMacros/JSSetterMacro.swift b/Plugins/BridgeJS/Sources/BridgeJSMacros/JSSetterMacro.swift index bb9fd1f21..3c7280079 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSMacros/JSSetterMacro.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSMacros/JSSetterMacro.swift @@ -25,13 +25,23 @@ extension JSSetterMacro: BodyMacro { let rawFunctionName = JSMacroHelper.stripBackticks(functionName) guard rawFunctionName.hasPrefix("set"), rawFunctionName.count > 3 else { context.diagnose(Diagnostic(node: Syntax(declaration), message: JSMacroMessage.invalidSetterName)) - return [] + return [ + CodeBlockItemSyntax( + stringLiteral: + "fatalError(\"@JSSetter function name must start with 'set' followed by a property name\")" + ) + ] } let propertyName = String(rawFunctionName.dropFirst(3)) guard !propertyName.isEmpty else { context.diagnose(Diagnostic(node: Syntax(declaration), message: JSMacroMessage.invalidSetterName)) - return [] + return [ + CodeBlockItemSyntax( + stringLiteral: + "fatalError(\"@JSSetter function name must start with 'set' followed by a property name\")" + ) + ] } // Convert first character to lowercase (e.g., "Foo" -> "foo") @@ -56,7 +66,11 @@ extension JSSetterMacro: BodyMacro { let parameters = functionDecl.signature.parameterClause.parameters guard let firstParam = parameters.first else { context.diagnose(Diagnostic(node: Syntax(declaration), message: JSMacroMessage.setterRequiresParameter)) - return [] + return [ + CodeBlockItemSyntax( + stringLiteral: "fatalError(\"@JSSetter function must have at least one parameter\")" + ) + ] } let paramName = firstParam.secondName ?? firstParam.firstName @@ -69,3 +83,21 @@ extension JSSetterMacro: BodyMacro { return [CodeBlockItemSyntax(stringLiteral: "try \(call)")] } } + +extension JSSetterMacro: PeerMacro { + /// Emits a diagnostic when @JSSetter is applied to a declaration that is not a function. + /// BodyMacro is only invoked for declarations with optional code blocks (e.g. functions). + public static func expansion( + of node: AttributeSyntax, + providingPeersOf declaration: some DeclSyntaxProtocol, + in context: some MacroExpansionContext + ) throws -> [DeclSyntax] { + guard declaration.is(FunctionDeclSyntax.self) else { + context.diagnose( + Diagnostic(node: Syntax(declaration), message: JSMacroMessage.unsupportedSetterDeclaration) + ) + return [] + } + return [] + } +} diff --git a/Plugins/BridgeJS/Tests/BridgeJSMacrosTests/JSClassMacroTests.swift b/Plugins/BridgeJS/Tests/BridgeJSMacrosTests/JSClassMacroTests.swift index 5d98a38af..7640916e4 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSMacrosTests/JSClassMacroTests.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSMacrosTests/JSClassMacroTests.swift @@ -73,11 +73,11 @@ import BridgeJSMacros """, expandedSource: """ struct MyClass { - let jsObject: JSObject - init(unsafelyWrapping jsObject: JSObject) { self.jsObject = jsObject } + + let jsObject: JSObject } extension MyClass: _JSBridgedClass { @@ -127,10 +127,10 @@ import BridgeJSMacros """, expandedSource: """ struct MyClass { - let jsObject: JSObject - var name: String + let jsObject: JSObject + init(unsafelyWrapping jsObject: JSObject) { self.jsObject = jsObject } @@ -223,10 +223,10 @@ import BridgeJSMacros """, expandedSource: """ struct MyClass { - let jsObject: JSObject - var otherProperty: String + let jsObject: JSObject + init(unsafelyWrapping jsObject: JSObject) { self.jsObject = jsObject } @@ -251,11 +251,11 @@ import BridgeJSMacros """, expandedSource: """ struct MyClass { - let jsObject: JSObject - init(name: String) { } + let jsObject: JSObject + init(unsafelyWrapping jsObject: JSObject) { self.jsObject = jsObject } @@ -280,11 +280,11 @@ import BridgeJSMacros """, expandedSource: """ struct MyClass { - let jsObject: JSObject - var name: String var age: Int + let jsObject: JSObject + init(unsafelyWrapping jsObject: JSObject) { self.jsObject = jsObject } @@ -309,6 +309,7 @@ import BridgeJSMacros expandedSource: """ /// Documentation comment struct MyClass { + let jsObject: JSObject init(unsafelyWrapping jsObject: JSObject) { @@ -333,6 +334,7 @@ import BridgeJSMacros """, expandedSource: """ struct MyClass: _JSBridgedClass { + let jsObject: JSObject init(unsafelyWrapping jsObject: JSObject) { diff --git a/Plugins/BridgeJS/Tests/BridgeJSMacrosTests/JSFunctionMacroTests.swift b/Plugins/BridgeJS/Tests/BridgeJSMacrosTests/JSFunctionMacroTests.swift index 22c6ced59..756935e7d 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSMacrosTests/JSFunctionMacroTests.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSMacrosTests/JSFunctionMacroTests.swift @@ -186,7 +186,7 @@ import BridgeJSMacros expandedSource: """ struct MyClass { static func create() -> MyClass { - return _$create() + return _$MyClass_create() } } """, @@ -206,7 +206,7 @@ import BridgeJSMacros expandedSource: """ class MyClass { class func create() -> MyClass { - return _$create() + return _$MyClass_create() } } """, @@ -285,7 +285,9 @@ import BridgeJSMacros init() """, expandedSource: """ - init() + init() { + fatalError("@JSFunction init must be inside a type") + } """, diagnostics: [ DiagnosticSpec( diff --git a/Plugins/BridgeJS/Tests/BridgeJSMacrosTests/JSGetterMacroTests.swift b/Plugins/BridgeJS/Tests/BridgeJSMacrosTests/JSGetterMacroTests.swift index 35b79d255..2796c1014 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSMacrosTests/JSGetterMacroTests.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSMacrosTests/JSGetterMacroTests.swift @@ -103,7 +103,7 @@ import BridgeJSMacros struct MyClass { static var version: String { get throws(JSException) { - return try _$version_get() + return try _$MyClass_version_get() } } } @@ -125,7 +125,7 @@ import BridgeJSMacros class MyClass { class var version: String { get throws(JSException) { - return try _$version_get() + return try _$MyClass_version_get() } } } @@ -231,11 +231,17 @@ import BridgeJSMacros """, diagnostics: [ DiagnosticSpec( - message: "@JSGetter can only be applied to single-variable declarations.", + message: "accessor macro can only be applied to a single variable", line: 1, column: 1, severity: .error - ) + ), + DiagnosticSpec( + message: "peer macro can only be applied to a single variable", + line: 1, + column: 1, + severity: .error + ), ], macroSpecs: macroSpecs, indentationWidth: indentationWidth, @@ -264,6 +270,8 @@ import BridgeJSMacros ) } + #if canImport(SwiftSyntax601) + // https://github.com/swiftlang/swift-syntax/pull/2722 @Test func variableWithTrailingComment() { TestSupport.assertMacroExpansion( """ @@ -281,6 +289,7 @@ import BridgeJSMacros indentationWidth: indentationWidth, ) } + #endif @Test func variableWithUnderscoreName() { TestSupport.assertMacroExpansion( @@ -291,7 +300,7 @@ import BridgeJSMacros expandedSource: """ var _internal: String { get throws(JSException) { - return try _$internal_get() + return try _$_internal_get() } } """, diff --git a/Plugins/BridgeJS/Tests/BridgeJSMacrosTests/JSSetterMacroTests.swift b/Plugins/BridgeJS/Tests/BridgeJSMacrosTests/JSSetterMacroTests.swift index 7e115bfac..1ed4e1081 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSMacrosTests/JSSetterMacroTests.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSMacrosTests/JSSetterMacroTests.swift @@ -74,7 +74,7 @@ import BridgeJSMacros expandedSource: """ struct MyClass { static func setVersion(_ version: String) throws(JSException) { - try _$version_set(version) + try _$MyClass_version_set(version) } } """, @@ -94,7 +94,7 @@ import BridgeJSMacros expandedSource: """ class MyClass { class func setConfig(_ config: Config) throws(JSException) { - try _$config_set(config) + try _$MyClass_config_set(config) } } """, @@ -168,7 +168,9 @@ import BridgeJSMacros func updateFoo(_ value: Foo) throws(JSException) """, expandedSource: """ - func updateFoo(_ value: Foo) throws(JSException) + func updateFoo(_ value: Foo) throws(JSException) { + fatalError("@JSSetter function name must start with 'set' followed by a property name") + } """, diagnostics: [ DiagnosticSpec( @@ -190,7 +192,9 @@ import BridgeJSMacros func set(_ value: Foo) throws(JSException) """, expandedSource: """ - func set(_ value: Foo) throws(JSException) + func set(_ value: Foo) throws(JSException) { + fatalError("@JSSetter function name must start with 'set' followed by a property name") + } """, diagnostics: [ DiagnosticSpec( @@ -212,7 +216,9 @@ import BridgeJSMacros func setFoo() throws(JSException) """, expandedSource: """ - func setFoo() throws(JSException) + func setFoo() throws(JSException) { + fatalError("@JSSetter function must have at least one parameter") + } """, diagnostics: [ DiagnosticSpec(