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
22using namespace clang;
23using namespace tooling;
24using namespace ast_matchers;
25namespace {
26using ::clang::transformer::addInclude;
27using ::clang::transformer::applyFirst;
28using ::clang::transformer::before;
29using ::clang::transformer::cat;
30using ::clang::transformer::changeTo;
31using ::clang::transformer::editList;
32using ::clang::transformer::makeRule;
33using ::clang::transformer::member;
34using ::clang::transformer::name;
35using ::clang::transformer::node;
36using ::clang::transformer::noEdits;
37using ::clang::transformer::remove;
38using ::clang::transformer::rewriteDescendants;
39using ::clang::transformer::RewriteRule;
40using ::clang::transformer::RewriteRuleWith;
41using ::clang::transformer::statement;
42using ::testing::ElementsAre;
43using ::testing::IsEmpty;
44using ::testing::ResultOf;
45using ::testing::UnorderedElementsAre;
46
47constexpr char KHeaderContents[] = 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
68static ast_matchers::internal::Matcher<clang::QualType>
69isOrPointsTo(const clang::ast_matchers::DeclarationMatcher &TypeMatcher) {
70 return anyOf(hasDeclaration(InnerMatcher: TypeMatcher), pointsTo(InnerMatcher: TypeMatcher));
71}
72
73static 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
86static 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).
98class ClangRefactoringTestBase : public testing::Test {
99protected:
100 void appendToHeader(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
197private:
198 FileContentMappings FileContents = {{"header.h", ""}};
199};
200
201class TransformerTest : public ClangRefactoringTestBase {
202protected:
203 TransformerTest() { appendToHeader(S: KHeaderContents); }
204};
205
206// Given string s, change strlen($s.c_str()) to REPLACED.
207static 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
220TEST_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.
227TEST_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.
233TEST_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
256TEST_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
274TEST_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
293TEST_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
311TEST_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
329TEST_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
346TEST_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
368TEST_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
388TEST_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
416TEST_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
450TEST_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
482TEST_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
488TEST_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
496TEST_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
505TEST_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
515TEST_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.
526TEST_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.
539TEST_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
549TEST_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
564TEST_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
579TEST_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
593TEST_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
608TEST_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
622TEST_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
644TEST_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
664TEST_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
681TEST_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
700TEST_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
721TEST_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
741TEST_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
763TEST_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
782TEST_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
814TEST_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
840TEST_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
867TEST_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
894TEST_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
922TEST_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
947TEST_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.
972TEST_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.
1000TEST_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.
1030TEST_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.
1058TEST_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, Msg: "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.
1080TEST_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).
1095TEST_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
1110TEST_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
1124TEST_F(TransformerTest, ImplicitNodes_ConstructorDecl) {
1125
1126 std::string OtherStructPrefix = R"cpp(
1127struct 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
1169TEST_F(TransformerTest, ImplicitNodes_RangeFor) {
1170
1171 std::string CodePrefix = R"cpp(
1172struct Container
1173{
1174 int* begin() const;
1175 int* end() const;
1176 int* cbegin() const;
1177 int* cend() const;
1178};
1179
1180void 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
1223TEST_F(TransformerTest, ImplicitNodes_ForStmt) {
1224
1225 std::string CodePrefix = R"cpp(
1226struct NonTrivial {
1227 NonTrivial() {}
1228 NonTrivial(NonTrivial&) {}
1229 NonTrivial& operator=(NonTrivial const&) { return *this; }
1230
1231 ~NonTrivial() {}
1232};
1233
1234struct ContainsArray {
1235 NonTrivial arr[2];
1236 ContainsArray& operator=(ContainsArray const&) = default;
1237};
1238
1239void 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
1295TEST_F(TransformerTest, ImplicitNodes_ForStmt2) {
1296
1297 std::string CodePrefix = R"cpp(
1298struct NonTrivial {
1299 NonTrivial() {}
1300 NonTrivial(NonTrivial&) {}
1301 NonTrivial& operator=(NonTrivial const&) { return *this; }
1302
1303 ~NonTrivial() {}
1304};
1305
1306struct ContainsArray {
1307 NonTrivial arr[2];
1308 ContainsArray& operator=(ContainsArray const&) = default;
1309};
1310
1311void 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
1366TEST_F(TransformerTest, TemplateInstantiation) {
1367
1368 std::string NonTemplatesInput = R"cpp(
1369struct S {
1370 int m_i;
1371};
1372)cpp";
1373 std::string NonTemplatesExpected = R"cpp(
1374struct S {
1375 safe_int m_i;
1376};
1377)cpp";
1378
1379 std::string TemplatesInput = R"cpp(
1380template<typename T>
1381struct TemplStruct {
1382 TemplStruct() {}
1383 ~TemplStruct() {}
1384
1385private:
1386 T m_t;
1387};
1388
1389void 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(
1407template<typename T>
1408struct TemplStruct {
1409 TemplStruct() {}
1410 ~TemplStruct() {}
1411
1412private:
1413 safe_int m_t;
1414};
1415
1416void 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.
1432TEST_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.
1450TEST_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.
1464TEST_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.
1479TEST_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.
1499TEST_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.
1514TEST_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.
1529TEST_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.
1547TEST_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.
1567TEST_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.
1582TEST_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.
1595TEST(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.
1608TEST_F(TransformerTest, MultipleFiles) {
1609 std::string Header = 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
1647TEST_F(TransformerTest, AddIncludeMultipleFiles) {
1648 std::string Header = 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.
1677TEST_F(TransformerTest, MultiFileEdit) {
1678 // NB: The fixture is unused for this test, but kept for the test suite name.
1679 std::string Header = 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
1717TEST_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
1727TEST_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
1736TEST_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, Msg: "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

source code of clang/unittests/Tooling/TransformerTest.cpp