diff --git a/Release Notes/602.md b/Release Notes/602.md index 1841abf4a7c..5ddcbd22511 100644 --- a/Release Notes/602.md +++ b/Release Notes/602.md @@ -3,7 +3,7 @@ ## New APIs - `DiagnosticMessage` has a new optional property, `category`, that providesa category name and documentation URL for a diagnostic. - - Description: Tools often have many different diagnostics. Diagnostic categories allow tools to group several diagnostics together with documentation that can help users understand what the diagnostics mean and how to address them. This API allows diagnostics to provide this category information. The diagnostic renderer will provide the category at the end of the diagnostic message in the form `[#CategoryName]`. + - Description: Tools often have many different diagnostics. Diagnostic categories allow tools to group several diagnostics together with documentation that can help users understand what the diagnostics mean and how to address them. This API allows diagnostics to provide this category information. The diagnostic renderer will provide the category at the end of the diagnostic message in the form `[#CategoryName]`, and can print categories as "footnotes" with its `categoryFootnotes` method. - Pull Request: https://github.com/swiftlang/swift-syntax/pull/2981 - Migration steps: None required. The new `category` property has optional type, and there is a default implementation that returns `nil`. Types that conform to `DiagnosticMessage` can choose to implement this property and provide a category when appropriate. diff --git a/Sources/SwiftDiagnostics/DiagnosticsFormatter.swift b/Sources/SwiftDiagnostics/DiagnosticsFormatter.swift index fa43eea76ee..327b678c082 100644 --- a/Sources/SwiftDiagnostics/DiagnosticsFormatter.swift +++ b/Sources/SwiftDiagnostics/DiagnosticsFormatter.swift @@ -351,4 +351,40 @@ public struct DiagnosticsFormatter { suffixTexts: [:] ) } + + /// Produce a string containing "footnotes" for each of the diagnostic + /// category provided that has associated documentation. Each category + /// is printed in Markdown link format, e.g., + /// + /// ``` + /// [#categoryName]: + /// ``` + /// + /// This function also deduplicates entries and alphabetizes the results. + /// + /// - Parameters: + /// - categories: the categories to print + /// - leadingText: text that is prefixed to the list of categories when + /// there is at least one category to print. + public func categoryFootnotes( + _ categories: [DiagnosticCategory], + leadingText: String = "\n" + ) -> String { + let categoriesInOrder = categories.compactMap { category in + if let documentationURL = category.documentationURL { + return (category.name, documentationURL) + } else { + return nil + } + }.sorted { $0.0.lowercased() < $1.0.lowercased() } + + if categoriesInOrder.isEmpty { + return "" + } + + return leadingText + + categoriesInOrder.map { name, url in + "[#\(name)]: <\(url)>" + }.joined(separator: "\n") + } } diff --git a/Tests/SwiftDiagnosticsTest/GroupDiagnosticsFormatterTests.swift b/Tests/SwiftDiagnosticsTest/GroupDiagnosticsFormatterTests.swift index ec95397f9b4..d4892a26020 100644 --- a/Tests/SwiftDiagnosticsTest/GroupDiagnosticsFormatterTests.swift +++ b/Tests/SwiftDiagnosticsTest/GroupDiagnosticsFormatterTests.swift @@ -233,4 +233,30 @@ final class GroupedDiagnosticsFormatterTests: XCTestCase { """ ) } + + func testCategoryFootnotes() { + let categories = [ + DiagnosticCategory( + name: "StrictMemorySafety", + documentationURL: "http://example.com/memory-safety" + ), + DiagnosticCategory( + name: "deprecated", + documentationURL: "http://example.com/deprecated" + ), + DiagnosticCategory(name: "nothing", documentationURL: nil), + ] + + assertStringsEqualWithDiff( + DiagnosticsFormatter().categoryFootnotes( + categories, + leadingText: "Footnotes:\n" + ), + """ + Footnotes: + [#deprecated]: + [#StrictMemorySafety]: + """ + ) + } }