@@ -103,10 +103,11 @@ public final class SwiftToSkeleton {
103103 if importedJSModules [ path] != nil {
104104 continue
105105 }
106+ let pathNode = importCollector. importedModulePathNodes [ path] ?? Syntax ( sourceFile)
106107 guard path. hasPrefix ( " / " ) else {
107108 importCollector. errors. append (
108109 DiagnosticError (
109- node: sourceFile ,
110+ node: pathNode ,
110111 message: " JavaScript module paths must start with '/' to indicate the Swift target root: "
111112 + " ' \( path) '. "
112113 )
@@ -116,7 +117,7 @@ public final class SwiftToSkeleton {
116117 guard let source = try javaScriptModuleSource ( path) else {
117118 importCollector. errors. append (
118119 DiagnosticError (
119- node: sourceFile ,
120+ node: pathNode ,
120121 message: " JavaScript module file was not found at ' \( path) '. "
121122 )
122123 )
@@ -2452,6 +2453,7 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor {
24522453 var importedFunctions : [ ImportedFunctionSkeleton ] = [ ]
24532454 var importedTypes : [ ImportedTypeSkeleton ] = [ ]
24542455 var importedGlobalGetters : [ ImportedGetterSkeleton ] = [ ]
2456+ var importedModulePathNodes : [ String : Syntax ] = [ : ]
24552457 var errors : [ DiagnosticError ] = [ ]
24562458
24572459 private let inputFilePath : String
@@ -2546,31 +2548,41 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor {
25462548 }
25472549 return nil
25482550 }
2551+ }
25492552
2550- /// Extracts the `from` argument value from an attribute, if present.
2551- static func extractJSImportFrom( from attribute: AttributeSyntax ) -> JSImportFrom ? {
2552- guard let arguments = attribute. arguments? . as ( LabeledExprListSyntax . self) else {
2553+ private func extractJSImportFrom( from attribute: AttributeSyntax ) -> JSImportFrom ? {
2554+ guard let arguments = attribute. arguments? . as ( LabeledExprListSyntax . self) ,
2555+ let argument = arguments. first ( where: { $0. label? . text == " from " } )
2556+ else {
2557+ return nil
2558+ }
2559+
2560+ if let call = argument. expression. as ( FunctionCallExprSyntax . self) ,
2561+ call. calledExpression. trimmedDescription. split ( separator: " . " ) . last == " module "
2562+ {
2563+ guard call. arguments. count == 1 ,
2564+ let pathExpression = call. arguments. first? . expression,
2565+ let literal = pathExpression. as ( StringLiteralExprSyntax . self) ,
2566+ let path = literal. representedLiteralValue
2567+ else {
2568+ errors. append (
2569+ DiagnosticError (
2570+ node: call. arguments. first? . expression ?? argument. expression,
2571+ message: " JavaScript module path must be a string literal. "
2572+ )
2573+ )
25532574 return nil
25542575 }
2555- for argument in arguments {
2556- guard argument. label? . text == " from " else { continue }
2557-
2558- if let call = argument. expression. as ( FunctionCallExprSyntax . self) ,
2559- call. calledExpression. trimmedDescription. split ( separator: " . " ) . last == " module " ,
2560- call. arguments. count == 1 ,
2561- let literal = call. arguments. first? . expression. as ( StringLiteralExprSyntax . self) ,
2562- let path = literal. representedLiteralValue
2563- {
2564- return . module( path)
2565- }
2566-
2567- // Accept `.global`, `JSImportFrom.global`, etc.
2568- let description = argument. expression. trimmedDescription
2569- let caseName = description. split ( separator: " . " ) . last. map ( String . init) ?? description
2570- return caseName == " global " ? . global : nil
2576+ if importedModulePathNodes [ path] == nil {
2577+ importedModulePathNodes [ path] = Syntax ( literal)
25712578 }
2572- return nil
2579+ return . module ( path )
25732580 }
2581+
2582+ // Accept `.global`, `JSImportFrom.global`, etc.
2583+ let description = argument. expression. trimmedDescription
2584+ let caseName = description. split ( separator: " . " ) . last. map ( String . init) ?? description
2585+ return caseName == " global " ? . global : nil
25742586 }
25752587
25762588 // MARK: - Validation Helpers
@@ -2753,7 +2765,7 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor {
27532765 if AttributeChecker . hasJSClassAttribute ( node. attributes) {
27542766 let attribute = AttributeChecker . firstJSClassAttribute ( node. attributes)
27552767 let jsName = attribute. flatMap ( AttributeChecker . extractJSName)
2756- let from = attribute. flatMap ( AttributeChecker . extractJSImportFrom)
2768+ let from = attribute. flatMap { extractJSImportFrom ( from : $0 ) }
27572769 let accessLevel = Self . bridgeAccessLevel ( from: node. modifiers)
27582770 enterJSClass ( node. name. text, jsName: jsName, from: from, accessLevel: accessLevel)
27592771 }
@@ -2770,7 +2782,7 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor {
27702782 if AttributeChecker . hasJSClassAttribute ( node. attributes) {
27712783 let attribute = AttributeChecker . firstJSClassAttribute ( node. attributes)
27722784 let jsName = attribute. flatMap ( AttributeChecker . extractJSName)
2773- let from = attribute. flatMap ( AttributeChecker . extractJSImportFrom)
2785+ let from = attribute. flatMap { extractJSImportFrom ( from : $0 ) }
27742786 let accessLevel = Self . bridgeAccessLevel ( from: node. modifiers)
27752787 enterJSClass ( node. name. text, jsName: jsName, from: from, accessLevel: accessLevel)
27762788 }
@@ -2964,7 +2976,7 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor {
29642976
29652977 let baseName = SwiftToSkeleton . normalizeIdentifier ( node. name. text)
29662978 let jsName = AttributeChecker . extractJSName ( from: jsFunction)
2967- let from = AttributeChecker . extractJSImportFrom ( from: jsFunction)
2979+ let from = extractJSImportFrom ( from: jsFunction)
29682980 let name = baseName
29692981
29702982 let parameters = parseParameters ( from: node. signature. parameterClause)
@@ -3017,7 +3029,7 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor {
30173029 }
30183030 let propertyName = SwiftToSkeleton . normalizeIdentifier ( identifier. identifier. text)
30193031 let jsName = AttributeChecker . extractJSName ( from: jsGetter)
3020- let from = AttributeChecker . extractJSImportFrom ( from: jsGetter)
3032+ let from = extractJSImportFrom ( from: jsGetter)
30213033 let accessLevel = Self . bridgeAccessLevel ( from: node. modifiers)
30223034 return ImportedGetterSkeleton (
30233035 name: propertyName,
0 commit comments