diff --git a/clang-tools-extra/clangd/unittests/SemanticSelectionTests.cpp b/clang-tools-extra/clangd/unittests/SemanticSelectionTests.cpp --- a/clang-tools-extra/clangd/unittests/SemanticSelectionTests.cpp +++ b/clang-tools-extra/clangd/unittests/SemanticSelectionTests.cpp @@ -234,6 +234,544 @@ } } +TEST(FoldingRanges, ControlFlow) { + const char *Tests[] = { + // If. + R"cpp( + int main() {[[ + bool B = true; + int I = 0; + char C = 'z'; + + if (B && I > 42 || ~(++I)) {[[ + ++I; + ]]} else {[[ + B = false; + ]]} + + if (B && (!B) || !((C == 'a'))) {[[ + ++I; + ]]} else if ([[!B && true != false && + 2 * 2 == 4]]) {[[ + --I; + ]]} else {[[ + C = 'a'; + ]]} + + if (B && ([["subexpression" != "multi-line" && + 1 > 0]])) + B = false; + + if ([[int Init = 0, Variable = 9000; + [[Init == 0 && Init > 0 || + Init < 5000]]]]) + Init = 42; + ]]} + )cpp", + // While. + R"cpp( + int main() {[[ + bool B; + while ([[B]]) {[[ + B = !B; + ]]} + + do {[[ + B = !B; + ]]} while ([[B]]); + ]]} + )cpp", + // For. + R"cpp( + int main() {[[ + for ([[int I = 0;I < 42; ++I]]) {[[ + --I; + ]]} + ]]} + )cpp", + // Switch. + R"cpp( + void noop(); + + int main() {[[ + int i = 2; + switch ([[i]]) {[[ + case 1:[[ noop();]] + case 2:[[ noop(); //[[execution starts at this case label]]]] + case 3:[[ noop();]] + case 4: + case 5:[[ noop(); + break; //[[execution of subsequent statements is terminated]]]] + case 6:[[ noop();]] + ]]} + ]]} + )cpp", + }; + for (const char *Test : Tests) { + auto T = Annotations(Test); + auto AST = TestTU::withCode(T.code()).build(); + EXPECT_THAT(gatherFoldingRanges(llvm::cantFail(getFoldingRanges(AST))), + UnorderedElementsAreArray(T.ranges())) + << Test; + } +} + +TEST(FoldingRanges, Misc) { + const char *Tests[] = { + // Statement groups. + R"cpp( + int main() {[[ + [[int X = 5; + int Y = 42; + bool B = 15;]] + if ([[B]]) {[[ ++X; ]]} + unsigned U = 9000; + ]]} + )cpp", + // Expressions (fold if they span multiple lines). + R"cpp( + int main() {[[ + bool B = 42 > 0; + B = (42 > 0); + B = (42 > 0 && 9000 > 2000); + B = 42 > 0 && (9000 > 2000 && 2000 > 1000); + B = [[42 > 0 && ([[9000 > 2000 && + 2000 > 1000]])]]; + ]]} + )cpp", + // Enum. + R"cpp( + enum Color {[[ + Green = 0, + YInMn, + Orange, + ]]}; + )cpp", + // Argument lists. + R"cpp( + int foo([[char C, bool B]]) {[[ return static_cast(C); ]]} + + foo([['z', true]]); + + struct Foo { + Foo([[int I, unsigned U, bool B=true]]) {} + }; + + Foo F = Foo([[/*[[I=]]*/1, /*[[U=]]*/2, /*[[B=]]false]]); + F = Foo([[1, 2]]); + )cpp", + // Namespace. + R"cpp( + namespace ns {[[ + int Variable = 42; + namespace nested {[[ + int NestedVariable = 50; + ]]} + ]]} + + namespace a {[[ + namespace b {[[ + namespace c {[[ + + ]]} //[[ namespacee c]] + ]]} //[[ namespacee b]] + ]]} //[[ namespacee a]] + + namespace modern::ns::syntax {[[ + ]]} //[[ namespace modern::ns::syntax]] + + namespace {[[ + ]]} //[[ namespace]] + + extern "C" {[[ + void Foo(); + ]]} + )cpp", + // Strings. + R"cpp( + std::string String = "[[ShortString]]"; + String = "[[Suuuuuuuuuuuuuuuuuper Looooooooooooooooong String]]"; + String = u8"[[Suuuuuuuuuuuuuuuuuper Looooooooooooooooong String]]"; + + String = R"raw([[Suuuuuuuuuuuuuuuuuper Looooooooooooooong String]])raw"; + + const char *text = + "[[This" + "is a multiline" + "string]]"; + + const char *text2 = + "[[Here is another \ + string \ + also \ + multilineeeeee]]"; + )cpp", + // Arrays. + R"cpp( + char Array[] = {[[ 'F', 'o', 'o', 'b', 'a', 'r', '\0' ]]}; + + Array = {[[ 'F', 'o', 'o', 'b', 'a', 'r', 'F', 'o', 'o', 'b', + 'a', 'r', '\0' ]]}; + + int Nested[3][4] = {[[{[[0,1,2,3]]}, {[[4,5,6,7]]}, {[[8,9,10,11]]}]]}; + )cpp", + // Templates. + R"cpp( + template <[[typename T, typename U, typename V, int X, char C, bool B]]> + class Foo {}; + + template <[[bool B]]> + class Foo<[[unsigned, unsigned, unsigned, -42, 'z', B]]> { + int Specialization; + }; + + template <> + class Foo<[[unsigned, unsigned, unsigned, -42, 'z', false]]> { + int Specialization; + }; + + Foo<[[char, int, bool, 42, 'x', false]]> F; + + template <[[typename T, unsigned U]]> + void foo([[T t, unsigned u]]) {[[ return; ]]} + + template <[[unsigned U]]> + void foo<[U, 50]>([[char t, U u]]) {} + + template <> + void foo<[char, 42]>([[char t, U u]]) {} + )cpp", + // Lambdas. + R"cpp( + int main() {[[ + bool B = true; + int I = 42; + auto Lambda = [=]([[auto a, auto&& b]]) {[[ return a < b; ]]}; + auto vglambda = [[[=, &B, &I]]]([[auto printer]]) { + return [=](auto&&... ts) + { + return [=] { printer(ts...); }; + }; + }; + ]]} + )cpp", + }; + for (const char *Test : Tests) { + auto T = Annotations(Test); + auto AST = TestTU::withCode(T.code()).build(); + EXPECT_THAT(gatherFoldingRanges(llvm::cantFail(getFoldingRanges(AST))), + UnorderedElementsAreArray(T.ranges())) + << Test; + } +} + +TEST(FoldingRanges, Comments) { + const char *Tests[] = { + R"cpp( + //[[ This is a one-line comment.]] + )cpp", + R"cpp( + //[[ Continuous + // Comment + // ...]] + + //[[ And another one + // But doesn't belong to the one above]] + + //[[ This time + // + // It's connected!]] + + ///[[ Doxygen]] + + ///[[ Doxygen + /// With more multiple lines. + /// + /// + /// The End]] + )cpp", + R"cpp( + /*[[ C-Style comment. ]]*/ + + [[/* Spanning + * multiple + * lines */]] + )cpp", + R"cpp( + [[/* C-Style + * multiple + * lines */]] + //[[ Followed by C++ Style + // Are still different comments!]] + )cpp", + R"cpp( + /*[[! + * ... text ... + ]]*/ + + /*[[* + * ... text ... + ]]*/ + + /*[[! + ... text ... + ]]*/ + + ///[[ + /// ... text ... + ///]] + + //[[! + //!... text ... + //!]] + + /*[[*******************************************//** + * ... text + **********************************************]]*/ + + //[[/////////////////////////////////////////////// + /// ... text ... + /////////////////////////////////////////////////]] + + /*[[*********************************************** + * ... text + **********************************************]]*/ + )cpp", + R"cpp( + /*[[ + Tricky C-Style + + void Foo(int I); + void Foo(int I, char C); + /*]]*/ + )cpp", + R"cpp( + int Foo([[int I, bool Boolean=false]]) {[[ + return 42; //[[ Do nothing, just return!]] + ]]} + + const auto Variable = Foo([[42, /*[[Boolean=]]*/true]]); + )cpp", + R"cpp( + //[[ Some documentation. + // @param + int Foo([[int I, bool Boolean=false]]) {[[ //[[ With a comment]] + return 42; //[[ Do nothing, just return!]] + ]]} + + const auto Variable = Foo([[42, /*[[Boolean=]]*/]]true]]); + )cpp", + R"cpp( + //[[ Empty comments don't generate folding ranges!]] + + // + /**/ + /// + )cpp", + }; + for (const char *Test : Tests) { + auto T = Annotations(Test); + auto AST = TestTU::withCode(T.code()).build(); + EXPECT_THAT(gatherFoldingRanges(llvm::cantFail(getFoldingRanges(AST))), + UnorderedElementsAreArray(T.ranges())) + << Test; + } +} + +TEST(FoldingRanges, Preprocessor) { + const char *Tests[] = { + R"cpp( + #include [[ + #include + #include ]] + + #include [[]] + + #include [["external/Logger.h" + #include "external/Vector.h" + #include "project/DataStructures/Trie.h"]] + + #include [["project/File.h" + #include + #include ]] + + #include [["math.h"]] + )cpp", + R"cpp( + #define true [[false]] + + #define getmax(a,b) [[((a)>(b)?(a):(b))]] + #define PI [[3.14159]] + )cpp", + R"cpp( + #ifndef [[FOO_H_INCLUDED]][[ /*[[ any name uniquely mapped to file name ]]*/ + #define FOO_H_INCLUDED + //[[ ^ Don't fold simple definition?]] + + //[[ contents of the file are here]] + ]]#endif + + #ifndef [[LIBRARY_FILENAME_H]][[ + #define LIBRARY_FILENAME_H + //[[ contents of the header]] + ]]#endif /*[[ LIBRARY_FILENAME_H ]]*/ + + #if [[__has_include()]][[ + # include + # define have_optional 1 + ]]#elif [[__has_include()]][[ + # include + # define have_optional 1 + # define experimental_optional 1 + ]]#else[[ + # define have_optional 0 + ]]#endif + + #if [[OK]][[ + # define NICE + ]]#else[[ + # define true [[false]] + ]]#endif + + #ifdef [[SOMETHING]][[ + //[[ Oh no!]] + #undef SOMETHING + ]]#else[[ + #define OKAY_GOOD + ]]#endif + )cpp", + }; + for (const char *Test : Tests) { + auto T = Annotations(Test); + auto AST = TestTU::withCode(T.code()).build(); + EXPECT_THAT(gatherFoldingRanges(llvm::cantFail(getFoldingRanges(AST))), + UnorderedElementsAreArray(T.ranges())) + << Test; + } +} + +TEST(FoldingRanges, Classes) { + const char *Tests[] = { + // Basics. + R"cpp( + class Foo {[[ + int I; + ]]}; + + struct Bar {[[ + int I; + ]]}; + + class Baz {[[ + public:[[ + Baz(); + + int I;]] + ]]}; + + class Chewbacca {}; + class Vader; + + struct FooStruct {[[ + int Field; + ]]}; + struct BarStruct {[[]]}; + struct FwdDeclaration; + )cpp", + // Accessor secions. + R"cpp( + class Foo {[[ + public: + private: + protected: + public: + ]]}; + + class Bar {[[ + public:[[ + Bar(); + Bar([[int X]]); +]] + private:[[ + int Field;]] + protected:[[ + bool ProtectThis;]] + public:[[ + int getField() {[[ return Field; ]]}]] + ]]}; + + class ImplicitFirstSection {[[[[ + ImplicitFirstSection() {} + + int Field;]] + public:[[ + + int getField();]] + ]]}; + )cpp", + // Nested classes with accessor sections. + R"cpp( + class Nested {[[ + public: + private:[[ + class Inner {[[ + public:[[ + Inner(); + Inner(int X); + ]] + private:[[ + int Field;]] + protected:[[ + bool ProtectThis;]] + public:[[ + int getField() {[[ return Field; ]]}]] + ]]};]] + protected: + public: + ]]}; + + struct NestedStruct {[[ + public: + private:[[ + class Inner {[[ + public:[[ + Inner(); + Inner(int X); + ]] + private:[[ + int Field;]] + protected:[[ + bool ProtectThis;]] + public:[[ + int getField() {[[ return Field; ]]}]] + ]]};]] + protected: + public: + ]]}; + )cpp", + // Member initializer lists. + R"cpp( + class Foo {[[ + public:[[ + Foo([[int Integer, char Char, bool Boolean]]) :[[ I(Integer), C(Char), B(Boolean) ]] {[[ + int X; + ]]}]] + private:[[ + int I; + char C; + bool B;]] + ]]}; + )cpp", + }; + for (const char *Test : Tests) { + auto T = Annotations(Test); + auto AST = TestTU::withCode(T.code()).build(); + EXPECT_THAT(gatherFoldingRanges(llvm::cantFail(getFoldingRanges(AST))), + UnorderedElementsAreArray(T.ranges())) + << Test; + } +} + } // namespace } // namespace clangd } // namespace clang