@@ -103,22 +103,6 @@ public struct SourceRange: Hashable, Codable, Sendable {
103103 }
104104}
105105
106- /// Collects all `PoundSourceLocationSyntax` directives in a file.
107- fileprivate class SourceLocationCollector : SyntaxVisitor {
108- private var sourceLocationDirectives : [ PoundSourceLocationSyntax ] = [ ]
109-
110- override func visit( _ node: PoundSourceLocationSyntax ) -> SyntaxVisitorContinueKind {
111- sourceLocationDirectives. append ( node)
112- return . skipChildren
113- }
114-
115- static func collectSourceLocations( in tree: some SyntaxProtocol ) -> [ PoundSourceLocationSyntax ] {
116- let collector = SourceLocationCollector ( viewMode: . sourceAccurate)
117- collector. walk ( tree)
118- return collector. sourceLocationDirectives
119- }
120- }
121-
122106fileprivate struct SourceLocationDirectiveArguments {
123107 enum Error : Swift . Error , CustomStringConvertible {
124108 case nonDecimalLineNumber( TokenSyntax )
@@ -169,8 +153,8 @@ public final class SourceLocationConverter {
169153 /// The information from all `#sourceLocation` directives in the file
170154 /// necessary to compute presumed locations.
171155 ///
172- /// - `sourceLine` is the line at which the `#sourceLocation` statement occurs
173- /// within the current file.
156+ /// - `sourceLine` is the physical line number of the end of the last token of
157+ /// `#sourceLocation(...)` directive within the current file.
174158 /// - `arguments` are the `file` and `line` arguments of the directive or `nil`
175159 /// if spelled as `#sourceLocation()` to reset the source location directive.
176160 private var sourceLocationDirectives : [ ( sourceLine: Int , arguments: SourceLocationDirectiveArguments ? ) ] = [ ]
@@ -189,21 +173,7 @@ public final class SourceLocationConverter {
189173 precondition ( tree. parent == nil , " SourceLocationConverter must be passed the root of the syntax tree " )
190174 self . fileName = fileName
191175 self . source = tree. syntaxTextBytes
192- ( self . lines, endOfFile) = computeLines ( tree: Syntax ( tree) )
193- precondition ( tree. totalLength. utf8Length == endOfFile. utf8Offset)
194-
195- for directive in SourceLocationCollector . collectSourceLocations ( in: tree) {
196- let location = self . physicalLocation ( for: directive. positionAfterSkippingLeadingTrivia)
197- if let args = directive. arguments {
198- if let parsedArgs = try ? SourceLocationDirectiveArguments ( args) {
199- // Ignore any malformed `#sourceLocation` directives.
200- sourceLocationDirectives. append ( ( sourceLine: location. line, arguments: parsedArgs) )
201- }
202- } else {
203- // `#sourceLocation()` without any arguments resets the `#sourceLocation` directive.
204- sourceLocationDirectives. append ( ( sourceLine: location. line, arguments: nil ) )
205- }
206- }
176+ ( self . lines, self . endOfFile, self . sourceLocationDirectives) = computeLines ( tree: Syntax ( tree) )
207177 }
208178
209179 /// Create a new ``SourceLocationConverter`` to convert between ``AbsolutePosition``
@@ -511,15 +481,21 @@ extension SyntaxProtocol {
511481/// the end-of-file position.
512482fileprivate func computeLines(
513483 tree: Syntax
514- ) -> ( [ AbsolutePosition ] , AbsolutePosition ) {
484+ ) -> (
485+ lines: [ AbsolutePosition ] ,
486+ endOfFile: AbsolutePosition ,
487+ sourceLocationDirectives: [ ( sourceLine: Int , arguments: SourceLocationDirectiveArguments ? ) ]
488+ ) {
515489 var lines : [ AbsolutePosition ] = [ . startOfFile]
516490 var position : AbsolutePosition = . startOfFile
517-
491+ var sourceLocationDirectives : [ ( sourceLine : Int , arguments : SourceLocationDirectiveArguments ? ) ] = [ ]
518492 let lastLineLength = tree. raw. forEachLineLength { lineLength in
519493 position += lineLength
520494 lines. append ( position)
495+ } handleSourceLocationDirective: { lineOffset, args in
496+ sourceLocationDirectives. append ( ( sourceLine: lines. count + lineOffset, arguments: args) )
521497 }
522- return ( lines, position + lastLineLength)
498+ return ( lines, position + lastLineLength, sourceLocationDirectives )
523499}
524500
525501fileprivate func computeLines( _ source: SyntaxText ) -> ( [ AbsolutePosition ] , AbsolutePosition ) {
@@ -652,7 +628,8 @@ fileprivate extension RawSyntax {
652628 /// - Returns: The leftover ``SourceLength`` at the end of the walk.
653629 func forEachLineLength(
654630 prefix: SourceLength = . zero,
655- body: ( SourceLength ) -> ( )
631+ body: ( SourceLength ) -> ( ) ,
632+ handleSourceLocationDirective: ( _ lineOffset: Int , _ arguments: SourceLocationDirectiveArguments ? ) -> ( )
656633 ) -> SourceLength {
657634 var curPrefix = prefix
658635 switch self . rawData. payload {
@@ -664,7 +641,40 @@ fileprivate extension RawSyntax {
664641 curPrefix = dat. trailingTrivia. forEachLineLength ( prefix: curPrefix, body: body)
665642 case . layout( let dat) :
666643 for case let node? in dat. layout where SyntaxTreeViewMode . sourceAccurate. shouldTraverse ( node: node) {
667- curPrefix = node. forEachLineLength ( prefix: curPrefix, body: body)
644+ curPrefix = node. forEachLineLength (
645+ prefix: curPrefix,
646+ body: body,
647+ handleSourceLocationDirective: handleSourceLocationDirective
648+ )
649+ }
650+
651+ // Handle '#sourceLocation' directive.
652+ if dat. kind == . poundSourceLocation {
653+ // Count newlines in the trailing trivia. The client want to get the
654+ // line of the _end_ of '#sourceLocation()' directive.
655+ var lineOffset = 0
656+ if let lastTok = self . lastToken ( viewMode: . sourceAccurate) {
657+ switch lastTok. raw. rawData. payload {
658+ case . parsedToken( let dat) :
659+ _ = dat. trailingTriviaText. forEachLineLength ( body: { _ in lineOffset -= 1 } )
660+ case . materializedToken( let dat) :
661+ _ = dat. trailingTrivia. forEachLineLength ( body: { _ in lineOffset -= 1 } )
662+ case . layout( _) :
663+ preconditionFailure ( " lastToken(viewMode:) returned non-token " )
664+ }
665+ }
666+
667+ let directive = Syntax . forRoot ( self , rawNodeArena: self . arenaReference. retained)
668+ . cast ( PoundSourceLocationSyntax . self)
669+ if let args = directive. arguments {
670+ if let parsedArgs = try ? SourceLocationDirectiveArguments ( args) {
671+ // Ignore any malformed `#sourceLocation` directives.
672+ handleSourceLocationDirective ( lineOffset, parsedArgs)
673+ }
674+ } else {
675+ // `#sourceLocation()` without any arguments resets the `#sourceLocation` directive.
676+ handleSourceLocationDirective ( lineOffset, nil )
677+ }
668678 }
669679 }
670680 return curPrefix
0 commit comments