diff --git a/clang/include/clang/Tooling/Transformer/MatchConsumer.h b/clang/include/clang/Tooling/Transformer/MatchConsumer.h --- a/clang/include/clang/Tooling/Transformer/MatchConsumer.h +++ b/clang/include/clang/Tooling/Transformer/MatchConsumer.h @@ -99,11 +99,5 @@ return Output; } } // namespace transformer - -namespace tooling { -// DEPRECATED: Temporary alias supporting client migration to the `transformer` -// namespace. -using transformer::ifBound; -} // namespace tooling } // namespace clang #endif // LLVM_CLANG_TOOLING_TRANSFORMER_MATCH_CONSUMER_H_ diff --git a/clang/include/clang/Tooling/Transformer/RewriteRule.h b/clang/include/clang/Tooling/Transformer/RewriteRule.h --- a/clang/include/clang/Tooling/Transformer/RewriteRule.h +++ b/clang/include/clang/Tooling/Transformer/RewriteRule.h @@ -107,9 +107,42 @@ /// clients. We recommend use of the \c AtomicChange or \c Replacements classes /// for assistance in detecting such conflicts. EditGenerator editList(llvm::SmallVector Edits); -// Convenience form of `editList` for a single edit. +/// Convenience form of `editList` for a single edit. EditGenerator edit(ASTEdit); +/// Convenience generator for a no-op edit generator. +inline EditGenerator noEdits() { return editList({}); } + +/// Convenience version of `ifBound` specialized to `ASTEdit`. +inline EditGenerator ifBound(std::string ID, ASTEdit TrueEdit, + ASTEdit FalseEdit) { + return ifBound(std::move(ID), edit(std::move(TrueEdit)), + edit(std::move(FalseEdit))); +} + +/// Convenience version of `ifBound` that has no "False" branch. If the node is +/// not bound, then no edits are produced. +inline EditGenerator ifBound(std::string ID, ASTEdit TrueEdit) { + return ifBound(std::move(ID), edit(std::move(TrueEdit)), noEdits()); +} + +/// Flattens a list of generators into a single generator whose elements are the +/// concatenation of the results of the argument generators. +EditGenerator flattenVector(SmallVector Generators); + +namespace detail { +/// Convenience function to construct an \c EditGenerator. Overloaded for common +/// cases so that user doesn't need to specify which factory function to +/// use. This pattern gives benefits similar to implicit constructors, while +/// maintaing a higher degree of explicitness. +inline EditGenerator injectEdits(ASTEdit E) { return edit(std::move(E)); } +inline EditGenerator injectEdits(EditGenerator G) { return G; } +} // namespace detail + +template EditGenerator flatten(Ts &&...Edits) { + return flattenVector({detail::injectEdits(std::forward(Edits))...}); +} + /// Format of the path in an include directive -- angle brackets or quotes. enum class IncludeFormat { Quoted, @@ -291,6 +324,14 @@ return Edit; } +/// Assuming that the inner range is enclosed by the outer range, creates +/// precision edits to remove the parts of the outer range that are not included +/// in the inner range. +inline EditGenerator shrinkTo(RangeSelector outer, RangeSelector inner) { + return editList({remove(enclose(before(outer), before(inner))), + remove(enclose(after(inner), after(outer)))}); +} + /// The following three functions are a low-level part of the RewriteRule /// API. We expose them for use in implementing the fixtures that interpret /// RewriteRule, like Transformer and TransfomerTidy, or for more advanced diff --git a/clang/lib/Tooling/Transformer/RewriteRule.cpp b/clang/lib/Tooling/Transformer/RewriteRule.cpp --- a/clang/lib/Tooling/Transformer/RewriteRule.cpp +++ b/clang/lib/Tooling/Transformer/RewriteRule.cpp @@ -68,6 +68,24 @@ }; } +EditGenerator +transformer::flattenVector(SmallVector Generators) { + if (Generators.size() == 1) + return std::move(Generators[0]); + return + [Gs = std::move(Generators)]( + const MatchResult &Result) -> llvm::Expected> { + SmallVector AllEdits; + for (const auto &G : Gs) { + llvm::Expected> Edits = G(Result); + if (!Edits) + return Edits.takeError(); + AllEdits.append(Edits->begin(), Edits->end()); + } + return AllEdits; + }; +} + ASTEdit transformer::changeTo(RangeSelector Target, TextGenerator Replacement) { ASTEdit E; E.TargetRange = std::move(Target); diff --git a/clang/unittests/Tooling/TransformerTest.cpp b/clang/unittests/Tooling/TransformerTest.cpp --- a/clang/unittests/Tooling/TransformerTest.cpp +++ b/clang/unittests/Tooling/TransformerTest.cpp @@ -10,6 +10,7 @@ #include "clang/ASTMatchers/ASTMatchers.h" #include "clang/Tooling/Tooling.h" #include "clang/Tooling/Transformer/RangeSelector.h" +#include "clang/Tooling/Transformer/RewriteRule.h" #include "clang/Tooling/Transformer/Stencil.h" #include "llvm/Support/Errc.h" #include "llvm/Support/Error.h" @@ -378,6 +379,41 @@ Input, Expected); } +TEST_F(TransformerTest, NoEdits) { + using transformer::noEdits; + std::string Input = "int f(int x) { return x; }"; + testRule(makeRule(returnStmt().bind("return"), noEdits()), Input, Input); +} + +TEST_F(TransformerTest, IfBound2Args) { + using transformer::ifBound; + std::string Input = "int f(int x) { return x; }"; + std::string Expected = "int f(int x) { CHANGE; }"; + testRule(makeRule(returnStmt().bind("return"), + ifBound("return", changeTo(cat("CHANGE;")))), + Input, Expected); +} + +TEST_F(TransformerTest, IfBound3Args) { + using transformer::ifBound; + std::string Input = "int f(int x) { return x; }"; + std::string Expected = "int f(int x) { CHANGE; }"; + testRule(makeRule(returnStmt().bind("return"), + ifBound("nothing", changeTo(cat("ERROR")), + changeTo(cat("CHANGE;")))), + Input, Expected); +} + +TEST_F(TransformerTest, ShrinkTo) { + using transformer::shrinkTo; + std::string Input = "int f(int x) { return x; }"; + std::string Expected = "return x;"; + testRule(makeRule(functionDecl(hasDescendant(returnStmt().bind("return"))) + .bind("function"), + shrinkTo(node("function"), node("return"))), + Input, Expected); +} + TEST_F(TransformerTest, InsertBeforeEdit) { std::string Input = R"cc( int f() { @@ -497,6 +533,90 @@ Input, Expected); } +TEST_F(TransformerTest, EditList) { + using clang::transformer::editList; + std::string Input = R"cc( + void foo() { + if (10 > 1.0) + log(1) << "oh no!"; + else + log(0) << "ok"; + } + )cc"; + std::string Expected = R"( + void foo() { + if (true) { /* then */ } + else { /* else */ } + } + )"; + + StringRef C = "C", T = "T", E = "E"; + testRule(makeRule(ifStmt(hasCondition(expr().bind(C)), + hasThen(stmt().bind(T)), hasElse(stmt().bind(E))), + editList({changeTo(node(std::string(C)), cat("true")), + changeTo(statement(std::string(T)), + cat("{ /* then */ }")), + changeTo(statement(std::string(E)), + cat("{ /* else */ }"))})), + Input, Expected); +} + +TEST_F(TransformerTest, Flatten) { + using clang::transformer::editList; + std::string Input = R"cc( + void foo() { + if (10 > 1.0) + log(1) << "oh no!"; + else + log(0) << "ok"; + } + )cc"; + std::string Expected = R"( + void foo() { + if (true) { /* then */ } + else { /* else */ } + } + )"; + + StringRef C = "C", T = "T", E = "E"; + testRule( + makeRule( + ifStmt(hasCondition(expr().bind(C)), hasThen(stmt().bind(T)), + hasElse(stmt().bind(E))), + flatten(changeTo(node(std::string(C)), cat("true")), + changeTo(statement(std::string(T)), cat("{ /* then */ }")), + changeTo(statement(std::string(E)), cat("{ /* else */ }")))), + Input, Expected); +} + +TEST_F(TransformerTest, FlattenWithMixedArgs) { + using clang::transformer::editList; + std::string Input = R"cc( + void foo() { + if (10 > 1.0) + log(1) << "oh no!"; + else + log(0) << "ok"; + } + )cc"; + std::string Expected = R"( + void foo() { + if (true) { /* then */ } + else { /* else */ } + } + )"; + + StringRef C = "C", T = "T", E = "E"; + testRule(makeRule(ifStmt(hasCondition(expr().bind(C)), + hasThen(stmt().bind(T)), hasElse(stmt().bind(E))), + flatten(changeTo(node(std::string(C)), cat("true")), + edit(changeTo(statement(std::string(T)), + cat("{ /* then */ }"))), + editList({changeTo(statement(std::string(E)), + cat("{ /* else */ }"))}))), + Input, Expected); +} + TEST_F(TransformerTest, OrderedRuleUnrelated) { StringRef Flag = "flag"; RewriteRule FlagRule = makeRule(