|  | //===- unittest/Tooling/TransformerTest.cpp -------------------------------===// | 
|  | // | 
|  | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | 
|  | // See https://llvm.org/LICENSE.txt for license information. | 
|  | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | 
|  | // | 
|  | //===----------------------------------------------------------------------===// | 
|  |  | 
|  | #include "clang/Tooling/Transformer/Transformer.h" | 
|  | #include "clang/ASTMatchers/ASTMatchers.h" | 
|  | #include "clang/Tooling/Tooling.h" | 
|  | #include "clang/Tooling/Transformer/RangeSelector.h" | 
|  | #include "clang/Tooling/Transformer/RewriteRule.h" | 
|  | #include "clang/Tooling/Transformer/Stencil.h" | 
|  | #include "llvm/ADT/STLExtras.h" | 
|  | #include "llvm/Support/Errc.h" | 
|  | #include "llvm/Support/Error.h" | 
|  | #include "gmock/gmock.h" | 
|  | #include "gtest/gtest.h" | 
|  | #include <optional> | 
|  |  | 
|  | using namespace clang; | 
|  | using namespace tooling; | 
|  | using namespace ast_matchers; | 
|  | namespace { | 
|  | using ::clang::transformer::addInclude; | 
|  | using ::clang::transformer::applyFirst; | 
|  | using ::clang::transformer::before; | 
|  | using ::clang::transformer::cat; | 
|  | using ::clang::transformer::changeTo; | 
|  | using ::clang::transformer::editList; | 
|  | using ::clang::transformer::makeRule; | 
|  | using ::clang::transformer::member; | 
|  | using ::clang::transformer::name; | 
|  | using ::clang::transformer::node; | 
|  | using ::clang::transformer::noEdits; | 
|  | using ::clang::transformer::remove; | 
|  | using ::clang::transformer::rewriteDescendants; | 
|  | using ::clang::transformer::RewriteRule; | 
|  | using ::clang::transformer::RewriteRuleWith; | 
|  | using ::clang::transformer::statement; | 
|  | using ::testing::ElementsAre; | 
|  | using ::testing::IsEmpty; | 
|  | using ::testing::ResultOf; | 
|  | using ::testing::UnorderedElementsAre; | 
|  |  | 
|  | constexpr char KHeaderContents[] = R"cc( | 
|  | struct string { | 
|  | string(const char*); | 
|  | char* c_str(); | 
|  | int size(); | 
|  | }; | 
|  | int strlen(const char*); | 
|  |  | 
|  | namespace proto { | 
|  | struct PCFProto { | 
|  | int foo(); | 
|  | }; | 
|  | struct ProtoCommandLineFlag : PCFProto { | 
|  | PCFProto& GetProto(); | 
|  | }; | 
|  | }  // namespace proto | 
|  | class Logger {}; | 
|  | void operator<<(Logger& l, string msg); | 
|  | Logger& log(int level); | 
|  | )cc"; | 
|  |  | 
|  | static ast_matchers::internal::Matcher<clang::QualType> | 
|  | isOrPointsTo(const clang::ast_matchers::DeclarationMatcher &TypeMatcher) { | 
|  | return anyOf(hasDeclaration(TypeMatcher), pointsTo(TypeMatcher)); | 
|  | } | 
|  |  | 
|  | static std::string format(StringRef Code) { | 
|  | const std::vector<Range> Ranges(1, Range(0, Code.size())); | 
|  | auto Style = format::getLLVMStyle(); | 
|  | const auto Replacements = format::reformat(Style, Code, Ranges); | 
|  | auto Formatted = applyAllReplacements(Code, Replacements); | 
|  | if (!Formatted) { | 
|  | ADD_FAILURE() << "Could not format code: " | 
|  | << llvm::toString(Formatted.takeError()); | 
|  | return std::string(); | 
|  | } | 
|  | return *Formatted; | 
|  | } | 
|  |  | 
|  | static void compareSnippets(StringRef Expected, | 
|  | const std::optional<std::string> &MaybeActual) { | 
|  | ASSERT_TRUE(MaybeActual) << "Rewrite failed. Expecting: " << Expected; | 
|  | auto Actual = *MaybeActual; | 
|  | std::string HL = "#include \"header.h\"\n"; | 
|  | auto I = Actual.find(HL); | 
|  | if (I != std::string::npos) | 
|  | Actual.erase(I, HL.size()); | 
|  | EXPECT_EQ(format(Expected), format(Actual)); | 
|  | } | 
|  |  | 
|  | // FIXME: consider separating this class into its own file(s). | 
|  | class ClangRefactoringTestBase : public testing::Test { | 
|  | protected: | 
|  | void appendToHeader(StringRef S) { FileContents[0].second += S; } | 
|  |  | 
|  | void addFile(StringRef Filename, StringRef Content) { | 
|  | FileContents.emplace_back(std::string(Filename), std::string(Content)); | 
|  | } | 
|  |  | 
|  | std::optional<std::string> rewrite(StringRef Input) { | 
|  | std::string Code = ("#include \"header.h\"\n" + Input).str(); | 
|  | auto Factory = newFrontendActionFactory(&MatchFinder); | 
|  | if (!runToolOnCodeWithArgs( | 
|  | Factory->create(), Code, std::vector<std::string>(), "input.cc", | 
|  | "clang-tool", std::make_shared<PCHContainerOperations>(), | 
|  | FileContents)) { | 
|  | llvm::errs() << "Running tool failed.\n"; | 
|  | return std::nullopt; | 
|  | } | 
|  | if (ErrorCount != 0) { | 
|  | llvm::errs() << "Generating changes failed.\n"; | 
|  | return std::nullopt; | 
|  | } | 
|  | auto ChangedCode = | 
|  | applyAtomicChanges("input.cc", Code, Changes, ApplyChangesSpec()); | 
|  | if (!ChangedCode) { | 
|  | llvm::errs() << "Applying changes failed: " | 
|  | << llvm::toString(ChangedCode.takeError()) << "\n"; | 
|  | return std::nullopt; | 
|  | } | 
|  | return *ChangedCode; | 
|  | } | 
|  |  | 
|  | Transformer::ChangeSetConsumer consumer() { | 
|  | return [this](Expected<MutableArrayRef<AtomicChange>> C) { | 
|  | if (C) { | 
|  | Changes.insert(Changes.end(), std::make_move_iterator(C->begin()), | 
|  | std::make_move_iterator(C->end())); | 
|  | } else { | 
|  | // FIXME: stash this error rather than printing. | 
|  | llvm::errs() << "Error generating changes: " | 
|  | << llvm::toString(C.takeError()) << "\n"; | 
|  | ++ErrorCount; | 
|  | } | 
|  | }; | 
|  | } | 
|  |  | 
|  | auto consumerWithStringMetadata() { | 
|  | return [this](Expected<TransformerResult<std::string>> C) { | 
|  | if (C) { | 
|  | Changes.insert(Changes.end(), | 
|  | std::make_move_iterator(C->Changes.begin()), | 
|  | std::make_move_iterator(C->Changes.end())); | 
|  | StringMetadata.push_back(std::move(C->Metadata)); | 
|  | } else { | 
|  | // FIXME: stash this error rather than printing. | 
|  | llvm::errs() << "Error generating changes: " | 
|  | << llvm::toString(C.takeError()) << "\n"; | 
|  | ++ErrorCount; | 
|  | } | 
|  | }; | 
|  | } | 
|  |  | 
|  | void testRule(RewriteRule Rule, StringRef Input, StringRef Expected) { | 
|  | Transformers.push_back( | 
|  | std::make_unique<Transformer>(std::move(Rule), consumer())); | 
|  | Transformers.back()->registerMatchers(&MatchFinder); | 
|  | compareSnippets(Expected, rewrite(Input)); | 
|  | } | 
|  |  | 
|  | void testRule(RewriteRuleWith<std::string> Rule, StringRef Input, | 
|  | StringRef Expected) { | 
|  | Transformers.push_back(std::make_unique<Transformer>( | 
|  | std::move(Rule), consumerWithStringMetadata())); | 
|  | Transformers.back()->registerMatchers(&MatchFinder); | 
|  | compareSnippets(Expected, rewrite(Input)); | 
|  | } | 
|  |  | 
|  | void testRuleFailure(RewriteRule Rule, StringRef Input) { | 
|  | Transformers.push_back( | 
|  | std::make_unique<Transformer>(std::move(Rule), consumer())); | 
|  | Transformers.back()->registerMatchers(&MatchFinder); | 
|  | ASSERT_FALSE(rewrite(Input)) << "Expected failure to rewrite code"; | 
|  | } | 
|  |  | 
|  | void testRuleFailure(RewriteRuleWith<std::string> Rule, StringRef Input) { | 
|  | Transformers.push_back(std::make_unique<Transformer>( | 
|  | std::move(Rule), consumerWithStringMetadata())); | 
|  | Transformers.back()->registerMatchers(&MatchFinder); | 
|  | ASSERT_FALSE(rewrite(Input)) << "Expected failure to rewrite code"; | 
|  | } | 
|  |  | 
|  | // Transformers are referenced by MatchFinder. | 
|  | std::vector<std::unique_ptr<Transformer>> Transformers; | 
|  | clang::ast_matchers::MatchFinder MatchFinder; | 
|  | // Records whether any errors occurred in individual changes. | 
|  | int ErrorCount = 0; | 
|  | AtomicChanges Changes; | 
|  | std::vector<std::string> StringMetadata; | 
|  |  | 
|  | private: | 
|  | FileContentMappings FileContents = {{"header.h", ""}}; | 
|  | }; | 
|  |  | 
|  | class TransformerTest : public ClangRefactoringTestBase { | 
|  | protected: | 
|  | TransformerTest() { appendToHeader(KHeaderContents); } | 
|  | }; | 
|  |  | 
|  | // Given string s, change strlen($s.c_str()) to REPLACED. | 
|  | static RewriteRuleWith<std::string> ruleStrlenSize() { | 
|  | StringRef StringExpr = "strexpr"; | 
|  | auto StringType = namedDecl(hasAnyName("::basic_string", "::string")); | 
|  | auto R = makeRule( | 
|  | callExpr(callee(functionDecl(hasName("strlen"))), | 
|  | hasArgument(0, cxxMemberCallExpr( | 
|  | on(expr(hasType(isOrPointsTo(StringType))) | 
|  | .bind(StringExpr)), | 
|  | callee(cxxMethodDecl(hasName("c_str")))))), | 
|  | changeTo(cat("REPLACED")), cat("Use size() method directly on string.")); | 
|  | return R; | 
|  | } | 
|  |  | 
|  | TEST_F(TransformerTest, StrlenSize) { | 
|  | std::string Input = "int f(string s) { return strlen(s.c_str()); }"; | 
|  | std::string Expected = "int f(string s) { return REPLACED; }"; | 
|  | testRule(ruleStrlenSize(), Input, Expected); | 
|  | } | 
|  |  | 
|  | // Tests that no change is applied when a match is not expected. | 
|  | TEST_F(TransformerTest, NoMatch) { | 
|  | std::string Input = "int f(string s) { return s.size(); }"; | 
|  | testRule(ruleStrlenSize(), Input, Input); | 
|  | } | 
|  |  | 
|  | // Tests replacing an expression. | 
|  | TEST_F(TransformerTest, Flag) { | 
|  | StringRef Flag = "flag"; | 
|  | RewriteRule Rule = makeRule( | 
|  | cxxMemberCallExpr(on(expr(hasType(cxxRecordDecl( | 
|  | hasName("proto::ProtoCommandLineFlag")))) | 
|  | .bind(Flag)), | 
|  | unless(callee(cxxMethodDecl(hasName("GetProto"))))), | 
|  | changeTo(node(std::string(Flag)), cat("EXPR"))); | 
|  |  | 
|  | std::string Input = R"cc( | 
|  | proto::ProtoCommandLineFlag flag; | 
|  | int x = flag.foo(); | 
|  | int y = flag.GetProto().foo(); | 
|  | )cc"; | 
|  | std::string Expected = R"cc( | 
|  | proto::ProtoCommandLineFlag flag; | 
|  | int x = EXPR.foo(); | 
|  | int y = flag.GetProto().foo(); | 
|  | )cc"; | 
|  |  | 
|  | testRule(std::move(Rule), Input, Expected); | 
|  | } | 
|  |  | 
|  | TEST_F(TransformerTest, AddIncludeQuoted) { | 
|  | RewriteRule Rule = | 
|  | makeRule(callExpr(callee(functionDecl(hasName("f")))), | 
|  | {addInclude("clang/OtherLib.h"), changeTo(cat("other()"))}); | 
|  |  | 
|  | std::string Input = R"cc( | 
|  | int f(int x); | 
|  | int h(int x) { return f(x); } | 
|  | )cc"; | 
|  | std::string Expected = R"cc(#include "clang/OtherLib.h" | 
|  |  | 
|  | int f(int x); | 
|  | int h(int x) { return other(); } | 
|  | )cc"; | 
|  |  | 
|  | testRule(Rule, Input, Expected); | 
|  | } | 
|  |  | 
|  | TEST_F(TransformerTest, AddIncludeAngled) { | 
|  | RewriteRule Rule = makeRule( | 
|  | callExpr(callee(functionDecl(hasName("f")))), | 
|  | {addInclude("clang/OtherLib.h", transformer::IncludeFormat::Angled), | 
|  | changeTo(cat("other()"))}); | 
|  |  | 
|  | std::string Input = R"cc( | 
|  | int f(int x); | 
|  | int h(int x) { return f(x); } | 
|  | )cc"; | 
|  | std::string Expected = R"cc(#include <clang/OtherLib.h> | 
|  |  | 
|  | int f(int x); | 
|  | int h(int x) { return other(); } | 
|  | )cc"; | 
|  |  | 
|  | testRule(Rule, Input, Expected); | 
|  | } | 
|  |  | 
|  | TEST_F(TransformerTest, AddIncludeQuotedForRule) { | 
|  | RewriteRule Rule = makeRule(callExpr(callee(functionDecl(hasName("f")))), | 
|  | changeTo(cat("other()"))); | 
|  | addInclude(Rule, "clang/OtherLib.h"); | 
|  |  | 
|  | std::string Input = R"cc( | 
|  | int f(int x); | 
|  | int h(int x) { return f(x); } | 
|  | )cc"; | 
|  | std::string Expected = R"cc(#include "clang/OtherLib.h" | 
|  |  | 
|  | int f(int x); | 
|  | int h(int x) { return other(); } | 
|  | )cc"; | 
|  |  | 
|  | testRule(Rule, Input, Expected); | 
|  | } | 
|  |  | 
|  | TEST_F(TransformerTest, AddIncludeAngledForRule) { | 
|  | RewriteRule Rule = makeRule(callExpr(callee(functionDecl(hasName("f")))), | 
|  | changeTo(cat("other()"))); | 
|  | addInclude(Rule, "clang/OtherLib.h", transformer::IncludeFormat::Angled); | 
|  |  | 
|  | std::string Input = R"cc( | 
|  | int f(int x); | 
|  | int h(int x) { return f(x); } | 
|  | )cc"; | 
|  | std::string Expected = R"cc(#include <clang/OtherLib.h> | 
|  |  | 
|  | int f(int x); | 
|  | int h(int x) { return other(); } | 
|  | )cc"; | 
|  |  | 
|  | testRule(Rule, Input, Expected); | 
|  | } | 
|  |  | 
|  | TEST_F(TransformerTest, NodePartNameNamedDecl) { | 
|  | StringRef Fun = "fun"; | 
|  | RewriteRule Rule = makeRule(functionDecl(hasName("bad")).bind(Fun), | 
|  | changeTo(name(std::string(Fun)), cat("good"))); | 
|  |  | 
|  | std::string Input = R"cc( | 
|  | int bad(int x); | 
|  | int bad(int x) { return x * x; } | 
|  | )cc"; | 
|  | std::string Expected = R"cc( | 
|  | int good(int x); | 
|  | int good(int x) { return x * x; } | 
|  | )cc"; | 
|  |  | 
|  | testRule(Rule, Input, Expected); | 
|  | } | 
|  |  | 
|  | TEST_F(TransformerTest, NodePartNameDeclRef) { | 
|  | std::string Input = R"cc( | 
|  | template <typename T> | 
|  | T bad(T x) { | 
|  | return x; | 
|  | } | 
|  | int neutral(int x) { return bad<int>(x) * x; } | 
|  | )cc"; | 
|  | std::string Expected = R"cc( | 
|  | template <typename T> | 
|  | T bad(T x) { | 
|  | return x; | 
|  | } | 
|  | int neutral(int x) { return good<int>(x) * x; } | 
|  | )cc"; | 
|  |  | 
|  | StringRef Ref = "ref"; | 
|  | testRule(makeRule(declRefExpr(to(functionDecl(hasName("bad")))).bind(Ref), | 
|  | changeTo(name(std::string(Ref)), cat("good"))), | 
|  | Input, Expected); | 
|  | } | 
|  |  | 
|  | TEST_F(TransformerTest, NodePartNameDeclRefFailure) { | 
|  | std::string Input = R"cc( | 
|  | struct Y { | 
|  | int operator*(); | 
|  | }; | 
|  | int neutral(int x) { | 
|  | Y y; | 
|  | int (Y::*ptr)() = &Y::operator*; | 
|  | return *y + x; | 
|  | } | 
|  | )cc"; | 
|  |  | 
|  | StringRef Ref = "ref"; | 
|  | Transformer T(makeRule(declRefExpr(to(functionDecl())).bind(Ref), | 
|  | changeTo(name(std::string(Ref)), cat("good"))), | 
|  | consumer()); | 
|  | T.registerMatchers(&MatchFinder); | 
|  | EXPECT_FALSE(rewrite(Input)); | 
|  | } | 
|  |  | 
|  | TEST_F(TransformerTest, NodePartMember) { | 
|  | StringRef E = "expr"; | 
|  | RewriteRule Rule = | 
|  | makeRule(memberExpr(clang::ast_matchers::member(hasName("bad"))).bind(E), | 
|  | changeTo(member(std::string(E)), cat("good"))); | 
|  |  | 
|  | std::string Input = R"cc( | 
|  | struct S { | 
|  | int bad; | 
|  | }; | 
|  | int g() { | 
|  | S s; | 
|  | return s.bad; | 
|  | } | 
|  | )cc"; | 
|  | std::string Expected = R"cc( | 
|  | struct S { | 
|  | int bad; | 
|  | }; | 
|  | int g() { | 
|  | S s; | 
|  | return s.good; | 
|  | } | 
|  | )cc"; | 
|  |  | 
|  | testRule(Rule, Input, Expected); | 
|  | } | 
|  |  | 
|  | TEST_F(TransformerTest, NodePartMemberQualified) { | 
|  | std::string Input = R"cc( | 
|  | struct S { | 
|  | int bad; | 
|  | int good; | 
|  | }; | 
|  | struct T : public S { | 
|  | int bad; | 
|  | }; | 
|  | int g() { | 
|  | T t; | 
|  | return t.S::bad; | 
|  | } | 
|  | )cc"; | 
|  | std::string Expected = R"cc( | 
|  | struct S { | 
|  | int bad; | 
|  | int good; | 
|  | }; | 
|  | struct T : public S { | 
|  | int bad; | 
|  | }; | 
|  | int g() { | 
|  | T t; | 
|  | return t.S::good; | 
|  | } | 
|  | )cc"; | 
|  |  | 
|  | StringRef E = "expr"; | 
|  | testRule(makeRule(memberExpr().bind(E), | 
|  | changeTo(member(std::string(E)), cat("good"))), | 
|  | Input, Expected); | 
|  | } | 
|  |  | 
|  | TEST_F(TransformerTest, NodePartMemberMultiToken) { | 
|  | std::string Input = R"cc( | 
|  | struct Y { | 
|  | int operator*(); | 
|  | int good(); | 
|  | template <typename T> void foo(T t); | 
|  | }; | 
|  | int neutral(int x) { | 
|  | Y y; | 
|  | y.template foo<int>(3); | 
|  | return y.operator *(); | 
|  | } | 
|  | )cc"; | 
|  | std::string Expected = R"cc( | 
|  | struct Y { | 
|  | int operator*(); | 
|  | int good(); | 
|  | template <typename T> void foo(T t); | 
|  | }; | 
|  | int neutral(int x) { | 
|  | Y y; | 
|  | y.template good<int>(3); | 
|  | return y.good(); | 
|  | } | 
|  | )cc"; | 
|  |  | 
|  | StringRef MemExpr = "member"; | 
|  | testRule(makeRule(memberExpr().bind(MemExpr), | 
|  | changeTo(member(std::string(MemExpr)), cat("good"))), | 
|  | Input, Expected); | 
|  | } | 
|  |  | 
|  | TEST_F(TransformerTest, NoEdits) { | 
|  | using transformer::noEdits; | 
|  | std::string Input = "int f(int x) { return x; }"; | 
|  | testRule(makeRule(returnStmt().bind("return"), noEdits()), Input, Input); | 
|  | } | 
|  |  | 
|  | TEST_F(TransformerTest, NoopEdit) { | 
|  | using transformer::node; | 
|  | using transformer::noopEdit; | 
|  | std::string Input = "int f(int x) { return x; }"; | 
|  | testRule(makeRule(returnStmt().bind("return"), noopEdit(node("return"))), | 
|  | Input, Input); | 
|  | } | 
|  |  | 
|  | TEST_F(TransformerTest, IfBound2Args) { | 
|  | using transformer::ifBound; | 
|  | std::string Input = "int f(int x) { return x; }"; | 
|  | std::string Expected = "int f(int x) { CHANGE; }"; | 
|  | testRule(makeRule(returnStmt().bind("return"), | 
|  | ifBound("return", changeTo(cat("CHANGE;")))), | 
|  | Input, Expected); | 
|  | } | 
|  |  | 
|  | TEST_F(TransformerTest, IfBound3Args) { | 
|  | using transformer::ifBound; | 
|  | std::string Input = "int f(int x) { return x; }"; | 
|  | std::string Expected = "int f(int x) { CHANGE; }"; | 
|  | testRule(makeRule(returnStmt().bind("return"), | 
|  | ifBound("nothing", changeTo(cat("ERROR")), | 
|  | changeTo(cat("CHANGE;")))), | 
|  | Input, Expected); | 
|  | } | 
|  |  | 
|  | TEST_F(TransformerTest, ShrinkTo) { | 
|  | using transformer::shrinkTo; | 
|  | std::string Input = "int f(int x) { return x; }"; | 
|  | std::string Expected = "return x;"; | 
|  | testRule(makeRule(functionDecl(hasDescendant(returnStmt().bind("return"))) | 
|  | .bind("function"), | 
|  | shrinkTo(node("function"), node("return"))), | 
|  | Input, Expected); | 
|  | } | 
|  |  | 
|  | // Rewrite various Stmts inside a Decl. | 
|  | TEST_F(TransformerTest, RewriteDescendantsDeclChangeStmt) { | 
|  | std::string Input = | 
|  | "int f(int x) { int y = x; { int z = x * x; } return x; }"; | 
|  | std::string Expected = | 
|  | "int f(int x) { int y = 3; { int z = 3 * 3; } return 3; }"; | 
|  | auto InlineX = | 
|  | makeRule(declRefExpr(to(varDecl(hasName("x")))), changeTo(cat("3"))); | 
|  | testRule(makeRule(functionDecl(hasName("f")).bind("fun"), | 
|  | rewriteDescendants("fun", InlineX)), | 
|  | Input, Expected); | 
|  | } | 
|  |  | 
|  | // Rewrite various TypeLocs inside a Decl. | 
|  | TEST_F(TransformerTest, RewriteDescendantsDeclChangeTypeLoc) { | 
|  | std::string Input = "int f(int *x) { return *x; }"; | 
|  | std::string Expected = "char f(char *x) { return *x; }"; | 
|  | auto IntToChar = makeRule(typeLoc(loc(qualType(isInteger(), builtinType()))), | 
|  | changeTo(cat("char"))); | 
|  | testRule(makeRule(functionDecl(hasName("f")).bind("fun"), | 
|  | rewriteDescendants("fun", IntToChar)), | 
|  | Input, Expected); | 
|  | } | 
|  |  | 
|  | TEST_F(TransformerTest, RewriteDescendantsStmt) { | 
|  | // Add an unrelated definition to the header that also has a variable named | 
|  | // "x", to test that the rewrite is limited to the scope we intend. | 
|  | appendToHeader(R"cc(int g(int x) { return x; })cc"); | 
|  | std::string Input = | 
|  | "int f(int x) { int y = x; { int z = x * x; } return x; }"; | 
|  | std::string Expected = | 
|  | "int f(int x) { int y = 3; { int z = 3 * 3; } return 3; }"; | 
|  | auto InlineX = | 
|  | makeRule(declRefExpr(to(varDecl(hasName("x")))), changeTo(cat("3"))); | 
|  | testRule(makeRule(functionDecl(hasName("f"), hasBody(stmt().bind("body"))), | 
|  | rewriteDescendants("body", InlineX)), | 
|  | Input, Expected); | 
|  | } | 
|  |  | 
|  | TEST_F(TransformerTest, RewriteDescendantsStmtWithAdditionalChange) { | 
|  | std::string Input = | 
|  | "int f(int x) { int y = x; { int z = x * x; } return x; }"; | 
|  | std::string Expected = | 
|  | "int newName(int x) { int y = 3; { int z = 3 * 3; } return 3; }"; | 
|  | auto InlineX = | 
|  | makeRule(declRefExpr(to(varDecl(hasName("x")))), changeTo(cat("3"))); | 
|  | testRule( | 
|  | makeRule( | 
|  | functionDecl(hasName("f"), hasBody(stmt().bind("body"))).bind("f"), | 
|  | flatten(changeTo(name("f"), cat("newName")), | 
|  | rewriteDescendants("body", InlineX))), | 
|  | Input, Expected); | 
|  | } | 
|  |  | 
|  | TEST_F(TransformerTest, RewriteDescendantsTypeLoc) { | 
|  | std::string Input = "int f(int *x) { return *x; }"; | 
|  | std::string Expected = "int f(char *x) { return *x; }"; | 
|  | auto IntToChar = | 
|  | makeRule(typeLoc(loc(qualType(isInteger(), builtinType()))).bind("loc"), | 
|  | changeTo(cat("char"))); | 
|  | testRule( | 
|  | makeRule(functionDecl(hasName("f"), | 
|  | hasParameter(0, varDecl(hasTypeLoc( | 
|  | typeLoc().bind("parmType"))))), | 
|  | rewriteDescendants("parmType", IntToChar)), | 
|  | Input, Expected); | 
|  | } | 
|  |  | 
|  | TEST_F(TransformerTest, RewriteDescendantsReferToParentBinding) { | 
|  | std::string Input = | 
|  | "int f(int p) { int y = p; { int z = p * p; } return p; }"; | 
|  | std::string Expected = | 
|  | "int f(int p) { int y = 3; { int z = 3 * 3; } return 3; }"; | 
|  | std::string VarId = "var"; | 
|  | auto InlineVar = makeRule(declRefExpr(to(varDecl(equalsBoundNode(VarId)))), | 
|  | changeTo(cat("3"))); | 
|  | testRule(makeRule(functionDecl(hasName("f"), | 
|  | hasParameter(0, varDecl().bind(VarId))) | 
|  | .bind("fun"), | 
|  | rewriteDescendants("fun", InlineVar)), | 
|  | Input, Expected); | 
|  | } | 
|  |  | 
|  | TEST_F(TransformerTest, RewriteDescendantsUnboundNode) { | 
|  | std::string Input = | 
|  | "int f(int x) { int y = x; { int z = x * x; } return x; }"; | 
|  | auto InlineX = | 
|  | makeRule(declRefExpr(to(varDecl(hasName("x")))), changeTo(cat("3"))); | 
|  | Transformer T(makeRule(functionDecl(hasName("f")), | 
|  | rewriteDescendants("UNBOUND", InlineX)), | 
|  | consumer()); | 
|  | T.registerMatchers(&MatchFinder); | 
|  | EXPECT_FALSE(rewrite(Input)); | 
|  | EXPECT_THAT(Changes, IsEmpty()); | 
|  | EXPECT_EQ(ErrorCount, 1); | 
|  | } | 
|  |  | 
|  | TEST_F(TransformerTest, RewriteDescendantsInvalidNodeType) { | 
|  | std::string Input = | 
|  | "int f(int x) { int y = x; { int z = x * x; } return x; }"; | 
|  | auto IntToChar = | 
|  | makeRule(qualType(isInteger(), builtinType()), changeTo(cat("char"))); | 
|  | Transformer T( | 
|  | makeRule(functionDecl( | 
|  | hasName("f"), | 
|  | hasParameter(0, varDecl(hasType(qualType().bind("type"))))), | 
|  | rewriteDescendants("type", IntToChar)), | 
|  | consumer()); | 
|  | T.registerMatchers(&MatchFinder); | 
|  | EXPECT_FALSE(rewrite(Input)); | 
|  | EXPECT_THAT(Changes, IsEmpty()); | 
|  | EXPECT_EQ(ErrorCount, 1); | 
|  | } | 
|  |  | 
|  | // | 
|  | // We include one test per typed overload. We don't test extensively since that | 
|  | // is already covered by the tests above. | 
|  | // | 
|  |  | 
|  | TEST_F(TransformerTest, RewriteDescendantsTypedStmt) { | 
|  | // Add an unrelated definition to the header that also has a variable named | 
|  | // "x", to test that the rewrite is limited to the scope we intend. | 
|  | appendToHeader(R"cc(int g(int x) { return x; })cc"); | 
|  | std::string Input = | 
|  | "int f(int x) { int y = x; { int z = x * x; } return x; }"; | 
|  | std::string Expected = | 
|  | "int f(int x) { int y = 3; { int z = 3 * 3; } return 3; }"; | 
|  | auto InlineX = | 
|  | makeRule(declRefExpr(to(varDecl(hasName("x")))), changeTo(cat("3"))); | 
|  | testRule(makeRule(functionDecl(hasName("f"), hasBody(stmt().bind("body"))), | 
|  | [&InlineX](const MatchFinder::MatchResult &R) { | 
|  | const auto *Node = R.Nodes.getNodeAs<Stmt>("body"); | 
|  | assert(Node != nullptr && "body must be bound"); | 
|  | return transformer::detail::rewriteDescendants( | 
|  | *Node, InlineX, R); | 
|  | }), | 
|  | Input, Expected); | 
|  | } | 
|  |  | 
|  | TEST_F(TransformerTest, RewriteDescendantsTypedDecl) { | 
|  | std::string Input = | 
|  | "int f(int x) { int y = x; { int z = x * x; } return x; }"; | 
|  | std::string Expected = | 
|  | "int f(int x) { int y = 3; { int z = 3 * 3; } return 3; }"; | 
|  | auto InlineX = | 
|  | makeRule(declRefExpr(to(varDecl(hasName("x")))), changeTo(cat("3"))); | 
|  | testRule(makeRule(functionDecl(hasName("f")).bind("fun"), | 
|  | [&InlineX](const MatchFinder::MatchResult &R) { | 
|  | const auto *Node = R.Nodes.getNodeAs<Decl>("fun"); | 
|  | assert(Node != nullptr && "fun must be bound"); | 
|  | return transformer::detail::rewriteDescendants( | 
|  | *Node, InlineX, R); | 
|  | }), | 
|  | Input, Expected); | 
|  | } | 
|  |  | 
|  | TEST_F(TransformerTest, RewriteDescendantsTypedTypeLoc) { | 
|  | std::string Input = "int f(int *x) { return *x; }"; | 
|  | std::string Expected = "int f(char *x) { return *x; }"; | 
|  | auto IntToChar = | 
|  | makeRule(typeLoc(loc(qualType(isInteger(), builtinType()))).bind("loc"), | 
|  | changeTo(cat("char"))); | 
|  | testRule( | 
|  | makeRule( | 
|  | functionDecl( | 
|  | hasName("f"), | 
|  | hasParameter(0, varDecl(hasTypeLoc(typeLoc().bind("parmType"))))), | 
|  | [&IntToChar](const MatchFinder::MatchResult &R) { | 
|  | const auto *Node = R.Nodes.getNodeAs<TypeLoc>("parmType"); | 
|  | assert(Node != nullptr && "parmType must be bound"); | 
|  | return transformer::detail::rewriteDescendants(*Node, IntToChar, R); | 
|  | }), | 
|  | Input, Expected); | 
|  | } | 
|  |  | 
|  | TEST_F(TransformerTest, RewriteDescendantsTypedDynTyped) { | 
|  | // Add an unrelated definition to the header that also has a variable named | 
|  | // "x", to test that the rewrite is limited to the scope we intend. | 
|  | appendToHeader(R"cc(int g(int x) { return x; })cc"); | 
|  | std::string Input = | 
|  | "int f(int x) { int y = x; { int z = x * x; } return x; }"; | 
|  | std::string Expected = | 
|  | "int f(int x) { int y = 3; { int z = 3 * 3; } return 3; }"; | 
|  | auto InlineX = | 
|  | makeRule(declRefExpr(to(varDecl(hasName("x")))), changeTo(cat("3"))); | 
|  | testRule( | 
|  | makeRule(functionDecl(hasName("f"), hasBody(stmt().bind("body"))), | 
|  | [&InlineX](const MatchFinder::MatchResult &R) { | 
|  | auto It = R.Nodes.getMap().find("body"); | 
|  | assert(It != R.Nodes.getMap().end() && "body must be bound"); | 
|  | return transformer::detail::rewriteDescendants(It->second, | 
|  | InlineX, R); | 
|  | }), | 
|  | Input, Expected); | 
|  | } | 
|  |  | 
|  | TEST_F(TransformerTest, InsertBeforeEdit) { | 
|  | std::string Input = R"cc( | 
|  | int f() { | 
|  | return 7; | 
|  | } | 
|  | )cc"; | 
|  | std::string Expected = R"cc( | 
|  | int f() { | 
|  | int y = 3; | 
|  | return 7; | 
|  | } | 
|  | )cc"; | 
|  |  | 
|  | StringRef Ret = "return"; | 
|  | testRule( | 
|  | makeRule(returnStmt().bind(Ret), | 
|  | insertBefore(statement(std::string(Ret)), cat("int y = 3;"))), | 
|  | Input, Expected); | 
|  | } | 
|  |  | 
|  | TEST_F(TransformerTest, InsertAfterEdit) { | 
|  | std::string Input = R"cc( | 
|  | int f() { | 
|  | int x = 5; | 
|  | return 7; | 
|  | } | 
|  | )cc"; | 
|  | std::string Expected = R"cc( | 
|  | int f() { | 
|  | int x = 5; | 
|  | int y = 3; | 
|  | return 7; | 
|  | } | 
|  | )cc"; | 
|  |  | 
|  | StringRef Decl = "decl"; | 
|  | testRule( | 
|  | makeRule(declStmt().bind(Decl), | 
|  | insertAfter(statement(std::string(Decl)), cat("int y = 3;"))), | 
|  | Input, Expected); | 
|  | } | 
|  |  | 
|  | TEST_F(TransformerTest, RemoveEdit) { | 
|  | std::string Input = R"cc( | 
|  | int f() { | 
|  | int x = 5; | 
|  | return 7; | 
|  | } | 
|  | )cc"; | 
|  | std::string Expected = R"cc( | 
|  | int f() { | 
|  | return 7; | 
|  | } | 
|  | )cc"; | 
|  |  | 
|  | StringRef Decl = "decl"; | 
|  | testRule( | 
|  | makeRule(declStmt().bind(Decl), remove(statement(std::string(Decl)))), | 
|  | Input, Expected); | 
|  | } | 
|  |  | 
|  | TEST_F(TransformerTest, WithMetadata) { | 
|  | auto makeMetadata = [](const MatchFinder::MatchResult &R) -> llvm::Any { | 
|  | int N = | 
|  | R.Nodes.getNodeAs<IntegerLiteral>("int")->getValue().getLimitedValue(); | 
|  | return N; | 
|  | }; | 
|  |  | 
|  | std::string Input = R"cc( | 
|  | int f() { | 
|  | int x = 5; | 
|  | return 7; | 
|  | } | 
|  | )cc"; | 
|  |  | 
|  | Transformer T( | 
|  | makeRule( | 
|  | declStmt(containsDeclaration(0, varDecl(hasInitializer( | 
|  | integerLiteral().bind("int"))))) | 
|  | .bind("decl"), | 
|  | withMetadata(remove(statement(std::string("decl"))), makeMetadata)), | 
|  | consumer()); | 
|  | T.registerMatchers(&MatchFinder); | 
|  | auto Factory = newFrontendActionFactory(&MatchFinder); | 
|  | EXPECT_TRUE(runToolOnCodeWithArgs( | 
|  | Factory->create(), Input, std::vector<std::string>(), "input.cc", | 
|  | "clang-tool", std::make_shared<PCHContainerOperations>(), {})); | 
|  | ASSERT_EQ(Changes.size(), 1u); | 
|  | const llvm::Any &Metadata = Changes[0].getMetadata(); | 
|  | ASSERT_TRUE(llvm::any_cast<int>(&Metadata)); | 
|  | EXPECT_THAT(llvm::any_cast<int>(Metadata), 5); | 
|  | } | 
|  |  | 
|  | TEST_F(TransformerTest, MultiChange) { | 
|  | std::string Input = R"cc( | 
|  | void foo() { | 
|  | if (10 > 1.0) | 
|  | log(1) << "oh no!"; | 
|  | else | 
|  | log(0) << "ok"; | 
|  | } | 
|  | )cc"; | 
|  | std::string Expected = R"( | 
|  | void foo() { | 
|  | if (true) { /* then */ } | 
|  | else { /* else */ } | 
|  | } | 
|  | )"; | 
|  |  | 
|  | StringRef C = "C", T = "T", E = "E"; | 
|  | testRule( | 
|  | makeRule(ifStmt(hasCondition(expr().bind(C)), hasThen(stmt().bind(T)), | 
|  | hasElse(stmt().bind(E))), | 
|  | {changeTo(node(std::string(C)), cat("true")), | 
|  | changeTo(statement(std::string(T)), cat("{ /* then */ }")), | 
|  | changeTo(statement(std::string(E)), cat("{ /* else */ }"))}), | 
|  | Input, Expected); | 
|  | } | 
|  |  | 
|  | TEST_F(TransformerTest, EditList) { | 
|  | std::string Input = R"cc( | 
|  | void foo() { | 
|  | if (10 > 1.0) | 
|  | log(1) << "oh no!"; | 
|  | else | 
|  | log(0) << "ok"; | 
|  | } | 
|  | )cc"; | 
|  | std::string Expected = R"( | 
|  | void foo() { | 
|  | if (true) { /* then */ } | 
|  | else { /* else */ } | 
|  | } | 
|  | )"; | 
|  |  | 
|  | StringRef C = "C", T = "T", E = "E"; | 
|  | testRule(makeRule(ifStmt(hasCondition(expr().bind(C)), | 
|  | hasThen(stmt().bind(T)), hasElse(stmt().bind(E))), | 
|  | editList({changeTo(node(std::string(C)), cat("true")), | 
|  | changeTo(statement(std::string(T)), | 
|  | cat("{ /* then */ }")), | 
|  | changeTo(statement(std::string(E)), | 
|  | cat("{ /* else */ }"))})), | 
|  | Input, Expected); | 
|  | } | 
|  |  | 
|  | TEST_F(TransformerTest, Flatten) { | 
|  | std::string Input = R"cc( | 
|  | void foo() { | 
|  | if (10 > 1.0) | 
|  | log(1) << "oh no!"; | 
|  | else | 
|  | log(0) << "ok"; | 
|  | } | 
|  | )cc"; | 
|  | std::string Expected = R"( | 
|  | void foo() { | 
|  | if (true) { /* then */ } | 
|  | else { /* else */ } | 
|  | } | 
|  | )"; | 
|  |  | 
|  | StringRef C = "C", T = "T", E = "E"; | 
|  | testRule( | 
|  | makeRule( | 
|  | ifStmt(hasCondition(expr().bind(C)), hasThen(stmt().bind(T)), | 
|  | hasElse(stmt().bind(E))), | 
|  | flatten(changeTo(node(std::string(C)), cat("true")), | 
|  | changeTo(statement(std::string(T)), cat("{ /* then */ }")), | 
|  | changeTo(statement(std::string(E)), cat("{ /* else */ }")))), | 
|  | Input, Expected); | 
|  | } | 
|  |  | 
|  | TEST_F(TransformerTest, FlattenWithMixedArgs) { | 
|  | using clang::transformer::editList; | 
|  | std::string Input = R"cc( | 
|  | void foo() { | 
|  | if (10 > 1.0) | 
|  | log(1) << "oh no!"; | 
|  | else | 
|  | log(0) << "ok"; | 
|  | } | 
|  | )cc"; | 
|  | std::string Expected = R"( | 
|  | void foo() { | 
|  | if (true) { /* then */ } | 
|  | else { /* else */ } | 
|  | } | 
|  | )"; | 
|  |  | 
|  | StringRef C = "C", T = "T", E = "E"; | 
|  | testRule(makeRule(ifStmt(hasCondition(expr().bind(C)), | 
|  | hasThen(stmt().bind(T)), hasElse(stmt().bind(E))), | 
|  | flatten(changeTo(node(std::string(C)), cat("true")), | 
|  | edit(changeTo(statement(std::string(T)), | 
|  | cat("{ /* then */ }"))), | 
|  | editList({changeTo(statement(std::string(E)), | 
|  | cat("{ /* else */ }"))}))), | 
|  | Input, Expected); | 
|  | } | 
|  |  | 
|  | TEST_F(TransformerTest, OrderedRuleUnrelated) { | 
|  | StringRef Flag = "flag"; | 
|  | RewriteRuleWith<std::string> FlagRule = makeRule( | 
|  | cxxMemberCallExpr(on(expr(hasType(cxxRecordDecl( | 
|  | hasName("proto::ProtoCommandLineFlag")))) | 
|  | .bind(Flag)), | 
|  | unless(callee(cxxMethodDecl(hasName("GetProto"))))), | 
|  | changeTo(node(std::string(Flag)), cat("PROTO")), cat("")); | 
|  |  | 
|  | std::string Input = R"cc( | 
|  | proto::ProtoCommandLineFlag flag; | 
|  | int x = flag.foo(); | 
|  | int y = flag.GetProto().foo(); | 
|  | int f(string s) { return strlen(s.c_str()); } | 
|  | )cc"; | 
|  | std::string Expected = R"cc( | 
|  | proto::ProtoCommandLineFlag flag; | 
|  | int x = PROTO.foo(); | 
|  | int y = flag.GetProto().foo(); | 
|  | int f(string s) { return REPLACED; } | 
|  | )cc"; | 
|  |  | 
|  | testRule(applyFirst({ruleStrlenSize(), FlagRule}), Input, Expected); | 
|  | } | 
|  |  | 
|  | TEST_F(TransformerTest, OrderedRuleRelated) { | 
|  | std::string Input = R"cc( | 
|  | void f1(); | 
|  | void f2(); | 
|  | void call_f1() { f1(); } | 
|  | void call_f2() { f2(); } | 
|  | )cc"; | 
|  | std::string Expected = R"cc( | 
|  | void f1(); | 
|  | void f2(); | 
|  | void call_f1() { REPLACE_F1; } | 
|  | void call_f2() { REPLACE_F1_OR_F2; } | 
|  | )cc"; | 
|  |  | 
|  | RewriteRule ReplaceF1 = | 
|  | makeRule(callExpr(callee(functionDecl(hasName("f1")))), | 
|  | changeTo(cat("REPLACE_F1"))); | 
|  | RewriteRule ReplaceF1OrF2 = | 
|  | makeRule(callExpr(callee(functionDecl(hasAnyName("f1", "f2")))), | 
|  | changeTo(cat("REPLACE_F1_OR_F2"))); | 
|  | testRule(applyFirst({ReplaceF1, ReplaceF1OrF2}), Input, Expected); | 
|  | } | 
|  |  | 
|  | // Change the order of the rules to get a different result. When `ReplaceF1OrF2` | 
|  | // comes first, it applies for both uses, so `ReplaceF1` never applies. | 
|  | TEST_F(TransformerTest, OrderedRuleRelatedSwapped) { | 
|  | std::string Input = R"cc( | 
|  | void f1(); | 
|  | void f2(); | 
|  | void call_f1() { f1(); } | 
|  | void call_f2() { f2(); } | 
|  | )cc"; | 
|  | std::string Expected = R"cc( | 
|  | void f1(); | 
|  | void f2(); | 
|  | void call_f1() { REPLACE_F1_OR_F2; } | 
|  | void call_f2() { REPLACE_F1_OR_F2; } | 
|  | )cc"; | 
|  |  | 
|  | RewriteRule ReplaceF1 = | 
|  | makeRule(callExpr(callee(functionDecl(hasName("f1")))), | 
|  | changeTo(cat("REPLACE_F1"))); | 
|  | RewriteRule ReplaceF1OrF2 = | 
|  | makeRule(callExpr(callee(functionDecl(hasAnyName("f1", "f2")))), | 
|  | changeTo(cat("REPLACE_F1_OR_F2"))); | 
|  | testRule(applyFirst({ReplaceF1OrF2, ReplaceF1}), Input, Expected); | 
|  | } | 
|  |  | 
|  | // Verify that a set of rules whose matchers have different base kinds works | 
|  | // properly, including that `applyFirst` produces multiple matchers.  We test | 
|  | // two different kinds of rules: Expr and Decl. We place the Decl rule in the | 
|  | // middle to test that `buildMatchers` works even when the kinds aren't grouped | 
|  | // together. | 
|  | TEST_F(TransformerTest, OrderedRuleMultipleKinds) { | 
|  | std::string Input = R"cc( | 
|  | void f1(); | 
|  | void f2(); | 
|  | void call_f1() { f1(); } | 
|  | void call_f2() { f2(); } | 
|  | )cc"; | 
|  | std::string Expected = R"cc( | 
|  | void f1(); | 
|  | void DECL_RULE(); | 
|  | void call_f1() { REPLACE_F1; } | 
|  | void call_f2() { REPLACE_F1_OR_F2; } | 
|  | )cc"; | 
|  |  | 
|  | RewriteRule ReplaceF1 = | 
|  | makeRule(callExpr(callee(functionDecl(hasName("f1")))), | 
|  | changeTo(cat("REPLACE_F1"))); | 
|  | RewriteRule ReplaceF1OrF2 = | 
|  | makeRule(callExpr(callee(functionDecl(hasAnyName("f1", "f2")))), | 
|  | changeTo(cat("REPLACE_F1_OR_F2"))); | 
|  | RewriteRule DeclRule = makeRule(functionDecl(hasName("f2")).bind("fun"), | 
|  | changeTo(name("fun"), cat("DECL_RULE"))); | 
|  |  | 
|  | RewriteRule Rule = applyFirst({ReplaceF1, DeclRule, ReplaceF1OrF2}); | 
|  | EXPECT_EQ(transformer::detail::buildMatchers(Rule).size(), 2UL); | 
|  | testRule(Rule, Input, Expected); | 
|  | } | 
|  |  | 
|  | // Verifies that a rule with a top-level matcher for an implicit node (like | 
|  | // `implicitCastExpr`) works correctly -- the implicit nodes are not skipped. | 
|  | TEST_F(TransformerTest, OrderedRuleImplicitMatched) { | 
|  | std::string Input = R"cc( | 
|  | void f1(); | 
|  | int f2(); | 
|  | void call_f1() { f1(); } | 
|  | float call_f2() { return f2(); } | 
|  | )cc"; | 
|  | std::string Expected = R"cc( | 
|  | void f1(); | 
|  | int f2(); | 
|  | void call_f1() { REPLACE_F1; } | 
|  | float call_f2() { return REPLACE_F2; } | 
|  | )cc"; | 
|  |  | 
|  | RewriteRule ReplaceF1 = | 
|  | makeRule(callExpr(callee(functionDecl(hasName("f1")))), | 
|  | changeTo(cat("REPLACE_F1"))); | 
|  | RewriteRule ReplaceF2 = | 
|  | makeRule(implicitCastExpr(hasSourceExpression(callExpr())), | 
|  | changeTo(cat("REPLACE_F2"))); | 
|  | testRule(applyFirst({ReplaceF1, ReplaceF2}), Input, Expected); | 
|  | } | 
|  |  | 
|  | // | 
|  | // Negative tests (where we expect no transformation to occur). | 
|  | // | 
|  |  | 
|  | // Tests for a conflict in edits from a single match for a rule. | 
|  | TEST_F(TransformerTest, TextGeneratorFailure) { | 
|  | std::string Input = "int conflictOneRule() { return 3 + 7; }"; | 
|  | // Try to change the whole binary-operator expression AND one its operands: | 
|  | StringRef O = "O"; | 
|  | class AlwaysFail : public transformer::MatchComputation<std::string> { | 
|  | llvm::Error eval(const ast_matchers::MatchFinder::MatchResult &, | 
|  | std::string *) const override { | 
|  | return llvm::createStringError(llvm::errc::invalid_argument, "ERROR"); | 
|  | } | 
|  | std::string toString() const override { return "AlwaysFail"; } | 
|  | }; | 
|  | Transformer T( | 
|  | makeRule(binaryOperator().bind(O), | 
|  | changeTo(node(std::string(O)), std::make_shared<AlwaysFail>())), | 
|  | consumer()); | 
|  | T.registerMatchers(&MatchFinder); | 
|  | EXPECT_FALSE(rewrite(Input)); | 
|  | EXPECT_THAT(Changes, IsEmpty()); | 
|  | EXPECT_EQ(ErrorCount, 1); | 
|  | } | 
|  |  | 
|  | // Tests for a conflict in edits from a single match for a rule. | 
|  | TEST_F(TransformerTest, OverlappingEditsInRule) { | 
|  | std::string Input = "int conflictOneRule() { return 3 + 7; }"; | 
|  | // Try to change the whole binary-operator expression AND one its operands: | 
|  | StringRef O = "O", L = "L"; | 
|  | Transformer T(makeRule(binaryOperator(hasLHS(expr().bind(L))).bind(O), | 
|  | {changeTo(node(std::string(O)), cat("DELETE_OP")), | 
|  | changeTo(node(std::string(L)), cat("DELETE_LHS"))}), | 
|  | consumer()); | 
|  | T.registerMatchers(&MatchFinder); | 
|  | EXPECT_FALSE(rewrite(Input)); | 
|  | EXPECT_THAT(Changes, IsEmpty()); | 
|  | EXPECT_EQ(ErrorCount, 1); | 
|  | } | 
|  |  | 
|  | // Tests for a conflict in edits across multiple matches (of the same rule). | 
|  | TEST_F(TransformerTest, OverlappingEditsMultipleMatches) { | 
|  | std::string Input = "int conflictOneRule() { return -7; }"; | 
|  | // Try to change the whole binary-operator expression AND one its operands: | 
|  | StringRef E = "E"; | 
|  | Transformer T(makeRule(expr().bind(E), | 
|  | changeTo(node(std::string(E)), cat("DELETE_EXPR"))), | 
|  | consumer()); | 
|  | T.registerMatchers(&MatchFinder); | 
|  | // The rewrite process fails because the changes conflict with each other... | 
|  | EXPECT_FALSE(rewrite(Input)); | 
|  | // ... but two changes were produced. | 
|  | EXPECT_EQ(Changes.size(), 2u); | 
|  | EXPECT_EQ(ErrorCount, 0); | 
|  | } | 
|  |  | 
|  | TEST_F(TransformerTest, ErrorOccurredMatchSkipped) { | 
|  | // Syntax error in the function body: | 
|  | std::string Input = "void errorOccurred() { 3 }"; | 
|  | Transformer T(makeRule(functionDecl(hasName("errorOccurred")), | 
|  | changeTo(cat("DELETED;"))), | 
|  | consumer()); | 
|  | T.registerMatchers(&MatchFinder); | 
|  | // The rewrite process itself fails... | 
|  | EXPECT_FALSE(rewrite(Input)); | 
|  | // ... and no changes or errors are produced in the process. | 
|  | EXPECT_THAT(Changes, IsEmpty()); | 
|  | EXPECT_EQ(ErrorCount, 0); | 
|  | } | 
|  |  | 
|  | TEST_F(TransformerTest, ImplicitNodes_ConstructorDecl) { | 
|  |  | 
|  | std::string OtherStructPrefix = R"cpp( | 
|  | struct Other { | 
|  | )cpp"; | 
|  | std::string OtherStructSuffix = "};"; | 
|  |  | 
|  | std::string CopyableStructName = "struct Copyable"; | 
|  | std::string BrokenStructName = "struct explicit Copyable"; | 
|  |  | 
|  | std::string CodeSuffix = R"cpp( | 
|  | { | 
|  | Other m_i; | 
|  | Copyable(); | 
|  | }; | 
|  | )cpp"; | 
|  |  | 
|  | std::string CopyCtor = "Other(const Other&) = default;"; | 
|  | std::string ExplicitCopyCtor = "explicit Other(const Other&) = default;"; | 
|  | std::string BrokenExplicitCopyCtor = | 
|  | "explicit explicit explicit Other(const Other&) = default;"; | 
|  |  | 
|  | std::string RewriteInput = OtherStructPrefix + CopyCtor + OtherStructSuffix + | 
|  | CopyableStructName + CodeSuffix; | 
|  | std::string ExpectedRewriteOutput = OtherStructPrefix + ExplicitCopyCtor + | 
|  | OtherStructSuffix + CopyableStructName + | 
|  | CodeSuffix; | 
|  | std::string BrokenRewriteOutput = OtherStructPrefix + BrokenExplicitCopyCtor + | 
|  | OtherStructSuffix + BrokenStructName + | 
|  | CodeSuffix; | 
|  |  | 
|  | auto MatchedRecord = | 
|  | cxxConstructorDecl(isCopyConstructor()).bind("copyConstructor"); | 
|  |  | 
|  | auto RewriteRule = | 
|  | changeTo(before(node("copyConstructor")), cat("explicit ")); | 
|  |  | 
|  | testRule(makeRule(traverse(TK_IgnoreUnlessSpelledInSource, MatchedRecord), | 
|  | RewriteRule), | 
|  | RewriteInput, ExpectedRewriteOutput); | 
|  |  | 
|  | testRule(makeRule(traverse(TK_AsIs, MatchedRecord), RewriteRule), | 
|  | RewriteInput, BrokenRewriteOutput); | 
|  | } | 
|  |  | 
|  | TEST_F(TransformerTest, ImplicitNodes_RangeFor) { | 
|  |  | 
|  | std::string CodePrefix = R"cpp( | 
|  | struct Container | 
|  | { | 
|  | int* begin() const; | 
|  | int* end() const; | 
|  | int* cbegin() const; | 
|  | int* cend() const; | 
|  | }; | 
|  |  | 
|  | void foo() | 
|  | { | 
|  | const Container c; | 
|  | )cpp"; | 
|  |  | 
|  | std::string BeginCallBefore = "  c.begin();"; | 
|  | std::string BeginCallAfter = "  c.cbegin();"; | 
|  |  | 
|  | std::string ForLoop = "for (auto i : c)"; | 
|  | std::string BrokenForLoop = "for (auto i :.cbegin() c)"; | 
|  |  | 
|  | std::string CodeSuffix = R"cpp( | 
|  | { | 
|  | } | 
|  | } | 
|  | )cpp"; | 
|  |  | 
|  | std::string RewriteInput = | 
|  | CodePrefix + BeginCallBefore + ForLoop + CodeSuffix; | 
|  | std::string ExpectedRewriteOutput = | 
|  | CodePrefix + BeginCallAfter + ForLoop + CodeSuffix; | 
|  | std::string BrokenRewriteOutput = | 
|  | CodePrefix + BeginCallAfter + BrokenForLoop + CodeSuffix; | 
|  |  | 
|  | auto MatchedRecord = | 
|  | cxxMemberCallExpr(on(expr(hasType(qualType(isConstQualified(), | 
|  | hasDeclaration(cxxRecordDecl( | 
|  | hasName("Container")))))) | 
|  | .bind("callTarget")), | 
|  | callee(cxxMethodDecl(hasName("begin")))) | 
|  | .bind("constBeginCall"); | 
|  |  | 
|  | auto RewriteRule = | 
|  | changeTo(node("constBeginCall"), cat(name("callTarget"), ".cbegin()")); | 
|  |  | 
|  | testRule(makeRule(traverse(TK_IgnoreUnlessSpelledInSource, MatchedRecord), | 
|  | RewriteRule), | 
|  | RewriteInput, ExpectedRewriteOutput); | 
|  |  | 
|  | testRule(makeRule(traverse(TK_AsIs, MatchedRecord), RewriteRule), | 
|  | RewriteInput, BrokenRewriteOutput); | 
|  | } | 
|  |  | 
|  | TEST_F(TransformerTest, ImplicitNodes_ForStmt) { | 
|  |  | 
|  | std::string CodePrefix = R"cpp( | 
|  | struct NonTrivial { | 
|  | NonTrivial() {} | 
|  | NonTrivial(NonTrivial&) {} | 
|  | NonTrivial& operator=(NonTrivial const&) { return *this; } | 
|  |  | 
|  | ~NonTrivial() {} | 
|  | }; | 
|  |  | 
|  | struct ContainsArray { | 
|  | NonTrivial arr[2]; | 
|  | ContainsArray& operator=(ContainsArray const&) = default; | 
|  | }; | 
|  |  | 
|  | void testIt() | 
|  | { | 
|  | ContainsArray ca1; | 
|  | ContainsArray ca2; | 
|  | ca2 = ca1; | 
|  | )cpp"; | 
|  |  | 
|  | auto CodeSuffix = "}"; | 
|  |  | 
|  | auto LoopBody = R"cpp( | 
|  | { | 
|  |  | 
|  | } | 
|  | )cpp"; | 
|  |  | 
|  | auto RawLoop = "for (auto i = 0; i != 5; ++i)"; | 
|  |  | 
|  | auto RangeLoop = "for (auto i : boost::irange(5))"; | 
|  |  | 
|  | // Expect to rewrite the raw loop to the ranged loop. | 
|  | // This works in TK_IgnoreUnlessSpelledInSource mode, but TK_AsIs | 
|  | // mode also matches the hidden for loop generated in the copy assignment | 
|  | // operator of ContainsArray. Transformer then fails to transform the code at | 
|  | // all. | 
|  |  | 
|  | auto RewriteInput = | 
|  | CodePrefix + RawLoop + LoopBody + RawLoop + LoopBody + CodeSuffix; | 
|  |  | 
|  | auto RewriteOutput = | 
|  | CodePrefix + RangeLoop + LoopBody + RangeLoop + LoopBody + CodeSuffix; | 
|  |  | 
|  | auto MatchedLoop = forStmt( | 
|  | has(declStmt(hasSingleDecl( | 
|  | varDecl(hasInitializer(integerLiteral(equals(0)))).bind("loopVar")))), | 
|  | has(binaryOperator(hasOperatorName("!="), | 
|  | hasLHS(ignoringImplicit(declRefExpr( | 
|  | to(varDecl(equalsBoundNode("loopVar")))))), | 
|  | hasRHS(expr().bind("upperBoundExpr")))), | 
|  | has(unaryOperator(hasOperatorName("++"), | 
|  | hasUnaryOperand(declRefExpr( | 
|  | to(varDecl(equalsBoundNode("loopVar")))))) | 
|  | .bind("incrementOp"))); | 
|  |  | 
|  | auto RewriteRule = | 
|  | changeTo(transformer::enclose(node("loopVar"), node("incrementOp")), | 
|  | cat("auto ", name("loopVar"), " : boost::irange(", | 
|  | node("upperBoundExpr"), ")")); | 
|  |  | 
|  | testRule(makeRule(traverse(TK_IgnoreUnlessSpelledInSource, MatchedLoop), | 
|  | RewriteRule), | 
|  | RewriteInput, RewriteOutput); | 
|  |  | 
|  | testRuleFailure(makeRule(traverse(TK_AsIs, MatchedLoop), RewriteRule), | 
|  | RewriteInput); | 
|  | } | 
|  |  | 
|  | TEST_F(TransformerTest, ImplicitNodes_ForStmt2) { | 
|  |  | 
|  | std::string CodePrefix = R"cpp( | 
|  | struct NonTrivial { | 
|  | NonTrivial() {} | 
|  | NonTrivial(NonTrivial&) {} | 
|  | NonTrivial& operator=(NonTrivial const&) { return *this; } | 
|  |  | 
|  | ~NonTrivial() {} | 
|  | }; | 
|  |  | 
|  | struct ContainsArray { | 
|  | NonTrivial arr[2]; | 
|  | ContainsArray& operator=(ContainsArray const&) = default; | 
|  | }; | 
|  |  | 
|  | void testIt() | 
|  | { | 
|  | ContainsArray ca1; | 
|  | ContainsArray ca2; | 
|  | ca2 = ca1; | 
|  | )cpp"; | 
|  |  | 
|  | auto CodeSuffix = "}"; | 
|  |  | 
|  | auto LoopBody = R"cpp( | 
|  | { | 
|  |  | 
|  | } | 
|  | )cpp"; | 
|  |  | 
|  | auto RawLoop = "for (auto i = 0; i != 5; ++i)"; | 
|  |  | 
|  | auto RangeLoop = "for (auto i : boost::irange(5))"; | 
|  |  | 
|  | // Expect to rewrite the raw loop to the ranged loop. | 
|  | // This works in TK_IgnoreUnlessSpelledInSource mode, but TK_AsIs | 
|  | // mode also matches the hidden for loop generated in the copy assignment | 
|  | // operator of ContainsArray. Transformer then fails to transform the code at | 
|  | // all. | 
|  |  | 
|  | auto RewriteInput = | 
|  | CodePrefix + RawLoop + LoopBody + RawLoop + LoopBody + CodeSuffix; | 
|  |  | 
|  | auto RewriteOutput = | 
|  | CodePrefix + RangeLoop + LoopBody + RangeLoop + LoopBody + CodeSuffix; | 
|  | auto MatchedLoop = forStmt( | 
|  | hasLoopInit(declStmt(hasSingleDecl( | 
|  | varDecl(hasInitializer(integerLiteral(equals(0)))).bind("loopVar")))), | 
|  | hasCondition(binaryOperator(hasOperatorName("!="), | 
|  | hasLHS(ignoringImplicit(declRefExpr(to( | 
|  | varDecl(equalsBoundNode("loopVar")))))), | 
|  | hasRHS(expr().bind("upperBoundExpr")))), | 
|  | hasIncrement(unaryOperator(hasOperatorName("++"), | 
|  | hasUnaryOperand(declRefExpr( | 
|  | to(varDecl(equalsBoundNode("loopVar")))))) | 
|  | .bind("incrementOp"))); | 
|  |  | 
|  | auto RewriteRule = | 
|  | changeTo(transformer::enclose(node("loopVar"), node("incrementOp")), | 
|  | cat("auto ", name("loopVar"), " : boost::irange(", | 
|  | node("upperBoundExpr"), ")")); | 
|  |  | 
|  | testRule(makeRule(traverse(TK_IgnoreUnlessSpelledInSource, MatchedLoop), | 
|  | RewriteRule), | 
|  | RewriteInput, RewriteOutput); | 
|  |  | 
|  | testRuleFailure(makeRule(traverse(TK_AsIs, MatchedLoop), RewriteRule), | 
|  | RewriteInput); | 
|  | } | 
|  |  | 
|  | TEST_F(TransformerTest, TemplateInstantiation) { | 
|  |  | 
|  | std::string NonTemplatesInput = R"cpp( | 
|  | struct S { | 
|  | int m_i; | 
|  | }; | 
|  | )cpp"; | 
|  | std::string NonTemplatesExpected = R"cpp( | 
|  | struct S { | 
|  | safe_int m_i; | 
|  | }; | 
|  | )cpp"; | 
|  |  | 
|  | std::string TemplatesInput = R"cpp( | 
|  | template<typename T> | 
|  | struct TemplStruct { | 
|  | TemplStruct() {} | 
|  | ~TemplStruct() {} | 
|  |  | 
|  | private: | 
|  | T m_t; | 
|  | }; | 
|  |  | 
|  | void instantiate() | 
|  | { | 
|  | TemplStruct<int> ti; | 
|  | } | 
|  | )cpp"; | 
|  |  | 
|  | auto MatchedField = fieldDecl(hasType(asString("int"))).bind("theField"); | 
|  |  | 
|  | // Changes the 'int' in 'S', but not the 'T' in 'TemplStruct': | 
|  | testRule(makeRule(traverse(TK_IgnoreUnlessSpelledInSource, MatchedField), | 
|  | changeTo(cat("safe_int ", name("theField"), ";"))), | 
|  | NonTemplatesInput + TemplatesInput, | 
|  | NonTemplatesExpected + TemplatesInput); | 
|  |  | 
|  | // In AsIs mode, template instantiations are modified, which is | 
|  | // often not desired: | 
|  |  | 
|  | std::string IncorrectTemplatesExpected = R"cpp( | 
|  | template<typename T> | 
|  | struct TemplStruct { | 
|  | TemplStruct() {} | 
|  | ~TemplStruct() {} | 
|  |  | 
|  | private: | 
|  | safe_int m_t; | 
|  | }; | 
|  |  | 
|  | void instantiate() | 
|  | { | 
|  | TemplStruct<int> ti; | 
|  | } | 
|  | )cpp"; | 
|  |  | 
|  | // Changes the 'int' in 'S', and (incorrectly) the 'T' in 'TemplStruct': | 
|  | testRule(makeRule(traverse(TK_AsIs, MatchedField), | 
|  | changeTo(cat("safe_int ", name("theField"), ";"))), | 
|  |  | 
|  | NonTemplatesInput + TemplatesInput, | 
|  | NonTemplatesExpected + IncorrectTemplatesExpected); | 
|  | } | 
|  |  | 
|  | // Transformation of macro source text when the change encompasses the entirety | 
|  | // of the expanded text. | 
|  | TEST_F(TransformerTest, SimpleMacro) { | 
|  | std::string Input = R"cc( | 
|  | #define ZERO 0 | 
|  | int f(string s) { return ZERO; } | 
|  | )cc"; | 
|  | std::string Expected = R"cc( | 
|  | #define ZERO 0 | 
|  | int f(string s) { return 999; } | 
|  | )cc"; | 
|  |  | 
|  | StringRef zero = "zero"; | 
|  | RewriteRule R = makeRule(integerLiteral(equals(0)).bind(zero), | 
|  | changeTo(node(std::string(zero)), cat("999"))); | 
|  | testRule(R, Input, Expected); | 
|  | } | 
|  |  | 
|  | // Transformation of macro source text when the change encompasses the entirety | 
|  | // of the expanded text, for the case of function-style macros. | 
|  | TEST_F(TransformerTest, FunctionMacro) { | 
|  | std::string Input = R"cc( | 
|  | #define MACRO(str) strlen((str).c_str()) | 
|  | int f(string s) { return MACRO(s); } | 
|  | )cc"; | 
|  | std::string Expected = R"cc( | 
|  | #define MACRO(str) strlen((str).c_str()) | 
|  | int f(string s) { return REPLACED; } | 
|  | )cc"; | 
|  |  | 
|  | testRule(ruleStrlenSize(), Input, Expected); | 
|  | } | 
|  |  | 
|  | // Tests that expressions in macro arguments can be rewritten. | 
|  | TEST_F(TransformerTest, MacroArg) { | 
|  | std::string Input = R"cc( | 
|  | #define PLUS(e) e + 1 | 
|  | int f(string s) { return PLUS(strlen(s.c_str())); } | 
|  | )cc"; | 
|  | std::string Expected = R"cc( | 
|  | #define PLUS(e) e + 1 | 
|  | int f(string s) { return PLUS(REPLACED); } | 
|  | )cc"; | 
|  |  | 
|  | testRule(ruleStrlenSize(), Input, Expected); | 
|  | } | 
|  |  | 
|  | // Tests that expressions in macro arguments can be rewritten, even when the | 
|  | // macro call occurs inside another macro's definition. | 
|  | TEST_F(TransformerTest, MacroArgInMacroDef) { | 
|  | std::string Input = R"cc( | 
|  | #define NESTED(e) e | 
|  | #define MACRO(str) NESTED(strlen((str).c_str())) | 
|  | int f(string s) { return MACRO(s); } | 
|  | )cc"; | 
|  | std::string Expected = R"cc( | 
|  | #define NESTED(e) e | 
|  | #define MACRO(str) NESTED(strlen((str).c_str())) | 
|  | int f(string s) { return REPLACED; } | 
|  | )cc"; | 
|  |  | 
|  | testRule(ruleStrlenSize(), Input, Expected); | 
|  | } | 
|  |  | 
|  | // Tests the corner case of the identity macro, specifically that it is | 
|  | // discarded in the rewrite rather than preserved (like PLUS is preserved in the | 
|  | // previous test).  This behavior is of dubious value (and marked with a FIXME | 
|  | // in the code), but we test it to verify (and demonstrate) how this case is | 
|  | // handled. | 
|  | TEST_F(TransformerTest, IdentityMacro) { | 
|  | std::string Input = R"cc( | 
|  | #define ID(e) e | 
|  | int f(string s) { return ID(strlen(s.c_str())); } | 
|  | )cc"; | 
|  | std::string Expected = R"cc( | 
|  | #define ID(e) e | 
|  | int f(string s) { return REPLACED; } | 
|  | )cc"; | 
|  |  | 
|  | testRule(ruleStrlenSize(), Input, Expected); | 
|  | } | 
|  |  | 
|  | // Tests that two changes in a single macro expansion do not lead to conflicts | 
|  | // in applying the changes. | 
|  | TEST_F(TransformerTest, TwoChangesInOneMacroExpansion) { | 
|  | std::string Input = R"cc( | 
|  | #define PLUS(a,b) (a) + (b) | 
|  | int f() { return PLUS(3, 4); } | 
|  | )cc"; | 
|  | std::string Expected = R"cc( | 
|  | #define PLUS(a,b) (a) + (b) | 
|  | int f() { return PLUS(LIT, LIT); } | 
|  | )cc"; | 
|  |  | 
|  | testRule(makeRule(integerLiteral(), changeTo(cat("LIT"))), Input, Expected); | 
|  | } | 
|  |  | 
|  | // Tests case where the rule's match spans both source from the macro and its | 
|  | // arg, with the begin location (the "anchor") being the arg. | 
|  | TEST_F(TransformerTest, MatchSpansMacroTextButChangeDoesNot) { | 
|  | std::string Input = R"cc( | 
|  | #define PLUS_ONE(a) a + 1 | 
|  | int f() { return PLUS_ONE(3); } | 
|  | )cc"; | 
|  | std::string Expected = R"cc( | 
|  | #define PLUS_ONE(a) a + 1 | 
|  | int f() { return PLUS_ONE(LIT); } | 
|  | )cc"; | 
|  |  | 
|  | StringRef E = "expr"; | 
|  | testRule(makeRule(binaryOperator(hasLHS(expr().bind(E))), | 
|  | changeTo(node(std::string(E)), cat("LIT"))), | 
|  | Input, Expected); | 
|  | } | 
|  |  | 
|  | // Tests case where the rule's match spans both source from the macro and its | 
|  | // arg, with the begin location (the "anchor") being inside the macro. | 
|  | TEST_F(TransformerTest, MatchSpansMacroTextButChangeDoesNotAnchoredInMacro) { | 
|  | std::string Input = R"cc( | 
|  | #define PLUS_ONE(a) 1 + a | 
|  | int f() { return PLUS_ONE(3); } | 
|  | )cc"; | 
|  | std::string Expected = R"cc( | 
|  | #define PLUS_ONE(a) 1 + a | 
|  | int f() { return PLUS_ONE(LIT); } | 
|  | )cc"; | 
|  |  | 
|  | StringRef E = "expr"; | 
|  | testRule(makeRule(binaryOperator(hasRHS(expr().bind(E))), | 
|  | changeTo(node(std::string(E)), cat("LIT"))), | 
|  | Input, Expected); | 
|  | } | 
|  |  | 
|  | // No rewrite is applied when the changed text does not encompass the entirety | 
|  | // of the expanded text. That is, the edit would have to be applied to the | 
|  | // macro's definition to succeed and editing the expansion point would not | 
|  | // suffice. | 
|  | TEST_F(TransformerTest, NoPartialRewriteOMacroExpansion) { | 
|  | std::string Input = R"cc( | 
|  | #define ZERO_PLUS 0 + 3 | 
|  | int f(string s) { return ZERO_PLUS; })cc"; | 
|  |  | 
|  | StringRef zero = "zero"; | 
|  | RewriteRule R = makeRule(integerLiteral(equals(0)).bind(zero), | 
|  | changeTo(node(std::string(zero)), cat("0"))); | 
|  | testRule(R, Input, Input); | 
|  | } | 
|  |  | 
|  | // This test handles the corner case where a macro expands within another macro | 
|  | // to matching code, but that code is an argument to the nested macro call.  A | 
|  | // simple check of isMacroArgExpansion() vs. isMacroBodyExpansion() will get | 
|  | // this wrong, and transform the code. | 
|  | TEST_F(TransformerTest, NoPartialRewriteOfMacroExpansionForMacroArgs) { | 
|  | std::string Input = R"cc( | 
|  | #define NESTED(e) e | 
|  | #define MACRO(str) 1 + NESTED(strlen((str).c_str())) | 
|  | int f(string s) { return MACRO(s); } | 
|  | )cc"; | 
|  |  | 
|  | testRule(ruleStrlenSize(), Input, Input); | 
|  | } | 
|  |  | 
|  | #if !defined(NDEBUG) && GTEST_HAS_DEATH_TEST | 
|  | // Verifies that `Type` and `QualType` are not allowed as top-level matchers in | 
|  | // rules. | 
|  | TEST(TransformerDeathTest, OrderedRuleTypes) { | 
|  | RewriteRule QualTypeRule = makeRule(qualType(), changeTo(cat("Q"))); | 
|  | EXPECT_DEATH(transformer::detail::buildMatchers(QualTypeRule), | 
|  | "Matcher must be.*node matcher"); | 
|  |  | 
|  | RewriteRule TypeRule = makeRule(arrayType(), changeTo(cat("T"))); | 
|  | EXPECT_DEATH(transformer::detail::buildMatchers(TypeRule), | 
|  | "Matcher must be.*node matcher"); | 
|  | } | 
|  | #endif | 
|  |  | 
|  | // Edits are able to span multiple files; in this case, a header and an | 
|  | // implementation file. | 
|  | TEST_F(TransformerTest, MultipleFiles) { | 
|  | std::string Header = R"cc(void RemoveThisFunction();)cc"; | 
|  | std::string Source = R"cc(#include "input.h" | 
|  | void RemoveThisFunction();)cc"; | 
|  | Transformer T( | 
|  | makeRule(functionDecl(hasName("RemoveThisFunction")), changeTo(cat(""))), | 
|  | consumer()); | 
|  | T.registerMatchers(&MatchFinder); | 
|  | auto Factory = newFrontendActionFactory(&MatchFinder); | 
|  | EXPECT_TRUE(runToolOnCodeWithArgs( | 
|  | Factory->create(), Source, std::vector<std::string>(), "input.cc", | 
|  | "clang-tool", std::make_shared<PCHContainerOperations>(), | 
|  | {{"input.h", Header}})); | 
|  |  | 
|  | llvm::sort(Changes, [](const AtomicChange &L, const AtomicChange &R) { | 
|  | return L.getFilePath() < R.getFilePath(); | 
|  | }); | 
|  |  | 
|  | ASSERT_EQ(llvm::sys::path::convert_to_slash(Changes[0].getFilePath()), | 
|  | "./input.h"); | 
|  | EXPECT_THAT(Changes[0].getInsertedHeaders(), IsEmpty()); | 
|  | EXPECT_THAT(Changes[0].getRemovedHeaders(), IsEmpty()); | 
|  | llvm::Expected<std::string> UpdatedCode = | 
|  | clang::tooling::applyAllReplacements(Header, | 
|  | Changes[0].getReplacements()); | 
|  | ASSERT_TRUE(static_cast<bool>(UpdatedCode)) | 
|  | << "Could not update code: " << llvm::toString(UpdatedCode.takeError()); | 
|  | EXPECT_EQ(format(*UpdatedCode), ""); | 
|  |  | 
|  | ASSERT_EQ(Changes[1].getFilePath(), "input.cc"); | 
|  | EXPECT_THAT(Changes[1].getInsertedHeaders(), IsEmpty()); | 
|  | EXPECT_THAT(Changes[1].getRemovedHeaders(), IsEmpty()); | 
|  | UpdatedCode = clang::tooling::applyAllReplacements( | 
|  | Source, Changes[1].getReplacements()); | 
|  | ASSERT_TRUE(static_cast<bool>(UpdatedCode)) | 
|  | << "Could not update code: " << llvm::toString(UpdatedCode.takeError()); | 
|  | EXPECT_EQ(format(*UpdatedCode), format("#include \"input.h\"\n")); | 
|  | } | 
|  |  | 
|  | TEST_F(TransformerTest, AddIncludeMultipleFiles) { | 
|  | std::string Header = R"cc(void RemoveThisFunction();)cc"; | 
|  | std::string Source = R"cc(#include "input.h" | 
|  | void Foo() {RemoveThisFunction();})cc"; | 
|  | Transformer T( | 
|  | makeRule(callExpr(callee( | 
|  | functionDecl(hasName("RemoveThisFunction")).bind("fun"))), | 
|  | addInclude(node("fun"), "header.h")), | 
|  | consumer()); | 
|  | T.registerMatchers(&MatchFinder); | 
|  | auto Factory = newFrontendActionFactory(&MatchFinder); | 
|  | EXPECT_TRUE(runToolOnCodeWithArgs( | 
|  | Factory->create(), Source, std::vector<std::string>(), "input.cc", | 
|  | "clang-tool", std::make_shared<PCHContainerOperations>(), | 
|  | {{"input.h", Header}})); | 
|  |  | 
|  | ASSERT_EQ(Changes.size(), 1U); | 
|  | ASSERT_EQ(llvm::sys::path::convert_to_slash(Changes[0].getFilePath()), | 
|  | "./input.h"); | 
|  | EXPECT_THAT(Changes[0].getInsertedHeaders(), ElementsAre("header.h")); | 
|  | EXPECT_THAT(Changes[0].getRemovedHeaders(), IsEmpty()); | 
|  | llvm::Expected<std::string> UpdatedCode = | 
|  | clang::tooling::applyAllReplacements(Header, | 
|  | Changes[0].getReplacements()); | 
|  | ASSERT_TRUE(static_cast<bool>(UpdatedCode)) | 
|  | << "Could not update code: " << llvm::toString(UpdatedCode.takeError()); | 
|  | EXPECT_EQ(format(*UpdatedCode), format(Header)); | 
|  | } | 
|  |  | 
|  | // A single change set can span multiple files. | 
|  | TEST_F(TransformerTest, MultiFileEdit) { | 
|  | // NB: The fixture is unused for this test, but kept for the test suite name. | 
|  | std::string Header = R"cc(void Func(int id);)cc"; | 
|  | std::string Source = R"cc(#include "input.h" | 
|  | void Caller() { | 
|  | int id = 0; | 
|  | Func(id); | 
|  | })cc"; | 
|  | int ErrorCount = 0; | 
|  | std::vector<AtomicChanges> ChangeSets; | 
|  | clang::ast_matchers::MatchFinder MatchFinder; | 
|  | Transformer T( | 
|  | makeRule(callExpr(callee(functionDecl(hasName("Func"))), | 
|  | forEachArgumentWithParam(expr().bind("arg"), | 
|  | parmVarDecl().bind("param"))), | 
|  | {changeTo(node("arg"), cat("ARG")), | 
|  | changeTo(node("param"), cat("PARAM"))}), | 
|  | [&](Expected<MutableArrayRef<AtomicChange>> Changes) { | 
|  | if (Changes) | 
|  | ChangeSets.push_back(AtomicChanges(Changes->begin(), Changes->end())); | 
|  | else | 
|  | ++ErrorCount; | 
|  | }); | 
|  | T.registerMatchers(&MatchFinder); | 
|  | auto Factory = newFrontendActionFactory(&MatchFinder); | 
|  | EXPECT_TRUE(runToolOnCodeWithArgs( | 
|  | Factory->create(), Source, std::vector<std::string>(), "input.cc", | 
|  | "clang-tool", std::make_shared<PCHContainerOperations>(), | 
|  | {{"input.h", Header}})); | 
|  |  | 
|  | auto GetPathWithSlashes = [](const AtomicChange &C) { | 
|  | return llvm::sys::path::convert_to_slash(C.getFilePath()); | 
|  | }; | 
|  |  | 
|  | EXPECT_EQ(ErrorCount, 0); | 
|  | EXPECT_THAT(ChangeSets, UnorderedElementsAre(UnorderedElementsAre( | 
|  | ResultOf(GetPathWithSlashes, "input.cc"), | 
|  | ResultOf(GetPathWithSlashes, "./input.h")))); | 
|  | } | 
|  |  | 
|  | TEST_F(TransformerTest, GeneratesMetadata) { | 
|  | std::string Input = R"cc(int target = 0;)cc"; | 
|  | std::string Expected = R"cc(REPLACE)cc"; | 
|  | RewriteRuleWith<std::string> Rule = makeRule( | 
|  | varDecl(hasName("target")), changeTo(cat("REPLACE")), cat("METADATA")); | 
|  | testRule(std::move(Rule), Input, Expected); | 
|  | EXPECT_EQ(ErrorCount, 0); | 
|  | EXPECT_THAT(StringMetadata, UnorderedElementsAre("METADATA")); | 
|  | } | 
|  |  | 
|  | TEST_F(TransformerTest, GeneratesMetadataWithNoEdits) { | 
|  | std::string Input = R"cc(int target = 0;)cc"; | 
|  | RewriteRuleWith<std::string> Rule = makeRule( | 
|  | varDecl(hasName("target")).bind("var"), noEdits(), cat("METADATA")); | 
|  | testRule(std::move(Rule), Input, Input); | 
|  | EXPECT_EQ(ErrorCount, 0); | 
|  | EXPECT_THAT(StringMetadata, UnorderedElementsAre("METADATA")); | 
|  | } | 
|  |  | 
|  | TEST_F(TransformerTest, PropagateMetadataErrors) { | 
|  | class AlwaysFail : public transformer::MatchComputation<std::string> { | 
|  | llvm::Error eval(const ast_matchers::MatchFinder::MatchResult &, | 
|  | std::string *) const override { | 
|  | return llvm::createStringError(llvm::errc::invalid_argument, "ERROR"); | 
|  | } | 
|  | std::string toString() const override { return "AlwaysFail"; } | 
|  | }; | 
|  | std::string Input = R"cc(int target = 0;)cc"; | 
|  | RewriteRuleWith<std::string> Rule = makeRule<std::string>( | 
|  | varDecl(hasName("target")).bind("var"), changeTo(cat("REPLACE")), | 
|  | std::make_shared<AlwaysFail>()); | 
|  | testRuleFailure(std::move(Rule), Input); | 
|  | EXPECT_EQ(ErrorCount, 1); | 
|  | } | 
|  |  | 
|  | } // namespace |