diff --git a/Documentation/RuleDocumentation.md b/Documentation/RuleDocumentation.md index 0b626d5cf..a8716982b 100644 --- a/Documentation/RuleDocumentation.md +++ b/Documentation/RuleDocumentation.md @@ -4,7 +4,7 @@ Use the rules below in the `rules` block of your `.swift-format` configuration file, as described in -[Configuration](Configuration.md). All of these rules can be +[Configuration](Documentation/Configuration.md). All of these rules can be applied in the linter, but only some of them can format your source code automatically. @@ -578,4 +578,7 @@ Documentation comments must be complete and valid. Lint: Documentation comments that are incomplete (e.g. missing parameter documentation) or invalid (uses `Parameters` when there is only one parameter) will yield a lint error. -`ValidateDocumentationComments` is a linter-only rule. +Format: Documentation comments that use `Parameters` with only one parameter, or that use + multiple `Parameter` lines, will be corrected. + +`ValidateDocumentationComments` rule can format your code automatically. diff --git a/Sources/SwiftFormat/Core/Pipelines+Generated.swift b/Sources/SwiftFormat/Core/Pipelines+Generated.swift index 8b0906e3e..0524fafdc 100644 --- a/Sources/SwiftFormat/Core/Pipelines+Generated.swift +++ b/Sources/SwiftFormat/Core/Pipelines+Generated.swift @@ -642,6 +642,7 @@ extension FormatPipeline { node = UseSingleLinePropertyGetter(context: context).rewrite(node) node = UseTripleSlashForDocumentationComments(context: context).rewrite(node) node = UseWhereClausesInForLoops(context: context).rewrite(node) + node = ValidateDocumentationComments(context: context).rewrite(node) return node } } diff --git a/Sources/SwiftFormat/Rules/ValidateDocumentationComments.swift b/Sources/SwiftFormat/Rules/ValidateDocumentationComments.swift index 3d257bf10..e89ad7450 100644 --- a/Sources/SwiftFormat/Rules/ValidateDocumentationComments.swift +++ b/Sources/SwiftFormat/Rules/ValidateDocumentationComments.swift @@ -20,23 +20,26 @@ import SwiftSyntax /// /// Lint: Documentation comments that are incomplete (e.g. missing parameter documentation) or /// invalid (uses `Parameters` when there is only one parameter) will yield a lint error. +/// +/// Format: Documentation comments that use `Parameters` with only one parameter, or that use +/// multiple `Parameter` lines, will be corrected. @_spi(Rules) -public final class ValidateDocumentationComments: SyntaxLintRule { +public final class ValidateDocumentationComments: SyntaxFormatRule { /// Identifies this rule as being opt-in. Accurate and complete documentation comments are /// important, but this rule isn't able to handle situations where portions of documentation are /// redundant. For example when the returns clause is redundant for a simple declaration. public override class var isOptIn: Bool { return true } - public override func visit(_ node: InitializerDeclSyntax) -> SyntaxVisitorContinueKind { - return checkFunctionLikeDocumentation( + public override func visit(_ node: InitializerDeclSyntax) -> DeclSyntax { + checkFunctionLikeDocumentation( DeclSyntax(node), name: "init", signature: node.signature ) } - public override func visit(_ node: FunctionDeclSyntax) -> SyntaxVisitorContinueKind { - return checkFunctionLikeDocumentation( + public override func visit(_ node: FunctionDeclSyntax) -> DeclSyntax { + checkFunctionLikeDocumentation( DeclSyntax(node), name: node.name.text, signature: node.signature, @@ -49,12 +52,12 @@ public final class ValidateDocumentationComments: SyntaxLintRule { name: String, signature: FunctionSignatureSyntax, returnClause: ReturnClauseSyntax? = nil - ) -> SyntaxVisitorContinueKind { + ) -> DeclSyntax { guard let docComment = DocumentationComment(extractedFrom: node), !docComment.parameters.isEmpty else { - return .skipChildren + return node } // If a single sentence summary is the only documentation, parameter(s) and @@ -64,7 +67,7 @@ public final class ValidateDocumentationComments: SyntaxLintRule { && docComment.parameters.isEmpty && docComment.returns == nil { - return .skipChildren + return node } validateThrows( @@ -80,27 +83,34 @@ public final class ValidateDocumentationComments: SyntaxLintRule { node: node ) let funcParameters = funcParametersIdentifiers(in: signature.parameterClause.parameters) + // Note: Don't try to restructure the documentation if there's a mismatch in + // the number of described parameters. + let docCountMatchesDeclCount = docComment.parameters.count == funcParameters.count - // If the documentation of the parameters is wrong 'docCommentInfo' won't - // parse the parameters correctly. First the documentation has to be fix + // If the documentation of the parameters is wrong, 'docCommentInfo' won't + // parse the parameters correctly. The documentation has to be fixed // in order to validate the other conditions. if docComment.parameterLayout != .separated && funcParameters.count == 1 { diagnose(.useSingularParameter, on: node) - return .skipChildren + return docCountMatchesDeclCount + ? convertToSeparated(node, docComment: docComment) + : node } else if docComment.parameterLayout != .outline && funcParameters.count > 1 { diagnose(.usePluralParameters, on: node) - return .skipChildren + return docCountMatchesDeclCount + ? convertToOutline(node, docComment: docComment) + : node } // Ensures that the parameters of the documentation and the function signature // are the same. - if (docComment.parameters.count != funcParameters.count) + if !docCountMatchesDeclCount || !parametersAreEqual(params: docComment.parameters, funcParam: funcParameters) { diagnose(.parametersDontMatch(funcName: name), on: node) } - return .skipChildren + return node } /// Ensures the function has a return documentation if it actually returns @@ -146,6 +156,91 @@ public final class ValidateDocumentationComments: SyntaxLintRule { diagnose(.documentErrorsThrown(funcName: name), on: throwsOrRethrowsKeyword) } } + + private func convertToSeparated( + _ node: DeclSyntax, + docComment: DocumentationComment + ) -> DeclSyntax { + guard #available(macOS 13, *) else { return node } // Regexes ahead + guard docComment.parameterLayout == .outline else { return node } + + // Find the start of the documentation that is attached to this + // identifier, skipping over any trivia that doesn't actually + // attach (like `//` comments or full blank lines). + var docCommentTrivia = Array(node.leadingTrivia) + guard let startOfActualDocumentation = findStartOfDocComments(in: docCommentTrivia) + else { return node } + + // We're required to have a '- Parameters:' header followed by exactly one + // '- identifier: ....' block; find the index of both of those lines. + guard + let headerIndex = docCommentTrivia[startOfActualDocumentation...] + .firstIndex(where: \.isOutlineParameterHeader) + else { return node } + + guard + let paramIndex = docCommentTrivia[headerIndex...].dropFirst() + .firstIndex(where: \.isOutlineParameter), + let originalCommentLine = docCommentTrivia[paramIndex].docLineString + else { return node } + + // Update the comment to be a single parameter description, and then remove + // the outline header. + docCommentTrivia[paramIndex] = .docLineComment( + originalCommentLine.replacing("/// - ", with: "/// - Parameter ") + ) + + let endOfHeader = docCommentTrivia[headerIndex...].dropFirst() + .firstIndex(where: { !$0.isWhitespace })! + docCommentTrivia.removeSubrange(headerIndex.. DeclSyntax { + guard #available(macOS 13, *) else { return node } // Regexes ahead + guard docComment.parameterLayout == .separated else { return node } + + // Find the start of the documentation that is attached to this + // identifier, skipping over any trivia that doesn't actually + // attach (like `//` comments or full blank lines). + var docCommentTrivia = Array(node.leadingTrivia) + guard let startOfActualDocumentation = findStartOfDocComments(in: docCommentTrivia) + else { return node } + + // Find the indexes of all the lines that start with a separate parameter + // doc pattern, then convert them to outline syntax. + let parameterIndexes = docCommentTrivia[startOfActualDocumentation...] + .indices + .filter { i in docCommentTrivia[i].isSeparateParameter } + + for i in parameterIndexes { + docCommentTrivia[i] = .docLineComment( + docCommentTrivia[i].docLineString!.replacing("/// - Parameter ", with: "/// - ") + ) + } + + // Add in the parameter outline header. + guard let firstParamIndex = parameterIndexes.first else { return node } + let interstitialSpace = docCommentTrivia[firstParamIndex...].dropFirst() + .prefix(while: \.isWhitespace) + + docCommentTrivia.insert( + contentsOf: [.docLineComment("/// - Parameters:")] + interstitialSpace, + at: firstParamIndex + ) + + // Return the original node with the modified trivia. + var result = node + result.leadingTrivia = Trivia(pieces: docCommentTrivia) + return result + } } /// Iterates through every parameter of paramList and returns a list of the @@ -176,6 +271,78 @@ fileprivate func parametersAreEqual( return true } +fileprivate func findStartOfDocComments(in trivia: [TriviaPiece]) -> Int? { + let startOfCommentSection = + trivia + .lastIndex(where: { !$0.continuesDocComment }) + ?? trivia.startIndex + return trivia[startOfCommentSection...].firstIndex(where: \.isDocComment) +} + +extension TriviaPiece { + fileprivate var docLineString: String? { + if case .docLineComment(let str) = self { return str } else { return nil } + } + + fileprivate var isDocComment: Bool { + switch self { + case .docBlockComment, .docLineComment: return true + default: return false + } + } + + fileprivate var continuesDocComment: Bool { + if isDocComment { return true } + switch self { + // Any amount of horizontal whitespace is okay + case .spaces, .tabs: + return true + // One line break is okay + case .newlines(1), .carriageReturns(1), .carriageReturnLineFeeds(1): + return true + default: + return false + } + } +} + +@available(macOS 13, *) +extension TriviaPiece { + fileprivate var isOutlineParameterHeader: Bool { + guard let docLineString else { return false } + return docLineString.contains(#/^\s*/// - Parameters:/#) + } + + fileprivate var isOutlineParameter: Bool { + guard let docLineString else { return false } + return docLineString.contains(#/^\s*/// - .+?:/#) + } + + fileprivate var isSeparateParameter: Bool { + guard let docLineString else { return false } + return docLineString.contains(#/^\s*/// - Parameter .+?:/#) + } + + fileprivate var isCommentBlankLine: Bool { + guard let docLineString else { return false } + return docLineString.contains(#/^\s*///\s*$/#) + } +} + +extension BidirectionalCollection { + fileprivate func suffix(while predicate: (Element) throws -> Bool) rethrows -> SubSequence { + var current = endIndex + while current > startIndex { + let prev = index(before: current) + if try !predicate(self[prev]) { + return self[current...] + } + current = prev + } + return self[...] + } +} + extension Finding.Message { fileprivate static func documentReturnValue(funcName: String) -> Finding.Message { "add a 'Returns:' section to document the return value of '\(funcName)'" diff --git a/Tests/SwiftFormatTests/Rules/ValidateDocumentationCommentsTests.swift b/Tests/SwiftFormatTests/Rules/ValidateDocumentationCommentsTests.swift index 3876bbc55..f81fd2ddd 100644 --- a/Tests/SwiftFormatTests/Rules/ValidateDocumentationCommentsTests.swift +++ b/Tests/SwiftFormatTests/Rules/ValidateDocumentationCommentsTests.swift @@ -4,26 +4,84 @@ import _SwiftFormatTestSupport // FIXME: Diagnostics should be emitted inside the comment, not at the beginning of the declaration. final class ValidateDocumentationCommentsTests: LintOrFormatRuleTestCase { func testParameterDocumentation() { - assertLint( + assertFormatting( ValidateDocumentationComments.self, - """ - /// Uses 'Parameters' when it only has one parameter. - /// - /// - Parameters: - /// - singular: singular description. - /// - Returns: A string containing the contents of a - /// description - 1️⃣func testPluralParamDesc(singular: String) -> Bool {} - - /// Returns the output generated by executing a command with the given string - /// used as standard input. - /// - /// - Parameter command: The command to execute in the shell environment. - /// - Parameter stdin: The string to use as standard input. - /// - Returns: A string containing the contents of the invoked process's - /// standard output. - 2️⃣func testInvalidParameterDesc(command: String, stdin: String) -> String {} - """, + input: """ + /// Uses 'Parameters' when it only has one parameter. + /// + /// - Parameters: + /// - singular: singular description. + /// - Returns: A string containing the contents of a + /// description + 1️⃣func testPluralParamDesc(singular: String) -> Bool {} + + /// Returns the output generated by executing a command with the given string + /// used as standard input. + /// + /// - Parameter command: The command to execute in the shell environment. + /// - Parameter stdin: The string to use as standard input. + /// - Returns: A string containing the contents of the invoked process's + /// standard output. + 2️⃣func testInvalidParameterDesc(command: String, stdin: String) -> String {} + + struct Greeting { + /// Creates a new greeting in the default language. + /// + /// You can create a greeting with an optional name. + /// + /// - Parameters: + /// - name: An optional name to greet. If `name` is `nil`, + /// a placeholder name is used instead. + 3️⃣init(name: String? = nil) {} + + /// Creates a new greeting. + /// + /// You can create a greeting with a language and, optionally, a name. + /// + /// - Parameter language: The language to greet in. + /// - Parameter name: An optional name to greet. If `name` is `nil`, + /// a placeholder name is used instead. + 4️⃣init(language: Language, name: String? = nil) {} + } + """, + expected: """ + /// Uses 'Parameters' when it only has one parameter. + /// + /// - Parameter singular: singular description. + /// - Returns: A string containing the contents of a + /// description + func testPluralParamDesc(singular: String) -> Bool {} + + /// Returns the output generated by executing a command with the given string + /// used as standard input. + /// + /// - Parameters: + /// - command: The command to execute in the shell environment. + /// - stdin: The string to use as standard input. + /// - Returns: A string containing the contents of the invoked process's + /// standard output. + func testInvalidParameterDesc(command: String, stdin: String) -> String {} + + struct Greeting { + /// Creates a new greeting in the default language. + /// + /// You can create a greeting with an optional name. + /// + /// - Parameter name: An optional name to greet. If `name` is `nil`, + /// a placeholder name is used instead. + init(name: String? = nil) {} + + /// Creates a new greeting. + /// + /// You can create a greeting with a language and, optionally, a name. + /// + /// - Parameters: + /// - language: The language to greet in. + /// - name: An optional name to greet. If `name` is `nil`, + /// a placeholder name is used instead. + init(language: Language, name: String? = nil) {} + } + """, findings: [ FindingSpec( "1️⃣", @@ -34,30 +92,56 @@ final class ValidateDocumentationCommentsTests: LintOrFormatRuleTestCase { message: "replace the singular inline 'Parameter' section with a plural 'Parameters:' section that has the parameters nested inside it" ), + FindingSpec( + "3️⃣", + message: "replace the plural 'Parameters:' section with a singular inline 'Parameter' section" + ), + FindingSpec( + "4️⃣", + message: + "replace the singular inline 'Parameter' section with a plural 'Parameters:' section that has the parameters nested inside it" + ), ] ) } func testParametersName() { - assertLint( + assertFormatting( ValidateDocumentationComments.self, - """ - /// Parameters dont match. - /// - /// - Parameters: - /// - sum: The sum of all numbers. - /// - avg: The average of all numbers. - /// - Returns: The sum of sum and avg. - 1️⃣func sum(avg: Int, sum: Int) -> Int {} - - /// Missing one parameter documentation. - /// - /// - Parameters: - /// - p1: Parameter 1. - /// - p2: Parameter 2. - /// - Returns: an integer. - 2️⃣func foo(p1: Int, p2: Int, p3: Int) -> Int {} - """, + input: """ + /// Parameters dont match. + /// + /// - Parameters: + /// - sum: The sum of all numbers. + /// - avg: The average of all numbers. + /// - Returns: The sum of sum and avg. + 1️⃣func sum(avg: Int, sum: Int) -> Int {} + + /// Missing one parameter documentation. + /// + /// - Parameters: + /// - p1: Parameter 1. + /// - p2: Parameter 2. + /// - Returns: an integer. + 2️⃣func foo(p1: Int, p2: Int, p3: Int) -> Int {} + """, + expected: """ + /// Parameters dont match. + /// + /// - Parameters: + /// - sum: The sum of all numbers. + /// - avg: The average of all numbers. + /// - Returns: The sum of sum and avg. + func sum(avg: Int, sum: Int) -> Int {} + + /// Missing one parameter documentation. + /// + /// - Parameters: + /// - p1: Parameter 1. + /// - p2: Parameter 2. + /// - Returns: an integer. + func foo(p1: Int, p2: Int, p3: Int) -> Int {} + """, findings: [ FindingSpec("1️⃣", message: "change the parameters of the documentation of 'sum' to match its parameters"), FindingSpec("2️⃣", message: "change the parameters of the documentation of 'foo' to match its parameters"), @@ -66,32 +150,56 @@ final class ValidateDocumentationCommentsTests: LintOrFormatRuleTestCase { } func testThrowsDocumentation() { - assertLint( + assertFormatting( ValidateDocumentationComments.self, - """ - /// One sentence summary. - /// - /// - Parameters: - /// - p1: Parameter 1. - /// - p2: Parameter 2. - /// - p3: Parameter 3. - /// - Throws: an error. - 1️⃣func doesNotThrow(p1: Int, p2: Int, p3: Int) {} - - /// One sentence summary. - /// - /// - Parameters: - /// - p1: Parameter 1. - /// - p2: Parameter 2. - /// - p3: Parameter 3. - func doesThrow(p1: Int, p2: Int, p3: Int) 2️⃣throws {} - - /// One sentence summary. - /// - /// - Parameter p1: Parameter 1. - /// - Throws: doesn't really throw, just rethrows - func doesRethrow(p1: (() throws -> ())) 3️⃣rethrows {} - """, + input: """ + /// One sentence summary. + /// + /// - Parameters: + /// - p1: Parameter 1. + /// - p2: Parameter 2. + /// - p3: Parameter 3. + /// - Throws: an error. + 1️⃣func doesNotThrow(p1: Int, p2: Int, p3: Int) {} + + /// One sentence summary. + /// + /// - Parameters: + /// - p1: Parameter 1. + /// - p2: Parameter 2. + /// - p3: Parameter 3. + func doesThrow(p1: Int, p2: Int, p3: Int) 2️⃣throws {} + + /// One sentence summary. + /// + /// - Parameter p1: Parameter 1. + /// - Throws: doesn't really throw, just rethrows + func doesRethrow(p1: (() throws -> ())) 3️⃣rethrows {} + """, + expected: """ + /// One sentence summary. + /// + /// - Parameters: + /// - p1: Parameter 1. + /// - p2: Parameter 2. + /// - p3: Parameter 3. + /// - Throws: an error. + func doesNotThrow(p1: Int, p2: Int, p3: Int) {} + + /// One sentence summary. + /// + /// - Parameters: + /// - p1: Parameter 1. + /// - p2: Parameter 2. + /// - p3: Parameter 3. + func doesThrow(p1: Int, p2: Int, p3: Int) throws {} + + /// One sentence summary. + /// + /// - Parameter p1: Parameter 1. + /// - Throws: doesn't really throw, just rethrows + func doesRethrow(p1: (() throws -> ())) rethrows {} + """, findings: [ FindingSpec("1️⃣", message: "remove the 'Throws:' sections of 'doesNotThrow'; it does not throw any errors"), FindingSpec("2️⃣", message: "add a 'Throws:' section to document the errors thrown by 'doesThrow'"), @@ -100,43 +208,78 @@ final class ValidateDocumentationCommentsTests: LintOrFormatRuleTestCase { } func testReturnDocumentation() { - assertLint( + assertFormatting( ValidateDocumentationComments.self, - """ - /// One sentence summary. - /// - /// - Parameters: - /// - p1: Parameter 1. - /// - p2: Parameter 2. - /// - p3: Parameter 3. - /// - Returns: an integer. - 1️⃣func noReturn(p1: Int, p2: Int, p3: Int) {} - - /// One sentence summary. - /// - /// - Parameters: - /// - p1: Parameter 1. - /// - p2: Parameter 2. - /// - p3: Parameter 3. - func foo(p1: Int, p2: Int, p3: Int) 2️⃣-> Int {} - - /// One sentence summary. - /// - /// - Parameters: - /// - p1: Parameter 1. - /// - p2: Parameter 2. - /// - p3: Parameter 3. - func neverReturns(p1: Int, p2: Int, p3: Int) -> Never {} - - /// One sentence summary. - /// - /// - Parameters: - /// - p1: Parameter 1. - /// - p2: Parameter 2. - /// - p3: Parameter 3. - /// - Returns: Never returns. - func documentedNeverReturns(p1: Int, p2: Int, p3: Int) -> Never {} - """, + input: """ + /// One sentence summary. + /// + /// - Parameters: + /// - p1: Parameter 1. + /// - p2: Parameter 2. + /// - p3: Parameter 3. + /// - Returns: an integer. + 1️⃣func noReturn(p1: Int, p2: Int, p3: Int) {} + + /// One sentence summary. + /// + /// - Parameters: + /// - p1: Parameter 1. + /// - p2: Parameter 2. + /// - p3: Parameter 3. + func foo(p1: Int, p2: Int, p3: Int) 2️⃣-> Int {} + + /// One sentence summary. + /// + /// - Parameters: + /// - p1: Parameter 1. + /// - p2: Parameter 2. + /// - p3: Parameter 3. + func neverReturns(p1: Int, p2: Int, p3: Int) -> Never {} + + /// One sentence summary. + /// + /// - Parameters: + /// - p1: Parameter 1. + /// - p2: Parameter 2. + /// - p3: Parameter 3. + /// - Returns: Never returns. + func documentedNeverReturns(p1: Int, p2: Int, p3: Int) -> Never {} + """, + expected: """ + /// One sentence summary. + /// + /// - Parameters: + /// - p1: Parameter 1. + /// - p2: Parameter 2. + /// - p3: Parameter 3. + /// - Returns: an integer. + func noReturn(p1: Int, p2: Int, p3: Int) {} + + /// One sentence summary. + /// + /// - Parameters: + /// - p1: Parameter 1. + /// - p2: Parameter 2. + /// - p3: Parameter 3. + func foo(p1: Int, p2: Int, p3: Int) -> Int {} + + /// One sentence summary. + /// + /// - Parameters: + /// - p1: Parameter 1. + /// - p2: Parameter 2. + /// - p3: Parameter 3. + func neverReturns(p1: Int, p2: Int, p3: Int) -> Never {} + + /// One sentence summary. + /// + /// - Parameters: + /// - p1: Parameter 1. + /// - p2: Parameter 2. + /// - p3: Parameter 3. + /// - Returns: Never returns. + func documentedNeverReturns(p1: Int, p2: Int, p3: Int) -> Never {} + """, findings: [ FindingSpec("1️⃣", message: "remove the 'Returns:' section of 'noReturn'; it does not return a value"), FindingSpec("2️⃣", message: "add a 'Returns:' section to document the return value of 'foo'"), @@ -145,130 +288,233 @@ final class ValidateDocumentationCommentsTests: LintOrFormatRuleTestCase { } func testValidDocumentation() { - assertLint( + assertFormatting( ValidateDocumentationComments.self, - """ - /// Returns the output generated by executing a command. - /// - /// - Parameter command: The command to execute in the shell environment. - /// - Returns: A string containing the contents of the invoked process's - /// standard output. - func singularParam(command: String) -> String { - // ... - } - - /// Returns the output generated by executing a command with the given string - /// used as standard input. - /// - /// - Parameters: - /// - command: The command to execute in the shell environment. - /// - stdin: The string to use as standard input. - /// - Throws: An error, possibly. - /// - Returns: A string containing the contents of the invoked process's - /// standard output. - func pluralParam(command: String, stdin: String) throws -> String { - // ... - } - - /// One sentence summary. - /// - /// - Parameter p1: Parameter 1. - func rethrower(p1: (() throws -> ())) rethrows { - // ... - } - - /// Parameter(s) and Returns tags may be omitted only if the single-sentence - /// brief summary fully describes the meaning of those items and including the - /// tags would only repeat what has already been said - func omittedFunc(p1: Int) - """, + input: """ + /// Returns the output generated by executing a command. + /// + /// - Parameter command: The command to execute in the shell environment. + /// - Returns: A string containing the contents of the invoked process's + /// standard output. + func singularParam(command: String) -> String { + // ... + } + + /// Returns the output generated by executing a command with the given string + /// used as standard input. + /// + /// - Parameters: + /// - command: The command to execute in the shell environment. + /// - stdin: The string to use as standard input. + /// - Throws: An error, possibly. + /// - Returns: A string containing the contents of the invoked process's + /// standard output. + func pluralParam(command: String, stdin: String) throws -> String { + // ... + } + + /// One sentence summary. + /// + /// - Parameter p1: Parameter 1. + func rethrower(p1: (() throws -> ())) rethrows { + // ... + } + + /// Parameter(s) and Returns tags may be omitted only if the single-sentence + /// brief summary fully describes the meaning of those items and including the + /// tags would only repeat what has already been said + func omittedFunc(p1: Int) + """, + expected: """ + /// Returns the output generated by executing a command. + /// + /// - Parameter command: The command to execute in the shell environment. + /// - Returns: A string containing the contents of the invoked process's + /// standard output. + func singularParam(command: String) -> String { + // ... + } + + /// Returns the output generated by executing a command with the given string + /// used as standard input. + /// + /// - Parameters: + /// - command: The command to execute in the shell environment. + /// - stdin: The string to use as standard input. + /// - Throws: An error, possibly. + /// - Returns: A string containing the contents of the invoked process's + /// standard output. + func pluralParam(command: String, stdin: String) throws -> String { + // ... + } + + /// One sentence summary. + /// + /// - Parameter p1: Parameter 1. + func rethrower(p1: (() throws -> ())) rethrows { + // ... + } + + /// Parameter(s) and Returns tags may be omitted only if the single-sentence + /// brief summary fully describes the meaning of those items and including the + /// tags would only repeat what has already been said + func omittedFunc(p1: Int) + """, findings: [] ) } func testSeparateLabelAndIdentifier() { - assertLint( - ValidateDocumentationComments.self, - """ - /// Returns the output generated by executing a command. - /// - /// - Parameter command: The command to execute in the shell environment. - /// - Returns: A string containing the contents of the invoked process's - /// standard output. - 1️⃣func incorrectParam(label commando: String) -> String { - // ... - } - - /// Returns the output generated by executing a command. - /// - /// - Parameter command: The command to execute in the shell environment. - /// - Returns: A string containing the contents of the invoked process's - /// standard output. - func singularParam(label command: String) -> String { - // ... - } - - /// Returns the output generated by executing a command with the given string - /// used as standard input. - /// - /// - Parameters: - /// - command: The command to execute in the shell environment. - /// - stdin: The string to use as standard input. - /// - Returns: A string containing the contents of the invoked process's - /// standard output. - func pluralParam(label command: String, label2 stdin: String) -> String { - // ... - } - """, - findings: [ - FindingSpec( - "1️⃣", - message: "change the parameters of the documentation of 'incorrectParam' to match its parameters" - ) - ] - ) - } - - func testInitializer() { - assertLint( + assertFormatting( ValidateDocumentationComments.self, - """ - struct SomeType { - /// Brief summary. + input: """ + /// Returns the output generated by executing a command. /// /// - Parameter command: The command to execute in the shell environment. - /// - Returns: Shouldn't be here. - 1️⃣2️⃣init(label commando: String) { + /// - Returns: A string containing the contents of the invoked process's + /// standard output. + 1️⃣func incorrectParam(label commando: String) -> String { // ... } - /// Brief summary. + /// Returns the output generated by executing a command. /// /// - Parameter command: The command to execute in the shell environment. - init(label command: String) { + /// - Returns: A string containing the contents of the invoked process's + /// standard output. + func singularParam(label command: String) -> String { // ... } - /// Brief summary. + /// Returns the output generated by executing a command with the given string + /// used as standard input. /// /// - Parameters: /// - command: The command to execute in the shell environment. /// - stdin: The string to use as standard input. - init(label command: String, label2 stdin: String) { + /// - Returns: A string containing the contents of the invoked process's + /// standard output. + func pluralParam(label command: String, label2 stdin: String) -> String { + // ... + } + """, + expected: """ + /// Returns the output generated by executing a command. + /// + /// - Parameter command: The command to execute in the shell environment. + /// - Returns: A string containing the contents of the invoked process's + /// standard output. + func incorrectParam(label commando: String) -> String { // ... } - /// Brief summary. + /// Returns the output generated by executing a command. + /// + /// - Parameter command: The command to execute in the shell environment. + /// - Returns: A string containing the contents of the invoked process's + /// standard output. + func singularParam(label command: String) -> String { + // ... + } + + /// Returns the output generated by executing a command with the given string + /// used as standard input. /// /// - Parameters: /// - command: The command to execute in the shell environment. /// - stdin: The string to use as standard input. - /// - Throws: An error. - init(label command: String, label2 stdin: String) throws { + /// - Returns: A string containing the contents of the invoked process's + /// standard output. + func pluralParam(label command: String, label2 stdin: String) -> String { // ... } - } - """, + """, + findings: [ + FindingSpec( + "1️⃣", + message: "change the parameters of the documentation of 'incorrectParam' to match its parameters" + ) + ] + ) + } + + func testInitializer() { + assertFormatting( + ValidateDocumentationComments.self, + input: """ + struct SomeType { + /// Brief summary. + /// + /// - Parameter command: The command to execute in the shell environment. + /// - Returns: Shouldn't be here. + 1️⃣2️⃣init(label commando: String) { + // ... + } + + /// Brief summary. + /// + /// - Parameter command: The command to execute in the shell environment. + init(label command: String) { + // ... + } + + /// Brief summary. + /// + /// - Parameters: + /// - command: The command to execute in the shell environment. + /// - stdin: The string to use as standard input. + init(label command: String, label2 stdin: String) { + // ... + } + + /// Brief summary. + /// + /// - Parameters: + /// - command: The command to execute in the shell environment. + /// - stdin: The string to use as standard input. + /// - Throws: An error. + init(label command: String, label2 stdin: String) throws { + // ... + } + } + """, + expected: """ + struct SomeType { + /// Brief summary. + /// + /// - Parameter command: The command to execute in the shell environment. + /// - Returns: Shouldn't be here. + init(label commando: String) { + // ... + } + + /// Brief summary. + /// + /// - Parameter command: The command to execute in the shell environment. + init(label command: String) { + // ... + } + + /// Brief summary. + /// + /// - Parameters: + /// - command: The command to execute in the shell environment. + /// - stdin: The string to use as standard input. + init(label command: String, label2 stdin: String) { + // ... + } + + /// Brief summary. + /// + /// - Parameters: + /// - command: The command to execute in the shell environment. + /// - stdin: The string to use as standard input. + /// - Throws: An error. + init(label command: String, label2 stdin: String) throws { + // ... + } + } + """, findings: [ FindingSpec("1️⃣", message: "remove the 'Returns:' section of 'init'; it does not return a value"), FindingSpec("2️⃣", message: "change the parameters of the documentation of 'init' to match its parameters"),