diff --git a/clang-tools-extra/clangd/Diagnostics.h b/clang-tools-extra/clangd/Diagnostics.h --- a/clang-tools-extra/clangd/Diagnostics.h +++ b/clang-tools-extra/clangd/Diagnostics.h @@ -106,6 +106,7 @@ std::vector Notes; /// *Alternative* fixes for this diagnostic, one should be chosen. std::vector Fixes; + std::vector Tags; }; llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Diag &D); diff --git a/clang-tools-extra/clangd/Diagnostics.cpp b/clang-tools-extra/clangd/Diagnostics.cpp --- a/clang-tools-extra/clangd/Diagnostics.cpp +++ b/clang-tools-extra/clangd/Diagnostics.cpp @@ -26,6 +26,7 @@ #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/ScopeExit.h" #include "llvm/ADT/SmallString.h" +#include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringRef.h" #include "llvm/ADT/Twine.h" #include "llvm/Support/Capacity.h" @@ -35,6 +36,7 @@ #include "llvm/Support/raw_ostream.h" #include #include +#include namespace clang { namespace clangd { @@ -324,6 +326,61 @@ OS.flush(); return capitalize(std::move(Result)); } + +std::vector getDiagnosticTags(unsigned DiagID) { + static const llvm::DenseSet DeprecatedDiags = { + diag::warn_access_decl_deprecated, + diag::warn_atl_uuid_deprecated, + diag::warn_deprecated, + diag::warn_deprecated_altivec_src_compat, + diag::warn_deprecated_comma_subscript, + diag::warn_deprecated_compound_assign_volatile, + diag::warn_deprecated_copy, + diag::warn_deprecated_copy_with_dtor, + diag::warn_deprecated_copy_with_user_provided_copy, + diag::warn_deprecated_copy_with_user_provided_dtor, + diag::warn_deprecated_def, + diag::warn_deprecated_increment_decrement_volatile, + diag::warn_deprecated_message, + diag::warn_deprecated_redundant_constexpr_static_def, + diag::warn_deprecated_register, + diag::warn_deprecated_simple_assign_volatile, + diag::warn_deprecated_string_literal_conversion, + diag::warn_deprecated_this_capture, + diag::warn_deprecated_volatile_param, + diag::warn_deprecated_volatile_return, + diag::warn_deprecated_volatile_structured_binding, + diag::warn_opencl_attr_deprecated_ignored, + diag::warn_property_method_deprecated, + diag::warn_vector_mode_deprecated, + }; + static const llvm::DenseSet UnusedDiags = { + diag::warn_opencl_attr_deprecated_ignored, + diag::warn_pragma_attribute_unused, + diag::warn_unused_but_set_parameter, + diag::warn_unused_but_set_variable, + diag::warn_unused_comparison, + diag::warn_unused_const_variable, + diag::warn_unused_exception_param, + diag::warn_unused_function, + diag::warn_unused_label, + diag::warn_unused_lambda_capture, + diag::warn_unused_local_typedef, + diag::warn_unused_member_function, + diag::warn_unused_parameter, + diag::warn_unused_private_field, + diag::warn_unused_property_backing_ivar, + diag::warn_unused_template, + diag::warn_unused_variable, + }; + std::vector Result; + if (DeprecatedDiags.contains(DiagID)) { + Result.push_back(DiagnosticTag::Deprecated); + } else if (UnusedDiags.contains(DiagID)) { + Result.push_back(DiagnosticTag::Unnecessary); + } + return Result; +} } // namespace llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const DiagBase &D) { @@ -461,6 +518,7 @@ Main.relatedInformation->push_back(std::move(RelInfo)); } } + Main.tags = D.Tags; OutFn(std::move(Main), D.Fixes); // If we didn't emit the notes as relatedLocations, emit separate diagnostics @@ -517,6 +575,7 @@ Diag.Name = std::string(Name); } Diag.Source = Diag::Clang; + Diag.Tags = getDiagnosticTags(Diag.ID); continue; } if (Tidy != nullptr) { @@ -524,6 +583,7 @@ if (!TidyDiag.empty()) { Diag.Name = std::move(TidyDiag); Diag.Source = Diag::ClangTidy; + // FIXME: Set diagnostic tags based on check names. // clang-tidy bakes the name into diagnostic messages. Strip it out. // It would be much nicer to make clang-tidy not do this. auto CleanMessage = [&](std::string &Msg) { @@ -545,7 +605,7 @@ // duplicated messages due to various reasons (e.g. the check doesn't handle // template instantiations well; clang-tidy alias checks). std::set> SeenDiags; - llvm::erase_if(Output, [&](const Diag& D) { + llvm::erase_if(Output, [&](const Diag &D) { return !SeenDiags.emplace(D.Range, D.Message).second; }); return std::move(Output); diff --git a/clang-tools-extra/clangd/Protocol.h b/clang-tools-extra/clangd/Protocol.h --- a/clang-tools-extra/clangd/Protocol.h +++ b/clang-tools-extra/clangd/Protocol.h @@ -811,6 +811,19 @@ }; llvm::json::Value toJSON(const DiagnosticRelatedInformation &); +enum DiagnosticTag { + /// Unused or unnecessary code. + /// + /// Clients are allowed to render diagnostics with this tag faded out instead + /// of having an error squiggle. + Unnecessary = 1, + /// Deprecated or obsolete code. + /// + /// Clients are allowed to rendered diagnostics with this tag strike through. + Deprecated = 2, +}; +llvm::json::Value toJSON(DiagnosticTag Tag); + struct CodeAction; struct Diagnostic { /// The range at which the message applies. @@ -830,6 +843,9 @@ /// The diagnostic's message. std::string message; + /// Additional metadata about the diagnostic. + std::vector tags; + /// An array of related diagnostic information, e.g. when symbol-names within /// a scope collide all definitions can be marked via this property. llvm::Optional> relatedInformation; diff --git a/clang-tools-extra/clangd/Protocol.cpp b/clang-tools-extra/clangd/Protocol.cpp --- a/clang-tools-extra/clangd/Protocol.cpp +++ b/clang-tools-extra/clangd/Protocol.cpp @@ -602,6 +602,8 @@ Diag["relatedInformation"] = *D.relatedInformation; if (!D.data.empty()) Diag["data"] = llvm::json::Object(D.data); + if (!D.tags.empty()) + Diag["tags"] = D.tags; // FIXME: workaround for older gcc/clang return std::move(Diag); } @@ -1450,5 +1452,6 @@ return OS; } +llvm::json::Value toJSON(DiagnosticTag Tag) { return static_cast(Tag); } } // namespace clangd } // namespace clang diff --git a/clang-tools-extra/clangd/unittests/DiagnosticsTests.cpp b/clang-tools-extra/clangd/unittests/DiagnosticsTests.cpp --- a/clang-tools-extra/clangd/unittests/DiagnosticsTests.cpp +++ b/clang-tools-extra/clangd/unittests/DiagnosticsTests.cpp @@ -65,6 +65,11 @@ return Field(&Diag::Notes, UnorderedElementsAre(NoteMatcher1, NoteMatcher2)); } +::testing::Matcher +WithTag(::testing::Matcher TagMatcher) { + return Field(&Diag::Tags, Contains(TagMatcher)); +} + MATCHER_P2(Diag, Range, Message, "Diag at " + llvm::to_string(Range) + " = [" + Message + "]") { return arg.Range == Range && arg.Message == Message; @@ -665,6 +670,7 @@ clangd::Diag D; D.ID = clang::diag::err_undeclared_var_use; + D.Tags = {DiagnosticTag::Unnecessary}; D.Name = "undeclared_var_use"; D.Source = clangd::Diag::Clang; D.Message = "something terrible happened"; @@ -710,6 +716,7 @@ ../foo/baz/header.h:10:11: note: declared somewhere in the header file)"; + MainLSP.tags = {DiagnosticTag::Unnecessary}; clangd::Diagnostic NoteInMainLSP; NoteInMainLSP.range = NoteInMain.Range; @@ -1419,6 +1426,24 @@ testing::Contains(Diag(Code.range(), "no newline at end of file"))); } } + +TEST(Diagnostics, Tags) { + TestTU TU; + TU.ExtraArgs = {"-Wunused", "-Wdeprecated"}; + Annotations Test(R"cpp( + void bar() __attribute__((deprecated)); + void foo() { + int $unused[[x]]; + $deprecated[[bar]](); + })cpp"); + TU.Code = Test.code().str(); + EXPECT_THAT(*TU.build().getDiagnostics(), + UnorderedElementsAre( + AllOf(Diag(Test.range("unused"), "unused variable 'x'"), + WithTag(DiagnosticTag::Unnecessary)), + AllOf(Diag(Test.range("deprecated"), "'bar' is deprecated"), + WithTag(DiagnosticTag::Deprecated)))); +} } // namespace } // namespace clangd } // namespace clang