@@ -130,52 +130,109 @@ struct ParametersAndReturnValidator {
130
130
}
131
131
132
132
var variants = DocumentationDataVariants < ParametersSection > ( )
133
- var allKnownParameterNames : Set < String > = [ ]
133
+ var allKnownFunctionParameterNames : Set < String > = [ ]
134
134
var parameterNamesByExternalName : [ String : Set < String > ] = [ : ]
135
- // Group the parameters by name to be able to diagnose parameters documented more than once.
136
- let parametersByName = [ String: [ Parameter] ] ( grouping: parameters, by: \. name)
135
+
136
+ // Wrap the documented parameters in classes so that `isMatchedInAnySignature` can be modified and tracked across different signatures.
137
+ // This is used later in this method body to raise warnings about documented parameters that wasn't found in any symbol representation's function signature.
138
+ class ParameterReference {
139
+ let wrapped : Parameter
140
+ init ( _ parameter: Parameter ) {
141
+ self . wrapped = parameter
142
+ }
143
+
144
+ var isMatchedInAnySignature : Bool = false
145
+ var name : String { wrapped. name }
146
+ }
147
+ let parameterReferences = parameters. map { ParameterReference ( $0) }
148
+ // Accumulate which unnamed parameters in the function signatures are missing documentation.
149
+ var undocumentedUnnamedParameters = Set < Int > ( )
137
150
138
151
for (trait, signature) in signatures {
139
- // Collect all language representations's parameter information from the function signatures .
140
- let knownParameterNames = Set ( signature. parameters. map ( \ . name ) )
141
- allKnownParameterNames . formUnion ( knownParameterNames )
152
+ // Insert parameter documentation in the order of the signature .
153
+ var orderedParameters = [ ParameterReference ? ] ( repeating : nil , count : signature. parameters. count )
154
+ var remainingDocumentedParameters = parameterReferences
142
155
143
- // Remove documented parameters that don't apply to this language representation's function signature.
144
- var languageApplicableParametersByName = parametersByName. filter { knownParameterNames. contains ( $0. key) }
156
+ // Some languages support both names and unnamed parameters, and allow mixing them in the same declaration.
157
+ // It's an uncommon style to mix both in the same declaration but if the developer's code does this we try to minimize warnings
158
+ // and display as much written documentation as possible on the rendered page by matching _named_ parameters first.
159
+ // This only matters if the developer _both_ mixes named and unnamed parameters in the same declaration and documents those parameters out-of-order.
160
+ //
161
+ // For example, consider this C function:
162
+ //
163
+ // /// - Parameters:
164
+ // /// - first: Some documentation
165
+ // /// - last: Some documentation
166
+ // /// - anything: Some documentation
167
+ // void functionName (int first, int /*unnamed*/, int last);
168
+ //
169
+ // Because there is a parameter named "last", the 2nd parameter documentation matches the 3rd (named) function parameter instead of the 2nd (unnamed).
170
+ // This means that the above doesn't raise any warnings and that the displayed order on the page is "first", "anything", "last".
171
+ //
172
+ // If we'd matched unnamed parameters first, the above would have matched the 2nd ("last") parameter documentation with the 2nd unnamed function parameter,
173
+ // which would have resulted in two confusing warnings:
174
+ // - One that the "last" parameter is missing documentation
175
+ // - One that the "anything" parameter doesn't exist in the function signature.
176
+ //
177
+ // Again, unless the developer documents the mixed named and unnamed parameters out-of-order, both approaches behave the same.
145
178
146
- // Add a missing error parameter documentation if needed.
147
- if trait == . objectiveC, Self . shouldAddObjectiveCErrorParameter ( signatures, parameters) {
148
- languageApplicableParametersByName [ " error " ] = [ Parameter ( name: " error " , contents: Self . objcErrorDescription) ] // This parameter is synthesized and doesn't have a source range.
179
+ // As described above, match the named function parameters first.
180
+ for (index, functionParameter) in signature. parameters. enumerated ( ) where !functionParameter. isUnnamed {
181
+ // While we're looping over the parameters, gather information about the function parameters to use in diagnostics later in this method body.
182
+ if let externalName = functionParameter. externalName {
183
+ parameterNamesByExternalName [ externalName, default: [ ] ] . insert ( functionParameter. name)
184
+ }
185
+ allKnownFunctionParameterNames. insert ( functionParameter. name)
186
+
187
+ // Match this function parameter with a named documented parameter.
188
+ guard let parameterIndex = remainingDocumentedParameters. firstIndex ( where: { $0. name == functionParameter. name } ) else {
189
+ continue
190
+ }
191
+ let parameter = remainingDocumentedParameters. remove ( at: parameterIndex)
192
+ parameter. isMatchedInAnySignature = true
193
+ orderedParameters [ index] = parameter
149
194
}
150
195
151
- // Ensure that the parameters are displayed in the order that they need to be passed.
152
- var sortedParameters : [ Parameter ] = [ ]
153
- sortedParameters. reserveCapacity ( languageApplicableParametersByName. count)
154
- for parameter in signature. parameters {
155
- if let foundParameter = languageApplicableParametersByName [ parameter. name] ? . first {
156
- sortedParameters. append ( foundParameter)
157
- }
158
- // While we're looping over the parameters, gather the parameters with external names so that this information can be
159
- // used to diagnose authored documentation that uses the "external name" instead of the "name" to refer to the parameter.
160
- if let externalName = parameter. externalName {
161
- parameterNamesByExternalName [ externalName, default: [ ] ] . insert ( parameter. name)
196
+ // As describe above, match the unnamed parameters last.
197
+ var unnamedParameterNumber = 0 // Track how many unnamed parameters we've encountered.
198
+ for (index, functionParameter) in signature. parameters. enumerated ( ) where functionParameter. isUnnamed {
199
+ defer { unnamedParameterNumber += 1 }
200
+ guard !remainingDocumentedParameters. isEmpty else {
201
+ // If the function signature has more unnamed parameters than there are documented parameters, the remaining function parameters are missing documentation.
202
+ undocumentedUnnamedParameters. insert ( unnamedParameterNumber)
203
+ continue
162
204
}
205
+ // Match this function parameter with a named documented parameter.
206
+ let parameter = remainingDocumentedParameters. removeFirst ( )
207
+ parameter. isMatchedInAnySignature = true
208
+ orderedParameters [ index] = parameter
163
209
}
164
210
165
- variants [ trait] = ParametersSection ( parameters: sortedParameters)
211
+ // Add a missing error parameter documentation if needed.
212
+ if trait == . objectiveC, Self . shouldAddObjectiveCErrorParameter ( signatures, parameters) {
213
+ orderedParameters. append (
214
+ ParameterReference (
215
+ Parameter ( name: " error " , contents: Self . objcErrorDescription) // This parameter is synthesized and doesn't have a source range.
216
+ )
217
+ )
218
+ }
219
+
220
+ variants [ trait] = ParametersSection ( parameters: orderedParameters. compactMap { $0? . wrapped } )
166
221
}
167
222
168
- // Diagnose documented parameters that's not found in any language representation's function signature.
169
- for parameter in parameters where !allKnownParameterNames. contains ( parameter. name) {
223
+ // Diagnose documented parameters that aren't found in any language representation's function signature.
224
+ for parameterReference in parameterReferences where !parameterReference. isMatchedInAnySignature && !allKnownFunctionParameterNames. contains ( parameterReference. name) {
225
+ let parameter = parameterReference. wrapped
170
226
if let matchingParameterNames = parameterNamesByExternalName [ parameter. name] {
171
227
diagnosticEngine. emit ( makeExternalParameterNameProblem ( parameter, knownParameterNamesWithSameExternalName: matchingParameterNames. sorted ( ) ) )
172
228
} else {
173
- diagnosticEngine. emit ( makeExtraParameterProblem ( parameter, knownParameterNames: allKnownParameterNames , symbolKind: symbolKind) )
229
+ diagnosticEngine. emit ( makeExtraParameterProblem ( parameter, knownParameterNames: allKnownFunctionParameterNames , symbolKind: symbolKind) )
174
230
}
175
231
}
176
232
177
233
// Diagnose parameters that are documented more than once.
178
- for (name, parameters) in parametersByName where allKnownParameterNames. contains ( name) && parameters. count > 1 {
234
+ let documentedParametersByName = [ String: [ Parameter] ] ( grouping: parameters, by: \. name)
235
+ for (name, parameters) in documentedParametersByName where allKnownFunctionParameterNames. contains ( name) && parameters. count > 1 {
179
236
let first = parameters. first! // Each group is guaranteed to be non-empty.
180
237
for parameter in parameters. dropFirst ( ) {
181
238
diagnosticEngine. emit ( makeDuplicateParameterProblem ( parameter, previous: first) )
@@ -191,23 +248,28 @@ struct ParametersAndReturnValidator {
191
248
if let parameterNameOrder = signatures. values. map ( \. parameters) . max ( by: { $0. count < $1. count } ) ? . map ( \. name) {
192
249
let parametersEndLocation = parameters. last? . range? . upperBound
193
250
194
- var missingParameterNames = allKnownParameterNames . subtracting ( [ " error " ] ) . filter { parametersByName [ $0] == nil }
251
+ var missingParameterNames = allKnownFunctionParameterNames . subtracting ( [ " error " ] ) . filter { documentedParametersByName [ $0] == nil }
195
252
for parameter in parameters {
196
253
// Parameters that are documented using their external name already has raised a more specific diagnostic.
197
254
if let documentedByExternalName = parameterNamesByExternalName [ parameter. name] {
198
255
missingParameterNames. subtract ( documentedByExternalName)
199
256
}
200
257
}
201
258
202
- for parameterName in missingParameterNames{
259
+ for parameterName in missingParameterNames {
203
260
// Look for the parameter that should come after the missing parameter to insert the placeholder documentation in the right location.
204
261
let parameterAfter = parameterNameOrder. drop ( while: { $0 != parameterName } ) . dropFirst ( )
205
- . mapFirst ( where: { parametersByName [ $0] ? . first! /* Each group is guaranteed to be non-empty */ } )
262
+ . mapFirst ( where: { documentedParametersByName [ $0] ? . first! /* Each group is guaranteed to be non-empty */ } )
206
263
207
264
// Match the placeholder formatting with the other parameters; either as a standalone parameter or as an item in a parameters outline.
208
- let standalone = parameterAfter? . isStandalone ?? parametersByName . first ? . value . first? . isStandalone ?? false
265
+ let standalone = parameterAfter? . isStandalone ?? parameters . first? . isStandalone ?? false
209
266
diagnosticEngine. emit ( makeMissingParameterProblem ( name: parameterName, before: parameterAfter, standalone: standalone, lastParameterEndLocation: parametersEndLocation) )
210
267
}
268
+
269
+ for unnamedParameterNumber in undocumentedUnnamedParameters. sorted ( ) {
270
+ let standalone = parameters. first? . isStandalone ?? false
271
+ diagnosticEngine. emit ( makeMissingUnnamedParameterProblem ( unnamedParameterNumber: unnamedParameterNumber, standalone: standalone, lastParameterEndLocation: parametersEndLocation) )
272
+ }
211
273
}
212
274
213
275
return variants
@@ -584,7 +646,7 @@ struct ParametersAndReturnValidator {
584
646
)
585
647
}
586
648
587
- /// Creates a new problem about a parameter that's missing documentation.
649
+ /// Creates a new problem about a named parameter that's missing documentation.
588
650
///
589
651
/// ## Example
590
652
///
@@ -603,6 +665,51 @@ struct ParametersAndReturnValidator {
603
665
/// - location: The end of the last parameter. Used as the diagnostic location when the undocumented parameter is the last parameter of the symbol.
604
666
/// - Returns: A new problem that suggests that the developer adds documentation for the parameter.
605
667
private func makeMissingParameterProblem( name: String , before nextParameter: Parameter ? , standalone: Bool , lastParameterEndLocation: SourceLocation ? ) -> Problem {
668
+ _makeMissingParameterProblem (
669
+ diagnosticSummary: " Parameter \( name. singleQuoted) is missing documentation " ,
670
+ solutionText: " Document \( name. singleQuoted) parameter " ,
671
+ replacementText: Self . newParameterDescription ( name: name, standalone: standalone) ,
672
+ before: nextParameter,
673
+ standalone: standalone,
674
+ lastParameterEndLocation: lastParameterEndLocation
675
+ )
676
+ }
677
+ /// Creates a new problem about an unnamed parameter that's missing documentation.
678
+ ///
679
+ /// ## Example
680
+ ///
681
+ /// ```c
682
+ /// /// - Parameters:
683
+ /// /// - firstValue: Description of the first parameter
684
+ /// /// ^
685
+ /// /// Unnamed parameter #1 is missing documentation
686
+ /// void doSomething(int firstValue, int /*unnamed*/);
687
+ /// ```
688
+ ///
689
+ /// - Parameters:
690
+ /// - unnamedParameterNumber: A number indicating which unnamed parameter this diagnostic refers to.
691
+ /// - standalone: `true` if the existing documented parameters use the standalone syntax `- Parameter someValue:` or `false` if the existing documented parameters use the `- someValue` syntax within a `- Parameters:` list.
692
+ /// - location: The end of the last parameter. Used as the diagnostic location when the undocumented parameter is the last parameter of the symbol.
693
+ /// - Returns: A new problem that suggests that the developer adds documentation for the parameter.
694
+ private func makeMissingUnnamedParameterProblem( unnamedParameterNumber: Int , standalone: Bool , lastParameterEndLocation: SourceLocation ? ) -> Problem {
695
+ _makeMissingParameterProblem (
696
+ diagnosticSummary: " Unnamed parameter # \( unnamedParameterNumber + 1 ) is missing documentation " ,
697
+ solutionText: " Document unnamed parameter # \( unnamedParameterNumber + 1 ) " ,
698
+ replacementText: Self . newUnnamedParameterDescription ( standalone: standalone) ,
699
+ before: nil ,
700
+ standalone: standalone,
701
+ lastParameterEndLocation: lastParameterEndLocation
702
+ )
703
+ }
704
+
705
+ private func _makeMissingParameterProblem(
706
+ diagnosticSummary: String ,
707
+ solutionText: String ,
708
+ replacementText: String ,
709
+ before nextParameter: Parameter ? ,
710
+ standalone: Bool ,
711
+ lastParameterEndLocation: SourceLocation ?
712
+ ) -> Problem {
606
713
let solutions : [ Solution ]
607
714
if let insertLocation = nextParameter? . range? . lowerBound ?? lastParameterEndLocation {
608
715
let extraWhitespace = " \n /// " + String( repeating: " " , count: ( nextParameter? . range? . lowerBound. column ?? 1 + ( standalone ? 0 : 2 ) /* indent items in a parameter outline by 2 spaces */) - 1 )
@@ -612,17 +719,17 @@ struct ParametersAndReturnValidator {
612
719
// /// - nextParameter: Description
613
720
// ^inserting "- parameterName: placeholder\n/// "
614
721
// ^^^^ add newline after to insert before the other parameter
615
- replacement = Self . newParameterDescription ( name : name , standalone : standalone ) + extraWhitespace
722
+ replacement = replacementText + extraWhitespace
616
723
} else {
617
724
// /// - Parameters:
618
725
// /// - otherParameter: Description
619
726
// ^inserting "\n/// - parameterName: placeholder"
620
727
// ^^^^ add newline before to insert after the last parameter
621
- replacement = extraWhitespace + Self . newParameterDescription ( name : name , standalone : standalone )
728
+ replacement = extraWhitespace + replacementText
622
729
}
623
730
solutions = [
624
731
Solution (
625
- summary: " Document \( name . singleQuoted ) parameter " ,
732
+ summary: solutionText ,
626
733
replacements: [
627
734
Replacement ( range: adjusted ( insertLocation ..< insertLocation) , replacement: replacement)
628
735
]
@@ -638,7 +745,7 @@ struct ParametersAndReturnValidator {
638
745
severity: . warning,
639
746
range: adjusted ( lastParameterEndLocation. map { $0 ..< $0 } ) ,
640
747
identifier: " org.swift.docc.MissingParameterDocumentation " ,
641
- summary: " Parameter \( name . singleQuoted ) is missing documentation "
748
+ summary: diagnosticSummary
642
749
) ,
643
750
possibleSolutions: solutions
644
751
)
@@ -663,6 +770,10 @@ struct ParametersAndReturnValidator {
663
770
private static func newParameterDescription( name: String , standalone: Bool ) -> String {
664
771
" - \( standalone ? " Parameter " : " " ) \( name) : <#parameter description#> "
665
772
}
773
+
774
+ private static func newUnnamedParameterDescription( standalone: Bool ) -> String {
775
+ " - \( standalone ? " Parameter " : " " ) <#uniqueParameterName#>: <#parameter description#> "
776
+ }
666
777
}
667
778
668
779
// MARK: Helper extensions
@@ -715,3 +826,11 @@ private extension SymbolGraph.Symbol.FunctionSignature {
715
826
}
716
827
}
717
828
}
829
+
830
+ private extension SymbolGraph . Symbol . FunctionSignature . FunctionParameter {
831
+ /// A Boolean value indicating whether this function parameter is "unnamed".
832
+ var isUnnamed : Bool {
833
+ // C and C++ use "" to indicate an unnamed function parameter whereas Swift use "_" to indicate an unnamed function parameter.
834
+ name. isEmpty || name == " _ "
835
+ }
836
+ }
0 commit comments