From 006d2668030356bee73f89e0d38d3c7f79d9e43e Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Sun, 9 Mar 2025 09:56:21 -0700 Subject: [PATCH] [Diagnostic formatting] Add a category footnote printer Categories associated with diagnostics are printed in a Markdown link style as part of diagnostics (e.g., `[#StrictMemorySafety]`). At the end of compilation, a tool may wish to provide documentation links to documentation for all of the categories that showed up in that compilation. This new function DiagnosticFormatter.categoryFootnotes() prints the categories it is given (also as Markdown) for that purpose, e.g., [#deprecated]: [#StrictMemorySafety]: --- Release Notes/602.md | 2 +- .../DiagnosticsFormatter.swift | 36 +++++++++++++++++++ .../GroupDiagnosticsFormatterTests.swift | 26 ++++++++++++++ 3 files changed, 63 insertions(+), 1 deletion(-) 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]: + """ + ) + } }