Skip to content

Commit d2351e2

Browse files
Implemented strict @JSFunction throws requirements and refactored tests. Highlights:
- JSFunction now emits an error + fix-it whenever the signature isn’t `throws(JSException)` (including missing throws); helper builds a typed throws clause even if none existed. - DRY helpers added for JSException notes/throws clauses; setter uses its own note text. - All JSFunction macro tests updated to require `throws(JSException)` and expect `try`/`try await` in generated bodies; missing-throws test now expects the diagnostic/fix-it. Tests: `swift test --package-path ./Plugins/BridgeJS --filter BridgeJSMacrosTests` passes.
1 parent 6b48b15 commit d2351e2

2 files changed

Lines changed: 83 additions & 52 deletions

File tree

Plugins/BridgeJS/Sources/BridgeJSMacros/JSMacroSupport.swift

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -202,20 +202,29 @@ enum JSMacroHelper {
202202
in context: some MacroExpansionContext,
203203
additionalNotes: [Note] = []
204204
) {
205-
guard let throwsClause = signature.effectSpecifiers?.throwsClause else { return }
206-
207-
let throwsTypeName = throwsClause.type?.as(IdentifierTypeSyntax.self)?.name.text
205+
let throwsTypeName = signature.effectSpecifiers?.throwsClause?.type?.as(IdentifierTypeSyntax.self)?.name.text
208206
let isJSException = throwsTypeName == "JSException"
209-
let isAllowedGenericError = throwsTypeName == nil || throwsTypeName == "Error" || throwsTypeName == "Swift.Error"
210207
guard !isJSException else { return }
211-
guard !isAllowedGenericError else { return }
212208

213-
let newThrowsClause = jsExceptionThrowsClause(from: throwsClause)
209+
let newThrowsClause = jsExceptionThrowsClause(from: signature.effectSpecifiers?.throwsClause)
214210

215-
let fixIt = FixIt(
216-
message: JSMacroFixItMessage(message: "Declare throws(JSException)"),
217-
changes: [.replace(oldNode: Syntax(throwsClause), newNode: Syntax(newThrowsClause))]
218-
)
211+
let fixIt: FixIt
212+
if let throwsClause = signature.effectSpecifiers?.throwsClause {
213+
fixIt = FixIt(
214+
message: JSMacroFixItMessage(message: "Declare throws(JSException)"),
215+
changes: [.replace(oldNode: Syntax(throwsClause), newNode: Syntax(newThrowsClause))]
216+
)
217+
} else {
218+
let newEffects = FunctionEffectSpecifiersSyntax(
219+
asyncSpecifier: signature.effectSpecifiers?.asyncSpecifier,
220+
throwsClause: newThrowsClause
221+
)
222+
let newSignature = signature.with(\.effectSpecifiers, newEffects)
223+
fixIt = FixIt(
224+
message: JSMacroFixItMessage(message: "Declare throws(JSException)"),
225+
changes: [.replace(oldNode: Syntax(signature), newNode: Syntax(newSignature))]
226+
)
227+
}
219228

220229
var notes: [Note] = [
221230
jsExceptionPropagationNote(on: node)

Plugins/BridgeJS/Tests/BridgeJSMacrosTests/JSFunctionMacroTests.swift

Lines changed: 64 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@ import BridgeJSMacros
1515
TestSupport.assertMacroExpansion(
1616
"""
1717
@JSFunction
18-
func greet(name: String) -> String
18+
func greet(name: String) throws(JSException) -> String
1919
""",
2020
expandedSource: """
21-
func greet(name: String) -> String {
22-
return _$greet(name)
21+
func greet(name: String) throws(JSException) -> String {
22+
return try _$greet(name)
2323
}
2424
""",
2525
macroSpecs: macroSpecs,
@@ -31,11 +31,11 @@ import BridgeJSMacros
3131
TestSupport.assertMacroExpansion(
3232
"""
3333
@JSFunction
34-
func log(message: String)
34+
func log(message: String) throws(JSException)
3535
""",
3636
expandedSource: """
37-
func log(message: String) {
38-
_$log(message)
37+
func log(message: String) throws(JSException) {
38+
try _$log(message)
3939
}
4040
""",
4141
macroSpecs: macroSpecs,
@@ -47,11 +47,11 @@ import BridgeJSMacros
4747
TestSupport.assertMacroExpansion(
4848
"""
4949
@JSFunction
50-
func log(message: String) -> Void
50+
func log(message: String) throws(JSException) -> Void
5151
""",
5252
expandedSource: """
53-
func log(message: String) -> Void {
54-
_$log(message)
53+
func log(message: String) throws(JSException) -> Void {
54+
try _$log(message)
5555
}
5656
""",
5757
macroSpecs: macroSpecs,
@@ -63,11 +63,11 @@ import BridgeJSMacros
6363
TestSupport.assertMacroExpansion(
6464
"""
6565
@JSFunction
66-
func log(message: String) -> ()
66+
func log(message: String) throws(JSException) -> ()
6767
""",
6868
expandedSource: """
69-
func log(message: String) -> () {
70-
_$log(message)
69+
func log(message: String) throws(JSException) -> () {
70+
try _$log(message)
7171
}
7272
""",
7373
macroSpecs: macroSpecs,
@@ -102,7 +102,29 @@ import BridgeJSMacros
102102
return try _$parse(json)
103103
}
104104
""",
105+
diagnostics: [
106+
DiagnosticSpec(
107+
message: "@JSFunction throws must be declared as throws(JSException).",
108+
line: 1,
109+
column: 1,
110+
notes: [
111+
NoteSpec(
112+
message: "@JSFunction must propagate JavaScript errors as JSException.",
113+
line: 1,
114+
column: 1
115+
)
116+
],
117+
fixIts: [
118+
FixItSpec(message: "Declare throws(JSException)")
119+
]
120+
)
121+
],
105122
macroSpecs: macroSpecs,
123+
applyFixIts: ["Declare throws(JSException)"],
124+
fixedSource: """
125+
@JSFunction
126+
func parse(json: String) throws(JSException) -> [String: Any]
127+
""",
106128
indentationWidth: indentationWidth,
107129
)
108130
}
@@ -150,11 +172,11 @@ import BridgeJSMacros
150172
TestSupport.assertMacroExpansion(
151173
"""
152174
@JSFunction
153-
func fetch(url: String) async -> String
175+
func fetch(url: String) async throws(JSException) -> String
154176
""",
155177
expandedSource: """
156-
func fetch(url: String) async -> String {
157-
return await _$fetch(url)
178+
func fetch(url: String) async throws(JSException) -> String {
179+
return try await _$fetch(url)
158180
}
159181
""",
160182
macroSpecs: macroSpecs,
@@ -182,11 +204,11 @@ import BridgeJSMacros
182204
TestSupport.assertMacroExpansion(
183205
"""
184206
@JSFunction
185-
func process(_ value: Int) -> Int
207+
func process(_ value: Int) throws(JSException) -> Int
186208
""",
187209
expandedSource: """
188-
func process(_ value: Int) -> Int {
189-
return _$process(value)
210+
func process(_ value: Int) throws(JSException) -> Int {
211+
return try _$process(value)
190212
}
191213
""",
192214
macroSpecs: macroSpecs,
@@ -198,11 +220,11 @@ import BridgeJSMacros
198220
TestSupport.assertMacroExpansion(
199221
"""
200222
@JSFunction
201-
func add(a: Int, b: Int) -> Int
223+
func add(a: Int, b: Int) throws(JSException) -> Int
202224
""",
203225
expandedSource: """
204-
func add(a: Int, b: Int) -> Int {
205-
return _$add(a, b)
226+
func add(a: Int, b: Int) throws(JSException) -> Int {
227+
return try _$add(a, b)
206228
}
207229
""",
208230
macroSpecs: macroSpecs,
@@ -215,13 +237,13 @@ import BridgeJSMacros
215237
"""
216238
struct MyClass {
217239
@JSFunction
218-
func getName() -> String
240+
func getName() throws(JSException) -> String
219241
}
220242
""",
221243
expandedSource: """
222244
struct MyClass {
223-
func getName() -> String {
224-
return _$MyClass_getName(self.jsObject)
245+
func getName() throws(JSException) -> String {
246+
return try _$MyClass_getName(self.jsObject)
225247
}
226248
}
227249
""",
@@ -235,13 +257,13 @@ import BridgeJSMacros
235257
"""
236258
struct MyClass {
237259
@JSFunction
238-
static func create() -> MyClass
260+
static func create() throws(JSException) -> MyClass
239261
}
240262
""",
241263
expandedSource: """
242264
struct MyClass {
243-
static func create() -> MyClass {
244-
return _$MyClass_create()
265+
static func create() throws(JSException) -> MyClass {
266+
return try _$MyClass_create()
245267
}
246268
}
247269
""",
@@ -255,13 +277,13 @@ import BridgeJSMacros
255277
"""
256278
class MyClass {
257279
@JSFunction
258-
class func create() -> MyClass
280+
class func create() throws(JSException) -> MyClass
259281
}
260282
""",
261283
expandedSource: """
262284
class MyClass {
263-
class func create() -> MyClass {
264-
return _$MyClass_create()
285+
class func create() throws(JSException) -> MyClass {
286+
return try _$MyClass_create()
265287
}
266288
}
267289
""",
@@ -275,13 +297,13 @@ import BridgeJSMacros
275297
"""
276298
struct MyClass {
277299
@JSFunction
278-
init(name: String)
300+
init(name: String) throws(JSException)
279301
}
280302
""",
281303
expandedSource: """
282304
struct MyClass {
283-
init(name: String) {
284-
let jsObject = _$MyClass_init(name)
305+
init(name: String) throws(JSException) {
306+
let jsObject = try _$MyClass_init(name)
285307
self.init(unsafelyWrapping: jsObject)
286308
}
287309
}
@@ -396,13 +418,13 @@ import BridgeJSMacros
396418
"""
397419
enum MyEnum {
398420
@JSFunction
399-
func getValue() -> Int
421+
func getValue() throws(JSException) -> Int
400422
}
401423
""",
402424
expandedSource: """
403425
enum MyEnum {
404-
func getValue() -> Int {
405-
return _$MyEnum_getValue(self.jsObject)
426+
func getValue() throws(JSException) -> Int {
427+
return try _$MyEnum_getValue(self.jsObject)
406428
}
407429
}
408430
""",
@@ -416,13 +438,13 @@ import BridgeJSMacros
416438
"""
417439
actor MyActor {
418440
@JSFunction
419-
func getValue() -> Int
441+
func getValue() throws(JSException) -> Int
420442
}
421443
""",
422444
expandedSource: """
423445
actor MyActor {
424-
func getValue() -> Int {
425-
return _$MyActor_getValue(self.jsObject)
446+
func getValue() throws(JSException) -> Int {
447+
return try _$MyActor_getValue(self.jsObject)
426448
}
427449
}
428450
""",
@@ -435,13 +457,13 @@ import BridgeJSMacros
435457
TestSupport.assertMacroExpansion(
436458
"""
437459
@JSFunction
438-
func greet(name: String) -> String {
460+
func greet(name: String) throws(JSException) -> String {
439461
return "Hello, \\(name)"
440462
}
441463
""",
442464
expandedSource: """
443-
func greet(name: String) -> String {
444-
return _$greet(name)
465+
func greet(name: String) throws(JSException) -> String {
466+
return try _$greet(name)
445467
}
446468
""",
447469
macroSpecs: macroSpecs,

0 commit comments

Comments
 (0)