Skip to content

Commit b0d6b3f

Browse files
committed
BridgeJS: Emit diagnostics from extensions properly
1 parent 3966530 commit b0d6b3f

2 files changed

Lines changed: 71 additions & 5 deletions

File tree

Plugins/BridgeJS/Sources/BridgeJSCore/SwiftToSkeleton.swift

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -222,15 +222,14 @@ public final class SwiftToSkeleton {
222222
validatedJavaScriptModulePaths.insert(path)
223223
}
224224

225-
let exportErrors = exportCollector.errors.filter { $0.severity == .error }
226225
let importErrorsFatal = importCollector.errors.filter {
227226
$0.severity == .error && !$0.message.contains("Unsupported type '")
228227
}
229-
let fileWarnings = (exportCollector.errors + importCollector.errors).filter { $0.severity == .warning }
228+
let fileWarnings = importCollector.errors.filter { $0.severity == .warning }
230229
warnings.append(contentsOf: fileWarnings.map { (file: inputFilePath, diagnostic: $0) })
231-
if !exportErrors.isEmpty || !importErrorsFatal.isEmpty {
230+
if !importErrorsFatal.isEmpty {
232231
perSourceErrors.append(
233-
(inputFilePath: inputFilePath, errors: exportErrors + importErrorsFatal)
232+
(inputFilePath: inputFilePath, errors: importErrorsFatal)
234233
)
235234
}
236235

@@ -249,6 +248,18 @@ public final class SwiftToSkeleton {
249248
source.resolveDeferredExtensions(against: exportCollectors)
250249
}
251250

251+
// We have to collect diagnostics after all deferred extensions are resolved, since they could generate some.
252+
for ((_, inputFilePath), exportCollector) in zip(sourceFiles, exportCollectors) {
253+
let exportErrors = exportCollector.errors.filter { $0.severity == .error }
254+
let fileWarnings = exportCollector.errors.filter { $0.severity == .warning }
255+
warnings.append(contentsOf: fileWarnings.map { (file: inputFilePath, diagnostic: $0) })
256+
if !exportErrors.isEmpty {
257+
perSourceErrors.append(
258+
(inputFilePath: inputFilePath, errors: exportErrors)
259+
)
260+
}
261+
}
262+
252263
for collector in exportCollectors {
253264
collector.finalize(&exported)
254265
}
@@ -858,6 +869,17 @@ extension AttributeListSyntax {
858869
}
859870
}
860871

872+
private final class JSAttributeFinder: SyntaxVisitor {
873+
private(set) var found = false
874+
875+
override func visit(_ node: AttributeSyntax) -> SyntaxVisitorContinueKind {
876+
if node.attributeNameText == "JS" {
877+
found = true
878+
}
879+
return .skipChildren
880+
}
881+
}
882+
861883
private final class ExportSwiftAPICollector: SyntaxAnyVisitor {
862884
var exportedFunctions: [ExportedFunction] = []
863885
/// The names of the exported classes, in the order they were written in the source file
@@ -1910,7 +1932,7 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor {
19101932
break
19111933
}
19121934
}
1913-
if !resolved {
1935+
if !resolved, containsJSAnnotatedDeclaration(ext.memberBlock.members) {
19141936
diagnose(
19151937
node: ext.extendedType,
19161938
message: "Unsupported type '\(ext.extendedType.trimmedDescription)'.",
@@ -1920,6 +1942,12 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor {
19201942
}
19211943
}
19221944

1945+
private func containsJSAnnotatedDeclaration(_ members: MemberBlockItemListSyntax) -> Bool {
1946+
let finder = JSAttributeFinder(viewMode: .sourceAccurate)
1947+
finder.walk(members)
1948+
return finder.found
1949+
}
1950+
19231951
/// Walks extension members under the matching type’s state, returning whether the type was found.
19241952
///
19251953
/// Note: The lookup scans dictionaries keyed by `makeKey(name:namespace:)`, matching only by

Plugins/BridgeJS/Tests/BridgeJSToolTests/DiagnosticsTests.swift

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,44 @@ import Testing
2626
}
2727
}
2828

29+
@Test
30+
func extensionOfUnknownTypeWithJSMemberProducesDiagnostic() throws {
31+
let source = """
32+
extension Unknown {
33+
@JS func bridged() -> Int { 42 }
34+
}
35+
"""
36+
let diagnostics = try #require(moduleDiagnostics(source: source))
37+
#expect(diagnostics.description.contains("Unsupported type 'Unknown'"))
38+
}
39+
40+
@Test
41+
func extensionWithoutJSMembersIsIgnored() throws {
42+
let source = """
43+
extension String {
44+
func helper() -> Int { 42 }
45+
}
46+
"""
47+
#expect(moduleDiagnostics(source: source) == nil)
48+
}
49+
50+
@Test
51+
func invalidJSMemberInsideExtensionProducesDiagnostic() throws {
52+
let source = """
53+
@JS class Host {
54+
@JS init() {}
55+
}
56+
57+
extension Host {
58+
@JS struct Bad {
59+
var field = 1
60+
}
61+
}
62+
"""
63+
let diagnostics = try #require(moduleDiagnostics(source: source))
64+
#expect(diagnostics.description.contains("Struct field must have explicit type annotation"))
65+
}
66+
2967
@Test
3068
func missingJavaScriptModuleProducesDiagnostic() throws {
3169
let source = """

0 commit comments

Comments
 (0)