@@ -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,21 +481,21 @@ extension SyntaxProtocol {
511481/// the end-of-file position.
512482fileprivate func computeLines(
513483 tree: Syntax
514- ) -> ( [ AbsolutePosition ] , AbsolutePosition ) {
515- var lines : [ AbsolutePosition ] = [ ]
516- // First line starts from the beginning.
517- lines. append ( . startOfFile)
484+ ) -> (
485+ lines: [ AbsolutePosition ] ,
486+ endOfFile: AbsolutePosition ,
487+ sourceLocationDirectives: [ ( sourceLine: Int , arguments: SourceLocationDirectiveArguments ? ) ]
488+ ) {
489+ var lines : [ AbsolutePosition ] = [ . startOfFile]
518490 var position : AbsolutePosition = . startOfFile
519- let addLine = { ( lineLength: SourceLength ) in
491+ var sourceLocationDirectives : [ ( sourceLine: Int , arguments: SourceLocationDirectiveArguments ? ) ] = [ ]
492+ let lastLineLength = tree. raw. forEachLineLength { lineLength in
520493 position += lineLength
521494 lines. append ( position)
495+ } handleSourceLocationDirective: { lineOffset, args in
496+ sourceLocationDirectives. append ( ( sourceLine: lines. count + lineOffset, arguments: args) )
522497 }
523- var curPrefix : SourceLength = . zero
524- for token in tree. tokens ( viewMode: . sourceAccurate) {
525- curPrefix = token. forEachLineLength ( prefix: curPrefix, body: addLine)
526- }
527- position += curPrefix
528- return ( lines, position)
498+ return ( lines, position + lastLineLength, sourceLocationDirectives)
529499}
530500
531501fileprivate func computeLines( _ source: SyntaxText ) -> ( [ AbsolutePosition ] , AbsolutePosition ) {
@@ -636,7 +606,7 @@ fileprivate extension RawTriviaPiece {
636606 }
637607}
638608
639- fileprivate extension Array where Element == RawTriviaPiece {
609+ fileprivate extension RawTriviaPieceBuffer {
640610 /// Walks and passes to `body` the ``SourceLength`` for every detected line,
641611 /// with the newline character included.
642612 /// - Returns: The leftover ``SourceLength`` at the end of the walk.
@@ -652,18 +622,61 @@ fileprivate extension Array where Element == RawTriviaPiece {
652622 }
653623}
654624
655- fileprivate extension TokenSyntax {
625+ fileprivate extension RawSyntax {
656626 /// Walks and passes to `body` the ``SourceLength`` for every detected line,
657627 /// with the newline character included.
658628 /// - Returns: The leftover ``SourceLength`` at the end of the walk.
659629 func forEachLineLength(
660630 prefix: SourceLength = . zero,
661- body: ( SourceLength ) -> ( )
631+ body: ( SourceLength ) -> ( ) ,
632+ handleSourceLocationDirective: ( _ lineOffset: Int , _ arguments: SourceLocationDirectiveArguments ? ) -> ( )
662633 ) -> SourceLength {
663634 var curPrefix = prefix
664- curPrefix = self . tokenView. leadingRawTriviaPieces. forEachLineLength ( prefix: curPrefix, body: body)
665- curPrefix = self . tokenView. rawText. forEachLineLength ( prefix: curPrefix, body: body)
666- curPrefix = self . tokenView. trailingRawTriviaPieces. forEachLineLength ( prefix: curPrefix, body: body)
635+ switch self . rawData. payload {
636+ case . parsedToken( let dat) :
637+ curPrefix = dat. wholeText. forEachLineLength ( prefix: curPrefix, body: body)
638+ case . materializedToken( let dat) :
639+ curPrefix = dat. leadingTrivia. forEachLineLength ( prefix: curPrefix, body: body)
640+ curPrefix = dat. tokenText. forEachLineLength ( prefix: curPrefix, body: body)
641+ curPrefix = dat. trailingTrivia. forEachLineLength ( prefix: curPrefix, body: body)
642+ case . layout( let dat) :
643+ for case let node? in dat. layout where SyntaxTreeViewMode . sourceAccurate. shouldTraverse ( node: node) {
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+ }
678+ }
679+ }
667680 return curPrefix
668681 }
669682}
0 commit comments