@@ -19,23 +19,27 @@ extension PathHierarchy {
19
19
///
20
20
/// Includes information about:
21
21
/// - The node that was found
22
- /// - The remaining portion of the path.
23
- typealias PartialResult = ( node: Node , path : [ PathComponent ] )
22
+ /// - The portion of the path up and including to the found node and its trailing path separator .
23
+ typealias PartialResult = ( node: Node , pathPrefix : Substring )
24
24
25
25
/// No element was found at the beginning of the path.
26
26
///
27
27
/// Includes information about:
28
+ /// - The portion of the path up to the first path component.
28
29
/// - The remaining portion of the path. This may be empty
29
30
/// - A list of the names for the top level elements.
30
- case notFound( remaining: [ PathComponent ] , availableChildren: Set < String > )
31
+ case notFound( pathPrefix : Substring , remaining: [ PathComponent ] , availableChildren: Set < String > )
31
32
32
33
/// Matched node does not correspond to a documentation page.
33
34
///
34
35
/// For partial symbol graph files, sometimes sparse nodes that don't correspond to known documentation need to be created to form a hierarchy. These nodes are not findable.
35
36
case unfindableMatch( Node )
36
37
37
38
/// A symbol link found a non-symbol match.
38
- case nonSymbolMatchForSymbolLink
39
+ ///
40
+ /// Includes information about:
41
+ /// - The path to the non-symbol match.
42
+ case nonSymbolMatchForSymbolLink( path: Substring )
39
43
40
44
/// Encountered an unknown disambiguation for a found node.
41
45
///
@@ -64,46 +68,31 @@ extension PathHierarchy {
64
68
}
65
69
66
70
extension PathHierarchy . Error {
67
- /// Generate a ``TopicReferenceResolutionError`` from this error using the given `context` and `originalReference`.
68
- ///
69
- /// The resulting ``TopicReferenceResolutionError`` is human-readable and provides helpful solutions.
70
- ///
71
+ /// Creates a value with structured information that can be used to present diagnostics about the error.
71
72
/// - Parameters:
72
- /// - context: The ``DocumentationContext`` the `originalReference` was resolved in.
73
- /// - originalReference: The raw input string that represents the body of the reference that failed to resolve. This string is
74
- /// used to calculate the proper replacement-ranges for fixits.
75
- ///
76
- /// - Note: `Replacement`s produced by this function use `SourceLocation`s relative to the `originalReference`, i.e. the beginning
77
- /// of the _body_ of the original reference.
78
- func asTopicReferenceResolutionErrorInfo( context: DocumentationContext , originalReference: String ) -> TopicReferenceResolutionErrorInfo {
79
-
80
- // This is defined inline because it captures `context`.
73
+ /// - fullNameOfNode: A closure that determines the full name of a node, to be displayed in collision diagnostics to precisely identify symbols and other pages.
74
+ /// - Note: `Replacement`s produced by this function use `SourceLocation`s relative to the link text excluding its surrounding syntax.
75
+ func makeTopicReferenceResolutionErrorInfo( fullNameOfNode: ( PathHierarchy . Node ) -> String ) -> TopicReferenceResolutionErrorInfo {
76
+ // This is defined inline because it captures `fullNameOfNode`.
81
77
func collisionIsBefore( _ lhs: ( node: PathHierarchy . Node , disambiguation: String ) , _ rhs: ( node: PathHierarchy . Node , disambiguation: String ) ) -> Bool {
82
- return lhs. node. fullNameOfValue ( context : context ) + lhs . disambiguation
83
- < rhs . node . fullNameOfValue ( context : context ) + rhs. disambiguation
78
+ return fullNameOfNode ( lhs. node) + lhs . disambiguation
79
+ < fullNameOfNode ( rhs. node) + rhs. disambiguation
84
80
}
85
81
86
82
switch self {
87
- case . notFound( remaining: let remaining, availableChildren: let availableChildren) :
83
+ case . notFound( pathPrefix : let pathPrefix , remaining: let remaining, availableChildren: let availableChildren) :
88
84
guard let firstPathComponent = remaining. first else {
89
85
return TopicReferenceResolutionErrorInfo (
90
86
" No local documentation matches this reference "
91
87
)
92
88
}
93
89
94
- let solutions : [ Solution ]
95
- if let pathComponentIndex = originalReference. range ( of: firstPathComponent. full) {
96
- let startColumn = originalReference. distance ( from: originalReference. startIndex, to: pathComponentIndex. lowerBound)
97
- let replacementRange = SourceRange . makeRelativeRange ( startColumn: startColumn, length: firstPathComponent. full. count)
98
-
99
- let nearMisses = NearMiss . bestMatches ( for: availableChildren, against: firstPathComponent. name)
100
- solutions = nearMisses. map { candidate in
101
- Solution ( summary: " \( Self . replacementOperationDescription ( from: firstPathComponent. full, to: candidate) ) " , replacements: [
102
- Replacement ( range: replacementRange, replacement: candidate)
103
- ] )
104
- }
105
- } else {
106
- solutions = [ ]
90
+ let replacementRange = SourceRange . makeRelativeRange ( startColumn: pathPrefix. count, length: firstPathComponent. full. count)
91
+ let nearMisses = NearMiss . bestMatches ( for: availableChildren, against: String ( firstPathComponent. name) )
92
+ let solutions = nearMisses. map { candidate in
93
+ Solution ( summary: " \( Self . replacementOperationDescription ( from: firstPathComponent. full, to: candidate) ) " , replacements: [
94
+ Replacement ( range: replacementRange, replacement: candidate)
95
+ ] )
107
96
}
108
97
109
98
return TopicReferenceResolutionErrorInfo ( """
@@ -117,31 +106,27 @@ extension PathHierarchy.Error {
117
106
\( node. name. singleQuoted) can't be linked to in a partial documentation build
118
107
""" )
119
108
120
- case . nonSymbolMatchForSymbolLink:
109
+ case . nonSymbolMatchForSymbolLink( path : let path ) :
121
110
return TopicReferenceResolutionErrorInfo ( " Symbol links can only resolve symbols " , solutions: [
122
111
Solution ( summary: " Use a '<doc:>' style reference. " , replacements: [
123
112
// the SourceRange points to the opening double-backtick
124
113
Replacement ( range: . makeRelativeRange( startColumn: - 2 , endColumn: 0 ) , replacement: " <doc: " ) ,
125
114
// the SourceRange points to the closing double-backtick
126
- Replacement ( range: . makeRelativeRange( startColumn: originalReference . count, endColumn: originalReference . count+ 2 ) , replacement: " > " ) ,
115
+ Replacement ( range: . makeRelativeRange( startColumn: path . count, endColumn: path . count+ 2 ) , replacement: " > " ) ,
127
116
] )
128
117
] )
129
118
130
119
case . unknownDisambiguation( partialResult: let partialResult, remaining: let remaining, candidates: let candidates) :
131
120
let nextPathComponent = remaining. first!
132
- var validPrefix = " "
133
- if !partialResult. path. isEmpty {
134
- validPrefix += PathHierarchy . joined ( partialResult. path) + " / "
135
- }
136
- validPrefix += nextPathComponent. name
121
+ let validPrefix = partialResult. pathPrefix + nextPathComponent. name
137
122
138
123
let disambiguations = nextPathComponent. full. dropFirst ( nextPathComponent. name. count)
139
124
let replacementRange = SourceRange . makeRelativeRange ( startColumn: validPrefix. count, length: disambiguations. count)
140
125
141
126
let solutions : [ Solution ] = candidates
142
127
. sorted ( by: collisionIsBefore)
143
128
. map { ( node: PathHierarchy . Node , disambiguation: String ) -> Solution in
144
- return Solution ( summary: " \( Self . replacementOperationDescription ( from: disambiguations. dropFirst ( ) , to: disambiguation) ) for \n \( node . fullNameOfValue ( context : context ) . singleQuoted) " , replacements: [
129
+ return Solution ( summary: " \( Self . replacementOperationDescription ( from: disambiguations. dropFirst ( ) , to: disambiguation) ) for \n \( fullNameOfNode ( node ) . singleQuoted) " , replacements: [
145
130
Replacement ( range: replacementRange, replacement: " - " + disambiguation)
146
131
] )
147
132
}
@@ -155,30 +140,27 @@ extension PathHierarchy.Error {
155
140
156
141
case . unknownName( partialResult: let partialResult, remaining: let remaining, availableChildren: let availableChildren) :
157
142
let nextPathComponent = remaining. first!
158
- let nearMisses = NearMiss . bestMatches ( for: availableChildren, against: nextPathComponent. name)
143
+ let nearMisses = NearMiss . bestMatches ( for: availableChildren, against: String ( nextPathComponent. name) )
159
144
160
145
// Use the authored disambiguation to try and reduce the possible near misses. For example, if the link was disambiguated with `-struct` we should
161
146
// only make suggestions for similarly spelled structs.
162
147
let filteredNearMisses = nearMisses. filter { name in
163
- ( try ? partialResult. node. children [ name] ? . find ( nextPathComponent. kind, nextPathComponent. hash) ) != nil
148
+ ( try ? partialResult. node. children [ name] ? . find ( nextPathComponent. kind. map ( String . init ) , nextPathComponent. hash. map ( String . init ) ) ) != nil
164
149
}
165
150
166
- var validPrefix = " "
167
- if !partialResult. path. isEmpty {
168
- validPrefix += PathHierarchy . joined ( partialResult. path) + " / "
169
- }
151
+ let pathPrefix = partialResult. pathPrefix
170
152
let solutions : [ Solution ]
171
153
if filteredNearMisses. isEmpty {
172
154
// If there are no near-misses where the authored disambiguation narrow down the results, replace the full path component
173
- let replacementRange = SourceRange . makeRelativeRange ( startColumn: validPrefix . count, length: nextPathComponent. full. count)
155
+ let replacementRange = SourceRange . makeRelativeRange ( startColumn: pathPrefix . count, length: nextPathComponent. full. count)
174
156
solutions = nearMisses. map { candidate in
175
157
Solution ( summary: " \( Self . replacementOperationDescription ( from: nextPathComponent. full, to: candidate) ) " , replacements: [
176
158
Replacement ( range: replacementRange, replacement: candidate)
177
159
] )
178
160
}
179
161
} else {
180
162
// If the authored disambiguation narrows down the possible near-misses, only replace the name part of the path component
181
- let replacementRange = SourceRange . makeRelativeRange ( startColumn: validPrefix . count, length: nextPathComponent. name. count)
163
+ let replacementRange = SourceRange . makeRelativeRange ( startColumn: pathPrefix . count, length: nextPathComponent. name. count)
182
164
solutions = filteredNearMisses. map { candidate in
183
165
Solution ( summary: " \( Self . replacementOperationDescription ( from: nextPathComponent. name, to: candidate) ) " , replacements: [
184
166
Replacement ( range: replacementRange, replacement: candidate)
@@ -190,23 +172,19 @@ extension PathHierarchy.Error {
190
172
\( nextPathComponent. full. singleQuoted) doesn't exist at \( partialResult. node. pathWithoutDisambiguation ( ) . singleQuoted)
191
173
""" ,
192
174
solutions: solutions,
193
- rangeAdjustment: . makeRelativeRange( startColumn: validPrefix . count, length: nextPathComponent. full. count)
175
+ rangeAdjustment: . makeRelativeRange( startColumn: pathPrefix . count, length: nextPathComponent. full. count)
194
176
)
195
177
196
178
case . lookupCollision( partialResult: let partialResult, remaining: let remaining, collisions: let collisions) :
197
179
let nextPathComponent = remaining. first!
198
180
199
- var validPrefix = " "
200
- if !partialResult. path. isEmpty {
201
- validPrefix += PathHierarchy . joined ( partialResult. path) + " / "
202
- }
203
- validPrefix += nextPathComponent. name
181
+ let pathPrefix = partialResult. pathPrefix + nextPathComponent. name
204
182
205
183
let disambiguations = nextPathComponent. full. dropFirst ( nextPathComponent. name. count)
206
- let replacementRange = SourceRange . makeRelativeRange ( startColumn: validPrefix . count, length: disambiguations. count)
184
+ let replacementRange = SourceRange . makeRelativeRange ( startColumn: pathPrefix . count, length: disambiguations. count)
207
185
208
186
let solutions : [ Solution ] = collisions. sorted ( by: collisionIsBefore) . map { ( node: PathHierarchy . Node , disambiguation: String ) -> Solution in
209
- return Solution ( summary: " \( Self . replacementOperationDescription ( from: disambiguations. dropFirst ( ) , to: disambiguation) ) for \n \( node . fullNameOfValue ( context : context ) . singleQuoted) " , replacements: [
187
+ return Solution ( summary: " \( Self . replacementOperationDescription ( from: disambiguations. dropFirst ( ) , to: disambiguation) ) for \n \( fullNameOfNode ( node ) . singleQuoted) " , replacements: [
210
188
Replacement ( range: replacementRange, replacement: " - " + disambiguation)
211
189
] )
212
190
}
@@ -215,7 +193,7 @@ extension PathHierarchy.Error {
215
193
\( nextPathComponent. full. singleQuoted) is ambiguous at \( partialResult. node. pathWithoutDisambiguation ( ) . singleQuoted)
216
194
""" ,
217
195
solutions: solutions,
218
- rangeAdjustment: . makeRelativeRange( startColumn: validPrefix . count - nextPathComponent. full. count, length: nextPathComponent. full. count)
196
+ rangeAdjustment: . makeRelativeRange( startColumn: pathPrefix . count - nextPathComponent. full. count, length: nextPathComponent. full. count)
219
197
)
220
198
}
221
199
}
@@ -244,26 +222,6 @@ private extension PathHierarchy.Node {
244
222
}
245
223
return " / " + components. joined ( separator: " / " )
246
224
}
247
-
248
- /// Determines the full name of a node's value using information from the documentation context.
249
- ///
250
- /// > Note: This value is only intended for error messages and other presentation.
251
- func fullNameOfValue( context: DocumentationContext ) -> String {
252
- guard let identifier = identifier else { return name }
253
- if let symbol = symbol {
254
- if let fragments = symbol [ mixin: SymbolGraph . Symbol. DeclarationFragments. self] ? . declarationFragments {
255
- return fragments. map ( \. spelling) . joined ( ) . split ( whereSeparator: { $0. isWhitespace || $0. isNewline } ) . joined ( separator: " " )
256
- }
257
- return context. nodeWithSymbolIdentifier ( symbol. identifier. precise) !. name. description
258
- }
259
- // This only gets called for PathHierarchy error messages, so hierarchyBasedLinkResolver is never nil.
260
- let reference = context. hierarchyBasedLinkResolver. resolvedReferenceMap [ identifier] !
261
- if reference. fragment != nil {
262
- return context. nodeAnchorSections [ reference] !. title
263
- } else {
264
- return context. documentationCache [ reference] !. name. description
265
- }
266
- }
267
225
}
268
226
269
227
private extension SourceRange {
0 commit comments