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 @@ -31,6 +31,7 @@ using transformer::RewriteRuleWith; using transformer::RootID; using transformer::statement; +using transformer::withNote; // Invert the code of an if-statement, while maintaining its semantics. RewriteRuleWith invertIf() { @@ -85,11 +86,33 @@ 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); } +TEST(TransformerClangTidyCheckTest, NotesCorrectlyGenerated) { + class DiagAndNoteCheck : public TransformerClangTidyCheck { + public: + DiagAndNoteCheck(StringRef Name, ClangTidyContext *Context) + : TransformerClangTidyCheck( + makeRule(returnStmt(), + withNote(noopEdit(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; }; @@ -246,6 +247,14 @@ return Edit; } +// Adds a note to the given edit or edits. If there are several edits, the note +// is added to each one of them. +// \code +// withNote(noopEdit(), cat("some note")) +// \endcode +EditGenerator withNote(EditGenerator Generator, TextGenerator Note); +ASTEdit withNote(ASTEdit Edit, TextGenerator Note); + /// Assuming that the inner range is enclosed by the outer range, creates /// precision edits to remove the parts of the outer range that are not included /// in the inner range. 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 @@ -50,17 +50,26 @@ // produces a bad range, whereas the latter will simply ignore A. if (!EditRange) return SmallVector(); - auto Replacement = E.Replacement->eval(Result); - if (!Replacement) - return Replacement.takeError(); - auto Metadata = E.Metadata(Result); - if (!Metadata) - return Metadata.takeError(); transformer::Edit T; T.Kind = E.Kind; T.Range = *EditRange; - T.Replacement = std::move(*Replacement); - T.Metadata = std::move(*Metadata); + if (E.Replacement) { + auto Replacement = E.Replacement->eval(Result); + if (!Replacement) + 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 (auto Metadata = E.Metadata(Result)) { + if (!Metadata) + return Metadata.takeError(); + T.Metadata = std::move(*Metadata); + } Edits.push_back(std::move(T)); } return Edits; @@ -121,6 +130,29 @@ return E; } +ASTEdit transformer::withNote(ASTEdit Edit, TextGenerator Note) { + Edit.Note = std::move(Note); + return Edit; +} + +EditGenerator transformer::withNote(EditGenerator Generator, + TextGenerator Note) { + return + [G = std::move(Generator), N = std::move(Note)]( + const MatchResult &Result) -> llvm::Expected> { + llvm::Expected> Edits = G(Result); + if (!Edits) + return Edits.takeError(); + llvm::Expected Note = N->eval(Result); + if (!Note) + return Note.takeError(); + for (Edit &E : *Edits) { + E.Note = *Note; + } + return Edits; + }; +} + namespace { /// A \c TextGenerator that always returns a fixed string. class SimpleTextGenerator : public MatchComputation {