diff --git a/clang/include/clang/Tooling/Refactoring/AtomicChange.h b/clang/include/clang/Tooling/Refactoring/AtomicChange.h index 7cb9987e80c7b4..f1034a3d057941 100644 --- 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 @@ class AtomicChange { /// 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 @@ class AtomicChange { return RemovedHeaders; } + const llvm::Any &getMetadata() const { return Metadata; } + private: AtomicChange() {} @@ -135,6 +141,12 @@ class AtomicChange { 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 index 0a961ccc475df8..d9e68717d5c8fd 100644 --- a/clang/include/clang/Tooling/Transformer/RewriteRule.h +++ b/clang/include/clang/Tooling/Transformer/RewriteRule.h @@ -21,6 +21,7 @@ #include "clang/Tooling/Refactoring/AtomicChange.h" #include "clang/Tooling/Transformer/MatchConsumer.h" #include "clang/Tooling/Transformer/RangeSelector.h" +#include "llvm/ADT/Any.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallVector.h" #include "llvm/Support/Error.h" @@ -35,6 +36,7 @@ namespace transformer { struct Edit { CharSourceRange Range; std::string Replacement; + llvm::Any Metadata; }; /// Maps a match result to a list of concrete edits (with possible @@ -85,6 +87,7 @@ struct ASTEdit { RangeSelector TargetRange; TextGenerator Replacement; TextGenerator Note; + llvm::Any Metadata; }; /// Lifts a list of `ASTEdit`s into an `EditGenerator`. @@ -258,6 +261,11 @@ inline ASTEdit insertAfter(RangeSelector S, TextGenerator Replacement) { /// 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 index 3be15b7d8509f5..069e9c1eb36ef9 100644 --- a/clang/lib/Tooling/Refactoring/AtomicChange.cpp +++ b/clang/lib/Tooling/Refactoring/AtomicChange.cpp @@ -204,6 +204,12 @@ AtomicChange::AtomicChange(const SourceManager &SM, 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 index ddce6ce5918515..995bec03cd6690 100644 --- a/clang/lib/Tooling/Transformer/RewriteRule.cpp +++ b/clang/lib/Tooling/Transformer/RewriteRule.cpp @@ -47,6 +47,7 @@ translateEdits(const MatchResult &Result, ArrayRef ASTEdits) { 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 index 71340bf2f676d6..e8fc00c4e953f2 100644 --- a/clang/lib/Tooling/Transformer/Transformer.cpp +++ b/clang/lib/Tooling/Transformer/Transformer.cpp @@ -53,7 +53,7 @@ void Transformer::run(const MatchFinder::MatchResult &Result) { 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 index d65c6dba77b24e..97a26a71deec7f 100644 --- a/clang/unittests/Tooling/RefactoringTest.cpp +++ b/clang/unittests/Tooling/RefactoringTest.cpp @@ -1296,6 +1296,18 @@ TEST_F(AtomicChangeTest, InsertAfterWithInvalidLocation) { Replacement(Context.Sources, SourceLocation(), 0, "b"))); } +TEST_F(AtomicChangeTest, Metadata) { + AtomicChange Change(Context.Sources, DefaultLoc, 17); + const llvm::Any &Metadata = Change.getMetadata(); + ASSERT_TRUE(llvm::any_isa(Metadata)); + EXPECT_EQ(llvm::any_cast(Metadata), 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 index d19f747a69b57e..7d6b6329374879 100644 --- a/clang/unittests/Tooling/TransformerTest.cpp +++ b/clang/unittests/Tooling/TransformerTest.cpp @@ -439,6 +439,29 @@ TEST_F(TransformerTest, RemoveEdit) { 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(), 1u); + 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() {