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" , null)] + /// "a, , c" <=> [("a" , ","), (null, "," ), ("c" , null)] + /// "a, b c" <=> [("a" , ","), ("b" , null), ("c" , null)] + /// "a, b," <=> [("a" , ","), ("b" , "," ), (null, null)] /// /// In a terminated or maybe-terminated list: - /// "a, b," <=> [("a", ","), ("b", ",")] + /// "a; b; c;" <=> [("a" , ";"), ("b" , ";" ), ("c" , ";" )] + /// "a; ; c;" <=> [("a" , ";"), (null, ";" ), ("c" , ";" )] + /// "a; b c;" <=> [("a" , ";"), ("b" , null), ("c" , ";" )] + /// "a; b; c" <=> [("a" , ";"), ("b" , ";" ), ("c" , null)] 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,8 @@ #include "clang/Tooling/Syntax/Tree.h" #include "TreeTestBase.h" #include "clang/Tooling/Syntax/BuildTree.h" +#include "clang/Tooling/Syntax/Nodes.h" +#include "llvm/ADT/STLExtras.h" #include "gtest/gtest.h" using namespace clang; @@ -122,4 +124,231 @@ } } +class ListTest : public SyntaxTreeTest { +private: + std::string dumpQuotedTokensOrNull(const Node *N) { + return N ? "'" + + StringRef(N->dumpTokens(Arena->getSourceManager())) + .trim() + .str() + + "'" + : "null"; + } + +protected: + std::string + dumpElementsAndDelimiters(ArrayRef> EDs) { + std::string Storage; + llvm::raw_string_ostream OS(Storage); + + OS << "["; + + llvm::interleaveComma( + EDs, OS, [&OS, this](const List::ElementAndDelimiter &ED) { + OS << "(" << dumpQuotedTokensOrNull(ED.element) << ", " + << dumpQuotedTokensOrNull(ED.delimiter) << ")"; + }); + + OS << "]"; + + return OS.str(); + } + + std::string dumpNodes(ArrayRef Nodes) { + std::string Storage; + llvm::raw_string_ostream OS(Storage); + + OS << "["; + + llvm::interleaveComma(Nodes, OS, [&OS, this](const Node *N) { + OS << dumpQuotedTokensOrNull(N); + }); + + OS << "]"; + + return OS.str(); + } +}; + +INSTANTIATE_TEST_CASE_P(TreeTests, ListTest, + ::testing::ValuesIn(allTestClangConfigs()), ); + +/// "a, b, c" <=> [("a", ","), ("b", ","), ("c", null)] +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(dumpElementsAndDelimiters(List->getElementsAsNodesAndDelimiters()), + "[('a', ','), ('b', ','), ('c', null)]"); + EXPECT_EQ(dumpNodes(List->getElementsAsNodes()), "['a', 'b', 'c']"); +} + +/// "a, , c" <=> [("a", ","), (null, ","), ("c", null)] +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(dumpElementsAndDelimiters(List->getElementsAsNodesAndDelimiters()), + "[('a', ','), (null, ','), ('c', null)]"); + EXPECT_EQ(dumpNodes(List->getElementsAsNodes()), "['a', null, 'c']"); +} + +/// "a, b c" <=> [("a", ","), ("b", null), ("c", null)] +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(dumpElementsAndDelimiters(List->getElementsAsNodesAndDelimiters()), + "[('a', ','), ('b', null), ('c', null)]"); + EXPECT_EQ(dumpNodes(List->getElementsAsNodes()), "['a', 'b', 'c']"); +} + +/// "a, b," <=> [("a", ","), ("b", ","), (null, null)] +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(dumpElementsAndDelimiters(List->getElementsAsNodesAndDelimiters()), + "[('a', ','), ('b', ','), (null, null)]"); + EXPECT_EQ(dumpNodes(List->getElementsAsNodes()), "['a', 'b', null]"); +} + +/// "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(dumpElementsAndDelimiters(List->getElementsAsNodesAndDelimiters()), + "[('a', '::'), ('b', '::'), ('c', '::')]"); + EXPECT_EQ(dumpNodes(List->getElementsAsNodes()), "['a', 'b', 'c']"); +} + +/// "a:: :: c::" <=> [("a", "::"), (null, "::"), ("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(dumpElementsAndDelimiters(List->getElementsAsNodesAndDelimiters()), + "[('a', '::'), (null, '::'), ('c', '::')]"); + EXPECT_EQ(dumpNodes(List->getElementsAsNodes()), "['a', null, 'c']"); +} + +/// "a:: b c::" <=> [("a", "::"), ("b", null), ("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(dumpElementsAndDelimiters(List->getElementsAsNodesAndDelimiters()), + "[('a', '::'), ('b', null), ('c', '::')]"); + EXPECT_EQ(dumpNodes(List->getElementsAsNodes()), "['a', 'b', 'c']"); +} + +/// "a:: b:: c" <=> [("a", "::"), ("b", "::"), ("c", null)] +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(dumpElementsAndDelimiters(List->getElementsAsNodesAndDelimiters()), + "[('a', '::'), ('b', '::'), ('c', null)]"); + EXPECT_EQ(dumpNodes(List->getElementsAsNodes()), "['a', 'b', 'c']"); +} + } // namespace