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"
30using llvm::FailedWithMessage;
34 return arg.line == int(Line) && arg.character == int(Col);
40Position position(
int Line,
int Character) {
75TEST(SourceCodeTests, lspLengthBadUTF8) {
78 const char *BadUTF8[] = {
"\xa0",
"\xff\xff\xff\xff\xff"};
83 for (
const char *Bad : BadUTF8) {
91const char File[] = R
"(0:0 = 0
101TEST(SourceCodeTests, PositionToOffset) {
222 for (Line L : FileLines) {
225 for (
unsigned I = 0; I <= L.Length; ++I)
227 llvm::HasValue(L.Offset + I));
228 EXPECT_THAT_EXPECTED(
230 llvm::HasValue(L.Offset + L.Length));
231 EXPECT_THAT_EXPECTED(
237TEST(SourceCodeTests, OffsetToPosition) {
279 for (Line L : FileLines) {
280 for (
unsigned I = 0; I <= L.Length; ++I)
286TEST(SourceCodeTests, SourceLocationInMainFile) {
287 Annotations Source(R
"cpp(
290 ^baz ^() {} {} {} {} { }^
293 SourceManagerForFile Owner("foo.cpp", Source.code());
294 SourceManager &SM = Owner.get();
296 SourceLocation StartOfFile = SM.getLocForStartOfFile(SM.getMainFileID());
300 EXPECT_THAT_EXPECTED(
302 HasValue(StartOfFile.getLocWithOffset(Source.code().size())));
311 for (
auto P : Source.points()) {
328TEST(SourceCodeTests, CollectIdentifiers) {
329 auto Style = format::getLLVMStyle();
332 void foo() { int xyz; int abc = xyz; return foo(); }
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);
345TEST(SourceCodeTests, CollectWords) {
349 std::string getSomeText() { return "magic word"; }
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);
358class SpelledWordsTest :
public ::testing::Test {
359 std::optional<ParsedAST> AST;
361 std::optional<SpelledWord> tryWord(
const char *
Text) {
362 llvm::Annotations
A(
Text);
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;
377 SpelledWord word(
const char *
Text) {
378 auto Result = tryWord(
Text);
379 EXPECT_TRUE(Result) <<
Text;
380 return Result.value_or(SpelledWord());
383 void noWord(
const char *
Text) { EXPECT_FALSE(tryWord(
Text)) <<
Text; }
386TEST_F(SpelledWordsTest, HeuristicBoundaries) {
387 word(
"// [[^foo]] ");
388 word(
"// [[f^oo]] ");
389 word(
"// [[foo^]] ");
390 word(
"// [[foo^]]+bar ");
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);
407TEST_F(SpelledWordsTest, Comment) {
408 auto W = word(
"// [[^foo]]");
409 EXPECT_FALSE(W.PartOfSpelledToken);
410 EXPECT_FALSE(W.SpelledToken);
411 EXPECT_FALSE(W.ExpandedToken);
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);
422TEST_F(SpelledWordsTest, DisabledSection) {
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);
434TEST_F(SpelledWordsTest, Macros) {
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);
446 #define OBJECT Expansion;
449 EXPECT_TRUE(W.SpelledToken);
450 EXPECT_FALSE(W.ExpandedToken) << "Expanded token is spelled differently";
453TEST(SourceCodeTests, VisibleNamespaces) {
454 std::vector<std::pair<const char *, std::vector<std::string>>> Cases = {
457 // Using directive resolved against enclosing namespaces.
462 {"ns",
"",
"bar",
"foo",
"ns::bar"},
466 // Don't include namespaces we've closed, ignore namespace aliases.
467 using namespace clang;
471 namespace ll = ::llvm;
479 // Using directives visible even if a namespace is reopened.
480 // Ignore anonymous namespaces.
481 namespace foo{ using namespace bar; }
482 namespace foo{ namespace {
484 {"foo",
"",
"bar",
"foo::bar"},
497 // Namespaces with multiple chunks.
499 using namespace c::d;
520 namespace bar{})cpp",
524 for (
const auto &Case : Cases) {
525 EXPECT_EQ(Case.second,
527 format::getLLVMStyle())))
532TEST(SourceCodeTests, GetMacros) {
533 Annotations
Code(R
"cpp(
538 auto AST = TU.build();
540 ASSERT_TRUE(
bool(CurLoc));
541 const auto *
Id = syntax::spelledIdentifierTouching(*CurLoc,
AST.getTokens());
545 EXPECT_THAT(*Result, macroName(
"MACRO"));
548TEST(SourceCodeTests, WorksAtBeginOfFile) {
549 Annotations
Code(
"^MACRO");
551 TU.HeaderCode =
"#define MACRO int x;";
552 auto AST = TU.build();
554 ASSERT_TRUE(
bool(CurLoc));
555 const auto *
Id = syntax::spelledIdentifierTouching(*CurLoc,
AST.getTokens());
559 EXPECT_THAT(*Result, macroName(
"MACRO"));
562TEST(SourceCodeTests, IsInsideMainFile) {
564 TU.HeaderCode = R
"cpp(
565 #define DEFINE_CLASS(X) class X {};
566 #define DEFINE_YY DEFINE_CLASS(YY)
569 DEFINE_CLASS(Header2)
573 #define DEFINE_MAIN4 class Main4{};
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) {
587 for (
const auto *HeaderDecl : {
"Header1",
"Header2",
"Header3"})
590 for (
const auto *MainDecl : {
"Main1",
"Main2",
"Main3",
"Main4",
"YY"})
598TEST(SourceCodeTests, HalfOpenFileRange) {
601 Annotations Test(R
"cpp(
602 #define FOO(X, Y) int Y = ++X
606 #define BUZZ BAZZ(ADD)
608 #define ADD(a) int f = a + 1;
613 $a[[P<P<P<P<P<int>>>>> a]];
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]]));
625 llvm::errs() << Test.code();
626 const SourceManager &SM =
AST.getSourceManager();
627 const LangOptions &LangOpts =
AST.getLangOpts();
629 auto SourceRangeToRange = [&SM](SourceRange SrcRange) {
633 auto CheckRange = [&](llvm::StringRef
Name) {
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]);
650TEST(SourceCodeTests, HalfOpenFileRangePathologicalPreprocessor) {
651 const char *Case = R
"cpp(
652#define MACRO while(1)
654[[#include "Expand.inc"
658 Annotations Test(Case);
660 TU.AdditionalFiles[
"Expand.inc"] =
"MACRO\n";
661 auto AST = TU.build();
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());
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);
675TEST(SourceCodeTests, IncludeHashLoc) {
676 const char *Case = R
"cpp(
677$foo^#include "foo.inc"
678#define HEADER "bar.inc"
679 $bar^# include HEADER
681 Annotations Test(Case);
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();
690 Test.llvm::Annotations::point(
"foo"));
693 Test.llvm::Annotations::point(
"bar"));
696TEST(SourceCodeTests, GetEligiblePoints) {
699 const char *FullyQualifiedName;
702 {R
"cpp(// FIXME: We should also mark positions before and after
703 //declarations/definitions as eligible.
705 namespace a { namespace ns2 {} }
715 "ns1::ns2::symbol",
"ns1::ns2::"},
718 namespace a { namespace ns2 {} }
722 "ns1::ns2::symbol",
"ns1::"},
725 namespace a { namespace ns2 {} }
729 "ns1::ns2::symbol",
""},
736 namespace ns1 {namespace ns2 {^^}})cpp",
737 "ns1::ns2::symbol",
"ns1::ns2::"},
744 namespace ns1 {^namespace ns {}^})cpp",
745 "ns1::ns2::symbol",
"ns1::"},
747 for (
auto Case : Cases) {
748 Annotations Test(Case.Code);
751 Test.code(), Case.FullyQualifiedName,
752 format::getFormattingLangOpts(format::getLLVMStyle()));
753 EXPECT_THAT(Res.EligiblePoints, testing::ElementsAreArray(Test.points()))
755 EXPECT_EQ(Res.EnclosingNamespace, Case.EnclosingNamespace) << Test.code();
759TEST(SourceCodeTests, IdentifierRanges) {
760 Annotations
Code(R
"cpp(
764 void f([[Foo]]* foo1) {
767// cross-line identifier is not supported.
773 LangOptions LangOpts;
774 LangOpts.CPlusPlus = true;
775 EXPECT_EQ(
Code.ranges(),
794 LangOptions LangOpts;
795 LangOpts.IsHeaderFile =
true;
799 LangOpts.IsHeaderFile =
false;
803TEST(SourceCodeTests, isKeywords) {
804 LangOptions LangOpts;
805 LangOpts.CPlusPlus20 =
true;
807 EXPECT_TRUE(
isKeyword(
"return", LangOpts));
808 EXPECT_TRUE(
isKeyword(
"co_await", LangOpts));
812 EXPECT_FALSE(
isKeyword(
"final", LangOpts));
813 EXPECT_FALSE(
isKeyword(
"override", LangOpts));
817 Annotations Test(
"");
819 const SourceManager &SM =
AST.getSourceManager();
832 SourceLocation::getFromRawEncoding(SourceLocation::UIntTy(1 << 31)), SM));
835struct IncrementalTestStep {
837 llvm::StringRef Contents;
840int rangeLength(llvm::StringRef
Code,
const Range &Rng) {
845 return *End - *Start;
849void stepByStep(llvm::ArrayRef<IncrementalTestStep> Steps) {
850 std::string
Code = Annotations(Steps.front().Src).code().str();
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{
858 rangeLength(SrcBefore.code(), SrcBefore.range()),
863 EXPECT_EQ(
Code, SrcAfter.code());
867TEST(ApplyEditsTest, Simple) {
869 IncrementalTestStep Steps[] =
904TEST(ApplyEditsTest, MultiLine) {
906 IncrementalTestStep Steps[] =
924R
"cpp(static char[[]]()
934 R"cpp(#include <stdio.h>
939 R
"cpp([[#include <stdio.h>
960TEST(ApplyEditsTest, WrongRangeLength) {
961 std::string
Code =
"int main() {}\n";
963 TextDocumentContentChangeEvent Change;
964 Change.range.emplace();
965 Change.range->start.line = 0;
966 Change.range->start.character = 0;
967 Change.range->end.line = 0;
968 Change.range->end.character = 2;
969 Change.rangeLength = 10;
972 FailedWithMessage(
"Change's rangeLength (10) doesn't match "
973 "the computed range length (2)."));
977TEST(ApplyEditsTets, BuggyNeovimEdits) {
978 TextDocumentContentChangeEvent Change;
979 Change.range.emplace();
983 std::string
Code =
"a";
984 Change.range->start.line = 1;
985 Change.range->start.character = 0;
986 Change.range->end.line = 1;
987 Change.range->start.character = 0;
988 Change.rangeLength = 0;
991 EXPECT_EQ(
Code,
"a\n\n");
996 Change.range->start.line = 0;
997 Change.range->start.character = 0;
998 Change.range->end.line = 1;
999 Change.range->end.character = 0;
1000 Change.rangeLength = 1;
1001 Change.text =
"\n\n";
1004 EXPECT_EQ(
Code,
"\n\n");
1008 Change.rangeLength = 0;
1010 FailedWithMessage(
"Change's rangeLength (0) doesn't match "
1011 "the computed range length (1)."));
1014TEST(ApplyEditsTest, EndBeforeStart) {
1015 std::string
Code =
"int main() {}\n";
1017 TextDocumentContentChangeEvent Change;
1018 Change.range.emplace();
1019 Change.range->start.line = 0;
1020 Change.range->start.character = 5;
1021 Change.range->end.line = 0;
1022 Change.range->end.character = 3;
1027 "Range's end position (0:3) is before start position (0:5)"));
1030TEST(ApplyEditsTest, StartCharOutOfRange) {
1031 std::string
Code =
"int main() {}\n";
1033 TextDocumentContentChangeEvent Change;
1034 Change.range.emplace();
1035 Change.range->start.line = 0;
1036 Change.range->start.character = 100;
1037 Change.range->end.line = 0;
1038 Change.range->end.character = 100;
1039 Change.text =
"foo";
1043 FailedWithMessage(
"utf-16 offset 100 is invalid for line 0"));
1046TEST(ApplyEditsTest, EndCharOutOfRange) {
1047 std::string
Code =
"int main() {}\n";
1049 TextDocumentContentChangeEvent Change;
1050 Change.range.emplace();
1051 Change.range->start.line = 0;
1052 Change.range->start.character = 0;
1053 Change.range->end.line = 0;
1054 Change.range->end.character = 100;
1055 Change.text =
"foo";
1059 FailedWithMessage(
"utf-16 offset 100 is invalid for line 0"));
1062TEST(ApplyEditsTest, StartLineOutOfRange) {
1063 std::string
Code =
"int main() {}\n";
1065 TextDocumentContentChangeEvent Change;
1066 Change.range.emplace();
1067 Change.range->start.line = 100;
1068 Change.range->start.character = 0;
1069 Change.range->end.line = 100;
1070 Change.range->end.character = 0;
1071 Change.text =
"foo";
1074 FailedWithMessage(
"Line value is out of range (100)"));
1077TEST(ApplyEditsTest, EndLineOutOfRange) {
1078 std::string
Code =
"int main() {}\n";
1080 TextDocumentContentChangeEvent Change;
1081 Change.range.emplace();
1082 Change.range->start.line = 0;
1083 Change.range->start.character = 0;
1084 Change.range->end.line = 100;
1085 Change.range->end.character = 0;
1086 Change.text =
"foo";
1089 FailedWithMessage(
"Line value is out of range (100)"));
1092TEST(FormatStyleForFile, LanguageGuessingHeuristic) {
1093 StringRef ObjCContent =
"@interface Foo\n@end\n";
1094 StringRef CppContent =
"class Foo {};\n";
1095 using LK = format::FormatStyle::LanguageKind;
1098 llvm::StringRef Contents;
1100 LK ExpectedLanguage;
1104 {
"foo.mm", ObjCContent,
true, LK::LK_ObjC},
1105 {
"foo.mm", ObjCContent,
false, LK::LK_ObjC},
1106 {
"foo.mm", CppContent,
true, LK::LK_ObjC},
1107 {
"foo.mm", CppContent,
false, LK::LK_ObjC},
1112 {
"foo.h", ObjCContent,
true, LK::LK_ObjC},
1113 {
"foo.h", CppContent,
true, LK::LK_Cpp},
1117 {
"foo.h", ObjCContent,
false, LK::LK_Cpp},
1118 {
"foo.h", CppContent,
false, LK::LK_Cpp},
1122 for (
const auto &[
Filename, Contents, FormatFile, ExpectedLanguage] :
const FunctionDecl * Decl
llvm::SmallString< 256U > Name
std::optional< std::string > EnclosingNamespace
CharSourceRange Range
SourceRange for the file name.
std::string Filename
Filename as a string.
std::optional< SourceRange > toHalfOpenFileRange(const SourceManager &SM, const LangOptions &LangOpts, SourceRange R)
Turns a token range into a half-open range and checks its correctness.
TEST_F(BackgroundIndexTest, NoCrashOnErrorFile)
const NamedDecl & findDecl(ParsedAST &AST, llvm::StringRef QName)
SourceLocation includeHashLoc(FileID IncludedFile, const SourceManager &SM)
Returns the #include location through which IncludedFIle was loaded.
llvm::Error applyChange(std::string &Contents, const TextDocumentContentChangeEvent &Change)
Apply an incremental update to a text document.
Position offsetToPosition(llvm::StringRef Code, size_t Offset)
Turn an offset in Code into a [line, column] pair.
size_t lspLength(llvm::StringRef Code)
bool isReservedName(llvm::StringRef Name)
Returns true if Name is reserved, like _Foo or __Vector_base.
Key< OffsetEncoding > kCurrentOffsetEncoding
bool isInsideMainFile(SourceLocation Loc, const SourceManager &SM)
Returns true iff Loc is inside the main file.
MATCHER_P2(hasFlag, Flag, Path, "")
OptionalMatcher< InnerMatcher > HasValue(const InnerMatcher &inner_matcher)
Position sourceLocToPosition(const SourceManager &SM, SourceLocation Loc)
Turn a SourceLocation into a [line, column] pair.
llvm::StringMap< unsigned > collectIdentifiers(llvm::StringRef Content, const format::FormatStyle &Style)
Collects identifiers with counts in the source code.
std::optional< DefinedMacro > locateMacroAt(const syntax::Token &SpelledTok, Preprocessor &PP)
Gets the macro referenced by SpelledTok.
std::vector< std::string > visibleNamespaces(llvm::StringRef Code, const LangOptions &LangOpts)
Heuristically determine namespaces visible at a point, without parsing Code.
TEST(BackgroundQueueTest, Priority)
const NamedDecl & findUnqualifiedDecl(ParsedAST &AST, llvm::StringRef Name)
EligibleRegion getEligiblePoints(llvm::StringRef Code, llvm::StringRef FullyQualifiedName, const LangOptions &LangOpts)
Returns most eligible region to insert a definition for FullyQualifiedName in the Code.
llvm::Expected< size_t > positionToOffset(llvm::StringRef Code, Position P, bool AllowColumnsBeyondLineLength)
Turn a [line, column] pair into an offset in Code.
std::vector< Range > collectIdentifierRanges(llvm::StringRef Identifier, llvm::StringRef Content, const LangOptions &LangOpts)
Collects all ranges of the given identifier in the source code.
llvm::StringSet collectWords(llvm::StringRef Content)
Collects words from the source code.
llvm::Expected< SourceLocation > sourceLocationInMainFile(const SourceManager &SM, Position P)
Return the file location, corresponding to P.
bool isKeyword(llvm::StringRef NewName, const LangOptions &LangOpts)
Return true if the TokenName is in the list of reversed keywords of the language.
bool isSpelledInSource(SourceLocation Loc, const SourceManager &SM)
Returns true if the token at Loc is spelled in the source code.
bool isHeaderFile(llvm::StringRef FileName, std::optional< LangOptions > LangOpts)
Infers whether this is a header from the FileName and LangOpts (if presents).
format::FormatStyle getFormatStyleForFile(llvm::StringRef File, llvm::StringRef Content, const ThreadsafeFS &TFS, bool FormatFile)
Choose the clang-format style we should apply to a certain file.
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
static std::optional< SpelledWord > touching(SourceLocation SpelledLoc, const syntax::TokenBuffer &TB, const LangOptions &LangOpts)
static TestTU withCode(llvm::StringRef Code)