diff --git a/clang/include/clang/Tooling/Syntax/Tree.h b/clang/include/clang/Tooling/Syntax/Tree.h --- a/clang/include/clang/Tooling/Syntax/Tree.h +++ b/clang/include/clang/Tooling/Syntax/Tree.h @@ -218,12 +218,16 @@ /// and delimiters are represented as null pointers. /// /// For example, in a separated list: - /// "a, b, c" <=> [("a", ","), ("b", ","), ("c", null)] - /// "a, , c" <=> [("a", ","), (null, ","), ("c", ",)] - /// "a, b," <=> [("a", ","), ("b", ","), (null, null)] + /// "a, b, c" <=> [("a", ","), ("b", ","), ("c", nul)] + /// "a, , c" <=> [("a", ","), (nul, ","), ("c", nul)] + /// "a, b c" <=> [("a", ","), ("b", nul), ("c", nul)] + /// "a, b," <=> [("a", ","), ("b", ","), (nul, nul)] /// /// In a terminated or maybe-terminated list: - /// "a, b," <=> [("a", ","), ("b", ",")] + /// "a; b; c;" <=> [("a", ";"), ("b", ";"), ("c", ";")] + /// "a; ; c;" <=> [("a", ";"), (nul, ";"), ("c", ";")] + /// "a; b c;" <=> [("a", ";"), ("b", nul), ("c", ";")] + /// "a; b; c" <=> [("a", ";"), ("b", ";"), ("c", nul)] std::vector> getElementsAsNodesAndDelimiters(); /// Returns the elements of the list. Missing elements are represented diff --git a/clang/unittests/Tooling/Syntax/TreeTest.cpp b/clang/unittests/Tooling/Syntax/TreeTest.cpp --- a/clang/unittests/Tooling/Syntax/TreeTest.cpp +++ b/clang/unittests/Tooling/Syntax/TreeTest.cpp @@ -9,6 +9,7 @@ #include "clang/Tooling/Syntax/Tree.h" #include "TreeTestBase.h" #include "clang/Tooling/Syntax/BuildTree.h" +#include "clang/Tooling/Syntax/Nodes.h" #include "gtest/gtest.h" using namespace clang; @@ -122,4 +123,210 @@ } } +class ListTest : public SyntaxTreeTest { +private: + std::string dump(const List::ElementAndDelimiter &ED) { + auto Format = [](StringRef s) { return "'" + s.trim().str() + "'"; }; + + std::string Delimiter = + ED.delimiter + ? Format(ED.delimiter->dumpTokens(Arena->getSourceManager())) + : "nul"; + + std::string Element = + ED.element ? Format(ED.element->dumpTokens(Arena->getSourceManager())) + : "nul"; + + return "(" + Element + ", " + Delimiter + ")"; + } + +protected: + std::string dump(ArrayRef> EDs) { + if (EDs.empty()) + return "[]"; + + std::string Storage; + llvm::raw_string_ostream OS(Storage); + + OS << "[" << dump(EDs.front()); + for (auto ED = std::next(EDs.begin()), End = EDs.end(); ED != End; ++ED) + OS << ", " << dump(*ED); + OS << "]"; + return OS.str(); + } +}; + +INSTANTIATE_TEST_CASE_P(TreeTests, ListTest, + ::testing::ValuesIn(allTestClangConfigs()), ); + +/// "a, b, c" <=> [("a", ","), ("b", ","), ("c", nul)] +TEST_P(ListTest, List_Separated_WellFormed) { + buildTree("", GetParam()); + + // "a, b, c" + auto *List = dyn_cast(syntax::createTree( + *Arena, + { + {createLeaf(*Arena, tok::identifier, "a"), NodeRole::ListElement}, + {createLeaf(*Arena, tok::comma), NodeRole::ListDelimiter}, + {createLeaf(*Arena, tok::identifier, "b"), NodeRole::ListElement}, + {createLeaf(*Arena, tok::comma), NodeRole::ListDelimiter}, + {createLeaf(*Arena, tok::identifier, "c"), NodeRole::ListElement}, + }, + NodeKind::CallArguments)); + + EXPECT_EQ(dump(List->getElementsAsNodesAndDelimiters()), + "[('a', ','), ('b', ','), ('c', nul)]"); +} + +/// "a, , c" <=> [("a", ","), (nul, ","), ("c", nul)] +TEST_P(ListTest, List_Separated_MissingElement) { + buildTree("", GetParam()); + + // "a, , c" + auto *List = dyn_cast(syntax::createTree( + *Arena, + { + {createLeaf(*Arena, tok::identifier, "a"), NodeRole::ListElement}, + {createLeaf(*Arena, tok::comma), NodeRole::ListDelimiter}, + {createLeaf(*Arena, tok::comma), NodeRole::ListDelimiter}, + {createLeaf(*Arena, tok::identifier, "c"), NodeRole::ListElement}, + }, + NodeKind::CallArguments)); + + EXPECT_EQ(dump(List->getElementsAsNodesAndDelimiters()), + "[('a', ','), (nul, ','), ('c', nul)]"); +} + +/// "a, b c" <=> [("a", ","), ("b", nul), ("c", nul)] +TEST_P(ListTest, List_Separated_MissingDelimiter) { + buildTree("", GetParam()); + + // "a, b c" + auto *List = dyn_cast(syntax::createTree( + *Arena, + { + {createLeaf(*Arena, tok::identifier, "a"), NodeRole::ListElement}, + {createLeaf(*Arena, tok::comma), NodeRole::ListDelimiter}, + {createLeaf(*Arena, tok::identifier, "b"), NodeRole::ListElement}, + {createLeaf(*Arena, tok::identifier, "c"), NodeRole::ListElement}, + }, + NodeKind::CallArguments)); + + EXPECT_EQ(dump(List->getElementsAsNodesAndDelimiters()), + "[('a', ','), ('b', nul), ('c', nul)]"); +} + +/// "a, b," <=> [("a", ","), ("b", ","), (nul, nul)] +TEST_P(ListTest, List_Separated_MissingLastElement) { + buildTree("", GetParam()); + + // "a, b, c" + auto *List = dyn_cast(syntax::createTree( + *Arena, + { + {createLeaf(*Arena, tok::identifier, "a"), NodeRole::ListElement}, + {createLeaf(*Arena, tok::comma), NodeRole::ListDelimiter}, + {createLeaf(*Arena, tok::identifier, "b"), NodeRole::ListElement}, + {createLeaf(*Arena, tok::comma), NodeRole::ListDelimiter}, + }, + NodeKind::CallArguments)); + + EXPECT_EQ(dump(List->getElementsAsNodesAndDelimiters()), + "[('a', ','), ('b', ','), (nul, nul)]"); +} + +/// "a:: b:: c::" <=> [("a", "::"), ("b", "::"), ("c", "::")] +TEST_P(ListTest, List_Terminated_WellFormed) { + if (!GetParam().isCXX()) { + return; + } + buildTree("", GetParam()); + + // "a:: b:: c::" + auto *List = dyn_cast(syntax::createTree( + *Arena, + { + {createLeaf(*Arena, tok::identifier, "a"), NodeRole::ListElement}, + {createLeaf(*Arena, tok::coloncolon), NodeRole::ListDelimiter}, + {createLeaf(*Arena, tok::identifier, "b"), NodeRole::ListElement}, + {createLeaf(*Arena, tok::coloncolon), NodeRole::ListDelimiter}, + {createLeaf(*Arena, tok::identifier, "c"), NodeRole::ListElement}, + {createLeaf(*Arena, tok::coloncolon), NodeRole::ListDelimiter}, + }, + NodeKind::NestedNameSpecifier)); + + EXPECT_EQ(dump(List->getElementsAsNodesAndDelimiters()), + "[('a', '::'), ('b', '::'), ('c', '::')]"); +} + +/// "a:: :: c::" <=> [("a", "::"), (nul, "::"), ("c", "::")] +TEST_P(ListTest, List_Terminated_MissingElement) { + if (!GetParam().isCXX()) { + return; + } + buildTree("", GetParam()); + + // "a:: b:: c::" + auto *List = dyn_cast(syntax::createTree( + *Arena, + { + {createLeaf(*Arena, tok::identifier, "a"), NodeRole::ListElement}, + {createLeaf(*Arena, tok::coloncolon), NodeRole::ListDelimiter}, + {createLeaf(*Arena, tok::coloncolon), NodeRole::ListDelimiter}, + {createLeaf(*Arena, tok::identifier, "c"), NodeRole::ListElement}, + {createLeaf(*Arena, tok::coloncolon), NodeRole::ListDelimiter}, + }, + NodeKind::NestedNameSpecifier)); + + EXPECT_EQ(dump(List->getElementsAsNodesAndDelimiters()), + "[('a', '::'), (nul, '::'), ('c', '::')]"); +} + +/// "a:: b c::" <=> [("a", "::"), ("b", nul), ("c", "::")] +TEST_P(ListTest, List_Terminated_MissingDelimiter) { + if (!GetParam().isCXX()) { + return; + } + buildTree("", GetParam()); + + // "a:: b c::" + auto *List = dyn_cast(syntax::createTree( + *Arena, + { + {createLeaf(*Arena, tok::identifier, "a"), NodeRole::ListElement}, + {createLeaf(*Arena, tok::coloncolon), NodeRole::ListDelimiter}, + {createLeaf(*Arena, tok::identifier, "b"), NodeRole::ListElement}, + {createLeaf(*Arena, tok::identifier, "c"), NodeRole::ListElement}, + {createLeaf(*Arena, tok::coloncolon), NodeRole::ListDelimiter}, + }, + NodeKind::NestedNameSpecifier)); + + EXPECT_EQ(dump(List->getElementsAsNodesAndDelimiters()), + "[('a', '::'), ('b', nul), ('c', '::')]"); +} + +/// "a:: b:: c" <=> [("a", "::"), ("b", "::"), ("c", nul)] +TEST_P(ListTest, List_Terminated_MissingLastDelimiter) { + if (!GetParam().isCXX()) { + return; + } + buildTree("", GetParam()); + + // "a:: b:: c" + auto *List = dyn_cast(syntax::createTree( + *Arena, + { + {createLeaf(*Arena, tok::identifier, "a"), NodeRole::ListElement}, + {createLeaf(*Arena, tok::coloncolon), NodeRole::ListDelimiter}, + {createLeaf(*Arena, tok::identifier, "b"), NodeRole::ListElement}, + {createLeaf(*Arena, tok::coloncolon), NodeRole::ListDelimiter}, + {createLeaf(*Arena, tok::identifier, "c"), NodeRole::ListElement}, + }, + NodeKind::NestedNameSpecifier)); + + EXPECT_EQ(dump(List->getElementsAsNodesAndDelimiters()), + "[('a', '::'), ('b', '::'), ('c', nul)]"); +} + } // namespace