Skip to content

Commit 87a73bd

Browse files
BridgeJS: Fix @JSClass on public/package access level struct
Also cover more access levels in `@JSFunction` tests.
1 parent 79bda2e commit 87a73bd

File tree

7 files changed

+433
-2
lines changed

7 files changed

+433
-2
lines changed

Plugins/BridgeJS/Sources/BridgeJSMacros/JSClassMacro.swift

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,21 @@ extension JSClassMacro: MemberMacro {
4747
}
4848

4949
var members: [DeclSyntax] = []
50+
guard let structDecl = declaration.as(StructDeclSyntax.self) else { return members }
51+
52+
if let accessLevel = accessLevel(from: structDecl.modifiers),
53+
accessLevel == .private || accessLevel == .fileprivate
54+
{
55+
context.diagnose(
56+
Diagnostic(
57+
node: Syntax(structDecl),
58+
message: JSMacroMessage.jsClassRequiresAtLeastInternal
59+
)
60+
)
61+
return []
62+
}
63+
64+
let memberAccessModifier = synthesizedMemberAccessModifier(for: declaration).map { "\($0) " } ?? ""
5065

5166
let existingMembers = declaration.memberBlock.members
5267
let hasJSObjectProperty = existingMembers.contains { member in
@@ -57,7 +72,7 @@ extension JSClassMacro: MemberMacro {
5772
}
5873

5974
if !hasJSObjectProperty {
60-
members.append(DeclSyntax("let jsObject: JSObject"))
75+
members.append(DeclSyntax("\(raw: memberAccessModifier)let jsObject: JSObject"))
6176
}
6277

6378
let hasUnsafelyWrappingInit = existingMembers.contains { member in
@@ -73,7 +88,7 @@ extension JSClassMacro: MemberMacro {
7388
members.append(
7489
DeclSyntax(
7590
"""
76-
init(unsafelyWrapping jsObject: JSObject) {
91+
\(raw: memberAccessModifier)init(unsafelyWrapping jsObject: JSObject) {
7792
self.jsObject = jsObject
7893
}
7994
"""
@@ -108,4 +123,37 @@ extension JSClassMacro: ExtensionMacro {
108123
try ExtensionDeclSyntax("extension \(type.trimmed): \(raw: conformanceList) {}")
109124
]
110125
}
126+
127+
private static func synthesizedMemberAccessModifier(for declaration: some DeclGroupSyntax) -> String? {
128+
guard let structDecl = declaration.as(StructDeclSyntax.self) else { return nil }
129+
switch accessLevel(from: structDecl.modifiers) {
130+
case .public: return "public"
131+
case .package: return "package"
132+
case .internal: return "internal"
133+
case .fileprivate, .private, .none: return nil
134+
}
135+
}
136+
137+
private enum AccessLevel {
138+
case `public`
139+
case package
140+
case `internal`
141+
case `fileprivate`
142+
case `private`
143+
}
144+
145+
private static func accessLevel(from modifiers: DeclModifierListSyntax?) -> AccessLevel? {
146+
guard let modifiers else { return nil }
147+
for modifier in modifiers {
148+
switch modifier.name.tokenKind {
149+
case .keyword(.public): return .public
150+
case .keyword(.package): return .package
151+
case .keyword(.internal): return .internal
152+
case .keyword(.fileprivate): return .fileprivate
153+
case .keyword(.private): return .private
154+
default: continue
155+
}
156+
}
157+
return nil
158+
}
111159
}

Plugins/BridgeJS/Sources/BridgeJSMacros/JSMacroSupport.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ enum JSMacroMessage: String, DiagnosticMessage {
1414
case setterRequiresThrows = "@JSSetter function must declare throws(JSException)."
1515
case jsFunctionRequiresThrows = "@JSFunction throws must be declared as throws(JSException)."
1616
case requiresJSClass = "JavaScript members must be declared inside a @JSClass struct."
17+
case jsClassRequiresAtLeastInternal =
18+
"@JSClass does not support private/fileprivate access level. Use internal, package, or public."
19+
case jsClassMemberRequiresAtLeastInternal =
20+
"@JSClass requires jsObject and init(unsafelyWrapping:) to be at least internal."
1721

1822
var message: String { rawValue }
1923
var diagnosticID: MessageID { MessageID(domain: "JavaScriptKitMacros", id: rawValue) }

Plugins/BridgeJS/Tests/BridgeJSMacrosTests/JSClassMacroTests.swift

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,4 +370,102 @@ import BridgeJSMacros
370370
indentationWidth: indentationWidth
371371
)
372372
}
373+
374+
@Test func publicStructSynthesizesPublicMembers() {
375+
TestSupport.assertMacroExpansion(
376+
"""
377+
@JSClass
378+
public struct MyClass {
379+
}
380+
""",
381+
expandedSource: """
382+
public struct MyClass {
383+
384+
public let jsObject: JSObject
385+
386+
public init(unsafelyWrapping jsObject: JSObject) {
387+
self.jsObject = jsObject
388+
}
389+
}
390+
391+
extension MyClass: _JSBridgedClass {
392+
}
393+
""",
394+
macroSpecs: macroSpecs,
395+
indentationWidth: indentationWidth
396+
)
397+
}
398+
399+
@Test func packageStructSynthesizesPackageMembers() {
400+
TestSupport.assertMacroExpansion(
401+
"""
402+
@JSClass
403+
package struct MyClass {
404+
}
405+
""",
406+
expandedSource: """
407+
package struct MyClass {
408+
409+
package let jsObject: JSObject
410+
411+
package init(unsafelyWrapping jsObject: JSObject) {
412+
self.jsObject = jsObject
413+
}
414+
}
415+
416+
extension MyClass: _JSBridgedClass {
417+
}
418+
""",
419+
macroSpecs: macroSpecs,
420+
indentationWidth: indentationWidth
421+
)
422+
}
423+
424+
@Test func privateStructIsRejected() {
425+
TestSupport.assertMacroExpansion(
426+
"""
427+
@JSClass
428+
private struct MyClass {
429+
}
430+
""",
431+
expandedSource: """
432+
private struct MyClass {
433+
}
434+
""",
435+
diagnostics: [
436+
DiagnosticSpec(
437+
message:
438+
"@JSClass does not support private/fileprivate access level. Use internal, package, or public.",
439+
line: 1,
440+
column: 1
441+
)
442+
],
443+
macroSpecs: macroSpecs,
444+
indentationWidth: indentationWidth
445+
)
446+
}
447+
448+
@Test func fileprivateStructIsRejected() {
449+
TestSupport.assertMacroExpansion(
450+
"""
451+
@JSClass
452+
fileprivate struct MyClass {
453+
}
454+
""",
455+
expandedSource: """
456+
fileprivate struct MyClass {
457+
}
458+
""",
459+
diagnostics: [
460+
DiagnosticSpec(
461+
message:
462+
"@JSClass does not support private/fileprivate access level. Use internal, package, or public.",
463+
line: 1,
464+
column: 1
465+
)
466+
],
467+
macroSpecs: macroSpecs,
468+
indentationWidth: indentationWidth
469+
)
470+
}
373471
}

Tests/BridgeJSRuntimeTests/Generated/BridgeJS.swift

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10156,6 +10156,137 @@ func _$jsTranslatePoint(_ point: Point, _ dx: Int, _ dy: Int) throws(JSException
1015610156
return Point.bridgeJSLiftReturn(ret)
1015710157
}
1015810158

10159+
#if arch(wasm32)
10160+
@_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_MyJSClassInternal_init")
10161+
fileprivate func bjs_MyJSClassInternal_init() -> Int32
10162+
#else
10163+
fileprivate func bjs_MyJSClassInternal_init() -> Int32 {
10164+
fatalError("Only available on WebAssembly")
10165+
}
10166+
#endif
10167+
10168+
func _$MyJSClassInternal_init() throws(JSException) -> JSObject {
10169+
let ret = bjs_MyJSClassInternal_init()
10170+
if let error = _swift_js_take_exception() {
10171+
throw error
10172+
}
10173+
return JSObject.bridgeJSLiftReturn(ret)
10174+
}
10175+
10176+
#if arch(wasm32)
10177+
@_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_MyJSClassPublic_init")
10178+
fileprivate func bjs_MyJSClassPublic_init() -> Int32
10179+
#else
10180+
fileprivate func bjs_MyJSClassPublic_init() -> Int32 {
10181+
fatalError("Only available on WebAssembly")
10182+
}
10183+
#endif
10184+
10185+
func _$MyJSClassPublic_init() throws(JSException) -> JSObject {
10186+
let ret = bjs_MyJSClassPublic_init()
10187+
if let error = _swift_js_take_exception() {
10188+
throw error
10189+
}
10190+
return JSObject.bridgeJSLiftReturn(ret)
10191+
}
10192+
10193+
#if arch(wasm32)
10194+
@_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_MyJSClassPackage_init")
10195+
fileprivate func bjs_MyJSClassPackage_init() -> Int32
10196+
#else
10197+
fileprivate func bjs_MyJSClassPackage_init() -> Int32 {
10198+
fatalError("Only available on WebAssembly")
10199+
}
10200+
#endif
10201+
10202+
func _$MyJSClassPackage_init() throws(JSException) -> JSObject {
10203+
let ret = bjs_MyJSClassPackage_init()
10204+
if let error = _swift_js_take_exception() {
10205+
throw error
10206+
}
10207+
return JSObject.bridgeJSLiftReturn(ret)
10208+
}
10209+
10210+
#if arch(wasm32)
10211+
@_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_jsFunctionWithPackageAccess")
10212+
fileprivate func bjs_jsFunctionWithPackageAccess() -> Void
10213+
#else
10214+
fileprivate func bjs_jsFunctionWithPackageAccess() -> Void {
10215+
fatalError("Only available on WebAssembly")
10216+
}
10217+
#endif
10218+
10219+
func _$jsFunctionWithPackageAccess() throws(JSException) -> Void {
10220+
bjs_jsFunctionWithPackageAccess()
10221+
if let error = _swift_js_take_exception() {
10222+
throw error
10223+
}
10224+
}
10225+
10226+
#if arch(wasm32)
10227+
@_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_jsFunctionWithPublicAccess")
10228+
fileprivate func bjs_jsFunctionWithPublicAccess() -> Void
10229+
#else
10230+
fileprivate func bjs_jsFunctionWithPublicAccess() -> Void {
10231+
fatalError("Only available on WebAssembly")
10232+
}
10233+
#endif
10234+
10235+
func _$jsFunctionWithPublicAccess() throws(JSException) -> Void {
10236+
bjs_jsFunctionWithPublicAccess()
10237+
if let error = _swift_js_take_exception() {
10238+
throw error
10239+
}
10240+
}
10241+
10242+
#if arch(wasm32)
10243+
@_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_jsFunctionWithInternalAccess")
10244+
fileprivate func bjs_jsFunctionWithInternalAccess() -> Void
10245+
#else
10246+
fileprivate func bjs_jsFunctionWithInternalAccess() -> Void {
10247+
fatalError("Only available on WebAssembly")
10248+
}
10249+
#endif
10250+
10251+
func _$jsFunctionWithInternalAccess() throws(JSException) -> Void {
10252+
bjs_jsFunctionWithInternalAccess()
10253+
if let error = _swift_js_take_exception() {
10254+
throw error
10255+
}
10256+
}
10257+
10258+
#if arch(wasm32)
10259+
@_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_jsFunctionWithFilePrivateAccess")
10260+
fileprivate func bjs_jsFunctionWithFilePrivateAccess() -> Void
10261+
#else
10262+
fileprivate func bjs_jsFunctionWithFilePrivateAccess() -> Void {
10263+
fatalError("Only available on WebAssembly")
10264+
}
10265+
#endif
10266+
10267+
func _$jsFunctionWithFilePrivateAccess() throws(JSException) -> Void {
10268+
bjs_jsFunctionWithFilePrivateAccess()
10269+
if let error = _swift_js_take_exception() {
10270+
throw error
10271+
}
10272+
}
10273+
10274+
#if arch(wasm32)
10275+
@_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_jsFunctionWithPrivateAccess")
10276+
fileprivate func bjs_jsFunctionWithPrivateAccess() -> Void
10277+
#else
10278+
fileprivate func bjs_jsFunctionWithPrivateAccess() -> Void {
10279+
fatalError("Only available on WebAssembly")
10280+
}
10281+
#endif
10282+
10283+
func _$jsFunctionWithPrivateAccess() throws(JSException) -> Void {
10284+
bjs_jsFunctionWithPrivateAccess()
10285+
if let error = _swift_js_take_exception() {
10286+
throw error
10287+
}
10288+
}
10289+
1015910290
#if arch(wasm32)
1016010291
@_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_OptionalSupportImports_jsRoundTripOptionalNumberNull_static")
1016110292
fileprivate func bjs_OptionalSupportImports_jsRoundTripOptionalNumberNull_static(_ valueIsSome: Int32, _ valueValue: Int32) -> Void

0 commit comments

Comments
 (0)