diff --git a/clang-tools-extra/clang-tidy/utils/TransformerClangTidyCheck.cpp b/clang-tools-extra/clang-tidy/utils/TransformerClangTidyCheck.cpp --- a/clang-tools-extra/clang-tidy/utils/TransformerClangTidyCheck.cpp +++ b/clang-tools-extra/clang-tidy/utils/TransformerClangTidyCheck.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// #include "TransformerClangTidyCheck.h" +#include "clang/Basic/DiagnosticIDs.h" #include "clang/Lex/Preprocessor.h" #include "llvm/ADT/STLExtras.h" @@ -126,18 +127,28 @@ } // Associate the diagnostic with the location of the first change. - DiagnosticBuilder Diag = - diag((*Edits)[0].Range.getBegin(), escapeForDiagnostic(*Explanation)); - for (const auto &T : *Edits) - switch (T.Kind) { - case transformer::EditKind::Range: - Diag << FixItHint::CreateReplacement(T.Range, T.Replacement); - break; - case transformer::EditKind::AddInclude: - Diag << Inserter.createIncludeInsertion( - Result.SourceManager->getFileID(T.Range.getBegin()), T.Replacement); - break; + { + DiagnosticBuilder Diag = + diag((*Edits)[0].Range.getBegin(), escapeForDiagnostic(*Explanation)); + for (const auto &T : *Edits) { + switch (T.Kind) { + case transformer::EditKind::Range: + Diag << FixItHint::CreateReplacement(T.Range, T.Replacement); + break; + case transformer::EditKind::AddInclude: + Diag << Inserter.createIncludeInsertion( + Result.SourceManager->getFileID(T.Range.getBegin()), T.Replacement); + break; + } } + } + // Emit potential notes. + for (const auto &T : *Edits) { + if (!T.Note.empty()) { + diag(T.Range.getBegin(), escapeForDiagnostic(T.Note), + DiagnosticIDs::Note); + } + } } void TransformerClangTidyCheck::storeOptions( diff --git a/clang-tools-extra/unittests/clang-tidy/TransformerClangTidyCheckTest.cpp b/clang-tools-extra/unittests/clang-tidy/TransformerClangTidyCheckTest.cpp --- a/clang-tools-extra/unittests/clang-tidy/TransformerClangTidyCheckTest.cpp +++ b/clang-tools-extra/unittests/clang-tidy/TransformerClangTidyCheckTest.cpp @@ -28,6 +28,7 @@ using transformer::makeRule; using transformer::node; using transformer::noopEdit; +using transformer::note; using transformer::RewriteRuleWith; using transformer::RootID; using transformer::statement; @@ -85,6 +86,7 @@ EXPECT_EQ(Errors.size(), 1U); EXPECT_EQ(Errors[0].Message.Message, "message"); EXPECT_THAT(Errors[0].Message.Ranges, testing::IsEmpty()); + EXPECT_THAT(Errors[0].Notes, testing::IsEmpty()); // The diagnostic is anchored to the match, "return 5". EXPECT_EQ(Errors[0].Message.FileOffset, 10U); @@ -116,6 +118,27 @@ EXPECT_EQ(Errors[0].Message.FileOffset, 10U); } +TEST(TransformerClangTidyCheckTest, NotesCorrectlyGenerated) { + class DiagAndNoteCheck : public TransformerClangTidyCheck { + public: + DiagAndNoteCheck(StringRef Name, ClangTidyContext *Context) + : TransformerClangTidyCheck( + makeRule(returnStmt(), + note(node(RootID), cat("some note")), + cat("message")), + Name, Context) {} + }; + std::string Input = "int h() { return 5; }"; + std::vector Errors; + EXPECT_EQ(Input, test::runCheckOnCode(Input, &Errors)); + EXPECT_EQ(Errors.size(), 1U); + EXPECT_EQ(Errors[0].Notes.size(), 1U); + EXPECT_EQ(Errors[0].Notes[0].Message, "some note"); + + // The note is anchored to the match, "return 5". + EXPECT_EQ(Errors[0].Notes[0].FileOffset, 10U); +} + TEST(TransformerClangTidyCheckTest, DiagnosticMessageEscaped) { class GiveDiagWithPercentSymbol : public TransformerClangTidyCheck { public: diff --git a/clang/include/clang/Tooling/Transformer/RewriteRule.h b/clang/include/clang/Tooling/Transformer/RewriteRule.h --- a/clang/include/clang/Tooling/Transformer/RewriteRule.h +++ b/clang/include/clang/Tooling/Transformer/RewriteRule.h @@ -46,6 +46,7 @@ EditKind Kind = EditKind::Range; CharSourceRange Range; std::string Replacement; + std::string Note; llvm::Any Metadata; }; @@ -138,6 +139,10 @@ /// diagnostic `Explanation` with the rule. EditGenerator noopEdit(RangeSelector Anchor); +/// Generates a single, no-op edit with the associated note anchored at the +/// start location of the specified range. +ASTEdit note(RangeSelector Anchor, TextGenerator Note); + /// Version of `ifBound` specialized to `ASTEdit`. inline EditGenerator ifBound(std::string ID, ASTEdit TrueEdit, ASTEdit FalseEdit) { diff --git a/clang/lib/Tooling/Transformer/RewriteRule.cpp b/clang/lib/Tooling/Transformer/RewriteRule.cpp --- a/clang/lib/Tooling/Transformer/RewriteRule.cpp +++ b/clang/lib/Tooling/Transformer/RewriteRule.cpp @@ -59,6 +59,12 @@ return Replacement.takeError(); T.Replacement = std::move(*Replacement); } + if (E.Note) { + auto Note = E.Note->eval(Result); + if (!Note) + return Note.takeError(); + T.Note = std::move(*Note); + } if (E.Metadata) { auto Metadata = E.Metadata(Result); if (!Metadata) @@ -125,6 +131,13 @@ return E; } +ASTEdit transformer::note(RangeSelector Anchor, TextGenerator Note) { + ASTEdit E; + E.TargetRange = transformer::before(Anchor); + E.Note = std::move(Note); + return E; +} + namespace { /// A \c TextGenerator that always returns a fixed string. class SimpleTextGenerator : public MatchComputation {