1134 lines · cpp
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-exception6//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 25namespace clang {26namespace clangd {27namespace {28 29using llvm::Failed;30using llvm::FailedWithMessage;31using llvm::HasValue;32 33MATCHER_P2(Pos, Line, Col, "") {34 return arg.line == int(Line) && arg.character == int(Col);35}36 37MATCHER_P(macroName, Name, "") { return arg.Name == Name; }38 39/// A helper to make tests easier to read.40Position position(int Line, int Character) {41 Position Pos;42 Pos.line = Line;43 Pos.character = Character;44 return Pos;45}46 47TEST(SourceCodeTests, lspLength) {48 EXPECT_EQ(lspLength(""), 0UL);49 EXPECT_EQ(lspLength("ascii"), 5UL);50 // BMP51 EXPECT_EQ(lspLength("↓"), 1UL);52 EXPECT_EQ(lspLength("¥"), 1UL);53 // astral54 EXPECT_EQ(lspLength("😂"), 2UL);55 56 WithContextValue UTF8(kCurrentOffsetEncoding, OffsetEncoding::UTF8);57 EXPECT_EQ(lspLength(""), 0UL);58 EXPECT_EQ(lspLength("ascii"), 5UL);59 // BMP60 EXPECT_EQ(lspLength("↓"), 3UL);61 EXPECT_EQ(lspLength("¥"), 2UL);62 // astral63 EXPECT_EQ(lspLength("😂"), 4UL);64 65 WithContextValue UTF32(kCurrentOffsetEncoding, OffsetEncoding::UTF32);66 EXPECT_EQ(lspLength(""), 0UL);67 EXPECT_EQ(lspLength("ascii"), 5UL);68 // BMP69 EXPECT_EQ(lspLength("↓"), 1UL);70 EXPECT_EQ(lspLength("¥"), 1UL);71 // astral72 EXPECT_EQ(lspLength("😂"), 1UL);73}74 75TEST(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).91const char File[] = R"(0:0 = 0921:0 → 8932:0 🡆 18)";94struct Line {95 unsigned Number;96 unsigned Offset;97 unsigned Length;98};99Line FileLines[] = {Line{0, 0, 7}, Line{1, 8, 9}, Line{2, 18, 11}};100 101TEST(SourceCodeTests, PositionToOffset) {102 // line out of bounds103 EXPECT_THAT_EXPECTED(positionToOffset(File, position(-1, 2)), llvm::Failed());104 // first line105 EXPECT_THAT_EXPECTED(positionToOffset(File, position(0, -1)),106 llvm::Failed()); // out of range107 EXPECT_THAT_EXPECTED(positionToOffset(File, position(0, 0)),108 llvm::HasValue(0)); // first character109 EXPECT_THAT_EXPECTED(positionToOffset(File, position(0, 3)),110 llvm::HasValue(3)); // middle character111 EXPECT_THAT_EXPECTED(positionToOffset(File, position(0, 6)),112 llvm::HasValue(6)); // last character113 EXPECT_THAT_EXPECTED(positionToOffset(File, position(0, 7)),114 llvm::HasValue(7)); // the newline itself115 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 range119 EXPECT_THAT_EXPECTED(positionToOffset(File, position(0, 8), false),120 llvm::Failed()); // out of range121 // middle line122 EXPECT_THAT_EXPECTED(positionToOffset(File, position(1, -1)),123 llvm::Failed()); // out of range124 EXPECT_THAT_EXPECTED(positionToOffset(File, position(1, 0)),125 llvm::HasValue(8)); // first character126 EXPECT_THAT_EXPECTED(positionToOffset(File, position(1, 3)),127 llvm::HasValue(11)); // middle character128 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 character132 EXPECT_THAT_EXPECTED(positionToOffset(File, position(1, 7)),133 llvm::HasValue(17)); // the newline itself134 EXPECT_THAT_EXPECTED(positionToOffset(File, position(1, 8)),135 llvm::HasValue(17)); // out of range136 EXPECT_THAT_EXPECTED(positionToOffset(File, position(1, 8), false),137 llvm::Failed()); // out of range138 // last line139 EXPECT_THAT_EXPECTED(positionToOffset(File, position(2, -1)),140 llvm::Failed()); // out of range141 EXPECT_THAT_EXPECTED(positionToOffset(File, position(2, 0)),142 llvm::HasValue(18)); // first character143 EXPECT_THAT_EXPECTED(positionToOffset(File, position(2, 3)),144 llvm::HasValue(21)); // middle character145 EXPECT_THAT_EXPECTED(positionToOffset(File, position(2, 5), false),146 llvm::Failed()); // middle of surrogate pair147 EXPECT_THAT_EXPECTED(positionToOffset(File, position(2, 5)),148 llvm::HasValue(26)); // middle of surrogate pair149 EXPECT_THAT_EXPECTED(positionToOffset(File, position(2, 6), false),150 llvm::HasValue(26)); // end of surrogate pair151 EXPECT_THAT_EXPECTED(positionToOffset(File, position(2, 8)),152 llvm::HasValue(28)); // last character153 EXPECT_THAT_EXPECTED(positionToOffset(File, position(2, 9)),154 llvm::HasValue(29)); // EOF155 EXPECT_THAT_EXPECTED(positionToOffset(File, position(2, 10), false),156 llvm::Failed()); // out of range157 // line out of bounds158 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 bounds164 EXPECT_THAT_EXPECTED(positionToOffset(File, position(-1, 2)), llvm::Failed());165 // first line166 EXPECT_THAT_EXPECTED(positionToOffset(File, position(0, -1)),167 llvm::Failed()); // out of range168 EXPECT_THAT_EXPECTED(positionToOffset(File, position(0, 0)),169 llvm::HasValue(0)); // first character170 EXPECT_THAT_EXPECTED(positionToOffset(File, position(0, 3)),171 llvm::HasValue(3)); // middle character172 EXPECT_THAT_EXPECTED(positionToOffset(File, position(0, 6)),173 llvm::HasValue(6)); // last character174 EXPECT_THAT_EXPECTED(positionToOffset(File, position(0, 7)),175 llvm::HasValue(7)); // the newline itself176 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 range180 EXPECT_THAT_EXPECTED(positionToOffset(File, position(0, 8), false),181 llvm::Failed()); // out of range182 // middle line183 EXPECT_THAT_EXPECTED(positionToOffset(File, position(1, -1)),184 llvm::Failed()); // out of range185 EXPECT_THAT_EXPECTED(positionToOffset(File, position(1, 0)),186 llvm::HasValue(8)); // first character187 EXPECT_THAT_EXPECTED(positionToOffset(File, position(1, 3)),188 llvm::HasValue(11)); // middle character189 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 character193 EXPECT_THAT_EXPECTED(positionToOffset(File, position(1, 7)),194 llvm::HasValue(17)); // the newline itself195 EXPECT_THAT_EXPECTED(positionToOffset(File, position(1, 8)),196 llvm::HasValue(17)); // out of range197 EXPECT_THAT_EXPECTED(positionToOffset(File, position(1, 8), false),198 llvm::Failed()); // out of range199 // last line200 EXPECT_THAT_EXPECTED(positionToOffset(File, position(2, -1)),201 llvm::Failed()); // out of range202 EXPECT_THAT_EXPECTED(positionToOffset(File, position(2, 0)),203 llvm::HasValue(18)); // first character204 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 character208 EXPECT_THAT_EXPECTED(positionToOffset(File, position(2, 7)),209 llvm::HasValue(28)); // last character210 EXPECT_THAT_EXPECTED(positionToOffset(File, position(2, 8)),211 llvm::HasValue(29)); // EOF212 EXPECT_THAT_EXPECTED(positionToOffset(File, position(2, 9), false),213 llvm::Failed()); // out of range214 // line out of bounds215 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 range225 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 range234 }235}236 237TEST(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 286TEST(SourceCodeTests, SourceLocationInMainFile) {287 Annotations Source(R"cpp(288 ^in^t ^foo289 ^bar290 ^baz ^() {} {} {} {} { }^291)cpp");292 293 SourceManagerForFile Owner("foo.cpp", Source.code());294 SourceManager &SM = Owner.get();295 296 SourceLocation StartOfFile = SM.getLocForStartOfFile(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(positionToOffset(Source.code(), P));313 EXPECT_THAT_EXPECTED(sourceLocationInMainFile(SM, P),314 HasValue(StartOfFile.getLocWithOffset(Offset)));315 }316}317 318TEST(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 328TEST(SourceCodeTests, CollectIdentifiers) {329 auto Style = format::getLLVMStyle();330 auto IDs = collectIdentifiers(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 345TEST(SourceCodeTests, CollectWords) {346 auto Words = collectWords(R"cpp(347 #define FIZZ_BUZZ348 // this is a comment349 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 358class 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(A.code());364 AST = TU.build();365 auto SW = SpelledWord::touching(366 AST->getSourceManager().getComposedLoc(367 AST->getSourceManager().getMainFileID(), A.point()),368 AST->getTokens(), AST->getLangOpts());369 if (A.ranges().size()) {370 llvm::StringRef Want = A.code().slice(A.range().Begin, A.range().End);371 EXPECT_EQ(Want, SW->Text) << Text;372 }373 return SW;374 }375 376protected:377 SpelledWord word(const char *Text) {378 auto Result = tryWord(Text);379 EXPECT_TRUE(Result) << Text;380 return Result.value_or(SpelledWord());381 }382 383 void noWord(const char *Text) { EXPECT_FALSE(tryWord(Text)) << Text; }384};385 386TEST_F(SpelledWordsTest, HeuristicBoundaries) {387 word("// [[^foo]] ");388 word("// [[f^oo]] ");389 word("// [[foo^]] ");390 word("// [[foo^]]+bar ");391 noWord("//^ foo ");392 noWord("// foo ^");393}394 395TEST_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 407TEST_F(SpelledWordsTest, Comment) {408 auto W = word("// [[^foo]]");409 EXPECT_FALSE(W.PartOfSpelledToken);410 EXPECT_FALSE(W.SpelledToken);411 EXPECT_FALSE(W.ExpandedToken);412}413 414TEST_F(SpelledWordsTest, PartOfString) {415 auto W = word(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 422TEST_F(SpelledWordsTest, DisabledSection) {423 auto W = word(R"cpp(424 #if 0425 foo [[^bar]] baz426 #endif427 )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 434TEST_F(SpelledWordsTest, Macros) {435 auto W = word(R"cpp(436 #define ID(X) X437 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(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 453TEST(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 braces489 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 EOF520 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 532TEST(SourceCodeTests, GetMacros) {533 Annotations Code(R"cpp(534 #define MACRO 123535 int abc = MA^CRO;536 )cpp");537 TestTU TU = TestTU::withCode(Code.code());538 auto AST = TU.build();539 auto CurLoc = sourceLocationInMainFile(AST.getSourceManager(), Code.point());540 ASSERT_TRUE(bool(CurLoc));541 const auto *Id = syntax::spelledIdentifierTouching(*CurLoc, AST.getTokens());542 ASSERT_TRUE(Id);543 auto Result = locateMacroAt(*Id, AST.getPreprocessor());544 ASSERT_TRUE(Result);545 EXPECT_THAT(*Result, macroName("MACRO"));546}547 548TEST(SourceCodeTests, WorksAtBeginOfFile) {549 Annotations Code("^MACRO");550 TestTU TU = TestTU::withCode(Code.code());551 TU.HeaderCode = "#define MACRO int x;";552 auto AST = TU.build();553 auto CurLoc = sourceLocationInMainFile(AST.getSourceManager(), Code.point());554 ASSERT_TRUE(bool(CurLoc));555 const auto *Id = syntax::spelledIdentifierTouching(*CurLoc, AST.getTokens());556 ASSERT_TRUE(Id);557 auto Result = locateMacroAt(*Id, AST.getPreprocessor());558 ASSERT_TRUE(Result);559 EXPECT_THAT(*Result, macroName("MACRO"));560}561 562TEST(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_YY577 class Main {};578 DEFINE_MAIN4579 )cpp";580 TU.ExtraArgs.push_back("-DHeader=Header3");581 TU.ExtraArgs.push_back("-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 *HeaderDecl : {"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 getHalfOpenFileRange598TEST(SourceCodeTests, HalfOpenFileRange) {599 // Each marked range should be the file range of the decl with the same name600 // and each name should be unique.601 Annotations Test(R"cpp(602 #define FOO(X, Y) int Y = ++X603 #define BAR(X) X + 1604 #define ECHO(X) X605 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(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 positions629 auto SourceRangeToRange = [&SM](SourceRange SrcRange) {630 return Range{sourceLocToPosition(SM, SrcRange.getBegin()),631 sourceLocToPosition(SM, 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 650TEST(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(Test.code());660 TU.AdditionalFiles["Expand.inc"] = "MACRO\n";661 auto AST = TU.build();662 663 const auto &Func = cast<FunctionDecl>(findDecl(AST, "test"));664 const auto &Body = cast<CompoundStmt>(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 675TEST(SourceCodeTests, IncludeHashLoc) {676 const char *Case = R"cpp(677$foo^#include "foo.inc"678#define HEADER "bar.inc"679 $bar^# include HEADER680 )cpp";681 Annotations Test(Case);682 auto TU = TestTU::withCode(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, "foo").getLocation());689 EXPECT_EQ(SM.getFileOffset(includeHashLoc(Foo, SM)),690 Test.llvm::Annotations::point("foo"));691 FileID Bar = SM.getFileID(findDecl(AST, "bar").getLocation());692 EXPECT_EQ(SM.getFileOffset(includeHashLoc(Bar, SM)),693 Test.llvm::Annotations::point("bar"));694}695 696TEST(SourceCodeTests, GetEligiblePoints) {697 constexpr struct {698 const char *Code;699 const char *FullyQualifiedName;700 const char *EnclosingNamespace;701 } Cases[] = {702 {R"cpp(// FIXME: We should also mark positions before and after703 //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 "ns1::ns2::symbol", "ns1::ns2::"},716 {R"cpp(717 namespace ns1 {^718 namespace a { namespace ns2 {} }719 namespace b {}720 namespace ns {}721 ^})cpp",722 "ns1::ns2::symbol", "ns1::"},723 {R"cpp(724 namespace x {725 namespace a { namespace ns2 {} }726 namespace b {}727 namespace ns {}728 }^)cpp",729 "ns1::ns2::symbol", ""},730 {R"cpp(731 namespace ns1 {732 namespace ns2 {^^}733 namespace b {}734 namespace ns2 {^^}735 }736 namespace ns1 {namespace ns2 {^^}})cpp",737 "ns1::ns2::symbol", "ns1::ns2::"},738 {R"cpp(739 namespace ns1 {^740 namespace ns {}741 namespace b {}742 namespace ns {}743 ^}744 namespace ns1 {^namespace ns {}^})cpp",745 "ns1::ns2::symbol", "ns1::"},746 };747 for (auto Case : Cases) {748 Annotations Test(Case.Code);749 750 auto Res = getEligiblePoints(751 Test.code(), Case.FullyQualifiedName,752 format::getFormattingLangOpts(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 759TEST(SourceCodeTests, IdentifierRanges) {760 Annotations Code(R"cpp(761 class [[Foo]] {};762 // Foo763 /* Foo */764 void f([[Foo]]* foo1) {765 [[Foo]] foo2;766 auto S = [[Foo]]();767// cross-line identifier is not supported.768F\769o\770o foo2;771 }772 )cpp");773 LangOptions LangOpts;774 LangOpts.CPlusPlus = true;775 EXPECT_EQ(Code.ranges(),776 collectIdentifierRanges("Foo", Code.code(), LangOpts));777}778 779TEST(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 options794 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 still798 // want to treat it as a header.799 LangOpts.IsHeaderFile = false;800 EXPECT_TRUE(isHeaderFile("header.h", LangOpts));801}802 803TEST(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 some811 // contexts.812 EXPECT_FALSE(isKeyword("final", LangOpts));813 EXPECT_FALSE(isKeyword("override", LangOpts));814}815 816TEST(SourceCodeTests, isSpelledInSource) {817 Annotations Test("");818 ParsedAST AST = TestTU::withCode(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 locations825 // gracefully.826 //827 // Returning true for SourceLocation() is a behavior that falls out of the828 // 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 835struct IncrementalTestStep {836 llvm::StringRef Src;837 llvm::StringRef Contents;838};839 840int rangeLength(llvm::StringRef Code, const Range &Rng) {841 llvm::Expected<size_t> Start = positionToOffset(Code, Rng.start);842 llvm::Expected<size_t> End = positionToOffset(Code, 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.849void 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 SrcBefore.range(),858 rangeLength(SrcBefore.code(), SrcBefore.range()),859 Contents.str(),860 };861 862 EXPECT_THAT_ERROR(applyChange(Code, Event), llvm::Succeeded());863 EXPECT_EQ(Code, SrcAfter.code());864 }865}866 867TEST(ApplyEditsTest, Simple) {868 // clang-format off869 IncrementalTestStep Steps[] =870 {871 // Replace a range872 {873R"cpp(static int874hello[[World]]()875{})cpp",876 "Universe"877 },878 // Delete a range879 {880R"cpp(static int881hello[[Universe]]()882{})cpp",883 ""884 },885 // Add a range886 {887R"cpp(static int888hello[[]]()889{})cpp",890 "Monde"891 },892 {893R"cpp(static int894helloMonde()895{})cpp",896 ""897 }898 };899 // clang-format on900 901 stepByStep(Steps);902}903 904TEST(ApplyEditsTest, MultiLine) {905 // clang-format off906 IncrementalTestStep Steps[] =907 {908 // Replace a range909 {910R"cpp(static [[int911helloWorld]]()912{})cpp",913R"cpp(char914welcome)cpp"915 },916 // Delete a range917 {918R"cpp(static char[[919welcome]]()920{})cpp",921 ""922 },923 // Add a range924 {925R"cpp(static char[[]]()926{})cpp",927 R"cpp(928cookies)cpp"929 },930 // Replace the whole file931 {932R"cpp([[static char933cookies()934{}]])cpp",935 R"cpp(#include <stdio.h>936)cpp"937 },938 // Delete the whole file939 {940 R"cpp([[#include <stdio.h>941]])cpp",942 "",943 },944 // Add something to an empty file945 {946 "[[]]",947 R"cpp(int main() {948)cpp",949 },950 {951 R"cpp(int main() {952)cpp",953 ""954 }955 };956 // clang-format on957 958 stepByStep(Steps);959}960 961TEST(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.978TEST(ApplyEditsTets, BuggyNeovimEdits) {979 TextDocumentContentChangeEvent Change;980 Change.range.emplace();981 982 // https://github.com/neovim/neovim/issues/17085983 // Adding a blank line after a (missing) newline984 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-1269162264995 // 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 1015TEST(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 1031TEST(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 1047TEST(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 1063TEST(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 1078TEST(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 1093TEST(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 guessed1104 // language should be ObjC regardless of content or FormatFile flag.1105 {"foo.mm", ObjCContent, true, LK::LK_ObjC},1106 {"foo.mm", ObjCContent, false, LK::LK_ObjC},1107 {"foo.mm", CppContent, true, LK::LK_ObjC},1108 {"foo.mm", CppContent, false, LK::LK_ObjC},1109 1110 // If the file extension is ambiguous like .h, FormatFile=true should1111 // result in using libFormat's heuristic to guess the language based1112 // on the file contents.1113 {"foo.h", ObjCContent, true, LK::LK_ObjC},1114 {"foo.h", CppContent, true, LK::LK_Cpp},1115 1116 // With FomatFile=false, the language guessing heuristic should be1117 // bypassed1118 {"foo.h", ObjCContent, false, LK::LK_Cpp},1119 {"foo.h", CppContent, false, 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} // namespace1132} // namespace clangd1133} // namespace clang1134