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 @@ -36,6 +36,7 @@ #include "llvm/Support/raw_ostream.h" #include #include +#include namespace clang { namespace clangd { @@ -326,24 +327,56 @@ return capitalize(std::move(Result)); } -llvm::SmallVector -getDiagnosticTags(const clang::Diagnostic &D) { - llvm::SmallVector Result; - if ((diag::warn_deprecated <= D.getID() && - D.getID() <= diag::warn_deprecated_volatile_structured_binding) || - D.getID() == diag::warn_access_decl_deprecated || - D.getID() == diag::warn_atl_uuid_deprecated || - D.getID() == diag::warn_opencl_attr_deprecated_ignored || - D.getID() == diag::warn_property_method_deprecated || - D.getID() == diag::warn_vector_mode_deprecated) { +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 ( - // Warnings about unused function results can be semantically important, - // e.g. an llvm::Error that needs to be checked. - D.getID() != diag::warn_unused_call && - ((diag::warn_unused_but_set_parameter <= D.getID() && - D.getID() <= diag::warn_unused_volatile) || - D.getID() == diag::warn_pragma_attribute_unused)) { + } else if (UnusedDiags.contains(DiagID)) { Result.push_back(DiagnosticTag::Unnecessary); } return Result; @@ -485,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 @@ -541,6 +575,7 @@ Diag.Name = std::string(Name); } Diag.Source = Diag::Clang; + Diag.Tags = getDiagnosticTags(Diag.ID); continue; } if (Tidy != nullptr) { @@ -548,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) { @@ -764,7 +800,6 @@ if (LastDiag->Severity == DiagnosticsEngine::Ignored) return; - LastDiag->Tags = getDiagnosticTags(Info); LastDiagLoc.emplace(Info.getLocation(), Info.getSourceManager()); LastDiagOriginallyError = OriginallyError; if (!Info.getFixItHints().empty()) 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 @@ -822,6 +822,8 @@ /// 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. 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 @@ -1452,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