@@ -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+
861883private 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
0 commit comments