13
13
/// Represents a source location in a Swift file.
14
14
public struct SourceLocation : Hashable , Codable , CustomDebugStringConvertible {
15
15
16
- /// The UTF-8 byte offset into the file where this location resides.
17
- public let offset : Int
18
-
19
16
/// The line in the file where this location resides. 1-based.
17
+ ///
18
+ /// ### See also
19
+ /// ``SourceLocation/presumedLine``
20
20
public var line : Int
21
21
22
22
/// The UTF-8 byte offset from the beginning of the line where this location
23
23
/// resides. 1-based.
24
24
public let column : Int
25
25
26
+ /// The UTF-8 byte offset into the file where this location resides.
27
+ public let offset : Int
28
+
26
29
/// The file in which this location resides.
30
+ ///
31
+ /// ### See also
32
+ /// ``SourceLocation/presumedFile``
27
33
public let file : String
28
34
35
+ /// The line of this location when respecting `#sourceLocation` directives.
36
+ ///
37
+ /// If the location hasn’t been adjusted using `#sourceLocation` directives,
38
+ /// this is the same as `file`.
39
+ public let presumedLine : Int
40
+
41
+ /// The file in which the the location resides when respecting `#sourceLocation`
42
+ /// directives.
43
+ ///
44
+ /// If the location has been adjusted using `#sourceLocation` directives, this
45
+ /// is the file mentioned in the last `#sourceLocation` directive before this
46
+ /// location, otherwise this is the same as `file`.
47
+ public let presumedFile : String
48
+
29
49
/// Returns the location as `<line>:<column>` for debugging purposes.
30
50
/// Do not rely on this output being stable.
31
51
public var debugDescription : String {
@@ -47,11 +67,26 @@ public struct SourceLocation: Hashable, Codable, CustomDebugStringConvertible {
47
67
/// location in the source file has `offset` 0.
48
68
/// - file: A string describing the name of the file in which this location
49
69
/// is contained.
50
- public init ( line: Int , column: Int , offset: Int , file: String ) {
70
+ /// - presumedLine: If the location has been adjusted using `#sourceLocation`
71
+ /// directives, the adjusted line. If `nil`, this defaults to
72
+ /// `line`.
73
+ /// - presumedFile: If the location has been adjusted using `#sourceLocation`
74
+ /// directives, the adjusted file. If `nil`, this defaults to
75
+ /// `file`.
76
+ public init (
77
+ line: Int ,
78
+ column: Int ,
79
+ offset: Int ,
80
+ file: String ,
81
+ presumedLine: Int ? = nil ,
82
+ presumedFile: String ? = nil
83
+ ) {
51
84
self . line = line
52
85
self . offset = offset
53
86
self . column = column
54
87
self . file = file
88
+ self . presumedLine = presumedLine ?? line
89
+ self . presumedFile = presumedFile ?? file
55
90
}
56
91
}
57
92
@@ -83,6 +118,22 @@ public struct SourceRange: Hashable, Codable, CustomDebugStringConvertible {
83
118
}
84
119
}
85
120
121
+ /// Collects all `PoundSourceLocationSyntax` directives in a file.
122
+ fileprivate class SourceLocationCollector : SyntaxVisitor {
123
+ private var sourceLocationDirectives : [ PoundSourceLocationSyntax ] = [ ]
124
+
125
+ override func visit( _ node: PoundSourceLocationSyntax ) -> SyntaxVisitorContinueKind {
126
+ sourceLocationDirectives. append ( node)
127
+ return . skipChildren
128
+ }
129
+
130
+ static func collectSourceLocations( in tree: some SyntaxProtocol ) -> [ PoundSourceLocationSyntax ] {
131
+ let collector = SourceLocationCollector ( viewMode: . sourceAccurate)
132
+ collector. walk ( tree)
133
+ return collector. sourceLocationDirectives
134
+ }
135
+ }
136
+
86
137
/// Converts ``AbsolutePosition``s of syntax nodes to ``SourceLocation``s, and
87
138
/// vice-versa. The ``AbsolutePosition``s must be originating from nodes that are
88
139
/// part of the same tree that was used to initialize this class.
@@ -95,6 +146,15 @@ public final class SourceLocationConverter {
95
146
/// Position at end of file.
96
147
let endOfFile : AbsolutePosition
97
148
149
+ /// The information from all `#sourceLocation` directives in the file
150
+ /// necessary to compute presumed locations.
151
+ ///
152
+ /// - `sourceLine` is the line at which the `#sourceLocation` statement occurs
153
+ /// within the current file.
154
+ /// - `fileArgument` is the `file` argument of the `#sourceLocation` directive.
155
+ /// - `lineArgument` is the `line` argument of the `#sourceLocation` directive.
156
+ var sourceLocationDirectives : [ ( sourceLine: Int , arguments: ( file: String , line: Int ) ? ) ] = [ ]
157
+
98
158
/// - Parameters:
99
159
/// - file: The file path associated with the syntax tree.
100
160
/// - tree: The root of the syntax tree to convert positions to line/columns for.
@@ -104,11 +164,31 @@ public final class SourceLocationConverter {
104
164
self . source = tree. syntaxTextBytes
105
165
( self . lines, endOfFile) = computeLines ( tree: Syntax ( tree) )
106
166
precondition ( tree. byteSize == endOfFile. utf8Offset)
167
+
168
+ for directive in SourceLocationCollector . collectSourceLocations ( in: tree) {
169
+ let location = self . physicalLocation ( for: directive. positionAfterSkippingLeadingTrivia)
170
+ if let args = directive. args {
171
+ let logicalFile = args. fileName. segments. description
172
+ guard let logicalLine = Int ( args. lineNumber. description) else {
173
+ // FIXME: Handle hex line numbers
174
+ continue
175
+ }
176
+ sourceLocationDirectives. append ( ( sourceLine: location. line, arguments: ( file: logicalFile, line: logicalLine) ) )
177
+ } else {
178
+ // `#sourceLocation()` without any arguments resets the `#sourceLocation` directive.
179
+ sourceLocationDirectives. append ( ( sourceLine: location. line, arguments: nil ) )
180
+ }
181
+ }
107
182
}
108
183
184
+ /// - Important: This initializer does not take `#sourceLocation` directives
185
+ /// into account and doesn’t produce `presumedFile` and
186
+ /// `presumedLine`.
187
+ ///
109
188
/// - Parameters:
110
189
/// - file: The file path associated with the syntax tree.
111
190
/// - source: The source code to convert positions to line/columns for.
191
+ @available ( * , deprecated, message: " Use init(file:tree:) instead " )
112
192
public init ( file: String , source: String ) {
113
193
self . file = file
114
194
self . source = Array ( source. utf8)
@@ -145,13 +225,40 @@ public final class SourceLocationConverter {
145
225
}
146
226
}
147
227
148
- /// Convert a ``AbsolutePosition`` to a ``SourceLocation``. If the position is
228
+ /// Convert a ``AbsolutePosition`` to a ``SourceLocation``.
229
+ ///
230
+ /// If the position is exceeding the file length then the ``SourceLocation``
231
+ /// for the end of file is returned. If position is negative the location for
232
+ /// start of file is returned.
233
+ public func location( for position: AbsolutePosition ) -> SourceLocation {
234
+ let physicalLocation = physicalLocation ( for: position)
235
+ if let lastSourceLocationDirective = sourceLocationDirectives. last ( where: { $0. sourceLine < physicalLocation. line } ) ,
236
+ let arguments = lastSourceLocationDirective. arguments
237
+ {
238
+ let presumedLine = arguments. line + physicalLocation. line - lastSourceLocationDirective. sourceLine - 1
239
+ return SourceLocation (
240
+ line: physicalLocation. line,
241
+ column: physicalLocation. column,
242
+ offset: physicalLocation. offset,
243
+ file: physicalLocation. file,
244
+ presumedLine: presumedLine,
245
+ presumedFile: arguments. file
246
+ )
247
+ }
248
+
249
+ return physicalLocation
250
+ }
251
+
252
+ /// Compute the location of `position` without taking `#sourceLocation`
253
+ /// directives into account.
254
+ ///
255
+ /// If the position is
149
256
/// exceeding the file length then the ``SourceLocation`` for the end of file
150
257
/// is returned. If position is negative the location for start of file is
151
258
/// returned.
152
- public func location ( for origpos : AbsolutePosition ) -> SourceLocation {
259
+ private func physicalLocation ( for position : AbsolutePosition ) -> SourceLocation {
153
260
// Clamp the given position to the end of file if needed.
154
- let pos = min ( origpos , endOfFile)
261
+ let pos = min ( position , endOfFile)
155
262
if pos. utf8Offset < 0 {
156
263
return SourceLocation ( line: 1 , column: 1 , offset: 0 , file: self . file)
157
264
}
0 commit comments