1 | //===-- SemanticSelectionTests.cpp ----------------*- C++ -*--------------===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | |
9 | #include "Annotations.h" |
10 | #include "ClangdServer.h" |
11 | #include "Protocol.h" |
12 | #include "SemanticSelection.h" |
13 | #include "SyncAPI.h" |
14 | #include "TestFS.h" |
15 | #include "TestTU.h" |
16 | #include "llvm/ADT/ArrayRef.h" |
17 | #include "llvm/Support/Error.h" |
18 | #include "gmock/gmock.h" |
19 | #include "gtest/gtest.h" |
20 | #include <vector> |
21 | |
22 | namespace clang { |
23 | namespace clangd { |
24 | namespace { |
25 | |
26 | using ::testing::ElementsAre; |
27 | using ::testing::ElementsAreArray; |
28 | using ::testing::UnorderedElementsAreArray; |
29 | |
30 | // front() is SR.range, back() is outermost range. |
31 | std::vector<Range> gatherRanges(const SelectionRange &SR) { |
32 | std::vector<Range> Ranges; |
33 | for (const SelectionRange *S = &SR; S; S = S->parent.get()) |
34 | Ranges.push_back(x: S->range); |
35 | return Ranges; |
36 | } |
37 | |
38 | std::vector<Range> |
39 | gatherFoldingRanges(llvm::ArrayRef<FoldingRange> FoldingRanges) { |
40 | std::vector<Range> Ranges; |
41 | Range ; |
42 | for (const auto &R : FoldingRanges) { |
43 | NextRange.start.line = R.startLine; |
44 | NextRange.start.character = R.startCharacter; |
45 | NextRange.end.line = R.endLine; |
46 | NextRange.end.character = R.endCharacter; |
47 | Ranges.push_back(x: NextRange); |
48 | } |
49 | return Ranges; |
50 | } |
51 | |
52 | TEST(SemanticSelection, All) { |
53 | const char *Tests[] = { |
54 | R"cpp( // Single statement in a function body. |
55 | [[void func() [[{ |
56 | [[[[int v = [[1^00]]]];]] |
57 | }]]]] |
58 | )cpp" , |
59 | R"cpp( // Expression |
60 | [[void func() [[{ |
61 | int a = 1; |
62 | // int v = (10 + 2) * (a + a); |
63 | [[[[int v = [[[[([[[[10^]] + 2]])]] * (a + a)]]]];]] |
64 | }]]]] |
65 | )cpp" , |
66 | R"cpp( // Function call. |
67 | int add(int x, int y) { return x + y; } |
68 | [[void callee() [[{ |
69 | // int res = add(11, 22); |
70 | [[[[int res = [[add([[1^1]], 22)]]]];]] |
71 | }]]]] |
72 | )cpp" , |
73 | R"cpp( // Tricky macros. |
74 | #define MUL ) * ( |
75 | [[void func() [[{ |
76 | // int var = (4 + 15 MUL 6 + 10); |
77 | [[[[int var = [[[[([[4 + [[1^5]]]] MUL]] 6 + 10)]]]];]] |
78 | }]]]] |
79 | )cpp" , |
80 | R"cpp( // Cursor inside a macro. |
81 | #define HASH(x) ((x) % 10) |
82 | [[void func() [[{ |
83 | [[[[int a = [[HASH([[[[2^3]] + 34]])]]]];]] |
84 | }]]]] |
85 | )cpp" , |
86 | R"cpp( // Cursor on a macro. |
87 | #define HASH(x) ((x) % 10) |
88 | [[void func() [[{ |
89 | [[[[int a = [[HA^SH(23)]]]];]] |
90 | }]]]] |
91 | )cpp" , |
92 | R"cpp( // Multiple declaration. |
93 | [[void func() [[{ |
94 | [[[[int var1, var^2]], var3;]] |
95 | }]]]] |
96 | )cpp" , |
97 | R"cpp( // Before comment. |
98 | [[void func() [[{ |
99 | int var1 = 1; |
100 | [[[[int var2 = [[[[var1]]^ /*some comment*/ + 41]]]];]] |
101 | }]]]] |
102 | )cpp" , |
103 | // Empty file. |
104 | "[[^]]" , |
105 | // FIXME: We should get the whole DeclStmt as a range. |
106 | R"cpp( // Single statement in TU. |
107 | [[int v = [[1^00]]]]; |
108 | )cpp" , |
109 | R"cpp( // Cursor at end of VarDecl. |
110 | [[int v = [[100]]^]]; |
111 | )cpp" , |
112 | // FIXME: No node found associated to the position. |
113 | R"cpp( // Cursor in between spaces. |
114 | void func() { |
115 | int v = 100 + [[^]] 100; |
116 | } |
117 | )cpp" , |
118 | // Structs. |
119 | R"cpp( |
120 | struct AAA { struct BBB { static int ccc(); };}; |
121 | [[void func() [[{ |
122 | // int x = AAA::BBB::ccc(); |
123 | [[[[int x = [[[[AAA::BBB::c^cc]]()]]]];]] |
124 | }]]]] |
125 | )cpp" , |
126 | R"cpp( |
127 | struct AAA { struct BBB { static int ccc(); };}; |
128 | [[void func() [[{ |
129 | // int x = AAA::BBB::ccc(); |
130 | [[[[int x = [[[[[[[[[[AA^A]]::]]BBB::]]ccc]]()]]]];]] |
131 | }]]]] |
132 | )cpp" , |
133 | R"cpp( // Inside struct. |
134 | struct A { static int a(); }; |
135 | [[struct B { |
136 | [[static int b() [[{ |
137 | [[return [[[[1^1]] + 2]]]]; |
138 | }]]]] |
139 | }]]; |
140 | )cpp" , |
141 | // Namespaces. |
142 | R"cpp( |
143 | [[namespace nsa { |
144 | [[namespace nsb { |
145 | static int ccc(); |
146 | [[void func() [[{ |
147 | // int x = nsa::nsb::ccc(); |
148 | [[[[int x = [[[[nsa::nsb::cc^c]]()]]]];]] |
149 | }]]]] |
150 | }]] |
151 | }]] |
152 | )cpp" , |
153 | |
154 | }; |
155 | |
156 | for (const char *Test : Tests) { |
157 | auto T = Annotations(Test); |
158 | auto AST = TestTU::withCode(Code: T.code()).build(); |
159 | EXPECT_THAT(gatherRanges(llvm::cantFail(getSemanticRanges(AST, T.point()))), |
160 | ElementsAreArray(T.ranges())) |
161 | << Test; |
162 | } |
163 | } |
164 | |
165 | TEST(SemanticSelection, RunViaClangdServer) { |
166 | MockFS FS; |
167 | MockCompilationDatabase CDB; |
168 | ClangdServer Server(CDB, FS, ClangdServer::optsForTest()); |
169 | |
170 | auto FooH = testPath(File: "foo.h" ); |
171 | FS.Files[FooH] = R"cpp( |
172 | int foo(int x); |
173 | #define HASH(x) ((x) % 10) |
174 | )cpp" ; |
175 | |
176 | auto FooCpp = testPath(File: "Foo.cpp" ); |
177 | const char *SourceContents = R"cpp( |
178 | #include "foo.h" |
179 | [[void bar(int& inp) [[{ |
180 | // inp = HASH(foo(inp)); |
181 | [[inp = [[HASH([[foo([[in^p]])]])]]]]; |
182 | }]]]] |
183 | $empty[[^]] |
184 | )cpp" ; |
185 | Annotations SourceAnnotations(SourceContents); |
186 | FS.Files[FooCpp] = std::string(SourceAnnotations.code()); |
187 | Server.addDocument(File: FooCpp, Contents: SourceAnnotations.code()); |
188 | |
189 | auto Ranges = runSemanticRanges(Server, File: FooCpp, Pos: SourceAnnotations.points()); |
190 | ASSERT_TRUE(bool(Ranges)) |
191 | << "getSemanticRange returned an error: " << Ranges.takeError(); |
192 | ASSERT_EQ(Ranges->size(), SourceAnnotations.points().size()); |
193 | EXPECT_THAT(gatherRanges(Ranges->front()), |
194 | ElementsAreArray(SourceAnnotations.ranges())); |
195 | EXPECT_THAT(gatherRanges(Ranges->back()), |
196 | ElementsAre(SourceAnnotations.range("empty" ))); |
197 | } |
198 | |
199 | TEST(FoldingRanges, ASTAll) { |
200 | const char *Tests[] = { |
201 | R"cpp( |
202 | #define FOO int foo() {\ |
203 | int Variable = 42; \ |
204 | } |
205 | |
206 | // Do not generate folding range for braces within macro expansion. |
207 | FOO |
208 | |
209 | // Do not generate folding range within macro arguments. |
210 | #define FUNCTOR(functor) functor |
211 | void func() {[[ |
212 | FUNCTOR([](){}); |
213 | ]]} |
214 | |
215 | // Do not generate folding range with a brace coming from macro. |
216 | #define LBRACE { |
217 | void bar() LBRACE |
218 | int X = 42; |
219 | } |
220 | )cpp" , |
221 | R"cpp( |
222 | void func() {[[ |
223 | int Variable = 100; |
224 | |
225 | if (Variable > 5) {[[ |
226 | Variable += 42; |
227 | ]]} else if (Variable++) |
228 | ++Variable; |
229 | else {[[ |
230 | Variable--; |
231 | ]]} |
232 | |
233 | // Do not generate FoldingRange for empty CompoundStmts. |
234 | for (;;) {} |
235 | |
236 | // If there are newlines between {}, we should generate one. |
237 | for (;;) {[[ |
238 | |
239 | ]]} |
240 | ]]} |
241 | )cpp" , |
242 | R"cpp( |
243 | class Foo { |
244 | public: |
245 | Foo() {[[ |
246 | int X = 1; |
247 | ]]} |
248 | |
249 | private: |
250 | int getBar() {[[ |
251 | return 42; |
252 | ]]} |
253 | |
254 | // Braces are located at the same line: no folding range here. |
255 | void getFooBar() { } |
256 | }; |
257 | )cpp" , |
258 | }; |
259 | for (const char *Test : Tests) { |
260 | auto T = Annotations(Test); |
261 | auto AST = TestTU::withCode(Code: T.code()).build(); |
262 | EXPECT_THAT(gatherFoldingRanges(llvm::cantFail(getFoldingRanges(AST))), |
263 | UnorderedElementsAreArray(T.ranges())) |
264 | << Test; |
265 | } |
266 | } |
267 | |
268 | TEST(FoldingRanges, PseudoParserWithoutLineFoldings) { |
269 | const char *Tests[] = { |
270 | R"cpp( |
271 | #define FOO int foo() {\ |
272 | int Variable = 42; \ |
273 | } |
274 | |
275 | // Do not generate folding range for braces within macro expansion. |
276 | FOO |
277 | |
278 | // Do not generate folding range within macro arguments. |
279 | #define FUNCTOR(functor) functor |
280 | void func() {[[ |
281 | FUNCTOR([](){}); |
282 | ]]} |
283 | |
284 | // Do not generate folding range with a brace coming from macro. |
285 | #define LBRACE { |
286 | void bar() LBRACE |
287 | int X = 42; |
288 | } |
289 | )cpp" , |
290 | R"cpp( |
291 | void func() {[[ |
292 | int Variable = 100; |
293 | |
294 | if (Variable > 5) {[[ |
295 | Variable += 42; |
296 | ]]} else if (Variable++) |
297 | ++Variable; |
298 | else {[[ |
299 | Variable--; |
300 | ]]} |
301 | |
302 | // Do not generate FoldingRange for empty CompoundStmts. |
303 | for (;;) {} |
304 | |
305 | // If there are newlines between {}, we should generate one. |
306 | for (;;) {[[ |
307 | |
308 | ]]} |
309 | ]]} |
310 | )cpp" , |
311 | R"cpp( |
312 | class Foo {[[ |
313 | public: |
314 | Foo() {[[ |
315 | int X = 1; |
316 | ]]} |
317 | |
318 | private: |
319 | int getBar() {[[ |
320 | return 42; |
321 | ]]} |
322 | |
323 | // Braces are located at the same line: no folding range here. |
324 | void getFooBar() { } |
325 | ]]}; |
326 | )cpp" , |
327 | R"cpp( |
328 | // Range boundaries on escaped newlines. |
329 | class Foo \ |
330 | \ |
331 | {[[ \ |
332 | public: |
333 | Foo() {[[\ |
334 | int X = 1; |
335 | ]]} \ |
336 | ]]}; |
337 | )cpp" , |
338 | R"cpp( |
339 | /*[[ Multi |
340 | * line |
341 | * comment |
342 | ]]*/ |
343 | )cpp" , |
344 | R"cpp( |
345 | //[[ Comment |
346 | // 1]] |
347 | |
348 | //[[ Comment |
349 | // 2]] |
350 | |
351 | // No folding for single line comment. |
352 | |
353 | /*[[ comment 3 |
354 | ]]*/ |
355 | |
356 | /*[[ comment 4 |
357 | ]]*/ |
358 | |
359 | /*[[ foo */ |
360 | /* bar ]]*/ |
361 | |
362 | /*[[ foo */ |
363 | // baz |
364 | /* bar ]]*/ |
365 | |
366 | /*[[ foo */ |
367 | /* bar*/ |
368 | // baz]] |
369 | |
370 | //[[ foo |
371 | /* bar */]] |
372 | )cpp" , |
373 | }; |
374 | for (const char *Test : Tests) { |
375 | auto T = Annotations(Test); |
376 | EXPECT_THAT(gatherFoldingRanges(llvm::cantFail(getFoldingRanges( |
377 | T.code().str(), /*LineFoldingsOnly=*/false))), |
378 | UnorderedElementsAreArray(T.ranges())) |
379 | << Test; |
380 | } |
381 | } |
382 | |
383 | TEST(FoldingRanges, PseudoParserLineFoldingsOnly) { |
384 | const char *Tests[] = { |
385 | R"cpp( |
386 | void func(int a) {[[ |
387 | a++;]] |
388 | } |
389 | )cpp" , |
390 | R"cpp( |
391 | // Always exclude last line for brackets. |
392 | void func(int a) {[[ |
393 | if(a == 1) {[[ |
394 | a++;]] |
395 | } else if (a == 2){[[ |
396 | a--;]] |
397 | } else { // No folding for 2 line bracketed ranges. |
398 | }]] |
399 | } |
400 | )cpp" , |
401 | R"cpp( |
402 | /*[[ comment |
403 | * comment]] |
404 | */ |
405 | |
406 | /* No folding for this comment. |
407 | */ |
408 | |
409 | // No folding for this comment. |
410 | |
411 | //[[ 2 single line comment. |
412 | // 2 single line comment.]] |
413 | |
414 | //[[ >=2 line comments. |
415 | // >=2 line comments. |
416 | // >=2 line comments.]] |
417 | |
418 | //[[ foo\ |
419 | bar\ |
420 | baz]] |
421 | |
422 | /*[[ foo */ |
423 | /* bar */]] |
424 | /* baz */ |
425 | |
426 | /*[[ foo */ |
427 | /* bar]] |
428 | * This does not fold me */ |
429 | |
430 | //[[ foo |
431 | /* bar */]] |
432 | )cpp" , |
433 | // FIXME: Support folding template arguments. |
434 | // R"cpp( |
435 | // template <[[typename foo, class bar]]> struct baz {}; |
436 | // )cpp", |
437 | |
438 | }; |
439 | auto StripColumns = [](const std::vector<Range> &Ranges) { |
440 | std::vector<Range> Res; |
441 | for (Range R : Ranges) { |
442 | R.start.character = R.end.character = 0; |
443 | Res.push_back(x: R); |
444 | } |
445 | return Res; |
446 | }; |
447 | for (const char *Test : Tests) { |
448 | auto T = Annotations(Test); |
449 | EXPECT_THAT( |
450 | StripColumns(gatherFoldingRanges(llvm::cantFail( |
451 | getFoldingRanges(T.code().str(), /*LineFoldingsOnly=*/true)))), |
452 | UnorderedElementsAreArray(StripColumns(T.ranges()))) |
453 | << Test; |
454 | } |
455 | } |
456 | } // namespace |
457 | } // namespace clangd |
458 | } // namespace clang |
459 | |