Author: Sam McCall Date: 2022-05-20T16:33:48+02:00 New Revision: edc7a0814575002abecb8ac7b49c669bf5f5ab7a
URL: https://github.com/llvm/llvm-project/commit/edc7a0814575002abecb8ac7b49c669bf5f5ab7a DIFF: https://github.com/llvm/llvm-project/commit/edc7a0814575002abecb8ac7b49c669bf5f5ab7a.diff LOG: [clangd] Provide links to clang-tidy and include-cleaner diagnostic docs LSP supports Diagnostic.codeInformation since 3.16. In VSCode, this turns the code (e.g. "unused-includes" or "bugprone-foo") into a clickable link that opens the docs in a web browser. Differential Revision: https://reviews.llvm.org/D126065 Added: Modified: clang-tools-extra/clangd/Diagnostics.cpp clang-tools-extra/clangd/Diagnostics.h clang-tools-extra/clangd/Protocol.cpp clang-tools-extra/clangd/Protocol.h clang-tools-extra/clangd/test/diagnostics-tidy.test clang-tools-extra/clangd/unittests/DiagnosticsTests.cpp Removed: ################################################################################ diff --git a/clang-tools-extra/clangd/Diagnostics.cpp b/clang-tools-extra/clangd/Diagnostics.cpp index 46a16a9f2eeb..316bc7d1ac66 100644 --- a/clang-tools-extra/clangd/Diagnostics.cpp +++ b/clang-tools-extra/clangd/Diagnostics.cpp @@ -477,6 +477,10 @@ void toLSPDiags( } Main.code = D.Name; + if (auto URI = getDiagnosticDocURI(D.Source, D.ID, D.Name)) { + Main.codeDescription.emplace(); + Main.codeDescription->href = std::move(*URI); + } switch (D.Source) { case Diag::Clang: Main.source = "clang"; @@ -903,5 +907,31 @@ llvm::StringRef normalizeSuppressedCode(llvm::StringRef Code) { return Code; } +llvm::Optional<std::string> getDiagnosticDocURI(Diag::DiagSource Source, + unsigned ID, + llvm::StringRef Name) { + switch (Source) { + case Diag::Unknown: + break; + case Diag::Clang: + // There is a page listing many warning flags, but it provides too little + // information to be worth linking. + // https://clang.llvm.org/docs/DiagnosticsReference.html + break; + case Diag::ClangTidy: + return {("https://clang.llvm.org/extra/clang-tidy/checks/" + Name + ".html") + .str()}; + case Diag::Clangd: + if (Name == "unused-includes") + return {"https://clangd.llvm.org/guides/include-cleaner"}; + break; + case Diag::ClangdConfig: + // FIXME: we should link to https://clangd.llvm.org/config + // However we have no diagnostic codes, which the link should describe! + break; + } + return llvm::None; +} + } // namespace clangd } // namespace clang diff --git a/clang-tools-extra/clangd/Diagnostics.h b/clang-tools-extra/clangd/Diagnostics.h index 0c8af97e6695..14627ea3d6a0 100644 --- a/clang-tools-extra/clangd/Diagnostics.h +++ b/clang-tools-extra/clangd/Diagnostics.h @@ -126,6 +126,10 @@ CodeAction toCodeAction(const Fix &D, const URIForFile &File); /// Convert from clang diagnostic level to LSP severity. int getSeverity(DiagnosticsEngine::Level L); +/// Returns a URI providing more information about a particular diagnostic. +llvm::Optional<std::string> getDiagnosticDocURI(Diag::DiagSource, unsigned ID, + llvm::StringRef Name); + /// StoreDiags collects the diagnostics that can later be reported by /// clangd. It groups all notes for a diagnostic into a single Diag /// and filters out diagnostics that don't mention the main file (i.e. neither diff --git a/clang-tools-extra/clangd/Protocol.cpp b/clang-tools-extra/clangd/Protocol.cpp index 5f8e4938c65a..aed4ac09d486 100644 --- a/clang-tools-extra/clangd/Protocol.cpp +++ b/clang-tools-extra/clangd/Protocol.cpp @@ -592,6 +592,10 @@ llvm::json::Value toJSON(const DiagnosticRelatedInformation &DRI) { llvm::json::Value toJSON(DiagnosticTag Tag) { return static_cast<int>(Tag); } +llvm::json::Value toJSON(const CodeDescription &D) { + return llvm::json::Object{{"href", D.href}}; +} + llvm::json::Value toJSON(const Diagnostic &D) { llvm::json::Object Diag{ {"range", D.range}, @@ -604,6 +608,8 @@ llvm::json::Value toJSON(const Diagnostic &D) { Diag["codeActions"] = D.codeActions; if (!D.code.empty()) Diag["code"] = D.code; + if (D.codeDescription.hasValue()) + Diag["codeDescription"] = *D.codeDescription; if (!D.source.empty()) Diag["source"] = D.source; if (D.relatedInformation) diff --git a/clang-tools-extra/clangd/Protocol.h b/clang-tools-extra/clangd/Protocol.h index 3f66cb2a5fcd..fd3b671d15f3 100644 --- a/clang-tools-extra/clangd/Protocol.h +++ b/clang-tools-extra/clangd/Protocol.h @@ -835,6 +835,13 @@ enum DiagnosticTag { }; llvm::json::Value toJSON(DiagnosticTag Tag); +/// Structure to capture a description for an error code. +struct CodeDescription { + /// An URI to open with more information about the diagnostic error. + std::string href; +}; +llvm::json::Value toJSON(const CodeDescription &); + struct CodeAction; struct Diagnostic { /// The range at which the message applies. @@ -847,6 +854,9 @@ struct Diagnostic { /// The diagnostic's code. Can be omitted. std::string code; + /// An optional property to describe the error code. + llvm::Optional<CodeDescription> codeDescription; + /// A human-readable string describing the source of this /// diagnostic, e.g. 'typescript' or 'super lint'. std::string source; @@ -874,7 +884,7 @@ struct Diagnostic { /// A data entry field that is preserved between a /// `textDocument/publishDiagnostics` notification - /// and`textDocument/codeAction` request. + /// and `textDocument/codeAction` request. /// Mutating users should associate their data with a unique key they can use /// to retrieve later on. llvm::json::Object data; diff --git a/clang-tools-extra/clangd/test/diagnostics-tidy.test b/clang-tools-extra/clangd/test/diagnostics-tidy.test index 1d10541be8bf..c7e79b0c6d5f 100644 --- a/clang-tools-extra/clangd/test/diagnostics-tidy.test +++ b/clang-tools-extra/clangd/test/diagnostics-tidy.test @@ -8,6 +8,9 @@ # CHECK-NEXT: "diagnostics": [ # CHECK-NEXT: { # CHECK-NEXT: "code": "bugprone-sizeof-expression", +# CHECK-NEXT: "codeDescription": { +# CHECK-NEXT: "href": "https://clang.llvm.org/extra/clang-tidy/checks/bugprone-sizeof-expression.html" +# CHECK-NEXT: }, # CHECK-NEXT: "message": "Suspicious usage of 'sizeof(K)'; did you mean 'K'?", # CHECK-NEXT: "range": { # CHECK-NEXT: "end": { diff --git a/clang-tools-extra/clangd/unittests/DiagnosticsTests.cpp b/clang-tools-extra/clangd/unittests/DiagnosticsTests.cpp index d83f43d83f3f..45ceee01ea9a 100644 --- a/clang-tools-extra/clangd/unittests/DiagnosticsTests.cpp +++ b/clang-tools-extra/clangd/unittests/DiagnosticsTests.cpp @@ -1828,13 +1828,17 @@ TEST(DiagnosticsTest, IncludeCleaner) { Cfg.Diagnostics.Includes.IgnoreHeader.emplace_back( [](llvm::StringRef Header) { return Header.endswith("ignore.h"); }); WithContextValue WithCfg(Config::Key, std::move(Cfg)); + auto AST = TU.build(); EXPECT_THAT( - *TU.build().getDiagnostics(), + *AST.getDiagnostics(), UnorderedElementsAre(AllOf( Diag(Test.range("diag"), "included header unused.h is not used directly"), withTag(DiagnosticTag::Unnecessary), diagSource(Diag::Clangd), withFix(Fix(Test.range("fix"), "", "remove #include directive"))))); + auto &Diag = AST.getDiagnostics()->front(); + EXPECT_EQ(getDiagnosticDocURI(Diag.Source, Diag.ID, Diag.Name), + std::string("https://clangd.llvm.org/guides/include-cleaner")); Cfg.Diagnostics.SuppressAll = true; WithContextValue SuppressAllWithCfg(Config::Key, std::move(Cfg)); EXPECT_THAT(*TU.build().getDiagnostics(), IsEmpty()); _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits