| 1 | //===- unittest/Tooling/TransformerTest.cpp -------------------------------===// |
| 2 | // |
| 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | // See https://llvm.org/LICENSE.txt for license information. |
| 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 6 | // |
| 7 | //===----------------------------------------------------------------------===// |
| 8 | |
| 9 | #include "clang/Tooling/Transformer/Transformer.h" |
| 10 | #include "clang/ASTMatchers/ASTMatchers.h" |
| 11 | #include "clang/Tooling/Tooling.h" |
| 12 | #include "clang/Tooling/Transformer/RangeSelector.h" |
| 13 | #include "clang/Tooling/Transformer/RewriteRule.h" |
| 14 | #include "clang/Tooling/Transformer/Stencil.h" |
| 15 | #include "llvm/ADT/STLExtras.h" |
| 16 | #include "llvm/Support/Errc.h" |
| 17 | #include "llvm/Support/Error.h" |
| 18 | #include "gmock/gmock.h" |
| 19 | #include "gtest/gtest.h" |
| 20 | #include <optional> |
| 21 | |
| 22 | using namespace clang; |
| 23 | using namespace tooling; |
| 24 | using namespace ast_matchers; |
| 25 | namespace { |
| 26 | using ::clang::transformer::addInclude; |
| 27 | using ::clang::transformer::applyFirst; |
| 28 | using ::clang::transformer::before; |
| 29 | using ::clang::transformer::cat; |
| 30 | using ::clang::transformer::changeTo; |
| 31 | using ::clang::transformer::editList; |
| 32 | using ::clang::transformer::makeRule; |
| 33 | using ::clang::transformer::member; |
| 34 | using ::clang::transformer::name; |
| 35 | using ::clang::transformer::node; |
| 36 | using ::clang::transformer::noEdits; |
| 37 | using ::clang::transformer::remove; |
| 38 | using ::clang::transformer::rewriteDescendants; |
| 39 | using ::clang::transformer::RewriteRule; |
| 40 | using ::clang::transformer::RewriteRuleWith; |
| 41 | using ::clang::transformer::statement; |
| 42 | using ::testing::ElementsAre; |
| 43 | using ::testing::IsEmpty; |
| 44 | using ::testing::ResultOf; |
| 45 | using ::testing::UnorderedElementsAre; |
| 46 | |
| 47 | constexpr char [] = R"cc( |
| 48 | struct string { |
| 49 | string(const char*); |
| 50 | char* c_str(); |
| 51 | int size(); |
| 52 | }; |
| 53 | int strlen(const char*); |
| 54 | |
| 55 | namespace proto { |
| 56 | struct PCFProto { |
| 57 | int foo(); |
| 58 | }; |
| 59 | struct ProtoCommandLineFlag : PCFProto { |
| 60 | PCFProto& GetProto(); |
| 61 | }; |
| 62 | } // namespace proto |
| 63 | class Logger {}; |
| 64 | void operator<<(Logger& l, string msg); |
| 65 | Logger& log(int level); |
| 66 | )cc" ; |
| 67 | |
| 68 | static ast_matchers::internal::Matcher<clang::QualType> |
| 69 | isOrPointsTo(const clang::ast_matchers::DeclarationMatcher &TypeMatcher) { |
| 70 | return anyOf(hasDeclaration(InnerMatcher: TypeMatcher), pointsTo(InnerMatcher: TypeMatcher)); |
| 71 | } |
| 72 | |
| 73 | static std::string format(StringRef Code) { |
| 74 | const std::vector<Range> Ranges(1, Range(0, Code.size())); |
| 75 | auto Style = format::getLLVMStyle(); |
| 76 | const auto Replacements = format::reformat(Style, Code, Ranges); |
| 77 | auto Formatted = applyAllReplacements(Code, Replaces: Replacements); |
| 78 | if (!Formatted) { |
| 79 | ADD_FAILURE() << "Could not format code: " |
| 80 | << llvm::toString(E: Formatted.takeError()); |
| 81 | return std::string(); |
| 82 | } |
| 83 | return *Formatted; |
| 84 | } |
| 85 | |
| 86 | static void compareSnippets(StringRef Expected, |
| 87 | const std::optional<std::string> &MaybeActual) { |
| 88 | ASSERT_TRUE(MaybeActual) << "Rewrite failed. Expecting: " << Expected; |
| 89 | auto Actual = *MaybeActual; |
| 90 | std::string HL = "#include \"header.h\"\n" ; |
| 91 | auto I = Actual.find(str: HL); |
| 92 | if (I != std::string::npos) |
| 93 | Actual.erase(pos: I, n: HL.size()); |
| 94 | EXPECT_EQ(format(Expected), format(Actual)); |
| 95 | } |
| 96 | |
| 97 | // FIXME: consider separating this class into its own file(s). |
| 98 | class ClangRefactoringTestBase : public testing::Test { |
| 99 | protected: |
| 100 | void (StringRef S) { FileContents[0].second += S; } |
| 101 | |
| 102 | void addFile(StringRef Filename, StringRef Content) { |
| 103 | FileContents.emplace_back(args: std::string(Filename), args: std::string(Content)); |
| 104 | } |
| 105 | |
| 106 | std::optional<std::string> rewrite(StringRef Input) { |
| 107 | std::string Code = ("#include \"header.h\"\n" + Input).str(); |
| 108 | auto Factory = newFrontendActionFactory(ConsumerFactory: &MatchFinder); |
| 109 | if (!runToolOnCodeWithArgs( |
| 110 | ToolAction: Factory->create(), Code, Args: std::vector<std::string>(), FileName: "input.cc" , |
| 111 | ToolName: "clang-tool" , PCHContainerOps: std::make_shared<PCHContainerOperations>(), |
| 112 | VirtualMappedFiles: FileContents)) { |
| 113 | llvm::errs() << "Running tool failed.\n" ; |
| 114 | return std::nullopt; |
| 115 | } |
| 116 | if (ErrorCount != 0) { |
| 117 | llvm::errs() << "Generating changes failed.\n" ; |
| 118 | return std::nullopt; |
| 119 | } |
| 120 | auto ChangedCode = |
| 121 | applyAtomicChanges(FilePath: "input.cc" , Code, Changes, Spec: ApplyChangesSpec()); |
| 122 | if (!ChangedCode) { |
| 123 | llvm::errs() << "Applying changes failed: " |
| 124 | << llvm::toString(E: ChangedCode.takeError()) << "\n" ; |
| 125 | return std::nullopt; |
| 126 | } |
| 127 | return *ChangedCode; |
| 128 | } |
| 129 | |
| 130 | Transformer::ChangeSetConsumer consumer() { |
| 131 | return [this](Expected<MutableArrayRef<AtomicChange>> C) { |
| 132 | if (C) { |
| 133 | Changes.insert(position: Changes.end(), first: std::make_move_iterator(i: C->begin()), |
| 134 | last: std::make_move_iterator(i: C->end())); |
| 135 | } else { |
| 136 | // FIXME: stash this error rather than printing. |
| 137 | llvm::errs() << "Error generating changes: " |
| 138 | << llvm::toString(E: C.takeError()) << "\n" ; |
| 139 | ++ErrorCount; |
| 140 | } |
| 141 | }; |
| 142 | } |
| 143 | |
| 144 | auto consumerWithStringMetadata() { |
| 145 | return [this](Expected<TransformerResult<std::string>> C) { |
| 146 | if (C) { |
| 147 | Changes.insert(position: Changes.end(), |
| 148 | first: std::make_move_iterator(i: C->Changes.begin()), |
| 149 | last: std::make_move_iterator(i: C->Changes.end())); |
| 150 | StringMetadata.push_back(x: std::move(C->Metadata)); |
| 151 | } else { |
| 152 | // FIXME: stash this error rather than printing. |
| 153 | llvm::errs() << "Error generating changes: " |
| 154 | << llvm::toString(E: C.takeError()) << "\n" ; |
| 155 | ++ErrorCount; |
| 156 | } |
| 157 | }; |
| 158 | } |
| 159 | |
| 160 | void testRule(RewriteRule Rule, StringRef Input, StringRef Expected) { |
| 161 | Transformers.push_back( |
| 162 | x: std::make_unique<Transformer>(args: std::move(Rule), args: consumer())); |
| 163 | Transformers.back()->registerMatchers(MatchFinder: &MatchFinder); |
| 164 | compareSnippets(Expected, MaybeActual: rewrite(Input)); |
| 165 | } |
| 166 | |
| 167 | void testRule(RewriteRuleWith<std::string> Rule, StringRef Input, |
| 168 | StringRef Expected) { |
| 169 | Transformers.push_back(x: std::make_unique<Transformer>( |
| 170 | args: std::move(Rule), args: consumerWithStringMetadata())); |
| 171 | Transformers.back()->registerMatchers(MatchFinder: &MatchFinder); |
| 172 | compareSnippets(Expected, MaybeActual: rewrite(Input)); |
| 173 | } |
| 174 | |
| 175 | void testRuleFailure(RewriteRule Rule, StringRef Input) { |
| 176 | Transformers.push_back( |
| 177 | x: std::make_unique<Transformer>(args: std::move(Rule), args: consumer())); |
| 178 | Transformers.back()->registerMatchers(MatchFinder: &MatchFinder); |
| 179 | ASSERT_FALSE(rewrite(Input)) << "Expected failure to rewrite code" ; |
| 180 | } |
| 181 | |
| 182 | void testRuleFailure(RewriteRuleWith<std::string> Rule, StringRef Input) { |
| 183 | Transformers.push_back(x: std::make_unique<Transformer>( |
| 184 | args: std::move(Rule), args: consumerWithStringMetadata())); |
| 185 | Transformers.back()->registerMatchers(MatchFinder: &MatchFinder); |
| 186 | ASSERT_FALSE(rewrite(Input)) << "Expected failure to rewrite code" ; |
| 187 | } |
| 188 | |
| 189 | // Transformers are referenced by MatchFinder. |
| 190 | std::vector<std::unique_ptr<Transformer>> Transformers; |
| 191 | clang::ast_matchers::MatchFinder MatchFinder; |
| 192 | // Records whether any errors occurred in individual changes. |
| 193 | int ErrorCount = 0; |
| 194 | AtomicChanges Changes; |
| 195 | std::vector<std::string> StringMetadata; |
| 196 | |
| 197 | private: |
| 198 | FileContentMappings FileContents = {{"header.h" , "" }}; |
| 199 | }; |
| 200 | |
| 201 | class TransformerTest : public ClangRefactoringTestBase { |
| 202 | protected: |
| 203 | TransformerTest() { appendToHeader(S: KHeaderContents); } |
| 204 | }; |
| 205 | |
| 206 | // Given string s, change strlen($s.c_str()) to REPLACED. |
| 207 | static RewriteRuleWith<std::string> ruleStrlenSize() { |
| 208 | StringRef StringExpr = "strexpr" ; |
| 209 | auto StringType = namedDecl(hasAnyName("::basic_string" , "::string" )); |
| 210 | auto R = makeRule( |
| 211 | M: callExpr(callee(InnerMatcher: functionDecl(hasName(Name: "strlen" ))), |
| 212 | hasArgument(N: 0, InnerMatcher: cxxMemberCallExpr( |
| 213 | on(InnerMatcher: expr(hasType(InnerMatcher: isOrPointsTo(TypeMatcher: StringType))) |
| 214 | .bind(ID: StringExpr)), |
| 215 | callee(InnerMatcher: cxxMethodDecl(hasName(Name: "c_str" )))))), |
| 216 | Edits: changeTo(Replacement: cat(Parts: "REPLACED" )), Metadata: cat(Parts: "Use size() method directly on string." )); |
| 217 | return R; |
| 218 | } |
| 219 | |
| 220 | TEST_F(TransformerTest, StrlenSize) { |
| 221 | std::string Input = "int f(string s) { return strlen(s.c_str()); }" ; |
| 222 | std::string Expected = "int f(string s) { return REPLACED; }" ; |
| 223 | testRule(Rule: ruleStrlenSize(), Input, Expected); |
| 224 | } |
| 225 | |
| 226 | // Tests that no change is applied when a match is not expected. |
| 227 | TEST_F(TransformerTest, NoMatch) { |
| 228 | std::string Input = "int f(string s) { return s.size(); }" ; |
| 229 | testRule(Rule: ruleStrlenSize(), Input, Expected: Input); |
| 230 | } |
| 231 | |
| 232 | // Tests replacing an expression. |
| 233 | TEST_F(TransformerTest, Flag) { |
| 234 | StringRef Flag = "flag" ; |
| 235 | RewriteRule Rule = makeRule( |
| 236 | M: cxxMemberCallExpr(on(InnerMatcher: expr(hasType(InnerMatcher: cxxRecordDecl( |
| 237 | hasName(Name: "proto::ProtoCommandLineFlag" )))) |
| 238 | .bind(ID: Flag)), |
| 239 | unless(callee(InnerMatcher: cxxMethodDecl(hasName(Name: "GetProto" ))))), |
| 240 | Edits: changeTo(Target: node(ID: std::string(Flag)), Replacement: cat(Parts: "EXPR" ))); |
| 241 | |
| 242 | std::string Input = R"cc( |
| 243 | proto::ProtoCommandLineFlag flag; |
| 244 | int x = flag.foo(); |
| 245 | int y = flag.GetProto().foo(); |
| 246 | )cc" ; |
| 247 | std::string Expected = R"cc( |
| 248 | proto::ProtoCommandLineFlag flag; |
| 249 | int x = EXPR.foo(); |
| 250 | int y = flag.GetProto().foo(); |
| 251 | )cc" ; |
| 252 | |
| 253 | testRule(Rule: std::move(Rule), Input, Expected); |
| 254 | } |
| 255 | |
| 256 | TEST_F(TransformerTest, AddIncludeQuoted) { |
| 257 | RewriteRule Rule = |
| 258 | makeRule(M: callExpr(callee(InnerMatcher: functionDecl(hasName(Name: "f" )))), |
| 259 | Edits: {addInclude(Header: "clang/OtherLib.h" ), changeTo(Replacement: cat(Parts: "other()" ))}); |
| 260 | |
| 261 | std::string Input = R"cc( |
| 262 | int f(int x); |
| 263 | int h(int x) { return f(x); } |
| 264 | )cc" ; |
| 265 | std::string Expected = R"cc(#include "clang/OtherLib.h" |
| 266 | |
| 267 | int f(int x); |
| 268 | int h(int x) { return other(); } |
| 269 | )cc" ; |
| 270 | |
| 271 | testRule(Rule, Input, Expected); |
| 272 | } |
| 273 | |
| 274 | TEST_F(TransformerTest, AddIncludeAngled) { |
| 275 | RewriteRule Rule = makeRule( |
| 276 | M: callExpr(callee(InnerMatcher: functionDecl(hasName(Name: "f" )))), |
| 277 | Edits: {addInclude(Header: "clang/OtherLib.h" , Format: transformer::IncludeFormat::Angled), |
| 278 | changeTo(Replacement: cat(Parts: "other()" ))}); |
| 279 | |
| 280 | std::string Input = R"cc( |
| 281 | int f(int x); |
| 282 | int h(int x) { return f(x); } |
| 283 | )cc" ; |
| 284 | std::string Expected = R"cc(#include <clang/OtherLib.h> |
| 285 | |
| 286 | int f(int x); |
| 287 | int h(int x) { return other(); } |
| 288 | )cc" ; |
| 289 | |
| 290 | testRule(Rule, Input, Expected); |
| 291 | } |
| 292 | |
| 293 | TEST_F(TransformerTest, AddIncludeQuotedForRule) { |
| 294 | RewriteRule Rule = makeRule(M: callExpr(callee(InnerMatcher: functionDecl(hasName(Name: "f" )))), |
| 295 | Edits: changeTo(Replacement: cat(Parts: "other()" ))); |
| 296 | addInclude(Rule, Header: "clang/OtherLib.h" ); |
| 297 | |
| 298 | std::string Input = R"cc( |
| 299 | int f(int x); |
| 300 | int h(int x) { return f(x); } |
| 301 | )cc" ; |
| 302 | std::string Expected = R"cc(#include "clang/OtherLib.h" |
| 303 | |
| 304 | int f(int x); |
| 305 | int h(int x) { return other(); } |
| 306 | )cc" ; |
| 307 | |
| 308 | testRule(Rule, Input, Expected); |
| 309 | } |
| 310 | |
| 311 | TEST_F(TransformerTest, AddIncludeAngledForRule) { |
| 312 | RewriteRule Rule = makeRule(M: callExpr(callee(InnerMatcher: functionDecl(hasName(Name: "f" )))), |
| 313 | Edits: changeTo(Replacement: cat(Parts: "other()" ))); |
| 314 | addInclude(Rule, Header: "clang/OtherLib.h" , Format: transformer::IncludeFormat::Angled); |
| 315 | |
| 316 | std::string Input = R"cc( |
| 317 | int f(int x); |
| 318 | int h(int x) { return f(x); } |
| 319 | )cc" ; |
| 320 | std::string Expected = R"cc(#include <clang/OtherLib.h> |
| 321 | |
| 322 | int f(int x); |
| 323 | int h(int x) { return other(); } |
| 324 | )cc" ; |
| 325 | |
| 326 | testRule(Rule, Input, Expected); |
| 327 | } |
| 328 | |
| 329 | TEST_F(TransformerTest, NodePartNameNamedDecl) { |
| 330 | StringRef Fun = "fun" ; |
| 331 | RewriteRule Rule = makeRule(M: functionDecl(hasName(Name: "bad" )).bind(ID: Fun), |
| 332 | Edits: changeTo(Target: name(ID: std::string(Fun)), Replacement: cat(Parts: "good" ))); |
| 333 | |
| 334 | std::string Input = R"cc( |
| 335 | int bad(int x); |
| 336 | int bad(int x) { return x * x; } |
| 337 | )cc" ; |
| 338 | std::string Expected = R"cc( |
| 339 | int good(int x); |
| 340 | int good(int x) { return x * x; } |
| 341 | )cc" ; |
| 342 | |
| 343 | testRule(Rule, Input, Expected); |
| 344 | } |
| 345 | |
| 346 | TEST_F(TransformerTest, NodePartNameDeclRef) { |
| 347 | std::string Input = R"cc( |
| 348 | template <typename T> |
| 349 | T bad(T x) { |
| 350 | return x; |
| 351 | } |
| 352 | int neutral(int x) { return bad<int>(x) * x; } |
| 353 | )cc" ; |
| 354 | std::string Expected = R"cc( |
| 355 | template <typename T> |
| 356 | T bad(T x) { |
| 357 | return x; |
| 358 | } |
| 359 | int neutral(int x) { return good<int>(x) * x; } |
| 360 | )cc" ; |
| 361 | |
| 362 | StringRef Ref = "ref" ; |
| 363 | testRule(Rule: makeRule(M: declRefExpr(to(InnerMatcher: functionDecl(hasName(Name: "bad" )))).bind(ID: Ref), |
| 364 | Edits: changeTo(Target: name(ID: std::string(Ref)), Replacement: cat(Parts: "good" ))), |
| 365 | Input, Expected); |
| 366 | } |
| 367 | |
| 368 | TEST_F(TransformerTest, NodePartNameDeclRefFailure) { |
| 369 | std::string Input = R"cc( |
| 370 | struct Y { |
| 371 | int operator*(); |
| 372 | }; |
| 373 | int neutral(int x) { |
| 374 | Y y; |
| 375 | int (Y::*ptr)() = &Y::operator*; |
| 376 | return *y + x; |
| 377 | } |
| 378 | )cc" ; |
| 379 | |
| 380 | StringRef Ref = "ref" ; |
| 381 | Transformer T(makeRule(M: declRefExpr(to(InnerMatcher: functionDecl())).bind(ID: Ref), |
| 382 | Edits: changeTo(Target: name(ID: std::string(Ref)), Replacement: cat(Parts: "good" ))), |
| 383 | consumer()); |
| 384 | T.registerMatchers(MatchFinder: &MatchFinder); |
| 385 | EXPECT_FALSE(rewrite(Input)); |
| 386 | } |
| 387 | |
| 388 | TEST_F(TransformerTest, NodePartMember) { |
| 389 | StringRef E = "expr" ; |
| 390 | RewriteRule Rule = |
| 391 | makeRule(M: memberExpr(clang::ast_matchers::member(InnerMatcher: hasName(Name: "bad" ))).bind(ID: E), |
| 392 | Edits: changeTo(Target: member(ID: std::string(E)), Replacement: cat(Parts: "good" ))); |
| 393 | |
| 394 | std::string Input = R"cc( |
| 395 | struct S { |
| 396 | int bad; |
| 397 | }; |
| 398 | int g() { |
| 399 | S s; |
| 400 | return s.bad; |
| 401 | } |
| 402 | )cc" ; |
| 403 | std::string Expected = R"cc( |
| 404 | struct S { |
| 405 | int bad; |
| 406 | }; |
| 407 | int g() { |
| 408 | S s; |
| 409 | return s.good; |
| 410 | } |
| 411 | )cc" ; |
| 412 | |
| 413 | testRule(Rule, Input, Expected); |
| 414 | } |
| 415 | |
| 416 | TEST_F(TransformerTest, NodePartMemberQualified) { |
| 417 | std::string Input = R"cc( |
| 418 | struct S { |
| 419 | int bad; |
| 420 | int good; |
| 421 | }; |
| 422 | struct T : public S { |
| 423 | int bad; |
| 424 | }; |
| 425 | int g() { |
| 426 | T t; |
| 427 | return t.S::bad; |
| 428 | } |
| 429 | )cc" ; |
| 430 | std::string Expected = R"cc( |
| 431 | struct S { |
| 432 | int bad; |
| 433 | int good; |
| 434 | }; |
| 435 | struct T : public S { |
| 436 | int bad; |
| 437 | }; |
| 438 | int g() { |
| 439 | T t; |
| 440 | return t.S::good; |
| 441 | } |
| 442 | )cc" ; |
| 443 | |
| 444 | StringRef E = "expr" ; |
| 445 | testRule(Rule: makeRule(M: memberExpr().bind(ID: E), |
| 446 | Edits: changeTo(Target: member(ID: std::string(E)), Replacement: cat(Parts: "good" ))), |
| 447 | Input, Expected); |
| 448 | } |
| 449 | |
| 450 | TEST_F(TransformerTest, NodePartMemberMultiToken) { |
| 451 | std::string Input = R"cc( |
| 452 | struct Y { |
| 453 | int operator*(); |
| 454 | int good(); |
| 455 | template <typename T> void foo(T t); |
| 456 | }; |
| 457 | int neutral(int x) { |
| 458 | Y y; |
| 459 | y.template foo<int>(3); |
| 460 | return y.operator *(); |
| 461 | } |
| 462 | )cc" ; |
| 463 | std::string Expected = R"cc( |
| 464 | struct Y { |
| 465 | int operator*(); |
| 466 | int good(); |
| 467 | template <typename T> void foo(T t); |
| 468 | }; |
| 469 | int neutral(int x) { |
| 470 | Y y; |
| 471 | y.template good<int>(3); |
| 472 | return y.good(); |
| 473 | } |
| 474 | )cc" ; |
| 475 | |
| 476 | StringRef MemExpr = "member" ; |
| 477 | testRule(Rule: makeRule(M: memberExpr().bind(ID: MemExpr), |
| 478 | Edits: changeTo(Target: member(ID: std::string(MemExpr)), Replacement: cat(Parts: "good" ))), |
| 479 | Input, Expected); |
| 480 | } |
| 481 | |
| 482 | TEST_F(TransformerTest, NoEdits) { |
| 483 | using transformer::noEdits; |
| 484 | std::string Input = "int f(int x) { return x; }" ; |
| 485 | testRule(Rule: makeRule(M: returnStmt().bind(ID: "return" ), Edits: noEdits()), Input, Expected: Input); |
| 486 | } |
| 487 | |
| 488 | TEST_F(TransformerTest, NoopEdit) { |
| 489 | using transformer::node; |
| 490 | using transformer::noopEdit; |
| 491 | std::string Input = "int f(int x) { return x; }" ; |
| 492 | testRule(Rule: makeRule(M: returnStmt().bind(ID: "return" ), Edits: noopEdit(Anchor: node(ID: "return" ))), |
| 493 | Input, Expected: Input); |
| 494 | } |
| 495 | |
| 496 | TEST_F(TransformerTest, IfBound2Args) { |
| 497 | using transformer::ifBound; |
| 498 | std::string Input = "int f(int x) { return x; }" ; |
| 499 | std::string Expected = "int f(int x) { CHANGE; }" ; |
| 500 | testRule(Rule: makeRule(M: returnStmt().bind(ID: "return" ), |
| 501 | Edits: ifBound(ID: "return" , TrueEdit: changeTo(Replacement: cat(Parts: "CHANGE;" )))), |
| 502 | Input, Expected); |
| 503 | } |
| 504 | |
| 505 | TEST_F(TransformerTest, IfBound3Args) { |
| 506 | using transformer::ifBound; |
| 507 | std::string Input = "int f(int x) { return x; }" ; |
| 508 | std::string Expected = "int f(int x) { CHANGE; }" ; |
| 509 | testRule(Rule: makeRule(M: returnStmt().bind(ID: "return" ), |
| 510 | Edits: ifBound(ID: "nothing" , TrueEdit: changeTo(Replacement: cat(Parts: "ERROR" )), |
| 511 | FalseEdit: changeTo(Replacement: cat(Parts: "CHANGE;" )))), |
| 512 | Input, Expected); |
| 513 | } |
| 514 | |
| 515 | TEST_F(TransformerTest, ShrinkTo) { |
| 516 | using transformer::shrinkTo; |
| 517 | std::string Input = "int f(int x) { return x; }" ; |
| 518 | std::string Expected = "return x;" ; |
| 519 | testRule(Rule: makeRule(M: functionDecl(hasDescendant(returnStmt().bind(ID: "return" ))) |
| 520 | .bind(ID: "function" ), |
| 521 | Edits: shrinkTo(outer: node(ID: "function" ), inner: node(ID: "return" ))), |
| 522 | Input, Expected); |
| 523 | } |
| 524 | |
| 525 | // Rewrite various Stmts inside a Decl. |
| 526 | TEST_F(TransformerTest, RewriteDescendantsDeclChangeStmt) { |
| 527 | std::string Input = |
| 528 | "int f(int x) { int y = x; { int z = x * x; } return x; }" ; |
| 529 | std::string Expected = |
| 530 | "int f(int x) { int y = 3; { int z = 3 * 3; } return 3; }" ; |
| 531 | auto InlineX = |
| 532 | makeRule(M: declRefExpr(to(InnerMatcher: varDecl(hasName(Name: "x" )))), Edits: changeTo(Replacement: cat(Parts: "3" ))); |
| 533 | testRule(Rule: makeRule(M: functionDecl(hasName(Name: "f" )).bind(ID: "fun" ), |
| 534 | Edits: rewriteDescendants(NodeId: "fun" , Rule: InlineX)), |
| 535 | Input, Expected); |
| 536 | } |
| 537 | |
| 538 | // Rewrite various TypeLocs inside a Decl. |
| 539 | TEST_F(TransformerTest, RewriteDescendantsDeclChangeTypeLoc) { |
| 540 | std::string Input = "int f(int *x) { return *x; }" ; |
| 541 | std::string Expected = "char f(char *x) { return *x; }" ; |
| 542 | auto IntToChar = makeRule(M: typeLoc(loc(InnerMatcher: qualType(isInteger(), builtinType()))), |
| 543 | Edits: changeTo(Replacement: cat(Parts: "char" ))); |
| 544 | testRule(Rule: makeRule(M: functionDecl(hasName(Name: "f" )).bind(ID: "fun" ), |
| 545 | Edits: rewriteDescendants(NodeId: "fun" , Rule: IntToChar)), |
| 546 | Input, Expected); |
| 547 | } |
| 548 | |
| 549 | TEST_F(TransformerTest, RewriteDescendantsStmt) { |
| 550 | // Add an unrelated definition to the header that also has a variable named |
| 551 | // "x", to test that the rewrite is limited to the scope we intend. |
| 552 | appendToHeader(S: R"cc(int g(int x) { return x; })cc" ); |
| 553 | std::string Input = |
| 554 | "int f(int x) { int y = x; { int z = x * x; } return x; }" ; |
| 555 | std::string Expected = |
| 556 | "int f(int x) { int y = 3; { int z = 3 * 3; } return 3; }" ; |
| 557 | auto InlineX = |
| 558 | makeRule(M: declRefExpr(to(InnerMatcher: varDecl(hasName(Name: "x" )))), Edits: changeTo(Replacement: cat(Parts: "3" ))); |
| 559 | testRule(Rule: makeRule(M: functionDecl(hasName(Name: "f" ), hasBody(InnerMatcher: stmt().bind(ID: "body" ))), |
| 560 | Edits: rewriteDescendants(NodeId: "body" , Rule: InlineX)), |
| 561 | Input, Expected); |
| 562 | } |
| 563 | |
| 564 | TEST_F(TransformerTest, RewriteDescendantsStmtWithAdditionalChange) { |
| 565 | std::string Input = |
| 566 | "int f(int x) { int y = x; { int z = x * x; } return x; }" ; |
| 567 | std::string Expected = |
| 568 | "int newName(int x) { int y = 3; { int z = 3 * 3; } return 3; }" ; |
| 569 | auto InlineX = |
| 570 | makeRule(M: declRefExpr(to(InnerMatcher: varDecl(hasName(Name: "x" )))), Edits: changeTo(Replacement: cat(Parts: "3" ))); |
| 571 | testRule( |
| 572 | Rule: makeRule( |
| 573 | M: functionDecl(hasName(Name: "f" ), hasBody(InnerMatcher: stmt().bind(ID: "body" ))).bind(ID: "f" ), |
| 574 | Edits: flatten(Edits: changeTo(Target: name(ID: "f" ), Replacement: cat(Parts: "newName" )), |
| 575 | Edits: rewriteDescendants(NodeId: "body" , Rule: InlineX))), |
| 576 | Input, Expected); |
| 577 | } |
| 578 | |
| 579 | TEST_F(TransformerTest, RewriteDescendantsTypeLoc) { |
| 580 | std::string Input = "int f(int *x) { return *x; }" ; |
| 581 | std::string Expected = "int f(char *x) { return *x; }" ; |
| 582 | auto IntToChar = |
| 583 | makeRule(M: typeLoc(loc(InnerMatcher: qualType(isInteger(), builtinType()))).bind(ID: "loc" ), |
| 584 | Edits: changeTo(Replacement: cat(Parts: "char" ))); |
| 585 | testRule( |
| 586 | Rule: makeRule(M: functionDecl(hasName(Name: "f" ), |
| 587 | hasParameter(N: 0, InnerMatcher: varDecl(hasTypeLoc( |
| 588 | Inner: typeLoc().bind(ID: "parmType" ))))), |
| 589 | Edits: rewriteDescendants(NodeId: "parmType" , Rule: IntToChar)), |
| 590 | Input, Expected); |
| 591 | } |
| 592 | |
| 593 | TEST_F(TransformerTest, RewriteDescendantsReferToParentBinding) { |
| 594 | std::string Input = |
| 595 | "int f(int p) { int y = p; { int z = p * p; } return p; }" ; |
| 596 | std::string Expected = |
| 597 | "int f(int p) { int y = 3; { int z = 3 * 3; } return 3; }" ; |
| 598 | std::string VarId = "var" ; |
| 599 | auto InlineVar = makeRule(M: declRefExpr(to(InnerMatcher: varDecl(equalsBoundNode(ID: VarId)))), |
| 600 | Edits: changeTo(Replacement: cat(Parts: "3" ))); |
| 601 | testRule(Rule: makeRule(M: functionDecl(hasName(Name: "f" ), |
| 602 | hasParameter(N: 0, InnerMatcher: varDecl().bind(ID: VarId))) |
| 603 | .bind(ID: "fun" ), |
| 604 | Edits: rewriteDescendants(NodeId: "fun" , Rule: InlineVar)), |
| 605 | Input, Expected); |
| 606 | } |
| 607 | |
| 608 | TEST_F(TransformerTest, RewriteDescendantsUnboundNode) { |
| 609 | std::string Input = |
| 610 | "int f(int x) { int y = x; { int z = x * x; } return x; }" ; |
| 611 | auto InlineX = |
| 612 | makeRule(M: declRefExpr(to(InnerMatcher: varDecl(hasName(Name: "x" )))), Edits: changeTo(Replacement: cat(Parts: "3" ))); |
| 613 | Transformer T(makeRule(M: functionDecl(hasName(Name: "f" )), |
| 614 | Edits: rewriteDescendants(NodeId: "UNBOUND" , Rule: InlineX)), |
| 615 | consumer()); |
| 616 | T.registerMatchers(MatchFinder: &MatchFinder); |
| 617 | EXPECT_FALSE(rewrite(Input)); |
| 618 | EXPECT_THAT(Changes, IsEmpty()); |
| 619 | EXPECT_EQ(ErrorCount, 1); |
| 620 | } |
| 621 | |
| 622 | TEST_F(TransformerTest, RewriteDescendantsInvalidNodeType) { |
| 623 | std::string Input = |
| 624 | "int f(int x) { int y = x; { int z = x * x; } return x; }" ; |
| 625 | auto IntToChar = |
| 626 | makeRule(M: qualType(isInteger(), builtinType()), Edits: changeTo(Replacement: cat(Parts: "char" ))); |
| 627 | Transformer T( |
| 628 | makeRule(M: functionDecl( |
| 629 | hasName(Name: "f" ), |
| 630 | hasParameter(N: 0, InnerMatcher: varDecl(hasType(InnerMatcher: qualType().bind(ID: "type" ))))), |
| 631 | Edits: rewriteDescendants(NodeId: "type" , Rule: IntToChar)), |
| 632 | consumer()); |
| 633 | T.registerMatchers(MatchFinder: &MatchFinder); |
| 634 | EXPECT_FALSE(rewrite(Input)); |
| 635 | EXPECT_THAT(Changes, IsEmpty()); |
| 636 | EXPECT_EQ(ErrorCount, 1); |
| 637 | } |
| 638 | |
| 639 | // |
| 640 | // We include one test per typed overload. We don't test extensively since that |
| 641 | // is already covered by the tests above. |
| 642 | // |
| 643 | |
| 644 | TEST_F(TransformerTest, RewriteDescendantsTypedStmt) { |
| 645 | // Add an unrelated definition to the header that also has a variable named |
| 646 | // "x", to test that the rewrite is limited to the scope we intend. |
| 647 | appendToHeader(S: R"cc(int g(int x) { return x; })cc" ); |
| 648 | std::string Input = |
| 649 | "int f(int x) { int y = x; { int z = x * x; } return x; }" ; |
| 650 | std::string Expected = |
| 651 | "int f(int x) { int y = 3; { int z = 3 * 3; } return 3; }" ; |
| 652 | auto InlineX = |
| 653 | makeRule(M: declRefExpr(to(InnerMatcher: varDecl(hasName(Name: "x" )))), Edits: changeTo(Replacement: cat(Parts: "3" ))); |
| 654 | testRule(Rule: makeRule(M: functionDecl(hasName(Name: "f" ), hasBody(InnerMatcher: stmt().bind(ID: "body" ))), |
| 655 | Edits: [&InlineX](const MatchFinder::MatchResult &R) { |
| 656 | const auto *Node = R.Nodes.getNodeAs<Stmt>(ID: "body" ); |
| 657 | assert(Node != nullptr && "body must be bound" ); |
| 658 | return transformer::detail::rewriteDescendants( |
| 659 | Node: *Node, Rule: InlineX, Result: R); |
| 660 | }), |
| 661 | Input, Expected); |
| 662 | } |
| 663 | |
| 664 | TEST_F(TransformerTest, RewriteDescendantsTypedDecl) { |
| 665 | std::string Input = |
| 666 | "int f(int x) { int y = x; { int z = x * x; } return x; }" ; |
| 667 | std::string Expected = |
| 668 | "int f(int x) { int y = 3; { int z = 3 * 3; } return 3; }" ; |
| 669 | auto InlineX = |
| 670 | makeRule(M: declRefExpr(to(InnerMatcher: varDecl(hasName(Name: "x" )))), Edits: changeTo(Replacement: cat(Parts: "3" ))); |
| 671 | testRule(Rule: makeRule(M: functionDecl(hasName(Name: "f" )).bind(ID: "fun" ), |
| 672 | Edits: [&InlineX](const MatchFinder::MatchResult &R) { |
| 673 | const auto *Node = R.Nodes.getNodeAs<Decl>(ID: "fun" ); |
| 674 | assert(Node != nullptr && "fun must be bound" ); |
| 675 | return transformer::detail::rewriteDescendants( |
| 676 | Node: *Node, Rule: InlineX, Result: R); |
| 677 | }), |
| 678 | Input, Expected); |
| 679 | } |
| 680 | |
| 681 | TEST_F(TransformerTest, RewriteDescendantsTypedTypeLoc) { |
| 682 | std::string Input = "int f(int *x) { return *x; }" ; |
| 683 | std::string Expected = "int f(char *x) { return *x; }" ; |
| 684 | auto IntToChar = |
| 685 | makeRule(M: typeLoc(loc(InnerMatcher: qualType(isInteger(), builtinType()))).bind(ID: "loc" ), |
| 686 | Edits: changeTo(Replacement: cat(Parts: "char" ))); |
| 687 | testRule( |
| 688 | Rule: makeRule( |
| 689 | M: functionDecl( |
| 690 | hasName(Name: "f" ), |
| 691 | hasParameter(N: 0, InnerMatcher: varDecl(hasTypeLoc(Inner: typeLoc().bind(ID: "parmType" ))))), |
| 692 | Edits: [&IntToChar](const MatchFinder::MatchResult &R) { |
| 693 | const auto *Node = R.Nodes.getNodeAs<TypeLoc>(ID: "parmType" ); |
| 694 | assert(Node != nullptr && "parmType must be bound" ); |
| 695 | return transformer::detail::rewriteDescendants(Node: *Node, Rule: IntToChar, Result: R); |
| 696 | }), |
| 697 | Input, Expected); |
| 698 | } |
| 699 | |
| 700 | TEST_F(TransformerTest, RewriteDescendantsTypedDynTyped) { |
| 701 | // Add an unrelated definition to the header that also has a variable named |
| 702 | // "x", to test that the rewrite is limited to the scope we intend. |
| 703 | appendToHeader(S: R"cc(int g(int x) { return x; })cc" ); |
| 704 | std::string Input = |
| 705 | "int f(int x) { int y = x; { int z = x * x; } return x; }" ; |
| 706 | std::string Expected = |
| 707 | "int f(int x) { int y = 3; { int z = 3 * 3; } return 3; }" ; |
| 708 | auto InlineX = |
| 709 | makeRule(M: declRefExpr(to(InnerMatcher: varDecl(hasName(Name: "x" )))), Edits: changeTo(Replacement: cat(Parts: "3" ))); |
| 710 | testRule( |
| 711 | Rule: makeRule(M: functionDecl(hasName(Name: "f" ), hasBody(InnerMatcher: stmt().bind(ID: "body" ))), |
| 712 | Edits: [&InlineX](const MatchFinder::MatchResult &R) { |
| 713 | auto It = R.Nodes.getMap().find(x: "body" ); |
| 714 | assert(It != R.Nodes.getMap().end() && "body must be bound" ); |
| 715 | return transformer::detail::rewriteDescendants(It->second, |
| 716 | InlineX, R); |
| 717 | }), |
| 718 | Input, Expected); |
| 719 | } |
| 720 | |
| 721 | TEST_F(TransformerTest, InsertBeforeEdit) { |
| 722 | std::string Input = R"cc( |
| 723 | int f() { |
| 724 | return 7; |
| 725 | } |
| 726 | )cc" ; |
| 727 | std::string Expected = R"cc( |
| 728 | int f() { |
| 729 | int y = 3; |
| 730 | return 7; |
| 731 | } |
| 732 | )cc" ; |
| 733 | |
| 734 | StringRef Ret = "return" ; |
| 735 | testRule( |
| 736 | Rule: makeRule(M: returnStmt().bind(ID: Ret), |
| 737 | Edits: insertBefore(S: statement(ID: std::string(Ret)), Replacement: cat(Parts: "int y = 3;" ))), |
| 738 | Input, Expected); |
| 739 | } |
| 740 | |
| 741 | TEST_F(TransformerTest, InsertAfterEdit) { |
| 742 | std::string Input = R"cc( |
| 743 | int f() { |
| 744 | int x = 5; |
| 745 | return 7; |
| 746 | } |
| 747 | )cc" ; |
| 748 | std::string Expected = R"cc( |
| 749 | int f() { |
| 750 | int x = 5; |
| 751 | int y = 3; |
| 752 | return 7; |
| 753 | } |
| 754 | )cc" ; |
| 755 | |
| 756 | StringRef Decl = "decl" ; |
| 757 | testRule( |
| 758 | Rule: makeRule(M: declStmt().bind(ID: Decl), |
| 759 | Edits: insertAfter(S: statement(ID: std::string(Decl)), Replacement: cat(Parts: "int y = 3;" ))), |
| 760 | Input, Expected); |
| 761 | } |
| 762 | |
| 763 | TEST_F(TransformerTest, RemoveEdit) { |
| 764 | std::string Input = R"cc( |
| 765 | int f() { |
| 766 | int x = 5; |
| 767 | return 7; |
| 768 | } |
| 769 | )cc" ; |
| 770 | std::string Expected = R"cc( |
| 771 | int f() { |
| 772 | return 7; |
| 773 | } |
| 774 | )cc" ; |
| 775 | |
| 776 | StringRef Decl = "decl" ; |
| 777 | testRule( |
| 778 | Rule: makeRule(M: declStmt().bind(ID: Decl), Edits: remove(S: statement(ID: std::string(Decl)))), |
| 779 | Input, Expected); |
| 780 | } |
| 781 | |
| 782 | TEST_F(TransformerTest, WithMetadata) { |
| 783 | auto makeMetadata = [](const MatchFinder::MatchResult &R) -> llvm::Any { |
| 784 | int N = |
| 785 | R.Nodes.getNodeAs<IntegerLiteral>(ID: "int" )->getValue().getLimitedValue(); |
| 786 | return N; |
| 787 | }; |
| 788 | |
| 789 | std::string Input = R"cc( |
| 790 | int f() { |
| 791 | int x = 5; |
| 792 | return 7; |
| 793 | } |
| 794 | )cc" ; |
| 795 | |
| 796 | Transformer T( |
| 797 | makeRule( |
| 798 | M: declStmt(containsDeclaration(N: 0, InnerMatcher: varDecl(hasInitializer( |
| 799 | InnerMatcher: integerLiteral().bind(ID: "int" ))))) |
| 800 | .bind(ID: "decl" ), |
| 801 | Edits: withMetadata(Edit: remove(S: statement(ID: std::string("decl" ))), Metadata: makeMetadata)), |
| 802 | consumer()); |
| 803 | T.registerMatchers(MatchFinder: &MatchFinder); |
| 804 | auto Factory = newFrontendActionFactory(ConsumerFactory: &MatchFinder); |
| 805 | EXPECT_TRUE(runToolOnCodeWithArgs( |
| 806 | Factory->create(), Input, std::vector<std::string>(), "input.cc" , |
| 807 | "clang-tool" , std::make_shared<PCHContainerOperations>(), {})); |
| 808 | ASSERT_EQ(Changes.size(), 1u); |
| 809 | const llvm::Any &Metadata = Changes[0].getMetadata(); |
| 810 | ASSERT_TRUE(llvm::any_cast<int>(&Metadata)); |
| 811 | EXPECT_THAT(llvm::any_cast<int>(Metadata), 5); |
| 812 | } |
| 813 | |
| 814 | TEST_F(TransformerTest, MultiChange) { |
| 815 | std::string Input = R"cc( |
| 816 | void foo() { |
| 817 | if (10 > 1.0) |
| 818 | log(1) << "oh no!"; |
| 819 | else |
| 820 | log(0) << "ok"; |
| 821 | } |
| 822 | )cc" ; |
| 823 | std::string Expected = R"( |
| 824 | void foo() { |
| 825 | if (true) { /* then */ } |
| 826 | else { /* else */ } |
| 827 | } |
| 828 | )" ; |
| 829 | |
| 830 | StringRef C = "C" , T = "T" , E = "E" ; |
| 831 | testRule( |
| 832 | Rule: makeRule(M: ifStmt(hasCondition(InnerMatcher: expr().bind(ID: C)), hasThen(InnerMatcher: stmt().bind(ID: T)), |
| 833 | hasElse(InnerMatcher: stmt().bind(ID: E))), |
| 834 | Edits: {changeTo(Target: node(ID: std::string(C)), Replacement: cat(Parts: "true" )), |
| 835 | changeTo(Target: statement(ID: std::string(T)), Replacement: cat(Parts: "{ /* then */ }" )), |
| 836 | changeTo(Target: statement(ID: std::string(E)), Replacement: cat(Parts: "{ /* else */ }" ))}), |
| 837 | Input, Expected); |
| 838 | } |
| 839 | |
| 840 | TEST_F(TransformerTest, EditList) { |
| 841 | std::string Input = R"cc( |
| 842 | void foo() { |
| 843 | if (10 > 1.0) |
| 844 | log(1) << "oh no!"; |
| 845 | else |
| 846 | log(0) << "ok"; |
| 847 | } |
| 848 | )cc" ; |
| 849 | std::string Expected = R"( |
| 850 | void foo() { |
| 851 | if (true) { /* then */ } |
| 852 | else { /* else */ } |
| 853 | } |
| 854 | )" ; |
| 855 | |
| 856 | StringRef C = "C" , T = "T" , E = "E" ; |
| 857 | testRule(Rule: makeRule(M: ifStmt(hasCondition(InnerMatcher: expr().bind(ID: C)), |
| 858 | hasThen(InnerMatcher: stmt().bind(ID: T)), hasElse(InnerMatcher: stmt().bind(ID: E))), |
| 859 | Edits: editList(Edits: {changeTo(Target: node(ID: std::string(C)), Replacement: cat(Parts: "true" )), |
| 860 | changeTo(Target: statement(ID: std::string(T)), |
| 861 | Replacement: cat(Parts: "{ /* then */ }" )), |
| 862 | changeTo(Target: statement(ID: std::string(E)), |
| 863 | Replacement: cat(Parts: "{ /* else */ }" ))})), |
| 864 | Input, Expected); |
| 865 | } |
| 866 | |
| 867 | TEST_F(TransformerTest, Flatten) { |
| 868 | std::string Input = R"cc( |
| 869 | void foo() { |
| 870 | if (10 > 1.0) |
| 871 | log(1) << "oh no!"; |
| 872 | else |
| 873 | log(0) << "ok"; |
| 874 | } |
| 875 | )cc" ; |
| 876 | std::string Expected = R"( |
| 877 | void foo() { |
| 878 | if (true) { /* then */ } |
| 879 | else { /* else */ } |
| 880 | } |
| 881 | )" ; |
| 882 | |
| 883 | StringRef C = "C" , T = "T" , E = "E" ; |
| 884 | testRule( |
| 885 | Rule: makeRule( |
| 886 | M: ifStmt(hasCondition(InnerMatcher: expr().bind(ID: C)), hasThen(InnerMatcher: stmt().bind(ID: T)), |
| 887 | hasElse(InnerMatcher: stmt().bind(ID: E))), |
| 888 | Edits: flatten(Edits: changeTo(Target: node(ID: std::string(C)), Replacement: cat(Parts: "true" )), |
| 889 | Edits: changeTo(Target: statement(ID: std::string(T)), Replacement: cat(Parts: "{ /* then */ }" )), |
| 890 | Edits: changeTo(Target: statement(ID: std::string(E)), Replacement: cat(Parts: "{ /* else */ }" )))), |
| 891 | Input, Expected); |
| 892 | } |
| 893 | |
| 894 | TEST_F(TransformerTest, FlattenWithMixedArgs) { |
| 895 | using clang::transformer::editList; |
| 896 | std::string Input = R"cc( |
| 897 | void foo() { |
| 898 | if (10 > 1.0) |
| 899 | log(1) << "oh no!"; |
| 900 | else |
| 901 | log(0) << "ok"; |
| 902 | } |
| 903 | )cc" ; |
| 904 | std::string Expected = R"( |
| 905 | void foo() { |
| 906 | if (true) { /* then */ } |
| 907 | else { /* else */ } |
| 908 | } |
| 909 | )" ; |
| 910 | |
| 911 | StringRef C = "C" , T = "T" , E = "E" ; |
| 912 | testRule(Rule: makeRule(M: ifStmt(hasCondition(InnerMatcher: expr().bind(ID: C)), |
| 913 | hasThen(InnerMatcher: stmt().bind(ID: T)), hasElse(InnerMatcher: stmt().bind(ID: E))), |
| 914 | Edits: flatten(Edits: changeTo(Target: node(ID: std::string(C)), Replacement: cat(Parts: "true" )), |
| 915 | Edits: edit(E: changeTo(Target: statement(ID: std::string(T)), |
| 916 | Replacement: cat(Parts: "{ /* then */ }" ))), |
| 917 | Edits: editList(Edits: {changeTo(Target: statement(ID: std::string(E)), |
| 918 | Replacement: cat(Parts: "{ /* else */ }" ))}))), |
| 919 | Input, Expected); |
| 920 | } |
| 921 | |
| 922 | TEST_F(TransformerTest, OrderedRuleUnrelated) { |
| 923 | StringRef Flag = "flag" ; |
| 924 | RewriteRuleWith<std::string> FlagRule = makeRule( |
| 925 | M: cxxMemberCallExpr(on(InnerMatcher: expr(hasType(InnerMatcher: cxxRecordDecl( |
| 926 | hasName(Name: "proto::ProtoCommandLineFlag" )))) |
| 927 | .bind(ID: Flag)), |
| 928 | unless(callee(InnerMatcher: cxxMethodDecl(hasName(Name: "GetProto" ))))), |
| 929 | Edits: changeTo(Target: node(ID: std::string(Flag)), Replacement: cat(Parts: "PROTO" )), Metadata: cat(Parts: "" )); |
| 930 | |
| 931 | std::string Input = R"cc( |
| 932 | proto::ProtoCommandLineFlag flag; |
| 933 | int x = flag.foo(); |
| 934 | int y = flag.GetProto().foo(); |
| 935 | int f(string s) { return strlen(s.c_str()); } |
| 936 | )cc" ; |
| 937 | std::string Expected = R"cc( |
| 938 | proto::ProtoCommandLineFlag flag; |
| 939 | int x = PROTO.foo(); |
| 940 | int y = flag.GetProto().foo(); |
| 941 | int f(string s) { return REPLACED; } |
| 942 | )cc" ; |
| 943 | |
| 944 | testRule(Rule: applyFirst(Rules: {ruleStrlenSize(), FlagRule}), Input, Expected); |
| 945 | } |
| 946 | |
| 947 | TEST_F(TransformerTest, OrderedRuleRelated) { |
| 948 | std::string Input = R"cc( |
| 949 | void f1(); |
| 950 | void f2(); |
| 951 | void call_f1() { f1(); } |
| 952 | void call_f2() { f2(); } |
| 953 | )cc" ; |
| 954 | std::string Expected = R"cc( |
| 955 | void f1(); |
| 956 | void f2(); |
| 957 | void call_f1() { REPLACE_F1; } |
| 958 | void call_f2() { REPLACE_F1_OR_F2; } |
| 959 | )cc" ; |
| 960 | |
| 961 | RewriteRule ReplaceF1 = |
| 962 | makeRule(M: callExpr(callee(InnerMatcher: functionDecl(hasName(Name: "f1" )))), |
| 963 | Edits: changeTo(Replacement: cat(Parts: "REPLACE_F1" ))); |
| 964 | RewriteRule ReplaceF1OrF2 = |
| 965 | makeRule(M: callExpr(callee(InnerMatcher: functionDecl(hasAnyName("f1" , "f2" )))), |
| 966 | Edits: changeTo(Replacement: cat(Parts: "REPLACE_F1_OR_F2" ))); |
| 967 | testRule(Rule: applyFirst(Rules: {ReplaceF1, ReplaceF1OrF2}), Input, Expected); |
| 968 | } |
| 969 | |
| 970 | // Change the order of the rules to get a different result. When `ReplaceF1OrF2` |
| 971 | // comes first, it applies for both uses, so `ReplaceF1` never applies. |
| 972 | TEST_F(TransformerTest, OrderedRuleRelatedSwapped) { |
| 973 | std::string Input = R"cc( |
| 974 | void f1(); |
| 975 | void f2(); |
| 976 | void call_f1() { f1(); } |
| 977 | void call_f2() { f2(); } |
| 978 | )cc" ; |
| 979 | std::string Expected = R"cc( |
| 980 | void f1(); |
| 981 | void f2(); |
| 982 | void call_f1() { REPLACE_F1_OR_F2; } |
| 983 | void call_f2() { REPLACE_F1_OR_F2; } |
| 984 | )cc" ; |
| 985 | |
| 986 | RewriteRule ReplaceF1 = |
| 987 | makeRule(M: callExpr(callee(InnerMatcher: functionDecl(hasName(Name: "f1" )))), |
| 988 | Edits: changeTo(Replacement: cat(Parts: "REPLACE_F1" ))); |
| 989 | RewriteRule ReplaceF1OrF2 = |
| 990 | makeRule(M: callExpr(callee(InnerMatcher: functionDecl(hasAnyName("f1" , "f2" )))), |
| 991 | Edits: changeTo(Replacement: cat(Parts: "REPLACE_F1_OR_F2" ))); |
| 992 | testRule(Rule: applyFirst(Rules: {ReplaceF1OrF2, ReplaceF1}), Input, Expected); |
| 993 | } |
| 994 | |
| 995 | // Verify that a set of rules whose matchers have different base kinds works |
| 996 | // properly, including that `applyFirst` produces multiple matchers. We test |
| 997 | // two different kinds of rules: Expr and Decl. We place the Decl rule in the |
| 998 | // middle to test that `buildMatchers` works even when the kinds aren't grouped |
| 999 | // together. |
| 1000 | TEST_F(TransformerTest, OrderedRuleMultipleKinds) { |
| 1001 | std::string Input = R"cc( |
| 1002 | void f1(); |
| 1003 | void f2(); |
| 1004 | void call_f1() { f1(); } |
| 1005 | void call_f2() { f2(); } |
| 1006 | )cc" ; |
| 1007 | std::string Expected = R"cc( |
| 1008 | void f1(); |
| 1009 | void DECL_RULE(); |
| 1010 | void call_f1() { REPLACE_F1; } |
| 1011 | void call_f2() { REPLACE_F1_OR_F2; } |
| 1012 | )cc" ; |
| 1013 | |
| 1014 | RewriteRule ReplaceF1 = |
| 1015 | makeRule(M: callExpr(callee(InnerMatcher: functionDecl(hasName(Name: "f1" )))), |
| 1016 | Edits: changeTo(Replacement: cat(Parts: "REPLACE_F1" ))); |
| 1017 | RewriteRule ReplaceF1OrF2 = |
| 1018 | makeRule(M: callExpr(callee(InnerMatcher: functionDecl(hasAnyName("f1" , "f2" )))), |
| 1019 | Edits: changeTo(Replacement: cat(Parts: "REPLACE_F1_OR_F2" ))); |
| 1020 | RewriteRule DeclRule = makeRule(M: functionDecl(hasName(Name: "f2" )).bind(ID: "fun" ), |
| 1021 | Edits: changeTo(Target: name(ID: "fun" ), Replacement: cat(Parts: "DECL_RULE" ))); |
| 1022 | |
| 1023 | RewriteRule Rule = applyFirst(Rules: {ReplaceF1, DeclRule, ReplaceF1OrF2}); |
| 1024 | EXPECT_EQ(transformer::detail::buildMatchers(Rule).size(), 2UL); |
| 1025 | testRule(Rule, Input, Expected); |
| 1026 | } |
| 1027 | |
| 1028 | // Verifies that a rule with a top-level matcher for an implicit node (like |
| 1029 | // `implicitCastExpr`) works correctly -- the implicit nodes are not skipped. |
| 1030 | TEST_F(TransformerTest, OrderedRuleImplicitMatched) { |
| 1031 | std::string Input = R"cc( |
| 1032 | void f1(); |
| 1033 | int f2(); |
| 1034 | void call_f1() { f1(); } |
| 1035 | float call_f2() { return f2(); } |
| 1036 | )cc" ; |
| 1037 | std::string Expected = R"cc( |
| 1038 | void f1(); |
| 1039 | int f2(); |
| 1040 | void call_f1() { REPLACE_F1; } |
| 1041 | float call_f2() { return REPLACE_F2; } |
| 1042 | )cc" ; |
| 1043 | |
| 1044 | RewriteRule ReplaceF1 = |
| 1045 | makeRule(M: callExpr(callee(InnerMatcher: functionDecl(hasName(Name: "f1" )))), |
| 1046 | Edits: changeTo(Replacement: cat(Parts: "REPLACE_F1" ))); |
| 1047 | RewriteRule ReplaceF2 = |
| 1048 | makeRule(M: implicitCastExpr(hasSourceExpression(InnerMatcher: callExpr())), |
| 1049 | Edits: changeTo(Replacement: cat(Parts: "REPLACE_F2" ))); |
| 1050 | testRule(Rule: applyFirst(Rules: {ReplaceF1, ReplaceF2}), Input, Expected); |
| 1051 | } |
| 1052 | |
| 1053 | // |
| 1054 | // Negative tests (where we expect no transformation to occur). |
| 1055 | // |
| 1056 | |
| 1057 | // Tests for a conflict in edits from a single match for a rule. |
| 1058 | TEST_F(TransformerTest, TextGeneratorFailure) { |
| 1059 | std::string Input = "int conflictOneRule() { return 3 + 7; }" ; |
| 1060 | // Try to change the whole binary-operator expression AND one its operands: |
| 1061 | StringRef O = "O" ; |
| 1062 | class AlwaysFail : public transformer::MatchComputation<std::string> { |
| 1063 | llvm::Error eval(const ast_matchers::MatchFinder::MatchResult &, |
| 1064 | std::string *) const override { |
| 1065 | return llvm::createStringError(EC: llvm::errc::invalid_argument, S: "ERROR" ); |
| 1066 | } |
| 1067 | std::string toString() const override { return "AlwaysFail" ; } |
| 1068 | }; |
| 1069 | Transformer T( |
| 1070 | makeRule(M: binaryOperator().bind(ID: O), |
| 1071 | Edits: changeTo(Target: node(ID: std::string(O)), Replacement: std::make_shared<AlwaysFail>())), |
| 1072 | consumer()); |
| 1073 | T.registerMatchers(MatchFinder: &MatchFinder); |
| 1074 | EXPECT_FALSE(rewrite(Input)); |
| 1075 | EXPECT_THAT(Changes, IsEmpty()); |
| 1076 | EXPECT_EQ(ErrorCount, 1); |
| 1077 | } |
| 1078 | |
| 1079 | // Tests for a conflict in edits from a single match for a rule. |
| 1080 | TEST_F(TransformerTest, OverlappingEditsInRule) { |
| 1081 | std::string Input = "int conflictOneRule() { return 3 + 7; }" ; |
| 1082 | // Try to change the whole binary-operator expression AND one its operands: |
| 1083 | StringRef O = "O" , L = "L" ; |
| 1084 | Transformer T(makeRule(M: binaryOperator(hasLHS(InnerMatcher: expr().bind(ID: L))).bind(ID: O), |
| 1085 | Edits: {changeTo(Target: node(ID: std::string(O)), Replacement: cat(Parts: "DELETE_OP" )), |
| 1086 | changeTo(Target: node(ID: std::string(L)), Replacement: cat(Parts: "DELETE_LHS" ))}), |
| 1087 | consumer()); |
| 1088 | T.registerMatchers(MatchFinder: &MatchFinder); |
| 1089 | EXPECT_FALSE(rewrite(Input)); |
| 1090 | EXPECT_THAT(Changes, IsEmpty()); |
| 1091 | EXPECT_EQ(ErrorCount, 1); |
| 1092 | } |
| 1093 | |
| 1094 | // Tests for a conflict in edits across multiple matches (of the same rule). |
| 1095 | TEST_F(TransformerTest, OverlappingEditsMultipleMatches) { |
| 1096 | std::string Input = "int conflictOneRule() { return -7; }" ; |
| 1097 | // Try to change the whole binary-operator expression AND one its operands: |
| 1098 | StringRef E = "E" ; |
| 1099 | Transformer T(makeRule(M: expr().bind(ID: E), |
| 1100 | Edits: changeTo(Target: node(ID: std::string(E)), Replacement: cat(Parts: "DELETE_EXPR" ))), |
| 1101 | consumer()); |
| 1102 | T.registerMatchers(MatchFinder: &MatchFinder); |
| 1103 | // The rewrite process fails because the changes conflict with each other... |
| 1104 | EXPECT_FALSE(rewrite(Input)); |
| 1105 | // ... but two changes were produced. |
| 1106 | EXPECT_EQ(Changes.size(), 2u); |
| 1107 | EXPECT_EQ(ErrorCount, 0); |
| 1108 | } |
| 1109 | |
| 1110 | TEST_F(TransformerTest, ErrorOccurredMatchSkipped) { |
| 1111 | // Syntax error in the function body: |
| 1112 | std::string Input = "void errorOccurred() { 3 }" ; |
| 1113 | Transformer T(makeRule(M: functionDecl(hasName(Name: "errorOccurred" )), |
| 1114 | Edits: changeTo(Replacement: cat(Parts: "DELETED;" ))), |
| 1115 | consumer()); |
| 1116 | T.registerMatchers(MatchFinder: &MatchFinder); |
| 1117 | // The rewrite process itself fails... |
| 1118 | EXPECT_FALSE(rewrite(Input)); |
| 1119 | // ... and no changes or errors are produced in the process. |
| 1120 | EXPECT_THAT(Changes, IsEmpty()); |
| 1121 | EXPECT_EQ(ErrorCount, 0); |
| 1122 | } |
| 1123 | |
| 1124 | TEST_F(TransformerTest, ImplicitNodes_ConstructorDecl) { |
| 1125 | |
| 1126 | std::string OtherStructPrefix = R"cpp( |
| 1127 | struct Other { |
| 1128 | )cpp" ; |
| 1129 | std::string OtherStructSuffix = "};" ; |
| 1130 | |
| 1131 | std::string CopyableStructName = "struct Copyable" ; |
| 1132 | std::string BrokenStructName = "struct explicit Copyable" ; |
| 1133 | |
| 1134 | std::string CodeSuffix = R"cpp( |
| 1135 | { |
| 1136 | Other m_i; |
| 1137 | Copyable(); |
| 1138 | }; |
| 1139 | )cpp" ; |
| 1140 | |
| 1141 | std::string CopyCtor = "Other(const Other&) = default;" ; |
| 1142 | std::string ExplicitCopyCtor = "explicit Other(const Other&) = default;" ; |
| 1143 | std::string BrokenExplicitCopyCtor = |
| 1144 | "explicit explicit explicit Other(const Other&) = default;" ; |
| 1145 | |
| 1146 | std::string RewriteInput = OtherStructPrefix + CopyCtor + OtherStructSuffix + |
| 1147 | CopyableStructName + CodeSuffix; |
| 1148 | std::string ExpectedRewriteOutput = OtherStructPrefix + ExplicitCopyCtor + |
| 1149 | OtherStructSuffix + CopyableStructName + |
| 1150 | CodeSuffix; |
| 1151 | std::string BrokenRewriteOutput = OtherStructPrefix + BrokenExplicitCopyCtor + |
| 1152 | OtherStructSuffix + BrokenStructName + |
| 1153 | CodeSuffix; |
| 1154 | |
| 1155 | auto MatchedRecord = |
| 1156 | cxxConstructorDecl(isCopyConstructor()).bind(ID: "copyConstructor" ); |
| 1157 | |
| 1158 | auto RewriteRule = |
| 1159 | changeTo(Target: before(Selector: node(ID: "copyConstructor" )), Replacement: cat(Parts: "explicit " )); |
| 1160 | |
| 1161 | testRule(Rule: makeRule(M: traverse(TK: TK_IgnoreUnlessSpelledInSource, InnerMatcher: MatchedRecord), |
| 1162 | Edits&: RewriteRule), |
| 1163 | Input: RewriteInput, Expected: ExpectedRewriteOutput); |
| 1164 | |
| 1165 | testRule(Rule: makeRule(M: traverse(TK: TK_AsIs, InnerMatcher: MatchedRecord), Edits&: RewriteRule), |
| 1166 | Input: RewriteInput, Expected: BrokenRewriteOutput); |
| 1167 | } |
| 1168 | |
| 1169 | TEST_F(TransformerTest, ImplicitNodes_RangeFor) { |
| 1170 | |
| 1171 | std::string CodePrefix = R"cpp( |
| 1172 | struct Container |
| 1173 | { |
| 1174 | int* begin() const; |
| 1175 | int* end() const; |
| 1176 | int* cbegin() const; |
| 1177 | int* cend() const; |
| 1178 | }; |
| 1179 | |
| 1180 | void foo() |
| 1181 | { |
| 1182 | const Container c; |
| 1183 | )cpp" ; |
| 1184 | |
| 1185 | std::string BeginCallBefore = " c.begin();" ; |
| 1186 | std::string BeginCallAfter = " c.cbegin();" ; |
| 1187 | |
| 1188 | std::string ForLoop = "for (auto i : c)" ; |
| 1189 | std::string BrokenForLoop = "for (auto i :.cbegin() c)" ; |
| 1190 | |
| 1191 | std::string CodeSuffix = R"cpp( |
| 1192 | { |
| 1193 | } |
| 1194 | } |
| 1195 | )cpp" ; |
| 1196 | |
| 1197 | std::string RewriteInput = |
| 1198 | CodePrefix + BeginCallBefore + ForLoop + CodeSuffix; |
| 1199 | std::string ExpectedRewriteOutput = |
| 1200 | CodePrefix + BeginCallAfter + ForLoop + CodeSuffix; |
| 1201 | std::string BrokenRewriteOutput = |
| 1202 | CodePrefix + BeginCallAfter + BrokenForLoop + CodeSuffix; |
| 1203 | |
| 1204 | auto MatchedRecord = |
| 1205 | cxxMemberCallExpr(on(InnerMatcher: expr(hasType(InnerMatcher: qualType(isConstQualified(), |
| 1206 | hasDeclaration(InnerMatcher: cxxRecordDecl( |
| 1207 | hasName(Name: "Container" )))))) |
| 1208 | .bind(ID: "callTarget" )), |
| 1209 | callee(InnerMatcher: cxxMethodDecl(hasName(Name: "begin" )))) |
| 1210 | .bind(ID: "constBeginCall" ); |
| 1211 | |
| 1212 | auto RewriteRule = |
| 1213 | changeTo(Target: node(ID: "constBeginCall" ), Replacement: cat(Parts: name(ID: "callTarget" ), Parts: ".cbegin()" )); |
| 1214 | |
| 1215 | testRule(Rule: makeRule(M: traverse(TK: TK_IgnoreUnlessSpelledInSource, InnerMatcher: MatchedRecord), |
| 1216 | Edits&: RewriteRule), |
| 1217 | Input: RewriteInput, Expected: ExpectedRewriteOutput); |
| 1218 | |
| 1219 | testRule(Rule: makeRule(M: traverse(TK: TK_AsIs, InnerMatcher: MatchedRecord), Edits&: RewriteRule), |
| 1220 | Input: RewriteInput, Expected: BrokenRewriteOutput); |
| 1221 | } |
| 1222 | |
| 1223 | TEST_F(TransformerTest, ImplicitNodes_ForStmt) { |
| 1224 | |
| 1225 | std::string CodePrefix = R"cpp( |
| 1226 | struct NonTrivial { |
| 1227 | NonTrivial() {} |
| 1228 | NonTrivial(NonTrivial&) {} |
| 1229 | NonTrivial& operator=(NonTrivial const&) { return *this; } |
| 1230 | |
| 1231 | ~NonTrivial() {} |
| 1232 | }; |
| 1233 | |
| 1234 | struct ContainsArray { |
| 1235 | NonTrivial arr[2]; |
| 1236 | ContainsArray& operator=(ContainsArray const&) = default; |
| 1237 | }; |
| 1238 | |
| 1239 | void testIt() |
| 1240 | { |
| 1241 | ContainsArray ca1; |
| 1242 | ContainsArray ca2; |
| 1243 | ca2 = ca1; |
| 1244 | )cpp" ; |
| 1245 | |
| 1246 | auto CodeSuffix = "}" ; |
| 1247 | |
| 1248 | auto LoopBody = R"cpp( |
| 1249 | { |
| 1250 | |
| 1251 | } |
| 1252 | )cpp" ; |
| 1253 | |
| 1254 | auto RawLoop = "for (auto i = 0; i != 5; ++i)" ; |
| 1255 | |
| 1256 | auto RangeLoop = "for (auto i : boost::irange(5))" ; |
| 1257 | |
| 1258 | // Expect to rewrite the raw loop to the ranged loop. |
| 1259 | // This works in TK_IgnoreUnlessSpelledInSource mode, but TK_AsIs |
| 1260 | // mode also matches the hidden for loop generated in the copy assignment |
| 1261 | // operator of ContainsArray. Transformer then fails to transform the code at |
| 1262 | // all. |
| 1263 | |
| 1264 | auto RewriteInput = |
| 1265 | CodePrefix + RawLoop + LoopBody + RawLoop + LoopBody + CodeSuffix; |
| 1266 | |
| 1267 | auto RewriteOutput = |
| 1268 | CodePrefix + RangeLoop + LoopBody + RangeLoop + LoopBody + CodeSuffix; |
| 1269 | |
| 1270 | auto MatchedLoop = forStmt( |
| 1271 | has(declStmt(hasSingleDecl( |
| 1272 | InnerMatcher: varDecl(hasInitializer(InnerMatcher: integerLiteral(equals(Value: 0)))).bind(ID: "loopVar" )))), |
| 1273 | has(binaryOperator(hasOperatorName(Name: "!=" ), |
| 1274 | hasLHS(InnerMatcher: ignoringImplicit(InnerMatcher: declRefExpr( |
| 1275 | to(InnerMatcher: varDecl(equalsBoundNode(ID: "loopVar" )))))), |
| 1276 | hasRHS(InnerMatcher: expr().bind(ID: "upperBoundExpr" )))), |
| 1277 | has(unaryOperator(hasOperatorName(Name: "++" ), |
| 1278 | hasUnaryOperand(InnerMatcher: declRefExpr( |
| 1279 | to(InnerMatcher: varDecl(equalsBoundNode(ID: "loopVar" )))))) |
| 1280 | .bind(ID: "incrementOp" ))); |
| 1281 | |
| 1282 | auto RewriteRule = |
| 1283 | changeTo(Target: transformer::enclose(Begin: node(ID: "loopVar" ), End: node(ID: "incrementOp" )), |
| 1284 | Replacement: cat(Parts: "auto " , Parts: name(ID: "loopVar" ), Parts: " : boost::irange(" , |
| 1285 | Parts: node(ID: "upperBoundExpr" ), Parts: ")" )); |
| 1286 | |
| 1287 | testRule(Rule: makeRule(M: traverse(TK: TK_IgnoreUnlessSpelledInSource, InnerMatcher: MatchedLoop), |
| 1288 | Edits&: RewriteRule), |
| 1289 | Input: RewriteInput, Expected: RewriteOutput); |
| 1290 | |
| 1291 | testRuleFailure(Rule: makeRule(M: traverse(TK: TK_AsIs, InnerMatcher: MatchedLoop), Edits&: RewriteRule), |
| 1292 | Input: RewriteInput); |
| 1293 | } |
| 1294 | |
| 1295 | TEST_F(TransformerTest, ImplicitNodes_ForStmt2) { |
| 1296 | |
| 1297 | std::string CodePrefix = R"cpp( |
| 1298 | struct NonTrivial { |
| 1299 | NonTrivial() {} |
| 1300 | NonTrivial(NonTrivial&) {} |
| 1301 | NonTrivial& operator=(NonTrivial const&) { return *this; } |
| 1302 | |
| 1303 | ~NonTrivial() {} |
| 1304 | }; |
| 1305 | |
| 1306 | struct ContainsArray { |
| 1307 | NonTrivial arr[2]; |
| 1308 | ContainsArray& operator=(ContainsArray const&) = default; |
| 1309 | }; |
| 1310 | |
| 1311 | void testIt() |
| 1312 | { |
| 1313 | ContainsArray ca1; |
| 1314 | ContainsArray ca2; |
| 1315 | ca2 = ca1; |
| 1316 | )cpp" ; |
| 1317 | |
| 1318 | auto CodeSuffix = "}" ; |
| 1319 | |
| 1320 | auto LoopBody = R"cpp( |
| 1321 | { |
| 1322 | |
| 1323 | } |
| 1324 | )cpp" ; |
| 1325 | |
| 1326 | auto RawLoop = "for (auto i = 0; i != 5; ++i)" ; |
| 1327 | |
| 1328 | auto RangeLoop = "for (auto i : boost::irange(5))" ; |
| 1329 | |
| 1330 | // Expect to rewrite the raw loop to the ranged loop. |
| 1331 | // This works in TK_IgnoreUnlessSpelledInSource mode, but TK_AsIs |
| 1332 | // mode also matches the hidden for loop generated in the copy assignment |
| 1333 | // operator of ContainsArray. Transformer then fails to transform the code at |
| 1334 | // all. |
| 1335 | |
| 1336 | auto RewriteInput = |
| 1337 | CodePrefix + RawLoop + LoopBody + RawLoop + LoopBody + CodeSuffix; |
| 1338 | |
| 1339 | auto RewriteOutput = |
| 1340 | CodePrefix + RangeLoop + LoopBody + RangeLoop + LoopBody + CodeSuffix; |
| 1341 | auto MatchedLoop = forStmt( |
| 1342 | hasLoopInit(InnerMatcher: declStmt(hasSingleDecl( |
| 1343 | InnerMatcher: varDecl(hasInitializer(InnerMatcher: integerLiteral(equals(Value: 0)))).bind(ID: "loopVar" )))), |
| 1344 | hasCondition(InnerMatcher: binaryOperator(hasOperatorName(Name: "!=" ), |
| 1345 | hasLHS(InnerMatcher: ignoringImplicit(InnerMatcher: declRefExpr(to( |
| 1346 | InnerMatcher: varDecl(equalsBoundNode(ID: "loopVar" )))))), |
| 1347 | hasRHS(InnerMatcher: expr().bind(ID: "upperBoundExpr" )))), |
| 1348 | hasIncrement(InnerMatcher: unaryOperator(hasOperatorName(Name: "++" ), |
| 1349 | hasUnaryOperand(InnerMatcher: declRefExpr( |
| 1350 | to(InnerMatcher: varDecl(equalsBoundNode(ID: "loopVar" )))))) |
| 1351 | .bind(ID: "incrementOp" ))); |
| 1352 | |
| 1353 | auto RewriteRule = |
| 1354 | changeTo(Target: transformer::enclose(Begin: node(ID: "loopVar" ), End: node(ID: "incrementOp" )), |
| 1355 | Replacement: cat(Parts: "auto " , Parts: name(ID: "loopVar" ), Parts: " : boost::irange(" , |
| 1356 | Parts: node(ID: "upperBoundExpr" ), Parts: ")" )); |
| 1357 | |
| 1358 | testRule(Rule: makeRule(M: traverse(TK: TK_IgnoreUnlessSpelledInSource, InnerMatcher: MatchedLoop), |
| 1359 | Edits&: RewriteRule), |
| 1360 | Input: RewriteInput, Expected: RewriteOutput); |
| 1361 | |
| 1362 | testRuleFailure(Rule: makeRule(M: traverse(TK: TK_AsIs, InnerMatcher: MatchedLoop), Edits&: RewriteRule), |
| 1363 | Input: RewriteInput); |
| 1364 | } |
| 1365 | |
| 1366 | TEST_F(TransformerTest, TemplateInstantiation) { |
| 1367 | |
| 1368 | std::string NonTemplatesInput = R"cpp( |
| 1369 | struct S { |
| 1370 | int m_i; |
| 1371 | }; |
| 1372 | )cpp" ; |
| 1373 | std::string NonTemplatesExpected = R"cpp( |
| 1374 | struct S { |
| 1375 | safe_int m_i; |
| 1376 | }; |
| 1377 | )cpp" ; |
| 1378 | |
| 1379 | std::string TemplatesInput = R"cpp( |
| 1380 | template<typename T> |
| 1381 | struct TemplStruct { |
| 1382 | TemplStruct() {} |
| 1383 | ~TemplStruct() {} |
| 1384 | |
| 1385 | private: |
| 1386 | T m_t; |
| 1387 | }; |
| 1388 | |
| 1389 | void instantiate() |
| 1390 | { |
| 1391 | TemplStruct<int> ti; |
| 1392 | } |
| 1393 | )cpp" ; |
| 1394 | |
| 1395 | auto MatchedField = fieldDecl(hasType(InnerMatcher: asString(Name: "int" ))).bind(ID: "theField" ); |
| 1396 | |
| 1397 | // Changes the 'int' in 'S', but not the 'T' in 'TemplStruct': |
| 1398 | testRule(Rule: makeRule(M: traverse(TK: TK_IgnoreUnlessSpelledInSource, InnerMatcher: MatchedField), |
| 1399 | Edits: changeTo(Replacement: cat(Parts: "safe_int " , Parts: name(ID: "theField" ), Parts: ";" ))), |
| 1400 | Input: NonTemplatesInput + TemplatesInput, |
| 1401 | Expected: NonTemplatesExpected + TemplatesInput); |
| 1402 | |
| 1403 | // In AsIs mode, template instantiations are modified, which is |
| 1404 | // often not desired: |
| 1405 | |
| 1406 | std::string IncorrectTemplatesExpected = R"cpp( |
| 1407 | template<typename T> |
| 1408 | struct TemplStruct { |
| 1409 | TemplStruct() {} |
| 1410 | ~TemplStruct() {} |
| 1411 | |
| 1412 | private: |
| 1413 | safe_int m_t; |
| 1414 | }; |
| 1415 | |
| 1416 | void instantiate() |
| 1417 | { |
| 1418 | TemplStruct<int> ti; |
| 1419 | } |
| 1420 | )cpp" ; |
| 1421 | |
| 1422 | // Changes the 'int' in 'S', and (incorrectly) the 'T' in 'TemplStruct': |
| 1423 | testRule(Rule: makeRule(M: traverse(TK: TK_AsIs, InnerMatcher: MatchedField), |
| 1424 | Edits: changeTo(Replacement: cat(Parts: "safe_int " , Parts: name(ID: "theField" ), Parts: ";" ))), |
| 1425 | |
| 1426 | Input: NonTemplatesInput + TemplatesInput, |
| 1427 | Expected: NonTemplatesExpected + IncorrectTemplatesExpected); |
| 1428 | } |
| 1429 | |
| 1430 | // Transformation of macro source text when the change encompasses the entirety |
| 1431 | // of the expanded text. |
| 1432 | TEST_F(TransformerTest, SimpleMacro) { |
| 1433 | std::string Input = R"cc( |
| 1434 | #define ZERO 0 |
| 1435 | int f(string s) { return ZERO; } |
| 1436 | )cc" ; |
| 1437 | std::string Expected = R"cc( |
| 1438 | #define ZERO 0 |
| 1439 | int f(string s) { return 999; } |
| 1440 | )cc" ; |
| 1441 | |
| 1442 | StringRef zero = "zero" ; |
| 1443 | RewriteRule R = makeRule(M: integerLiteral(equals(Value: 0)).bind(ID: zero), |
| 1444 | Edits: changeTo(Target: node(ID: std::string(zero)), Replacement: cat(Parts: "999" ))); |
| 1445 | testRule(Rule: R, Input, Expected); |
| 1446 | } |
| 1447 | |
| 1448 | // Transformation of macro source text when the change encompasses the entirety |
| 1449 | // of the expanded text, for the case of function-style macros. |
| 1450 | TEST_F(TransformerTest, FunctionMacro) { |
| 1451 | std::string Input = R"cc( |
| 1452 | #define MACRO(str) strlen((str).c_str()) |
| 1453 | int f(string s) { return MACRO(s); } |
| 1454 | )cc" ; |
| 1455 | std::string Expected = R"cc( |
| 1456 | #define MACRO(str) strlen((str).c_str()) |
| 1457 | int f(string s) { return REPLACED; } |
| 1458 | )cc" ; |
| 1459 | |
| 1460 | testRule(Rule: ruleStrlenSize(), Input, Expected); |
| 1461 | } |
| 1462 | |
| 1463 | // Tests that expressions in macro arguments can be rewritten. |
| 1464 | TEST_F(TransformerTest, MacroArg) { |
| 1465 | std::string Input = R"cc( |
| 1466 | #define PLUS(e) e + 1 |
| 1467 | int f(string s) { return PLUS(strlen(s.c_str())); } |
| 1468 | )cc" ; |
| 1469 | std::string Expected = R"cc( |
| 1470 | #define PLUS(e) e + 1 |
| 1471 | int f(string s) { return PLUS(REPLACED); } |
| 1472 | )cc" ; |
| 1473 | |
| 1474 | testRule(Rule: ruleStrlenSize(), Input, Expected); |
| 1475 | } |
| 1476 | |
| 1477 | // Tests that expressions in macro arguments can be rewritten, even when the |
| 1478 | // macro call occurs inside another macro's definition. |
| 1479 | TEST_F(TransformerTest, MacroArgInMacroDef) { |
| 1480 | std::string Input = R"cc( |
| 1481 | #define NESTED(e) e |
| 1482 | #define MACRO(str) NESTED(strlen((str).c_str())) |
| 1483 | int f(string s) { return MACRO(s); } |
| 1484 | )cc" ; |
| 1485 | std::string Expected = R"cc( |
| 1486 | #define NESTED(e) e |
| 1487 | #define MACRO(str) NESTED(strlen((str).c_str())) |
| 1488 | int f(string s) { return REPLACED; } |
| 1489 | )cc" ; |
| 1490 | |
| 1491 | testRule(Rule: ruleStrlenSize(), Input, Expected); |
| 1492 | } |
| 1493 | |
| 1494 | // Tests the corner case of the identity macro, specifically that it is |
| 1495 | // discarded in the rewrite rather than preserved (like PLUS is preserved in the |
| 1496 | // previous test). This behavior is of dubious value (and marked with a FIXME |
| 1497 | // in the code), but we test it to verify (and demonstrate) how this case is |
| 1498 | // handled. |
| 1499 | TEST_F(TransformerTest, IdentityMacro) { |
| 1500 | std::string Input = R"cc( |
| 1501 | #define ID(e) e |
| 1502 | int f(string s) { return ID(strlen(s.c_str())); } |
| 1503 | )cc" ; |
| 1504 | std::string Expected = R"cc( |
| 1505 | #define ID(e) e |
| 1506 | int f(string s) { return REPLACED; } |
| 1507 | )cc" ; |
| 1508 | |
| 1509 | testRule(Rule: ruleStrlenSize(), Input, Expected); |
| 1510 | } |
| 1511 | |
| 1512 | // Tests that two changes in a single macro expansion do not lead to conflicts |
| 1513 | // in applying the changes. |
| 1514 | TEST_F(TransformerTest, TwoChangesInOneMacroExpansion) { |
| 1515 | std::string Input = R"cc( |
| 1516 | #define PLUS(a,b) (a) + (b) |
| 1517 | int f() { return PLUS(3, 4); } |
| 1518 | )cc" ; |
| 1519 | std::string Expected = R"cc( |
| 1520 | #define PLUS(a,b) (a) + (b) |
| 1521 | int f() { return PLUS(LIT, LIT); } |
| 1522 | )cc" ; |
| 1523 | |
| 1524 | testRule(Rule: makeRule(M: integerLiteral(), Edits: changeTo(Replacement: cat(Parts: "LIT" ))), Input, Expected); |
| 1525 | } |
| 1526 | |
| 1527 | // Tests case where the rule's match spans both source from the macro and its |
| 1528 | // arg, with the begin location (the "anchor") being the arg. |
| 1529 | TEST_F(TransformerTest, MatchSpansMacroTextButChangeDoesNot) { |
| 1530 | std::string Input = R"cc( |
| 1531 | #define PLUS_ONE(a) a + 1 |
| 1532 | int f() { return PLUS_ONE(3); } |
| 1533 | )cc" ; |
| 1534 | std::string Expected = R"cc( |
| 1535 | #define PLUS_ONE(a) a + 1 |
| 1536 | int f() { return PLUS_ONE(LIT); } |
| 1537 | )cc" ; |
| 1538 | |
| 1539 | StringRef E = "expr" ; |
| 1540 | testRule(Rule: makeRule(M: binaryOperator(hasLHS(InnerMatcher: expr().bind(ID: E))), |
| 1541 | Edits: changeTo(Target: node(ID: std::string(E)), Replacement: cat(Parts: "LIT" ))), |
| 1542 | Input, Expected); |
| 1543 | } |
| 1544 | |
| 1545 | // Tests case where the rule's match spans both source from the macro and its |
| 1546 | // arg, with the begin location (the "anchor") being inside the macro. |
| 1547 | TEST_F(TransformerTest, MatchSpansMacroTextButChangeDoesNotAnchoredInMacro) { |
| 1548 | std::string Input = R"cc( |
| 1549 | #define PLUS_ONE(a) 1 + a |
| 1550 | int f() { return PLUS_ONE(3); } |
| 1551 | )cc" ; |
| 1552 | std::string Expected = R"cc( |
| 1553 | #define PLUS_ONE(a) 1 + a |
| 1554 | int f() { return PLUS_ONE(LIT); } |
| 1555 | )cc" ; |
| 1556 | |
| 1557 | StringRef E = "expr" ; |
| 1558 | testRule(Rule: makeRule(M: binaryOperator(hasRHS(InnerMatcher: expr().bind(ID: E))), |
| 1559 | Edits: changeTo(Target: node(ID: std::string(E)), Replacement: cat(Parts: "LIT" ))), |
| 1560 | Input, Expected); |
| 1561 | } |
| 1562 | |
| 1563 | // No rewrite is applied when the changed text does not encompass the entirety |
| 1564 | // of the expanded text. That is, the edit would have to be applied to the |
| 1565 | // macro's definition to succeed and editing the expansion point would not |
| 1566 | // suffice. |
| 1567 | TEST_F(TransformerTest, NoPartialRewriteOMacroExpansion) { |
| 1568 | std::string Input = R"cc( |
| 1569 | #define ZERO_PLUS 0 + 3 |
| 1570 | int f(string s) { return ZERO_PLUS; })cc" ; |
| 1571 | |
| 1572 | StringRef zero = "zero" ; |
| 1573 | RewriteRule R = makeRule(M: integerLiteral(equals(Value: 0)).bind(ID: zero), |
| 1574 | Edits: changeTo(Target: node(ID: std::string(zero)), Replacement: cat(Parts: "0" ))); |
| 1575 | testRule(Rule: R, Input, Expected: Input); |
| 1576 | } |
| 1577 | |
| 1578 | // This test handles the corner case where a macro expands within another macro |
| 1579 | // to matching code, but that code is an argument to the nested macro call. A |
| 1580 | // simple check of isMacroArgExpansion() vs. isMacroBodyExpansion() will get |
| 1581 | // this wrong, and transform the code. |
| 1582 | TEST_F(TransformerTest, NoPartialRewriteOfMacroExpansionForMacroArgs) { |
| 1583 | std::string Input = R"cc( |
| 1584 | #define NESTED(e) e |
| 1585 | #define MACRO(str) 1 + NESTED(strlen((str).c_str())) |
| 1586 | int f(string s) { return MACRO(s); } |
| 1587 | )cc" ; |
| 1588 | |
| 1589 | testRule(Rule: ruleStrlenSize(), Input, Expected: Input); |
| 1590 | } |
| 1591 | |
| 1592 | #if !defined(NDEBUG) && GTEST_HAS_DEATH_TEST |
| 1593 | // Verifies that `Type` and `QualType` are not allowed as top-level matchers in |
| 1594 | // rules. |
| 1595 | TEST(TransformerDeathTest, OrderedRuleTypes) { |
| 1596 | RewriteRule QualTypeRule = makeRule(M: qualType(), Edits: changeTo(Replacement: cat(Parts: "Q" ))); |
| 1597 | EXPECT_DEATH(transformer::detail::buildMatchers(QualTypeRule), |
| 1598 | "Matcher must be.*node matcher" ); |
| 1599 | |
| 1600 | RewriteRule TypeRule = makeRule(M: arrayType(), Edits: changeTo(Replacement: cat(Parts: "T" ))); |
| 1601 | EXPECT_DEATH(transformer::detail::buildMatchers(TypeRule), |
| 1602 | "Matcher must be.*node matcher" ); |
| 1603 | } |
| 1604 | #endif |
| 1605 | |
| 1606 | // Edits are able to span multiple files; in this case, a header and an |
| 1607 | // implementation file. |
| 1608 | TEST_F(TransformerTest, MultipleFiles) { |
| 1609 | std::string = R"cc(void RemoveThisFunction();)cc" ; |
| 1610 | std::string Source = R"cc(#include "input.h" |
| 1611 | void RemoveThisFunction();)cc" ; |
| 1612 | Transformer T( |
| 1613 | makeRule(M: functionDecl(hasName(Name: "RemoveThisFunction" )), Edits: changeTo(Replacement: cat(Parts: "" ))), |
| 1614 | consumer()); |
| 1615 | T.registerMatchers(MatchFinder: &MatchFinder); |
| 1616 | auto Factory = newFrontendActionFactory(ConsumerFactory: &MatchFinder); |
| 1617 | EXPECT_TRUE(runToolOnCodeWithArgs( |
| 1618 | Factory->create(), Source, std::vector<std::string>(), "input.cc" , |
| 1619 | "clang-tool" , std::make_shared<PCHContainerOperations>(), |
| 1620 | {{"input.h" , Header}})); |
| 1621 | |
| 1622 | llvm::sort(C&: Changes, Comp: [](const AtomicChange &L, const AtomicChange &R) { |
| 1623 | return L.getFilePath() < R.getFilePath(); |
| 1624 | }); |
| 1625 | |
| 1626 | ASSERT_EQ(llvm::sys::path::convert_to_slash(Changes[0].getFilePath()), |
| 1627 | "./input.h" ); |
| 1628 | EXPECT_THAT(Changes[0].getInsertedHeaders(), IsEmpty()); |
| 1629 | EXPECT_THAT(Changes[0].getRemovedHeaders(), IsEmpty()); |
| 1630 | llvm::Expected<std::string> UpdatedCode = |
| 1631 | clang::tooling::applyAllReplacements(Code: Header, |
| 1632 | Replaces: Changes[0].getReplacements()); |
| 1633 | ASSERT_TRUE(static_cast<bool>(UpdatedCode)) |
| 1634 | << "Could not update code: " << llvm::toString(E: UpdatedCode.takeError()); |
| 1635 | EXPECT_EQ(format(*UpdatedCode), "" ); |
| 1636 | |
| 1637 | ASSERT_EQ(Changes[1].getFilePath(), "input.cc" ); |
| 1638 | EXPECT_THAT(Changes[1].getInsertedHeaders(), IsEmpty()); |
| 1639 | EXPECT_THAT(Changes[1].getRemovedHeaders(), IsEmpty()); |
| 1640 | UpdatedCode = clang::tooling::applyAllReplacements( |
| 1641 | Code: Source, Replaces: Changes[1].getReplacements()); |
| 1642 | ASSERT_TRUE(static_cast<bool>(UpdatedCode)) |
| 1643 | << "Could not update code: " << llvm::toString(E: UpdatedCode.takeError()); |
| 1644 | EXPECT_EQ(format(*UpdatedCode), format("#include \"input.h\"\n" )); |
| 1645 | } |
| 1646 | |
| 1647 | TEST_F(TransformerTest, AddIncludeMultipleFiles) { |
| 1648 | std::string = R"cc(void RemoveThisFunction();)cc" ; |
| 1649 | std::string Source = R"cc(#include "input.h" |
| 1650 | void Foo() {RemoveThisFunction();})cc" ; |
| 1651 | Transformer T( |
| 1652 | makeRule(M: callExpr(callee( |
| 1653 | InnerMatcher: functionDecl(hasName(Name: "RemoveThisFunction" )).bind(ID: "fun" ))), |
| 1654 | Edits: addInclude(Target: node(ID: "fun" ), Header: "header.h" )), |
| 1655 | consumer()); |
| 1656 | T.registerMatchers(MatchFinder: &MatchFinder); |
| 1657 | auto Factory = newFrontendActionFactory(ConsumerFactory: &MatchFinder); |
| 1658 | EXPECT_TRUE(runToolOnCodeWithArgs( |
| 1659 | Factory->create(), Source, std::vector<std::string>(), "input.cc" , |
| 1660 | "clang-tool" , std::make_shared<PCHContainerOperations>(), |
| 1661 | {{"input.h" , Header}})); |
| 1662 | |
| 1663 | ASSERT_EQ(Changes.size(), 1U); |
| 1664 | ASSERT_EQ(llvm::sys::path::convert_to_slash(Changes[0].getFilePath()), |
| 1665 | "./input.h" ); |
| 1666 | EXPECT_THAT(Changes[0].getInsertedHeaders(), ElementsAre("header.h" )); |
| 1667 | EXPECT_THAT(Changes[0].getRemovedHeaders(), IsEmpty()); |
| 1668 | llvm::Expected<std::string> UpdatedCode = |
| 1669 | clang::tooling::applyAllReplacements(Code: Header, |
| 1670 | Replaces: Changes[0].getReplacements()); |
| 1671 | ASSERT_TRUE(static_cast<bool>(UpdatedCode)) |
| 1672 | << "Could not update code: " << llvm::toString(E: UpdatedCode.takeError()); |
| 1673 | EXPECT_EQ(format(*UpdatedCode), format(Header)); |
| 1674 | } |
| 1675 | |
| 1676 | // A single change set can span multiple files. |
| 1677 | TEST_F(TransformerTest, MultiFileEdit) { |
| 1678 | // NB: The fixture is unused for this test, but kept for the test suite name. |
| 1679 | std::string = R"cc(void Func(int id);)cc" ; |
| 1680 | std::string Source = R"cc(#include "input.h" |
| 1681 | void Caller() { |
| 1682 | int id = 0; |
| 1683 | Func(id); |
| 1684 | })cc" ; |
| 1685 | int ErrorCount = 0; |
| 1686 | std::vector<AtomicChanges> ChangeSets; |
| 1687 | clang::ast_matchers::MatchFinder MatchFinder; |
| 1688 | Transformer T( |
| 1689 | makeRule(M: callExpr(callee(InnerMatcher: functionDecl(hasName(Name: "Func" ))), |
| 1690 | forEachArgumentWithParam(ArgMatcher: expr().bind(ID: "arg" ), |
| 1691 | ParamMatcher: parmVarDecl().bind(ID: "param" ))), |
| 1692 | Edits: {changeTo(Target: node(ID: "arg" ), Replacement: cat(Parts: "ARG" )), |
| 1693 | changeTo(Target: node(ID: "param" ), Replacement: cat(Parts: "PARAM" ))}), |
| 1694 | [&](Expected<MutableArrayRef<AtomicChange>> Changes) { |
| 1695 | if (Changes) |
| 1696 | ChangeSets.push_back(x: AtomicChanges(Changes->begin(), Changes->end())); |
| 1697 | else |
| 1698 | ++ErrorCount; |
| 1699 | }); |
| 1700 | T.registerMatchers(MatchFinder: &MatchFinder); |
| 1701 | auto Factory = newFrontendActionFactory(ConsumerFactory: &MatchFinder); |
| 1702 | EXPECT_TRUE(runToolOnCodeWithArgs( |
| 1703 | Factory->create(), Source, std::vector<std::string>(), "input.cc" , |
| 1704 | "clang-tool" , std::make_shared<PCHContainerOperations>(), |
| 1705 | {{"input.h" , Header}})); |
| 1706 | |
| 1707 | auto GetPathWithSlashes = [](const AtomicChange &C) { |
| 1708 | return llvm::sys::path::convert_to_slash(path: C.getFilePath()); |
| 1709 | }; |
| 1710 | |
| 1711 | EXPECT_EQ(ErrorCount, 0); |
| 1712 | EXPECT_THAT(ChangeSets, UnorderedElementsAre(UnorderedElementsAre( |
| 1713 | ResultOf(GetPathWithSlashes, "input.cc" ), |
| 1714 | ResultOf(GetPathWithSlashes, "./input.h" )))); |
| 1715 | } |
| 1716 | |
| 1717 | TEST_F(TransformerTest, GeneratesMetadata) { |
| 1718 | std::string Input = R"cc(int target = 0;)cc" ; |
| 1719 | std::string Expected = R"cc(REPLACE)cc" ; |
| 1720 | RewriteRuleWith<std::string> Rule = makeRule( |
| 1721 | M: varDecl(hasName(Name: "target" )), Edits: changeTo(Replacement: cat(Parts: "REPLACE" )), Metadata: cat(Parts: "METADATA" )); |
| 1722 | testRule(Rule: std::move(Rule), Input, Expected); |
| 1723 | EXPECT_EQ(ErrorCount, 0); |
| 1724 | EXPECT_THAT(StringMetadata, UnorderedElementsAre("METADATA" )); |
| 1725 | } |
| 1726 | |
| 1727 | TEST_F(TransformerTest, GeneratesMetadataWithNoEdits) { |
| 1728 | std::string Input = R"cc(int target = 0;)cc" ; |
| 1729 | RewriteRuleWith<std::string> Rule = makeRule( |
| 1730 | M: varDecl(hasName(Name: "target" )).bind(ID: "var" ), Edits: noEdits(), Metadata: cat(Parts: "METADATA" )); |
| 1731 | testRule(Rule: std::move(Rule), Input, Expected: Input); |
| 1732 | EXPECT_EQ(ErrorCount, 0); |
| 1733 | EXPECT_THAT(StringMetadata, UnorderedElementsAre("METADATA" )); |
| 1734 | } |
| 1735 | |
| 1736 | TEST_F(TransformerTest, PropagateMetadataErrors) { |
| 1737 | class AlwaysFail : public transformer::MatchComputation<std::string> { |
| 1738 | llvm::Error eval(const ast_matchers::MatchFinder::MatchResult &, |
| 1739 | std::string *) const override { |
| 1740 | return llvm::createStringError(EC: llvm::errc::invalid_argument, S: "ERROR" ); |
| 1741 | } |
| 1742 | std::string toString() const override { return "AlwaysFail" ; } |
| 1743 | }; |
| 1744 | std::string Input = R"cc(int target = 0;)cc" ; |
| 1745 | RewriteRuleWith<std::string> Rule = makeRule<std::string>( |
| 1746 | M: varDecl(hasName(Name: "target" )).bind(ID: "var" ), Edits: changeTo(Replacement: cat(Parts: "REPLACE" )), |
| 1747 | Metadata: std::make_shared<AlwaysFail>()); |
| 1748 | testRuleFailure(Rule: std::move(Rule), Input); |
| 1749 | EXPECT_EQ(ErrorCount, 1); |
| 1750 | } |
| 1751 | |
| 1752 | } // namespace |
| 1753 | |