1 | //===-- SourceCodeTests.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 | #include "Annotations.h" |
9 | #include "Protocol.h" |
10 | #include "SourceCode.h" |
11 | #include "TestTU.h" |
12 | #include "support/Context.h" |
13 | #include "clang/Basic/LangOptions.h" |
14 | #include "clang/Basic/SourceLocation.h" |
15 | #include "clang/Basic/TokenKinds.h" |
16 | #include "clang/Format/Format.h" |
17 | #include "llvm/Support/Error.h" |
18 | #include "llvm/Testing/Annotations/Annotations.h" |
19 | #include "llvm/Testing/Support/Error.h" |
20 | #include "gmock/gmock.h" |
21 | #include "gtest/gtest.h" |
22 | #include <optional> |
23 | #include <tuple> |
24 | |
25 | namespace clang { |
26 | namespace clangd { |
27 | namespace { |
28 | |
29 | using llvm::Failed; |
30 | using llvm::FailedWithMessage; |
31 | using llvm::HasValue; |
32 | |
33 | MATCHER_P2(Pos, Line, Col, "" ) { |
34 | return arg.line == int(Line) && arg.character == int(Col); |
35 | } |
36 | |
37 | MATCHER_P(macroName, Name, "" ) { return arg.Name == Name; } |
38 | |
39 | /// A helper to make tests easier to read. |
40 | Position position(int Line, int Character) { |
41 | Position Pos; |
42 | Pos.line = Line; |
43 | Pos.character = Character; |
44 | return Pos; |
45 | } |
46 | |
47 | TEST(SourceCodeTests, lspLength) { |
48 | EXPECT_EQ(lspLength("" ), 0UL); |
49 | EXPECT_EQ(lspLength("ascii" ), 5UL); |
50 | // BMP |
51 | EXPECT_EQ(lspLength("↓" ), 1UL); |
52 | EXPECT_EQ(lspLength("¥" ), 1UL); |
53 | // astral |
54 | EXPECT_EQ(lspLength("😂" ), 2UL); |
55 | |
56 | WithContextValue UTF8(kCurrentOffsetEncoding, OffsetEncoding::UTF8); |
57 | EXPECT_EQ(lspLength("" ), 0UL); |
58 | EXPECT_EQ(lspLength("ascii" ), 5UL); |
59 | // BMP |
60 | EXPECT_EQ(lspLength("↓" ), 3UL); |
61 | EXPECT_EQ(lspLength("¥" ), 2UL); |
62 | // astral |
63 | EXPECT_EQ(lspLength("😂" ), 4UL); |
64 | |
65 | WithContextValue UTF32(kCurrentOffsetEncoding, OffsetEncoding::UTF32); |
66 | EXPECT_EQ(lspLength("" ), 0UL); |
67 | EXPECT_EQ(lspLength("ascii" ), 5UL); |
68 | // BMP |
69 | EXPECT_EQ(lspLength("↓" ), 1UL); |
70 | EXPECT_EQ(lspLength("¥" ), 1UL); |
71 | // astral |
72 | EXPECT_EQ(lspLength("😂" ), 1UL); |
73 | } |
74 | |
75 | TEST(SourceCodeTests, lspLengthBadUTF8) { |
76 | // Results are not well-defined if source file isn't valid UTF-8. |
77 | // However we shouldn't crash or return something totally wild. |
78 | const char *BadUTF8[] = {"\xa0" , "\xff\xff\xff\xff\xff" }; |
79 | |
80 | for (OffsetEncoding Encoding : |
81 | {OffsetEncoding::UTF8, OffsetEncoding::UTF16, OffsetEncoding::UTF32}) { |
82 | WithContextValue UTF32(kCurrentOffsetEncoding, Encoding); |
83 | for (const char *Bad : BadUTF8) { |
84 | EXPECT_GE(lspLength(Bad), 0u); |
85 | EXPECT_LE(lspLength(Bad), strlen(Bad)); |
86 | } |
87 | } |
88 | } |
89 | |
90 | // The = → 🡆 below are ASCII (1 byte), BMP (3 bytes), and astral (4 bytes). |
91 | const char File[] = R"(0:0 = 0 |
92 | 1:0 → 8 |
93 | 2:0 🡆 18)" ; |
94 | struct Line { |
95 | unsigned Number; |
96 | unsigned Offset; |
97 | unsigned Length; |
98 | }; |
99 | Line FileLines[] = {Line{.Number: 0, .Offset: 0, .Length: 7}, Line{.Number: 1, .Offset: 8, .Length: 9}, Line{.Number: 2, .Offset: 18, .Length: 11}}; |
100 | |
101 | TEST(SourceCodeTests, PositionToOffset) { |
102 | // line out of bounds |
103 | EXPECT_THAT_EXPECTED(positionToOffset(File, position(-1, 2)), llvm::Failed()); |
104 | // first line |
105 | EXPECT_THAT_EXPECTED(positionToOffset(File, position(0, -1)), |
106 | llvm::Failed()); // out of range |
107 | EXPECT_THAT_EXPECTED(positionToOffset(File, position(0, 0)), |
108 | llvm::HasValue(0)); // first character |
109 | EXPECT_THAT_EXPECTED(positionToOffset(File, position(0, 3)), |
110 | llvm::HasValue(3)); // middle character |
111 | EXPECT_THAT_EXPECTED(positionToOffset(File, position(0, 6)), |
112 | llvm::HasValue(6)); // last character |
113 | EXPECT_THAT_EXPECTED(positionToOffset(File, position(0, 7)), |
114 | llvm::HasValue(7)); // the newline itself |
115 | EXPECT_THAT_EXPECTED(positionToOffset(File, position(0, 7), false), |
116 | llvm::HasValue(7)); |
117 | EXPECT_THAT_EXPECTED(positionToOffset(File, position(0, 8)), |
118 | llvm::HasValue(7)); // out of range |
119 | EXPECT_THAT_EXPECTED(positionToOffset(File, position(0, 8), false), |
120 | llvm::Failed()); // out of range |
121 | // middle line |
122 | EXPECT_THAT_EXPECTED(positionToOffset(File, position(1, -1)), |
123 | llvm::Failed()); // out of range |
124 | EXPECT_THAT_EXPECTED(positionToOffset(File, position(1, 0)), |
125 | llvm::HasValue(8)); // first character |
126 | EXPECT_THAT_EXPECTED(positionToOffset(File, position(1, 3)), |
127 | llvm::HasValue(11)); // middle character |
128 | EXPECT_THAT_EXPECTED(positionToOffset(File, position(1, 3), false), |
129 | llvm::HasValue(11)); |
130 | EXPECT_THAT_EXPECTED(positionToOffset(File, position(1, 6)), |
131 | llvm::HasValue(16)); // last character |
132 | EXPECT_THAT_EXPECTED(positionToOffset(File, position(1, 7)), |
133 | llvm::HasValue(17)); // the newline itself |
134 | EXPECT_THAT_EXPECTED(positionToOffset(File, position(1, 8)), |
135 | llvm::HasValue(17)); // out of range |
136 | EXPECT_THAT_EXPECTED(positionToOffset(File, position(1, 8), false), |
137 | llvm::Failed()); // out of range |
138 | // last line |
139 | EXPECT_THAT_EXPECTED(positionToOffset(File, position(2, -1)), |
140 | llvm::Failed()); // out of range |
141 | EXPECT_THAT_EXPECTED(positionToOffset(File, position(2, 0)), |
142 | llvm::HasValue(18)); // first character |
143 | EXPECT_THAT_EXPECTED(positionToOffset(File, position(2, 3)), |
144 | llvm::HasValue(21)); // middle character |
145 | EXPECT_THAT_EXPECTED(positionToOffset(File, position(2, 5), false), |
146 | llvm::Failed()); // middle of surrogate pair |
147 | EXPECT_THAT_EXPECTED(positionToOffset(File, position(2, 5)), |
148 | llvm::HasValue(26)); // middle of surrogate pair |
149 | EXPECT_THAT_EXPECTED(positionToOffset(File, position(2, 6), false), |
150 | llvm::HasValue(26)); // end of surrogate pair |
151 | EXPECT_THAT_EXPECTED(positionToOffset(File, position(2, 8)), |
152 | llvm::HasValue(28)); // last character |
153 | EXPECT_THAT_EXPECTED(positionToOffset(File, position(2, 9)), |
154 | llvm::HasValue(29)); // EOF |
155 | EXPECT_THAT_EXPECTED(positionToOffset(File, position(2, 10), false), |
156 | llvm::Failed()); // out of range |
157 | // line out of bounds |
158 | EXPECT_THAT_EXPECTED(positionToOffset(File, position(3, 0)), llvm::Failed()); |
159 | EXPECT_THAT_EXPECTED(positionToOffset(File, position(3, 1)), llvm::Failed()); |
160 | |
161 | // Codepoints are similar, except near astral characters. |
162 | WithContextValue UTF32(kCurrentOffsetEncoding, OffsetEncoding::UTF32); |
163 | // line out of bounds |
164 | EXPECT_THAT_EXPECTED(positionToOffset(File, position(-1, 2)), llvm::Failed()); |
165 | // first line |
166 | EXPECT_THAT_EXPECTED(positionToOffset(File, position(0, -1)), |
167 | llvm::Failed()); // out of range |
168 | EXPECT_THAT_EXPECTED(positionToOffset(File, position(0, 0)), |
169 | llvm::HasValue(0)); // first character |
170 | EXPECT_THAT_EXPECTED(positionToOffset(File, position(0, 3)), |
171 | llvm::HasValue(3)); // middle character |
172 | EXPECT_THAT_EXPECTED(positionToOffset(File, position(0, 6)), |
173 | llvm::HasValue(6)); // last character |
174 | EXPECT_THAT_EXPECTED(positionToOffset(File, position(0, 7)), |
175 | llvm::HasValue(7)); // the newline itself |
176 | EXPECT_THAT_EXPECTED(positionToOffset(File, position(0, 7), false), |
177 | llvm::HasValue(7)); |
178 | EXPECT_THAT_EXPECTED(positionToOffset(File, position(0, 8)), |
179 | llvm::HasValue(7)); // out of range |
180 | EXPECT_THAT_EXPECTED(positionToOffset(File, position(0, 8), false), |
181 | llvm::Failed()); // out of range |
182 | // middle line |
183 | EXPECT_THAT_EXPECTED(positionToOffset(File, position(1, -1)), |
184 | llvm::Failed()); // out of range |
185 | EXPECT_THAT_EXPECTED(positionToOffset(File, position(1, 0)), |
186 | llvm::HasValue(8)); // first character |
187 | EXPECT_THAT_EXPECTED(positionToOffset(File, position(1, 3)), |
188 | llvm::HasValue(11)); // middle character |
189 | EXPECT_THAT_EXPECTED(positionToOffset(File, position(1, 3), false), |
190 | llvm::HasValue(11)); |
191 | EXPECT_THAT_EXPECTED(positionToOffset(File, position(1, 6)), |
192 | llvm::HasValue(16)); // last character |
193 | EXPECT_THAT_EXPECTED(positionToOffset(File, position(1, 7)), |
194 | llvm::HasValue(17)); // the newline itself |
195 | EXPECT_THAT_EXPECTED(positionToOffset(File, position(1, 8)), |
196 | llvm::HasValue(17)); // out of range |
197 | EXPECT_THAT_EXPECTED(positionToOffset(File, position(1, 8), false), |
198 | llvm::Failed()); // out of range |
199 | // last line |
200 | EXPECT_THAT_EXPECTED(positionToOffset(File, position(2, -1)), |
201 | llvm::Failed()); // out of range |
202 | EXPECT_THAT_EXPECTED(positionToOffset(File, position(2, 0)), |
203 | llvm::HasValue(18)); // first character |
204 | EXPECT_THAT_EXPECTED(positionToOffset(File, position(2, 4)), |
205 | llvm::HasValue(22)); // Before astral character. |
206 | EXPECT_THAT_EXPECTED(positionToOffset(File, position(2, 5), false), |
207 | llvm::HasValue(26)); // after astral character |
208 | EXPECT_THAT_EXPECTED(positionToOffset(File, position(2, 7)), |
209 | llvm::HasValue(28)); // last character |
210 | EXPECT_THAT_EXPECTED(positionToOffset(File, position(2, 8)), |
211 | llvm::HasValue(29)); // EOF |
212 | EXPECT_THAT_EXPECTED(positionToOffset(File, position(2, 9), false), |
213 | llvm::Failed()); // out of range |
214 | // line out of bounds |
215 | EXPECT_THAT_EXPECTED(positionToOffset(File, position(3, 0)), llvm::Failed()); |
216 | EXPECT_THAT_EXPECTED(positionToOffset(File, position(3, 1)), llvm::Failed()); |
217 | |
218 | // Test UTF-8, where transformations are trivial. |
219 | WithContextValue UTF8(kCurrentOffsetEncoding, OffsetEncoding::UTF8); |
220 | EXPECT_THAT_EXPECTED(positionToOffset(File, position(-1, 2)), llvm::Failed()); |
221 | EXPECT_THAT_EXPECTED(positionToOffset(File, position(3, 0)), llvm::Failed()); |
222 | for (Line L : FileLines) { |
223 | EXPECT_THAT_EXPECTED(positionToOffset(File, position(L.Number, -1)), |
224 | llvm::Failed()); // out of range |
225 | for (unsigned I = 0; I <= L.Length; ++I) |
226 | EXPECT_THAT_EXPECTED(positionToOffset(File, position(L.Number, I)), |
227 | llvm::HasValue(L.Offset + I)); |
228 | EXPECT_THAT_EXPECTED( |
229 | positionToOffset(File, position(L.Number, L.Length + 1)), |
230 | llvm::HasValue(L.Offset + L.Length)); |
231 | EXPECT_THAT_EXPECTED( |
232 | positionToOffset(File, position(L.Number, L.Length + 1), false), |
233 | llvm::Failed()); // out of range |
234 | } |
235 | } |
236 | |
237 | TEST(SourceCodeTests, OffsetToPosition) { |
238 | EXPECT_THAT(offsetToPosition(File, 0), Pos(0, 0)) << "start of file" ; |
239 | EXPECT_THAT(offsetToPosition(File, 3), Pos(0, 3)) << "in first line" ; |
240 | EXPECT_THAT(offsetToPosition(File, 6), Pos(0, 6)) << "end of first line" ; |
241 | EXPECT_THAT(offsetToPosition(File, 7), Pos(0, 7)) << "first newline" ; |
242 | EXPECT_THAT(offsetToPosition(File, 8), Pos(1, 0)) << "start of second line" ; |
243 | EXPECT_THAT(offsetToPosition(File, 12), Pos(1, 4)) << "before BMP char" ; |
244 | EXPECT_THAT(offsetToPosition(File, 13), Pos(1, 5)) << "in BMP char" ; |
245 | EXPECT_THAT(offsetToPosition(File, 15), Pos(1, 5)) << "after BMP char" ; |
246 | EXPECT_THAT(offsetToPosition(File, 16), Pos(1, 6)) << "end of second line" ; |
247 | EXPECT_THAT(offsetToPosition(File, 17), Pos(1, 7)) << "second newline" ; |
248 | EXPECT_THAT(offsetToPosition(File, 18), Pos(2, 0)) << "start of last line" ; |
249 | EXPECT_THAT(offsetToPosition(File, 21), Pos(2, 3)) << "in last line" ; |
250 | EXPECT_THAT(offsetToPosition(File, 22), Pos(2, 4)) << "before astral char" ; |
251 | EXPECT_THAT(offsetToPosition(File, 24), Pos(2, 6)) << "in astral char" ; |
252 | EXPECT_THAT(offsetToPosition(File, 26), Pos(2, 6)) << "after astral char" ; |
253 | EXPECT_THAT(offsetToPosition(File, 28), Pos(2, 8)) << "end of last line" ; |
254 | EXPECT_THAT(offsetToPosition(File, 29), Pos(2, 9)) << "EOF" ; |
255 | EXPECT_THAT(offsetToPosition(File, 30), Pos(2, 9)) << "out of bounds" ; |
256 | |
257 | // Codepoints are similar, except near astral characters. |
258 | WithContextValue UTF32(kCurrentOffsetEncoding, OffsetEncoding::UTF32); |
259 | EXPECT_THAT(offsetToPosition(File, 0), Pos(0, 0)) << "start of file" ; |
260 | EXPECT_THAT(offsetToPosition(File, 3), Pos(0, 3)) << "in first line" ; |
261 | EXPECT_THAT(offsetToPosition(File, 6), Pos(0, 6)) << "end of first line" ; |
262 | EXPECT_THAT(offsetToPosition(File, 7), Pos(0, 7)) << "first newline" ; |
263 | EXPECT_THAT(offsetToPosition(File, 8), Pos(1, 0)) << "start of second line" ; |
264 | EXPECT_THAT(offsetToPosition(File, 12), Pos(1, 4)) << "before BMP char" ; |
265 | EXPECT_THAT(offsetToPosition(File, 13), Pos(1, 5)) << "in BMP char" ; |
266 | EXPECT_THAT(offsetToPosition(File, 15), Pos(1, 5)) << "after BMP char" ; |
267 | EXPECT_THAT(offsetToPosition(File, 16), Pos(1, 6)) << "end of second line" ; |
268 | EXPECT_THAT(offsetToPosition(File, 17), Pos(1, 7)) << "second newline" ; |
269 | EXPECT_THAT(offsetToPosition(File, 18), Pos(2, 0)) << "start of last line" ; |
270 | EXPECT_THAT(offsetToPosition(File, 21), Pos(2, 3)) << "in last line" ; |
271 | EXPECT_THAT(offsetToPosition(File, 22), Pos(2, 4)) << "before astral char" ; |
272 | EXPECT_THAT(offsetToPosition(File, 24), Pos(2, 5)) << "in astral char" ; |
273 | EXPECT_THAT(offsetToPosition(File, 26), Pos(2, 5)) << "after astral char" ; |
274 | EXPECT_THAT(offsetToPosition(File, 28), Pos(2, 7)) << "end of last line" ; |
275 | EXPECT_THAT(offsetToPosition(File, 29), Pos(2, 8)) << "EOF" ; |
276 | EXPECT_THAT(offsetToPosition(File, 30), Pos(2, 8)) << "out of bounds" ; |
277 | |
278 | WithContextValue UTF8(kCurrentOffsetEncoding, OffsetEncoding::UTF8); |
279 | for (Line L : FileLines) { |
280 | for (unsigned I = 0; I <= L.Length; ++I) |
281 | EXPECT_THAT(offsetToPosition(File, L.Offset + I), Pos(L.Number, I)); |
282 | } |
283 | EXPECT_THAT(offsetToPosition(File, 30), Pos(2, 11)) << "out of bounds" ; |
284 | } |
285 | |
286 | TEST(SourceCodeTests, SourceLocationInMainFile) { |
287 | Annotations Source(R"cpp( |
288 | ^in^t ^foo |
289 | ^bar |
290 | ^baz ^() {} {} {} {} { }^ |
291 | )cpp" ); |
292 | |
293 | SourceManagerForFile Owner("foo.cpp" , Source.code()); |
294 | SourceManager &SM = Owner.get(); |
295 | |
296 | SourceLocation StartOfFile = SM.getLocForStartOfFile(FID: SM.getMainFileID()); |
297 | EXPECT_THAT_EXPECTED(sourceLocationInMainFile(SM, position(0, 0)), |
298 | HasValue(StartOfFile)); |
299 | // End of file. |
300 | EXPECT_THAT_EXPECTED( |
301 | sourceLocationInMainFile(SM, position(4, 0)), |
302 | HasValue(StartOfFile.getLocWithOffset(Source.code().size()))); |
303 | // Column number is too large. |
304 | EXPECT_THAT_EXPECTED(sourceLocationInMainFile(SM, position(0, 1)), Failed()); |
305 | EXPECT_THAT_EXPECTED(sourceLocationInMainFile(SM, position(0, 100)), |
306 | Failed()); |
307 | EXPECT_THAT_EXPECTED(sourceLocationInMainFile(SM, position(4, 1)), Failed()); |
308 | // Line number is too large. |
309 | EXPECT_THAT_EXPECTED(sourceLocationInMainFile(SM, position(5, 0)), Failed()); |
310 | // Check all positions mentioned in the test return valid results. |
311 | for (auto P : Source.points()) { |
312 | size_t Offset = llvm::cantFail(ValOrErr: positionToOffset(Code: Source.code(), P)); |
313 | EXPECT_THAT_EXPECTED(sourceLocationInMainFile(SM, P), |
314 | HasValue(StartOfFile.getLocWithOffset(Offset))); |
315 | } |
316 | } |
317 | |
318 | TEST(SourceCodeTests, isReservedName) { |
319 | EXPECT_FALSE(isReservedName("" )); |
320 | EXPECT_FALSE(isReservedName("_" )); |
321 | EXPECT_FALSE(isReservedName("foo" )); |
322 | EXPECT_FALSE(isReservedName("_foo" )); |
323 | EXPECT_TRUE(isReservedName("__foo" )); |
324 | EXPECT_TRUE(isReservedName("_Foo" )); |
325 | EXPECT_FALSE(isReservedName("foo__bar" )) << "FIXME" ; |
326 | } |
327 | |
328 | TEST(SourceCodeTests, CollectIdentifiers) { |
329 | auto Style = format::getLLVMStyle(); |
330 | auto IDs = collectIdentifiers(Content: R"cpp( |
331 | #include "a.h" |
332 | void foo() { int xyz; int abc = xyz; return foo(); } |
333 | )cpp" , |
334 | Style); |
335 | EXPECT_EQ(IDs.size(), 7u); |
336 | EXPECT_EQ(IDs["include" ], 1u); |
337 | EXPECT_EQ(IDs["void" ], 1u); |
338 | EXPECT_EQ(IDs["int" ], 2u); |
339 | EXPECT_EQ(IDs["xyz" ], 2u); |
340 | EXPECT_EQ(IDs["abc" ], 1u); |
341 | EXPECT_EQ(IDs["return" ], 1u); |
342 | EXPECT_EQ(IDs["foo" ], 2u); |
343 | } |
344 | |
345 | TEST(SourceCodeTests, CollectWords) { |
346 | auto Words = collectWords(Content: R"cpp( |
347 | #define FIZZ_BUZZ |
348 | // this is a comment |
349 | std::string getSomeText() { return "magic word"; } |
350 | )cpp" ); |
351 | std::set<StringRef> ActualWords(Words.keys().begin(), Words.keys().end()); |
352 | std::set<StringRef> ExpectedWords = {"define" , "fizz" , "buzz" , "this" , |
353 | "comment" , "string" , "some" , "text" , |
354 | "return" , "magic" , "word" }; |
355 | EXPECT_EQ(ActualWords, ExpectedWords); |
356 | } |
357 | |
358 | class SpelledWordsTest : public ::testing::Test { |
359 | std::optional<ParsedAST> AST; |
360 | |
361 | std::optional<SpelledWord> tryWord(const char *Text) { |
362 | llvm::Annotations A(Text); |
363 | auto TU = TestTU::withCode(Code: A.code()); |
364 | AST = TU.build(); |
365 | auto SW = SpelledWord::touching( |
366 | SpelledLoc: AST->getSourceManager().getComposedLoc( |
367 | FID: AST->getSourceManager().getMainFileID(), Offset: A.point()), |
368 | TB: AST->getTokens(), LangOpts: AST->getLangOpts()); |
369 | if (A.ranges().size()) { |
370 | llvm::StringRef Want = A.code().slice(Start: A.range().Begin, End: A.range().End); |
371 | EXPECT_EQ(Want, SW->Text) << Text; |
372 | } |
373 | return SW; |
374 | } |
375 | |
376 | protected: |
377 | SpelledWord word(const char *Text) { |
378 | auto Result = tryWord(Text); |
379 | EXPECT_TRUE(Result) << Text; |
380 | return Result.value_or(u: SpelledWord()); |
381 | } |
382 | |
383 | void noWord(const char *Text) { EXPECT_FALSE(tryWord(Text)) << Text; } |
384 | }; |
385 | |
386 | TEST_F(SpelledWordsTest, HeuristicBoundaries) { |
387 | word(Text: "// [[^foo]] " ); |
388 | word(Text: "// [[f^oo]] " ); |
389 | word(Text: "// [[foo^]] " ); |
390 | word(Text: "// [[foo^]]+bar " ); |
391 | noWord(Text: "//^ foo " ); |
392 | noWord(Text: "// foo ^" ); |
393 | } |
394 | |
395 | TEST_F(SpelledWordsTest, LikelyIdentifier) { |
396 | EXPECT_FALSE(word("// ^foo " ).LikelyIdentifier); |
397 | EXPECT_TRUE(word("// [[^foo_bar]] " ).LikelyIdentifier); |
398 | EXPECT_TRUE(word("// [[^fooBar]] " ).LikelyIdentifier); |
399 | EXPECT_FALSE(word("// H^TTP " ).LikelyIdentifier); |
400 | EXPECT_TRUE(word("// \\p [[^foo]] " ).LikelyIdentifier); |
401 | EXPECT_TRUE(word("// @param[in] [[^foo]] " ).LikelyIdentifier); |
402 | EXPECT_TRUE(word("// `[[f^oo]]` " ).LikelyIdentifier); |
403 | EXPECT_TRUE(word("// bar::[[f^oo]] " ).LikelyIdentifier); |
404 | EXPECT_TRUE(word("// [[f^oo]]::bar " ).LikelyIdentifier); |
405 | } |
406 | |
407 | TEST_F(SpelledWordsTest, Comment) { |
408 | auto W = word(Text: "// [[^foo]]" ); |
409 | EXPECT_FALSE(W.PartOfSpelledToken); |
410 | EXPECT_FALSE(W.SpelledToken); |
411 | EXPECT_FALSE(W.ExpandedToken); |
412 | } |
413 | |
414 | TEST_F(SpelledWordsTest, PartOfString) { |
415 | auto W = word(Text: R"( auto str = "foo [[^bar]] baz"; )" ); |
416 | ASSERT_TRUE(W.PartOfSpelledToken); |
417 | EXPECT_EQ(W.PartOfSpelledToken->kind(), tok::string_literal); |
418 | EXPECT_FALSE(W.SpelledToken); |
419 | EXPECT_FALSE(W.ExpandedToken); |
420 | } |
421 | |
422 | TEST_F(SpelledWordsTest, DisabledSection) { |
423 | auto W = word(Text: R"cpp( |
424 | #if 0 |
425 | foo [[^bar]] baz |
426 | #endif |
427 | )cpp" ); |
428 | ASSERT_TRUE(W.SpelledToken); |
429 | EXPECT_EQ(W.SpelledToken->kind(), tok::identifier); |
430 | EXPECT_EQ(W.SpelledToken, W.PartOfSpelledToken); |
431 | EXPECT_FALSE(W.ExpandedToken); |
432 | } |
433 | |
434 | TEST_F(SpelledWordsTest, Macros) { |
435 | auto W = word(Text: R"cpp( |
436 | #define ID(X) X |
437 | ID(int [[^i]]); |
438 | )cpp" ); |
439 | ASSERT_TRUE(W.SpelledToken); |
440 | EXPECT_EQ(W.SpelledToken->kind(), tok::identifier); |
441 | EXPECT_EQ(W.SpelledToken, W.PartOfSpelledToken); |
442 | ASSERT_TRUE(W.ExpandedToken); |
443 | EXPECT_EQ(W.ExpandedToken->kind(), tok::identifier); |
444 | |
445 | W = word(Text: R"cpp( |
446 | #define OBJECT Expansion; |
447 | int [[^OBJECT]]; |
448 | )cpp" ); |
449 | EXPECT_TRUE(W.SpelledToken); |
450 | EXPECT_FALSE(W.ExpandedToken) << "Expanded token is spelled differently" ; |
451 | } |
452 | |
453 | TEST(SourceCodeTests, VisibleNamespaces) { |
454 | std::vector<std::pair<const char *, std::vector<std::string>>> Cases = { |
455 | { |
456 | R"cpp( |
457 | // Using directive resolved against enclosing namespaces. |
458 | using namespace foo; |
459 | namespace ns { |
460 | using namespace bar; |
461 | )cpp" , |
462 | {"ns" , "" , "bar" , "foo" , "ns::bar" }, |
463 | }, |
464 | { |
465 | R"cpp( |
466 | // Don't include namespaces we've closed, ignore namespace aliases. |
467 | using namespace clang; |
468 | using std::swap; |
469 | namespace clang { |
470 | namespace clangd {} |
471 | namespace ll = ::llvm; |
472 | } |
473 | namespace clang { |
474 | )cpp" , |
475 | {"clang" , "" }, |
476 | }, |
477 | { |
478 | R"cpp( |
479 | // Using directives visible even if a namespace is reopened. |
480 | // Ignore anonymous namespaces. |
481 | namespace foo{ using namespace bar; } |
482 | namespace foo{ namespace { |
483 | )cpp" , |
484 | {"foo" , "" , "bar" , "foo::bar" }, |
485 | }, |
486 | { |
487 | R"cpp( |
488 | // Mismatched braces |
489 | namespace foo{} |
490 | }}} |
491 | namespace bar{ |
492 | )cpp" , |
493 | {"bar" , "" }, |
494 | }, |
495 | { |
496 | R"cpp( |
497 | // Namespaces with multiple chunks. |
498 | namespace a::b { |
499 | using namespace c::d; |
500 | namespace e::f { |
501 | )cpp" , |
502 | { |
503 | "a::b::e::f" , |
504 | "" , |
505 | "a" , |
506 | "a::b" , |
507 | "a::b::c::d" , |
508 | "a::b::e" , |
509 | "a::c::d" , |
510 | "c::d" , |
511 | }, |
512 | }, |
513 | { |
514 | "" , |
515 | {"" }, |
516 | }, |
517 | { |
518 | R"cpp( |
519 | // Parse until EOF |
520 | namespace bar{})cpp" , |
521 | {"" }, |
522 | }, |
523 | }; |
524 | for (const auto &Case : Cases) { |
525 | EXPECT_EQ(Case.second, |
526 | visibleNamespaces(Case.first, format::getFormattingLangOpts( |
527 | format::getLLVMStyle()))) |
528 | << Case.first; |
529 | } |
530 | } |
531 | |
532 | TEST(SourceCodeTests, GetMacros) { |
533 | Annotations Code(R"cpp( |
534 | #define MACRO 123 |
535 | int abc = MA^CRO; |
536 | )cpp" ); |
537 | TestTU TU = TestTU::withCode(Code: Code.code()); |
538 | auto AST = TU.build(); |
539 | auto CurLoc = sourceLocationInMainFile(SM: AST.getSourceManager(), P: Code.point()); |
540 | ASSERT_TRUE(bool(CurLoc)); |
541 | const auto *Id = syntax::spelledIdentifierTouching(Loc: *CurLoc, Tokens: AST.getTokens()); |
542 | ASSERT_TRUE(Id); |
543 | auto Result = locateMacroAt(SpelledTok: *Id, PP&: AST.getPreprocessor()); |
544 | ASSERT_TRUE(Result); |
545 | EXPECT_THAT(*Result, macroName("MACRO" )); |
546 | } |
547 | |
548 | TEST(SourceCodeTests, WorksAtBeginOfFile) { |
549 | Annotations Code("^MACRO" ); |
550 | TestTU TU = TestTU::withCode(Code: Code.code()); |
551 | TU.HeaderCode = "#define MACRO int x;" ; |
552 | auto AST = TU.build(); |
553 | auto CurLoc = sourceLocationInMainFile(SM: AST.getSourceManager(), P: Code.point()); |
554 | ASSERT_TRUE(bool(CurLoc)); |
555 | const auto *Id = syntax::spelledIdentifierTouching(Loc: *CurLoc, Tokens: AST.getTokens()); |
556 | ASSERT_TRUE(Id); |
557 | auto Result = locateMacroAt(SpelledTok: *Id, PP&: AST.getPreprocessor()); |
558 | ASSERT_TRUE(Result); |
559 | EXPECT_THAT(*Result, macroName("MACRO" )); |
560 | } |
561 | |
562 | TEST(SourceCodeTests, IsInsideMainFile) { |
563 | TestTU TU; |
564 | TU.HeaderCode = R"cpp( |
565 | #define DEFINE_CLASS(X) class X {}; |
566 | #define DEFINE_YY DEFINE_CLASS(YY) |
567 | |
568 | class Header1 {}; |
569 | DEFINE_CLASS(Header2) |
570 | class Header {}; |
571 | )cpp" ; |
572 | TU.Code = R"cpp( |
573 | #define DEFINE_MAIN4 class Main4{}; |
574 | class Main1 {}; |
575 | DEFINE_CLASS(Main2) |
576 | DEFINE_YY |
577 | class Main {}; |
578 | DEFINE_MAIN4 |
579 | )cpp" ; |
580 | TU.ExtraArgs.push_back(x: "-DHeader=Header3" ); |
581 | TU.ExtraArgs.push_back(x: "-DMain=Main3" ); |
582 | auto AST = TU.build(); |
583 | const auto &SM = AST.getSourceManager(); |
584 | auto DeclLoc = [&AST](llvm::StringRef Name) { |
585 | return findDecl(AST, Name).getLocation(); |
586 | }; |
587 | for (const auto * : {"Header1" , "Header2" , "Header3" }) |
588 | EXPECT_FALSE(isInsideMainFile(DeclLoc(HeaderDecl), SM)) << HeaderDecl; |
589 | |
590 | for (const auto *MainDecl : {"Main1" , "Main2" , "Main3" , "Main4" , "YY" }) |
591 | EXPECT_TRUE(isInsideMainFile(DeclLoc(MainDecl), SM)) << MainDecl; |
592 | |
593 | // Main4 is *spelled* in the preamble, but in the main-file part of it. |
594 | EXPECT_TRUE(isInsideMainFile(SM.getSpellingLoc(DeclLoc("Main4" )), SM)); |
595 | } |
596 | |
597 | // Test for functions toHalfOpenFileRange and getHalfOpenFileRange |
598 | TEST(SourceCodeTests, HalfOpenFileRange) { |
599 | // Each marked range should be the file range of the decl with the same name |
600 | // and each name should be unique. |
601 | Annotations Test(R"cpp( |
602 | #define FOO(X, Y) int Y = ++X |
603 | #define BAR(X) X + 1 |
604 | #define ECHO(X) X |
605 | |
606 | #define BUZZ BAZZ(ADD) |
607 | #define BAZZ(m) m(1) |
608 | #define ADD(a) int f = a + 1; |
609 | template<typename T> |
610 | class P {}; |
611 | |
612 | int main() { |
613 | $a[[P<P<P<P<P<int>>>>> a]]; |
614 | $b[[int b = 1]]; |
615 | $c[[FOO(b, c)]]; |
616 | $d[[FOO(BAR(BAR(b)), d)]]; |
617 | // FIXME: We might want to select everything inside the outer ECHO. |
618 | ECHO(ECHO($e[[int) ECHO(e]])); |
619 | // Shouldn't crash. |
620 | $f[[BUZZ]]; |
621 | } |
622 | )cpp" ); |
623 | |
624 | ParsedAST AST = TestTU::withCode(Code: Test.code()).build(); |
625 | llvm::errs() << Test.code(); |
626 | const SourceManager &SM = AST.getSourceManager(); |
627 | const LangOptions &LangOpts = AST.getLangOpts(); |
628 | // Turn a SourceLocation into a pair of positions |
629 | auto SourceRangeToRange = [&SM](SourceRange SrcRange) { |
630 | return Range{.start: sourceLocToPosition(SM, Loc: SrcRange.getBegin()), |
631 | .end: sourceLocToPosition(SM, Loc: SrcRange.getEnd())}; |
632 | }; |
633 | auto CheckRange = [&](llvm::StringRef Name) { |
634 | const NamedDecl &Decl = findUnqualifiedDecl(AST, Name); |
635 | auto FileRange = toHalfOpenFileRange(SM, LangOpts, Decl.getSourceRange()); |
636 | SCOPED_TRACE("Checking range: " + Name); |
637 | ASSERT_NE(FileRange, std::nullopt); |
638 | Range HalfOpenRange = SourceRangeToRange(*FileRange); |
639 | EXPECT_EQ(HalfOpenRange, Test.ranges(Name)[0]); |
640 | }; |
641 | |
642 | CheckRange("a" ); |
643 | CheckRange("b" ); |
644 | CheckRange("c" ); |
645 | CheckRange("d" ); |
646 | CheckRange("e" ); |
647 | CheckRange("f" ); |
648 | } |
649 | |
650 | TEST(SourceCodeTests, HalfOpenFileRangePathologicalPreprocessor) { |
651 | const char *Case = R"cpp( |
652 | #define MACRO while(1) |
653 | void test() { |
654 | [[#include "Expand.inc" |
655 | br^eak]]; |
656 | } |
657 | )cpp" ; |
658 | Annotations Test(Case); |
659 | auto TU = TestTU::withCode(Code: Test.code()); |
660 | TU.AdditionalFiles["Expand.inc" ] = "MACRO\n" ; |
661 | auto AST = TU.build(); |
662 | |
663 | const auto &Func = cast<FunctionDecl>(Val: findDecl(AST, QName: "test" )); |
664 | const auto &Body = cast<CompoundStmt>(Val: Func.getBody()); |
665 | const auto &Loop = cast<WhileStmt>(*Body->child_begin()); |
666 | std::optional<SourceRange> Range = toHalfOpenFileRange( |
667 | AST.getSourceManager(), AST.getLangOpts(), Loop->getSourceRange()); |
668 | ASSERT_TRUE(Range) << "Failed to get file range" ; |
669 | EXPECT_EQ(AST.getSourceManager().getFileOffset(Range->getBegin()), |
670 | Test.llvm::Annotations::range().Begin); |
671 | EXPECT_EQ(AST.getSourceManager().getFileOffset(Range->getEnd()), |
672 | Test.llvm::Annotations::range().End); |
673 | } |
674 | |
675 | TEST(SourceCodeTests, IncludeHashLoc) { |
676 | const char *Case = R"cpp( |
677 | $foo^#include "foo.inc" |
678 | #define HEADER "bar.inc" |
679 | $bar^# include HEADER |
680 | )cpp" ; |
681 | Annotations Test(Case); |
682 | auto TU = TestTU::withCode(Code: Test.code()); |
683 | TU.AdditionalFiles["foo.inc" ] = "int foo;\n" ; |
684 | TU.AdditionalFiles["bar.inc" ] = "int bar;\n" ; |
685 | auto AST = TU.build(); |
686 | const auto &SM = AST.getSourceManager(); |
687 | |
688 | FileID Foo = SM.getFileID(findDecl(AST, QName: "foo" ).getLocation()); |
689 | EXPECT_EQ(SM.getFileOffset(includeHashLoc(Foo, SM)), |
690 | Test.llvm::Annotations::point("foo" )); |
691 | FileID Bar = SM.getFileID(findDecl(AST, QName: "bar" ).getLocation()); |
692 | EXPECT_EQ(SM.getFileOffset(includeHashLoc(Bar, SM)), |
693 | Test.llvm::Annotations::point("bar" )); |
694 | } |
695 | |
696 | TEST(SourceCodeTests, GetEligiblePoints) { |
697 | constexpr struct { |
698 | const char *Code; |
699 | const char *FullyQualifiedName; |
700 | const char *EnclosingNamespace; |
701 | } Cases[] = { |
702 | {.Code: R"cpp(// FIXME: We should also mark positions before and after |
703 | //declarations/definitions as eligible. |
704 | namespace ns1 { |
705 | namespace a { namespace ns2 {} } |
706 | namespace ns2 {^ |
707 | void foo(); |
708 | namespace {} |
709 | void bar() {} |
710 | namespace ns3 {} |
711 | class T {}; |
712 | ^} |
713 | using namespace ns2; |
714 | })cpp" , |
715 | .FullyQualifiedName: "ns1::ns2::symbol" , .EnclosingNamespace: "ns1::ns2::" }, |
716 | {.Code: R"cpp( |
717 | namespace ns1 {^ |
718 | namespace a { namespace ns2 {} } |
719 | namespace b {} |
720 | namespace ns {} |
721 | ^})cpp" , |
722 | .FullyQualifiedName: "ns1::ns2::symbol" , .EnclosingNamespace: "ns1::" }, |
723 | {.Code: R"cpp( |
724 | namespace x { |
725 | namespace a { namespace ns2 {} } |
726 | namespace b {} |
727 | namespace ns {} |
728 | }^)cpp" , |
729 | .FullyQualifiedName: "ns1::ns2::symbol" , .EnclosingNamespace: "" }, |
730 | {.Code: R"cpp( |
731 | namespace ns1 { |
732 | namespace ns2 {^^} |
733 | namespace b {} |
734 | namespace ns2 {^^} |
735 | } |
736 | namespace ns1 {namespace ns2 {^^}})cpp" , |
737 | .FullyQualifiedName: "ns1::ns2::symbol" , .EnclosingNamespace: "ns1::ns2::" }, |
738 | {.Code: R"cpp( |
739 | namespace ns1 {^ |
740 | namespace ns {} |
741 | namespace b {} |
742 | namespace ns {} |
743 | ^} |
744 | namespace ns1 {^namespace ns {}^})cpp" , |
745 | .FullyQualifiedName: "ns1::ns2::symbol" , .EnclosingNamespace: "ns1::" }, |
746 | }; |
747 | for (auto Case : Cases) { |
748 | Annotations Test(Case.Code); |
749 | |
750 | auto Res = getEligiblePoints( |
751 | Code: Test.code(), FullyQualifiedName: Case.FullyQualifiedName, |
752 | LangOpts: format::getFormattingLangOpts(Style: format::getLLVMStyle())); |
753 | EXPECT_THAT(Res.EligiblePoints, testing::ElementsAreArray(Test.points())) |
754 | << Test.code(); |
755 | EXPECT_EQ(Res.EnclosingNamespace, Case.EnclosingNamespace) << Test.code(); |
756 | } |
757 | } |
758 | |
759 | TEST(SourceCodeTests, IdentifierRanges) { |
760 | Annotations Code(R"cpp( |
761 | class [[Foo]] {}; |
762 | // Foo |
763 | /* Foo */ |
764 | void f([[Foo]]* foo1) { |
765 | [[Foo]] foo2; |
766 | auto S = [[Foo]](); |
767 | // cross-line identifier is not supported. |
768 | F\ |
769 | o\ |
770 | o foo2; |
771 | } |
772 | )cpp" ); |
773 | LangOptions LangOpts; |
774 | LangOpts.CPlusPlus = true; |
775 | EXPECT_EQ(Code.ranges(), |
776 | collectIdentifierRanges("Foo" , Code.code(), LangOpts)); |
777 | } |
778 | |
779 | TEST(SourceCodeTests, isHeaderFile) { |
780 | // Without lang options. |
781 | EXPECT_TRUE(isHeaderFile("foo.h" )); |
782 | EXPECT_TRUE(isHeaderFile("foo.hh" )); |
783 | EXPECT_TRUE(isHeaderFile("foo.hpp" )); |
784 | |
785 | EXPECT_FALSE(isHeaderFile("foo.cpp" )); |
786 | EXPECT_FALSE(isHeaderFile("foo.c++" )); |
787 | EXPECT_FALSE(isHeaderFile("foo.cxx" )); |
788 | EXPECT_FALSE(isHeaderFile("foo.cc" )); |
789 | EXPECT_FALSE(isHeaderFile("foo.c" )); |
790 | EXPECT_FALSE(isHeaderFile("foo.mm" )); |
791 | EXPECT_FALSE(isHeaderFile("foo.m" )); |
792 | |
793 | // With lang options |
794 | LangOptions LangOpts; |
795 | LangOpts.IsHeaderFile = true; |
796 | EXPECT_TRUE(isHeaderFile("string" , LangOpts)); |
797 | // Emulate cases where there is no "-x header" flag for a .h file, we still |
798 | // want to treat it as a header. |
799 | LangOpts.IsHeaderFile = false; |
800 | EXPECT_TRUE(isHeaderFile("header.h" , LangOpts)); |
801 | } |
802 | |
803 | TEST(SourceCodeTests, isKeywords) { |
804 | LangOptions LangOpts; |
805 | LangOpts.CPlusPlus20 = true; |
806 | EXPECT_TRUE(isKeyword("int" , LangOpts)); |
807 | EXPECT_TRUE(isKeyword("return" , LangOpts)); |
808 | EXPECT_TRUE(isKeyword("co_await" , LangOpts)); |
809 | |
810 | // these are identifiers (not keywords!) with special meaning in some |
811 | // contexts. |
812 | EXPECT_FALSE(isKeyword("final" , LangOpts)); |
813 | EXPECT_FALSE(isKeyword("override" , LangOpts)); |
814 | } |
815 | |
816 | TEST(SourceCodeTests, isSpelledInSource) { |
817 | Annotations Test("" ); |
818 | ParsedAST AST = TestTU::withCode(Code: Test.code()).build(); |
819 | const SourceManager &SM = AST.getSourceManager(); |
820 | |
821 | EXPECT_TRUE( |
822 | isSpelledInSource(SM.getLocForStartOfFile(SM.getMainFileID()), SM)); |
823 | |
824 | // Check that isSpelledInSource() handles various invalid source locations |
825 | // gracefully. |
826 | // |
827 | // Returning true for SourceLocation() is a behavior that falls out of the |
828 | // current implementation, which has an early exit for isFileID(). |
829 | // FIXME: Should it return false on SourceLocation()? Does it matter? |
830 | EXPECT_TRUE(isSpelledInSource(SourceLocation(), SM)); |
831 | EXPECT_FALSE(isSpelledInSource( |
832 | SourceLocation::getFromRawEncoding(SourceLocation::UIntTy(1 << 31)), SM)); |
833 | } |
834 | |
835 | struct IncrementalTestStep { |
836 | llvm::StringRef Src; |
837 | llvm::StringRef Contents; |
838 | }; |
839 | |
840 | int rangeLength(llvm::StringRef Code, const Range &Rng) { |
841 | llvm::Expected<size_t> Start = positionToOffset(Code, P: Rng.start); |
842 | llvm::Expected<size_t> End = positionToOffset(Code, P: Rng.end); |
843 | assert(Start); |
844 | assert(End); |
845 | return *End - *Start; |
846 | } |
847 | |
848 | /// Send the changes one by one to updateDraft, verify the intermediate results. |
849 | void stepByStep(llvm::ArrayRef<IncrementalTestStep> Steps) { |
850 | std::string Code = Annotations(Steps.front().Src).code().str(); |
851 | |
852 | for (size_t I = 1; I < Steps.size(); I++) { |
853 | Annotations SrcBefore(Steps[I - 1].Src); |
854 | Annotations SrcAfter(Steps[I].Src); |
855 | llvm::StringRef Contents = Steps[I - 1].Contents; |
856 | TextDocumentContentChangeEvent Event{ |
857 | .range: SrcBefore.range(), |
858 | .rangeLength: rangeLength(Code: SrcBefore.code(), Rng: SrcBefore.range()), |
859 | .text: Contents.str(), |
860 | }; |
861 | |
862 | EXPECT_THAT_ERROR(applyChange(Code, Event), llvm::Succeeded()); |
863 | EXPECT_EQ(Code, SrcAfter.code()); |
864 | } |
865 | } |
866 | |
867 | TEST(ApplyEditsTest, Simple) { |
868 | // clang-format off |
869 | IncrementalTestStep Steps[] = |
870 | { |
871 | // Replace a range |
872 | { |
873 | .Src: R"cpp(static int |
874 | hello[[World]]() |
875 | {})cpp" , |
876 | .Contents: "Universe" |
877 | }, |
878 | // Delete a range |
879 | { |
880 | .Src: R"cpp(static int |
881 | hello[[Universe]]() |
882 | {})cpp" , |
883 | .Contents: "" |
884 | }, |
885 | // Add a range |
886 | { |
887 | .Src: R"cpp(static int |
888 | hello[[]]() |
889 | {})cpp" , |
890 | .Contents: "Monde" |
891 | }, |
892 | { |
893 | .Src: R"cpp(static int |
894 | helloMonde() |
895 | {})cpp" , |
896 | .Contents: "" |
897 | } |
898 | }; |
899 | // clang-format on |
900 | |
901 | stepByStep(Steps); |
902 | } |
903 | |
904 | TEST(ApplyEditsTest, MultiLine) { |
905 | // clang-format off |
906 | IncrementalTestStep Steps[] = |
907 | { |
908 | // Replace a range |
909 | { |
910 | .Src: R"cpp(static [[int |
911 | helloWorld]]() |
912 | {})cpp" , |
913 | .Contents: R"cpp(char |
914 | welcome)cpp" |
915 | }, |
916 | // Delete a range |
917 | { |
918 | .Src: R"cpp(static char[[ |
919 | welcome]]() |
920 | {})cpp" , |
921 | .Contents: "" |
922 | }, |
923 | // Add a range |
924 | { |
925 | .Src: R"cpp(static char[[]]() |
926 | {})cpp" , |
927 | .Contents: R"cpp( |
928 | cookies)cpp" |
929 | }, |
930 | // Replace the whole file |
931 | { |
932 | .Src: R"cpp([[static char |
933 | cookies() |
934 | {}]])cpp" , |
935 | .Contents: R"cpp(#include <stdio.h> |
936 | )cpp" |
937 | }, |
938 | // Delete the whole file |
939 | { |
940 | .Src: R"cpp([[#include <stdio.h> |
941 | ]])cpp" , |
942 | .Contents: "" , |
943 | }, |
944 | // Add something to an empty file |
945 | { |
946 | .Src: "[[]]" , |
947 | .Contents: R"cpp(int main() { |
948 | )cpp" , |
949 | }, |
950 | { |
951 | .Src: R"cpp(int main() { |
952 | )cpp" , |
953 | .Contents: "" |
954 | } |
955 | }; |
956 | // clang-format on |
957 | |
958 | stepByStep(Steps); |
959 | } |
960 | |
961 | TEST(ApplyEditsTest, WrongRangeLength) { |
962 | std::string Code = "int main() {}\n" ; |
963 | |
964 | TextDocumentContentChangeEvent Change; |
965 | Change.range.emplace(); |
966 | Change.range->start.line = 0; |
967 | Change.range->start.character = 0; |
968 | Change.range->end.line = 0; |
969 | Change.range->end.character = 2; |
970 | Change.rangeLength = 10; |
971 | |
972 | EXPECT_THAT_ERROR(applyChange(Code, Change), |
973 | FailedWithMessage("Change's rangeLength (10) doesn't match " |
974 | "the computed range length (2)." )); |
975 | } |
976 | |
977 | // Test that we correct observed buggy edits from Neovim. |
978 | TEST(ApplyEditsTets, BuggyNeovimEdits) { |
979 | TextDocumentContentChangeEvent Change; |
980 | Change.range.emplace(); |
981 | |
982 | // https://github.com/neovim/neovim/issues/17085 |
983 | // Adding a blank line after a (missing) newline |
984 | std::string Code = "a" ; |
985 | Change.range->start.line = 1; |
986 | Change.range->start.character = 0; |
987 | Change.range->end.line = 1; |
988 | Change.range->start.character = 0; |
989 | Change.rangeLength = 0; |
990 | Change.text = "\n" ; |
991 | EXPECT_THAT_ERROR(applyChange(Code, Change), llvm::Succeeded()); |
992 | EXPECT_EQ(Code, "a\n\n" ); |
993 | |
994 | // https://github.com/neovim/neovim/issues/17085#issuecomment-1269162264 |
995 | // Replacing the (missing) newline with \n\n in an empty file. |
996 | Code = "" ; |
997 | Change.range->start.line = 0; |
998 | Change.range->start.character = 0; |
999 | Change.range->end.line = 1; |
1000 | Change.range->end.character = 0; |
1001 | Change.rangeLength = 1; |
1002 | Change.text = "\n\n" ; |
1003 | |
1004 | EXPECT_THAT_ERROR(applyChange(Code, Change), llvm::Succeeded()); |
1005 | EXPECT_EQ(Code, "\n\n" ); |
1006 | |
1007 | // We do not apply the heuristic fixes if the rangeLength doesn't match. |
1008 | Code = "" ; |
1009 | Change.rangeLength = 0; |
1010 | EXPECT_THAT_ERROR(applyChange(Code, Change), |
1011 | FailedWithMessage("Change's rangeLength (0) doesn't match " |
1012 | "the computed range length (1)." )); |
1013 | } |
1014 | |
1015 | TEST(ApplyEditsTest, EndBeforeStart) { |
1016 | std::string Code = "int main() {}\n" ; |
1017 | |
1018 | TextDocumentContentChangeEvent Change; |
1019 | Change.range.emplace(); |
1020 | Change.range->start.line = 0; |
1021 | Change.range->start.character = 5; |
1022 | Change.range->end.line = 0; |
1023 | Change.range->end.character = 3; |
1024 | |
1025 | EXPECT_THAT_ERROR( |
1026 | applyChange(Code, Change), |
1027 | FailedWithMessage( |
1028 | "Range's end position (0:3) is before start position (0:5)" )); |
1029 | } |
1030 | |
1031 | TEST(ApplyEditsTest, StartCharOutOfRange) { |
1032 | std::string Code = "int main() {}\n" ; |
1033 | |
1034 | TextDocumentContentChangeEvent Change; |
1035 | Change.range.emplace(); |
1036 | Change.range->start.line = 0; |
1037 | Change.range->start.character = 100; |
1038 | Change.range->end.line = 0; |
1039 | Change.range->end.character = 100; |
1040 | Change.text = "foo" ; |
1041 | |
1042 | EXPECT_THAT_ERROR( |
1043 | applyChange(Code, Change), |
1044 | FailedWithMessage("utf-16 offset 100 is invalid for line 0" )); |
1045 | } |
1046 | |
1047 | TEST(ApplyEditsTest, EndCharOutOfRange) { |
1048 | std::string Code = "int main() {}\n" ; |
1049 | |
1050 | TextDocumentContentChangeEvent Change; |
1051 | Change.range.emplace(); |
1052 | Change.range->start.line = 0; |
1053 | Change.range->start.character = 0; |
1054 | Change.range->end.line = 0; |
1055 | Change.range->end.character = 100; |
1056 | Change.text = "foo" ; |
1057 | |
1058 | EXPECT_THAT_ERROR( |
1059 | applyChange(Code, Change), |
1060 | FailedWithMessage("utf-16 offset 100 is invalid for line 0" )); |
1061 | } |
1062 | |
1063 | TEST(ApplyEditsTest, StartLineOutOfRange) { |
1064 | std::string Code = "int main() {}\n" ; |
1065 | |
1066 | TextDocumentContentChangeEvent Change; |
1067 | Change.range.emplace(); |
1068 | Change.range->start.line = 100; |
1069 | Change.range->start.character = 0; |
1070 | Change.range->end.line = 100; |
1071 | Change.range->end.character = 0; |
1072 | Change.text = "foo" ; |
1073 | |
1074 | EXPECT_THAT_ERROR(applyChange(Code, Change), |
1075 | FailedWithMessage("Line value is out of range (100)" )); |
1076 | } |
1077 | |
1078 | TEST(ApplyEditsTest, EndLineOutOfRange) { |
1079 | std::string Code = "int main() {}\n" ; |
1080 | |
1081 | TextDocumentContentChangeEvent Change; |
1082 | Change.range.emplace(); |
1083 | Change.range->start.line = 0; |
1084 | Change.range->start.character = 0; |
1085 | Change.range->end.line = 100; |
1086 | Change.range->end.character = 0; |
1087 | Change.text = "foo" ; |
1088 | |
1089 | EXPECT_THAT_ERROR(applyChange(Code, Change), |
1090 | FailedWithMessage("Line value is out of range (100)" )); |
1091 | } |
1092 | |
1093 | TEST(FormatStyleForFile, LanguageGuessingHeuristic) { |
1094 | StringRef ObjCContent = "@interface Foo\n@end\n" ; |
1095 | StringRef CppContent = "class Foo {};\n" ; |
1096 | using LK = format::FormatStyle::LanguageKind; |
1097 | struct TestCase { |
1098 | llvm::StringRef Filename; |
1099 | llvm::StringRef Contents; |
1100 | bool FormatFile; |
1101 | LK ExpectedLanguage; |
1102 | } TestCases[] = { |
1103 | // If the file extension identifies the file as ObjC, the guessed |
1104 | // language should be ObjC regardless of content or FormatFile flag. |
1105 | {.Filename: "foo.mm" , .Contents: ObjCContent, .FormatFile: true, .ExpectedLanguage: LK::LK_ObjC}, |
1106 | {.Filename: "foo.mm" , .Contents: ObjCContent, .FormatFile: false, .ExpectedLanguage: LK::LK_ObjC}, |
1107 | {.Filename: "foo.mm" , .Contents: CppContent, .FormatFile: true, .ExpectedLanguage: LK::LK_ObjC}, |
1108 | {.Filename: "foo.mm" , .Contents: CppContent, .FormatFile: false, .ExpectedLanguage: LK::LK_ObjC}, |
1109 | |
1110 | // If the file extension is ambiguous like .h, FormatFile=true should |
1111 | // result in using libFormat's heuristic to guess the language based |
1112 | // on the file contents. |
1113 | {.Filename: "foo.h" , .Contents: ObjCContent, .FormatFile: true, .ExpectedLanguage: LK::LK_ObjC}, |
1114 | {.Filename: "foo.h" , .Contents: CppContent, .FormatFile: true, .ExpectedLanguage: LK::LK_Cpp}, |
1115 | |
1116 | // With FomatFile=false, the language guessing heuristic should be |
1117 | // bypassed |
1118 | {.Filename: "foo.h" , .Contents: ObjCContent, .FormatFile: false, .ExpectedLanguage: LK::LK_Cpp}, |
1119 | {.Filename: "foo.h" , .Contents: CppContent, .FormatFile: false, .ExpectedLanguage: LK::LK_Cpp}, |
1120 | }; |
1121 | |
1122 | MockFS FS; |
1123 | for (const auto &[Filename, Contents, FormatFile, ExpectedLanguage] : |
1124 | TestCases) { |
1125 | EXPECT_EQ( |
1126 | getFormatStyleForFile(Filename, Contents, FS, FormatFile).Language, |
1127 | ExpectedLanguage); |
1128 | } |
1129 | } |
1130 | |
1131 | } // namespace |
1132 | } // namespace clangd |
1133 | } // namespace clang |
1134 | |