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));
816struct IncrementalTestStep {
818 llvm::StringRef Contents;
821int rangeLength(llvm::StringRef
Code,
const Range &Rng) {
826 return *End - *Start;
830void stepByStep(llvm::ArrayRef<IncrementalTestStep> Steps) {
831 std::string
Code = Annotations(Steps.front().Src).code().str();
833 for (
size_t I = 1; I < Steps.size(); I++) {
834 Annotations SrcBefore(Steps[I - 1].
Src);
835 Annotations SrcAfter(Steps[I].
Src);
836 llvm::StringRef Contents = Steps[I - 1].Contents;
837 TextDocumentContentChangeEvent
Event{
839 rangeLength(SrcBefore.code(), SrcBefore.range()),
844 EXPECT_EQ(
Code, SrcAfter.code());
848TEST(ApplyEditsTest, Simple) {
850 IncrementalTestStep Steps[] =
885TEST(ApplyEditsTest, MultiLine) {
887 IncrementalTestStep Steps[] =
905R
"cpp(static char[[]]()
915 R"cpp(#include <stdio.h>
920 R
"cpp([[#include <stdio.h>
941TEST(ApplyEditsTest, WrongRangeLength) {
942 std::string
Code =
"int main() {}\n";
944 TextDocumentContentChangeEvent Change;
945 Change.range.emplace();
946 Change.range->start.line = 0;
947 Change.range->start.character = 0;
948 Change.range->end.line = 0;
949 Change.range->end.character = 2;
950 Change.rangeLength = 10;
953 FailedWithMessage(
"Change's rangeLength (10) doesn't match "
954 "the computed range length (2)."));
958TEST(ApplyEditsTets, BuggyNeovimEdits) {
959 TextDocumentContentChangeEvent Change;
960 Change.range.emplace();
964 std::string
Code =
"a";
965 Change.range->start.line = 1;
966 Change.range->start.character = 0;
967 Change.range->end.line = 1;
968 Change.range->start.character = 0;
969 Change.rangeLength = 0;
972 EXPECT_EQ(
Code,
"a\n\n");
977 Change.range->start.line = 0;
978 Change.range->start.character = 0;
979 Change.range->end.line = 1;
980 Change.range->end.character = 0;
981 Change.rangeLength = 1;
982 Change.text =
"\n\n";
985 EXPECT_EQ(
Code,
"\n\n");
989 Change.rangeLength = 0;
991 FailedWithMessage(
"Change's rangeLength (0) doesn't match "
992 "the computed range length (1)."));
995TEST(ApplyEditsTest, EndBeforeStart) {
996 std::string
Code =
"int main() {}\n";
998 TextDocumentContentChangeEvent Change;
999 Change.range.emplace();
1000 Change.range->start.line = 0;
1001 Change.range->start.character = 5;
1002 Change.range->end.line = 0;
1003 Change.range->end.character = 3;
1008 "Range's end position (0:3) is before start position (0:5)"));
1011TEST(ApplyEditsTest, StartCharOutOfRange) {
1012 std::string
Code =
"int main() {}\n";
1014 TextDocumentContentChangeEvent Change;
1015 Change.range.emplace();
1016 Change.range->start.line = 0;
1017 Change.range->start.character = 100;
1018 Change.range->end.line = 0;
1019 Change.range->end.character = 100;
1020 Change.text =
"foo";
1024 FailedWithMessage(
"utf-16 offset 100 is invalid for line 0"));
1027TEST(ApplyEditsTest, EndCharOutOfRange) {
1028 std::string
Code =
"int main() {}\n";
1030 TextDocumentContentChangeEvent Change;
1031 Change.range.emplace();
1032 Change.range->start.line = 0;
1033 Change.range->start.character = 0;
1034 Change.range->end.line = 0;
1035 Change.range->end.character = 100;
1036 Change.text =
"foo";
1040 FailedWithMessage(
"utf-16 offset 100 is invalid for line 0"));
1043TEST(ApplyEditsTest, StartLineOutOfRange) {
1044 std::string
Code =
"int main() {}\n";
1046 TextDocumentContentChangeEvent Change;
1047 Change.range.emplace();
1048 Change.range->start.line = 100;
1049 Change.range->start.character = 0;
1050 Change.range->end.line = 100;
1051 Change.range->end.character = 0;
1052 Change.text =
"foo";
1055 FailedWithMessage(
"Line value is out of range (100)"));
1058TEST(ApplyEditsTest, EndLineOutOfRange) {
1059 std::string
Code =
"int main() {}\n";
1061 TextDocumentContentChangeEvent Change;
1062 Change.range.emplace();
1063 Change.range->start.line = 0;
1064 Change.range->start.character = 0;
1065 Change.range->end.line = 100;
1066 Change.range->end.character = 0;
1067 Change.text =
"foo";
1070 FailedWithMessage(
"Line value is out of range (100)"));
const FunctionDecl * Decl
std::optional< std::string > EnclosingNamespace
CharSourceRange Range
SourceRange for the file name.
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 isHeaderFile(llvm::StringRef FileName, std::optional< LangOptions > LangOpts)
Infers whether this is a header from the FileName and LangOpts (if presents).
===– 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)