diff --git a/Sources/SwiftDocC/Semantics/Metadata/Metadata.swift b/Sources/SwiftDocC/Semantics/Metadata/Metadata.swift index 4f767e38a..2744085cf 100644 --- a/Sources/SwiftDocC/Semantics/Metadata/Metadata.swift +++ b/Sources/SwiftDocC/Semantics/Metadata/Metadata.swift @@ -224,18 +224,20 @@ public final class Metadata: Semantic, AutomaticDirectiveConvertible { problems.append( contentsOf: namesAndRanges.map { (name, range) in - Problem( - diagnostic: Diagnostic( - source: symbolSource, - severity: .warning, - range: range, - identifier: "org.swift.docc.\(Metadata.directiveName).Invalid\(name)InDocumentationComment", - summary: "Invalid use of \(name.singleQuoted) directive in documentation comment; configuration will be ignored", - explanation: "Specify this configuration in a documentation extension file" - - // TODO: It would be nice to offer a solution here that removes the directive for you (#1111, rdar://140846407) - ) + let diagnostic = Diagnostic( + source: symbolSource, + severity: .warning, + range: range, + identifier: "org.swift.docc.\(Metadata.directiveName).Invalid\(name)InDocumentationComment", + summary: "Invalid use of \(name.singleQuoted) directive in documentation comment; configuration will be ignored", + explanation: "Specify this configuration in a documentation extension file" ) + + let solutions = range.map { + [Solution(summary: "Remove this \(name.singleQuoted) directive.", replacements: [Replacement(range: $0, replacement: "")])] + } ?? [] + + return Problem(diagnostic: diagnostic, possibleSolutions: solutions) } ) diff --git a/Tests/SwiftDocCTests/Semantics/MetadataTests.swift b/Tests/SwiftDocCTests/Semantics/MetadataTests.swift index c807d451a..e5eded2e1 100644 --- a/Tests/SwiftDocCTests/Semantics/MetadataTests.swift +++ b/Tests/SwiftDocCTests/Semantics/MetadataTests.swift @@ -433,5 +433,32 @@ class MetadataTests: XCTestCase { return (problemIDs, metadata) } + + func testInvalidMetadataDirectivesInDocumentationCommentHaveSolution() throws { + let source = """ + @Metadata { + @DisplayName("Custom Name") + @TechnologyRoot + } + """ + let document = Document(parsing: source, options: .parseBlockDirectives) + let directive = document.child(at: 0)! as! BlockDirective + let (bundle, _) = try testBundleAndContext() + var problems = [Problem]() + let metadata = Metadata(from: directive, source: nil, for: bundle, problems: &problems) + + metadata?.validateForUseInDocumentationComment(symbolSource: nil, problems: &problems) + + XCTAssertEqual(problems.count, 2) + + // Verify that each problem has a solution to remove the directive + for problem in problems { + XCTAssertNotNil(problem.possibleSolutions) + XCTAssertEqual(problem.possibleSolutions?.count, 1) + XCTAssertEqual(problem.possibleSolutions?.first?.summary, "Remove this \(problem.diagnostic.identifier.split(separator: ".").last!) directive.") + XCTAssertEqual(problem.possibleSolutions?.first?.replacements.count, 1) + XCTAssertEqual(problem.possibleSolutions?.first?.replacements.first?.replacement, "") + } + } }