diff --git a/clang/include/clang/Tooling/Refactoring/AtomicChange.h b/clang/include/clang/Tooling/Refactoring/AtomicChange.h --- a/clang/include/clang/Tooling/Refactoring/AtomicChange.h +++ b/clang/include/clang/Tooling/Refactoring/AtomicChange.h @@ -17,6 +17,7 @@ #include "clang/Basic/SourceManager.h" #include "clang/Format/Format.h" #include "clang/Tooling/Core/Replacement.h" +#include "llvm/ADT/Any.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/Error.h" @@ -41,6 +42,9 @@ /// is being changed, e.g. the call to a refactored method. AtomicChange(const SourceManager &SM, SourceLocation KeyPosition); + AtomicChange(const SourceManager &SM, SourceLocation KeyPosition, + llvm::Any Metadata); + /// Creates an atomic change for \p FilePath with a customized key. AtomicChange(llvm::StringRef FilePath, llvm::StringRef Key) : Key(Key), FilePath(FilePath) {} @@ -120,6 +124,8 @@ return RemovedHeaders; } + const llvm::Any &getMetadata() const { return Metadata; } + private: AtomicChange() {} @@ -135,6 +141,12 @@ std::vector InsertedHeaders; std::vector RemovedHeaders; tooling::Replacements Replaces; + + // This field stores metadata which is ignored for the purposes of applying + // edits to source, but may be useful for other consumers of AtomicChanges. In + // particular, consumers can use this to direct how they want to consume each + // edit. + llvm::Any Metadata; }; using AtomicChanges = std::vector; 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 @@ -35,6 +35,7 @@ struct Edit { CharSourceRange Range; std::string Replacement; + llvm::Any Metadata; }; /// Maps a match result to a list of concrete edits (with possible @@ -85,6 +86,7 @@ RangeSelector TargetRange; TextGenerator Replacement; TextGenerator Note; + llvm::Any Metadata; }; /// Lifts a list of `ASTEdit`s into an `EditGenerator`. @@ -258,6 +260,11 @@ /// Removes the source selected by \p S. ASTEdit remove(RangeSelector S); +inline ASTEdit withMetadata(ASTEdit edit, llvm::Any Metadata) { + edit.Metadata = std::move(Metadata); + return edit; +} + /// The following three functions are a low-level part of the RewriteRule /// API. We expose them for use in implementing the fixtures that interpret /// RewriteRule, like Transformer and TransfomerTidy, or for more advanced diff --git a/clang/lib/Tooling/Refactoring/AtomicChange.cpp b/clang/lib/Tooling/Refactoring/AtomicChange.cpp --- a/clang/lib/Tooling/Refactoring/AtomicChange.cpp +++ b/clang/lib/Tooling/Refactoring/AtomicChange.cpp @@ -204,6 +204,12 @@ Key = FilePath + ":" + std::to_string(FileIDAndOffset.second); } +AtomicChange::AtomicChange(const SourceManager &SM, SourceLocation KeyPosition, + llvm::Any M) + : AtomicChange(SM, KeyPosition) { + Metadata = std::move(M); +} + AtomicChange::AtomicChange(std::string Key, std::string FilePath, std::string Error, std::vector InsertedHeaders, 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 @@ -47,6 +47,7 @@ transformer::Edit T; T.Range = *EditRange; T.Replacement = std::move(*Replacement); + T.Metadata = E.Metadata; Edits.push_back(std::move(T)); } return Edits; diff --git a/clang/lib/Tooling/Transformer/Transformer.cpp b/clang/lib/Tooling/Transformer/Transformer.cpp --- a/clang/lib/Tooling/Transformer/Transformer.cpp +++ b/clang/lib/Tooling/Transformer/Transformer.cpp @@ -53,7 +53,7 @@ auto ID = Result.SourceManager->getFileID(T.Range.getBegin()); auto Iter = ChangesByFileID .emplace(ID, AtomicChange(*Result.SourceManager, - T.Range.getBegin())) + T.Range.getBegin(), T.Metadata)) .first; auto &AC = Iter->second; if (auto Err = AC.replace(*Result.SourceManager, T.Range, T.Replacement)) { diff --git a/clang/unittests/Tooling/RefactoringTest.cpp b/clang/unittests/Tooling/RefactoringTest.cpp --- a/clang/unittests/Tooling/RefactoringTest.cpp +++ b/clang/unittests/Tooling/RefactoringTest.cpp @@ -1296,6 +1296,22 @@ Replacement(Context.Sources, SourceLocation(), 0, "b"))); } +struct SomeMetadata { + int Data; +}; + +TEST_F(AtomicChangeTest, Metadata) { + AtomicChange Change(Context.Sources, DefaultLoc, SomeMetadata{17}); + const llvm::Any &Metadata = Change.getMetadata(); + ASSERT_TRUE(llvm::any_isa(Metadata)); + EXPECT_EQ(llvm::any_cast(Metadata).Data, 17); +} + +TEST_F(AtomicChangeTest, NoMetadata) { + AtomicChange Change(Context.Sources, DefaultLoc); + EXPECT_FALSE(Change.getMetadata().hasValue()); +} + class ApplyAtomicChangesTest : public ::testing::Test { protected: ApplyAtomicChangesTest() : FilePath("file.cc") { diff --git a/clang/unittests/Tooling/TransformerTest.cpp b/clang/unittests/Tooling/TransformerTest.cpp --- a/clang/unittests/Tooling/TransformerTest.cpp +++ b/clang/unittests/Tooling/TransformerTest.cpp @@ -439,6 +439,29 @@ Input, Expected); } +TEST_F(TransformerTest, WithMetadata) { + std::string Input = R"cc( + int f() { + int x = 5; + return 7; + } + )cc"; + + Transformer T( + makeRule(declStmt().bind("decl"), + withMetadata(remove(statement(std::string("decl"))), 17)), + consumer()); + T.registerMatchers(&MatchFinder); + auto Factory = newFrontendActionFactory(&MatchFinder); + EXPECT_TRUE(runToolOnCodeWithArgs( + Factory->create(), Input, std::vector(), "input.cc", + "clang-tool", std::make_shared(), {})); + ASSERT_EQ(Changes.size(), 1); + const llvm::Any &Metadata = Changes[0].getMetadata(); + ASSERT_TRUE(llvm::any_isa(Metadata)); + EXPECT_THAT(llvm::any_cast(Metadata), 17); +} + TEST_F(TransformerTest, MultiChange) { std::string Input = R"cc( void foo() {