diff --git a/clang/include/clang/Tooling/Refactoring/Stencil.h b/clang/include/clang/Tooling/Refactoring/Stencil.h --- a/clang/include/clang/Tooling/Refactoring/Stencil.h +++ b/clang/include/clang/Tooling/Refactoring/Stencil.h @@ -50,6 +50,11 @@ virtual bool isEqual(const StencilPartInterface &other) const = 0; + // Returns a string representation for the StencilPart. StencilParts generated + // by the `selection` and `run `functions do not have a unique string + // representation. + virtual std::string toString() const = 0; + const void *typeId() const { return TypeId; } protected: @@ -86,6 +91,12 @@ return Impl->isEqual(*Other.Impl); } + std::string toString() const { + if (Impl == nullptr) + return ""; + return Impl->toString(); + } + private: std::shared_ptr Impl; }; @@ -120,6 +131,16 @@ return eval(Result); } + // Returns a string representation for the Stencil. The string is not + // guaranteed to be unique. + std::string toString() const { + std::vector PartStrings; + PartStrings.reserve(Parts.size()); + for (const auto &Part : Parts) + PartStrings.push_back(Part.toString()); + return llvm::join(PartStrings, ", "); + } + private: friend bool operator==(const Stencil &A, const Stencil &B); static StencilPart wrap(llvm::StringRef Text); diff --git a/clang/lib/Tooling/Refactoring/Stencil.cpp b/clang/lib/Tooling/Refactoring/Stencil.cpp --- a/clang/lib/Tooling/Refactoring/Stencil.cpp +++ b/clang/lib/Tooling/Refactoring/Stencil.cpp @@ -15,6 +15,7 @@ #include "clang/Lex/Lexer.h" #include "clang/Tooling/Refactoring/SourceCode.h" #include "clang/Tooling/Refactoring/SourceCodeBuilders.h" +#include "llvm/ADT/Twine.h" #include "llvm/Support/Errc.h" #include #include @@ -128,6 +129,54 @@ return false; } +std::string toStringData(const RawTextData &Data) { + std::string Result; + llvm::raw_string_ostream OS(Result); + OS << "\""; + OS.write_escaped(Data.Text); + OS << "\""; + OS.flush(); + return Result; +} + +std::string toStringData(const DebugPrintNodeData &Data) { + return (llvm::Twine("dPrint(\"") + Data.Id + "\")").str(); +} + +std::string toStringData(const UnaryOperationData &Data) { + StringRef OpName; + switch (Data.Op) { + case UnaryNodeOperator::Parens: + OpName = "expression"; + break; + case UnaryNodeOperator::Deref: + OpName = "deref"; + break; + case UnaryNodeOperator::Address: + OpName = "addressOf"; + break; + } + return (OpName + "(\"" + Data.Id + "\")").str(); +} + +std::string toStringData(const SelectorData &) { return "SelectorData()"; } + +std::string toStringData(const AccessData &Data) { + return (llvm::Twine("access(\"") + Data.BaseId + "\", " + + Data.Member.toString() + ")") + .str(); +} + +std::string toStringData(const IfBoundData &Data) { + return (llvm::Twine("ifBound(\"") + Data.Id + "\", " + + Data.TruePart.toString() + ", " + Data.FalsePart.toString() + ")") + .str(); +} + +std::string toStringData(const MatchConsumer &) { + return "MatchConsumer()"; +} + // The `evalData()` overloads evaluate the given stencil data to a string, given // the match result, and append it to `Result`. We define an overload for each // type of stencil data. @@ -247,6 +296,8 @@ return isEqualData(Data, OtherPtr->Data); return false; } + + std::string toString() const override { return toStringData(Data); } }; } // namespace diff --git a/clang/unittests/Tooling/StencilTest.cpp b/clang/unittests/Tooling/StencilTest.cpp --- a/clang/unittests/Tooling/StencilTest.cpp +++ b/clang/unittests/Tooling/StencilTest.cpp @@ -389,4 +389,59 @@ auto S2 = cat(run(F)); EXPECT_NE(S1, S2); } + +TEST(StencilToStringTest, RawTextOp) { + auto S = cat("foo bar baz"); + EXPECT_EQ(S.toString(), R"("foo bar baz")"); +} + +TEST(StencilToStringTest, RawTextOpEscaping) { + auto S = cat("foo \"bar\" baz\\n"); + EXPECT_EQ(S.toString(), R"("foo \"bar\" baz\\n")"); +} + +TEST(StencilToStringTest, DebugPrintNodeOp) { + auto S = cat(dPrint("Id")); + EXPECT_EQ(S.toString(), R"repr(dPrint("Id"))repr"); +} + +TEST(StencilToStringTest, ExpressionOp) { + auto S = cat(expression("Id")); + EXPECT_EQ(S.toString(), R"repr(expression("Id"))repr"); +} + +TEST(StencilToStringTest, DerefOp) { + auto S = cat(deref("Id")); + EXPECT_EQ(S.toString(), R"repr(deref("Id"))repr"); +} + +TEST(StencilToStringTest, AddressOfOp) { + auto S = cat(addressOf("Id")); + EXPECT_EQ(S.toString(), R"repr(addressOf("Id"))repr"); +} + +TEST(StencilToStringTest, AccessOp) { + auto S = cat(access("Id", text("memberData"))); + EXPECT_EQ(S.toString(), R"repr(access("Id", "memberData"))repr"); +} + +TEST(StencilToStringTest, AccessOpStencilPart) { + auto S = cat(access("Id", access("subId", "memberData"))); + EXPECT_EQ(S.toString(), + R"repr(access("Id", access("subId", "memberData")))repr"); +} + +TEST(StencilToStringTest, IfBoundOp) { + auto S = cat(ifBound("Id", text("trueText"), access("exprId", "memberData"))); + EXPECT_EQ( + S.toString(), + R"repr(ifBound("Id", "trueText", access("exprId", "memberData")))repr"); +} + +TEST(StencilToStringTest, MultipleOp) { + auto S = cat("foo", access("x", "m()"), "bar", + ifBound("x", text("t"), access("e", "f"))); + EXPECT_EQ(S.toString(), R"repr("foo", access("x", "m()"), "bar", )repr" + R"repr(ifBound("x", "t", access("e", "f")))repr"); +} } // namespace