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 @@ -19,6 +19,7 @@ #include "llvm/ADT/None.h" #include "llvm/ADT/Optional.h" #include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringSet.h" #include "llvm/Support/JSON.h" #include "llvm/Support/SourceMgr.h" @@ -106,6 +107,7 @@ std::vector Notes; /// *Alternative* fixes for this diagnostic, one should be chosen. std::vector Fixes; + llvm::SmallVector 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,60 @@ OS.flush(); return capitalize(std::move(Result)); } + +void setTags(clangd::Diag &D) { + static const auto *DeprecatedDiags = new llvm::DenseSet{ + 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 auto *UnusedDiags = new llvm::DenseSet{ + 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, + }; + if (DeprecatedDiags->contains(D.ID)) { + D.Tags.push_back(DiagnosticTag::Deprecated); + } else if (UnusedDiags->contains(D.ID)) { + D.Tags.push_back(DiagnosticTag::Unnecessary); + } + // FIXME: Set tags for tidy-based diagnostics too. +} } // namespace llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const DiagBase &D) { @@ -461,6 +517,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 @@ -504,6 +561,7 @@ // Fill in name/source now that we have all the context needed to map them. for (auto &Diag : Output) { + setTags(Diag); if (const char *ClangDiag = getDiagnosticCode(Diag.ID)) { // Warnings controlled by -Wfoo are better recognized by that name. StringRef Warning = DiagnosticIDs::getWarningOptionForDiag(Diag.ID); @@ -545,7 +603,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 @@ -28,6 +28,7 @@ #include "support/MemoryTree.h" #include "clang/Index/IndexSymbol.h" #include "llvm/ADT/Optional.h" +#include "llvm/ADT/SmallVector.h" #include "llvm/Support/JSON.h" #include "llvm/Support/raw_ostream.h" #include @@ -811,6 +812,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 +844,9 @@ /// The diagnostic's message. std::string message; + /// Additional metadata about the diagnostic. + llvm::SmallVector 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 @@ -584,6 +584,8 @@ }; } +llvm::json::Value toJSON(DiagnosticTag Tag) { return static_cast(Tag); } + llvm::json::Value toJSON(const Diagnostic &D) { llvm::json::Object Diag{ {"range", D.range}, @@ -602,6 +604,8 @@ Diag["relatedInformation"] = *D.relatedInformation; if (!D.data.empty()) Diag["data"] = llvm::json::Object(D.data); + if (!D.tags.empty()) + Diag["tags"] = llvm::json::Array{D.tags}; // FIXME: workaround for older gcc/clang return std::move(Diag); } 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