|  | //=== unittests/Sema/CodeCompleteTest.cpp - Code Complete tests ==============// | 
|  | // | 
|  | //                     The LLVM Compiler Infrastructure | 
|  | // | 
|  | // This file is distributed under the University of Illinois Open Source | 
|  | // License. See LICENSE.TXT for details. | 
|  | // | 
|  | //===----------------------------------------------------------------------===// | 
|  |  | 
|  | #include "clang/Frontend/CompilerInstance.h" | 
|  | #include "clang/Frontend/FrontendActions.h" | 
|  | #include "clang/Lex/Preprocessor.h" | 
|  | #include "clang/Parse/ParseAST.h" | 
|  | #include "clang/Sema/Sema.h" | 
|  | #include "clang/Sema/SemaDiagnostic.h" | 
|  | #include "clang/Tooling/Tooling.h" | 
|  | #include "gtest/gtest.h" | 
|  | #include "gmock/gmock.h" | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | using namespace clang; | 
|  | using namespace clang::tooling; | 
|  | using ::testing::UnorderedElementsAre; | 
|  |  | 
|  | const char TestCCName[] = "test.cc"; | 
|  | using VisitedContextResults = std::vector<std::string>; | 
|  |  | 
|  | class VisitedContextFinder: public CodeCompleteConsumer { | 
|  | public: | 
|  | VisitedContextFinder(VisitedContextResults &Results) | 
|  | : CodeCompleteConsumer(/*CodeCompleteOpts=*/{}, | 
|  | /*CodeCompleteConsumer*/ false), | 
|  | VCResults(Results), | 
|  | CCTUInfo(std::make_shared<GlobalCodeCompletionAllocator>()) {} | 
|  |  | 
|  | void ProcessCodeCompleteResults(Sema &S, CodeCompletionContext Context, | 
|  | CodeCompletionResult *Results, | 
|  | unsigned NumResults) override { | 
|  | VisitedContexts = Context.getVisitedContexts(); | 
|  | VCResults = getVisitedNamespace(); | 
|  | } | 
|  |  | 
|  | CodeCompletionAllocator &getAllocator() override { | 
|  | return CCTUInfo.getAllocator(); | 
|  | } | 
|  |  | 
|  | CodeCompletionTUInfo &getCodeCompletionTUInfo() override { return CCTUInfo; } | 
|  |  | 
|  | std::vector<std::string> getVisitedNamespace() const { | 
|  | std::vector<std::string> NSNames; | 
|  | for (const auto *Context : VisitedContexts) | 
|  | if (const auto *NS = llvm::dyn_cast<NamespaceDecl>(Context)) | 
|  | NSNames.push_back(NS->getQualifiedNameAsString()); | 
|  | return NSNames; | 
|  | } | 
|  |  | 
|  | private: | 
|  | VisitedContextResults& VCResults; | 
|  | CodeCompletionTUInfo CCTUInfo; | 
|  | CodeCompletionContext::VisitedContextSet VisitedContexts; | 
|  | }; | 
|  |  | 
|  | class CodeCompleteAction : public SyntaxOnlyAction { | 
|  | public: | 
|  | CodeCompleteAction(ParsedSourceLocation P, VisitedContextResults &Results) | 
|  | : CompletePosition(std::move(P)), VCResults(Results) {} | 
|  |  | 
|  | bool BeginInvocation(CompilerInstance &CI) override { | 
|  | CI.getFrontendOpts().CodeCompletionAt = CompletePosition; | 
|  | CI.setCodeCompletionConsumer(new VisitedContextFinder(VCResults)); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | private: | 
|  | // 1-based code complete position <Line, Col>; | 
|  | ParsedSourceLocation CompletePosition; | 
|  | VisitedContextResults& VCResults; | 
|  | }; | 
|  |  | 
|  | ParsedSourceLocation offsetToPosition(llvm::StringRef Code, size_t Offset) { | 
|  | Offset = std::min(Code.size(), Offset); | 
|  | StringRef Before = Code.substr(0, Offset); | 
|  | int Lines = Before.count('\n'); | 
|  | size_t PrevNL = Before.rfind('\n'); | 
|  | size_t StartOfLine = (PrevNL == StringRef::npos) ? 0 : (PrevNL + 1); | 
|  | return {TestCCName, static_cast<unsigned>(Lines + 1), | 
|  | static_cast<unsigned>(Offset - StartOfLine + 1)}; | 
|  | } | 
|  |  | 
|  | VisitedContextResults runCodeCompleteOnCode(StringRef Code) { | 
|  | VisitedContextResults Results; | 
|  | auto TokenOffset = Code.find('^'); | 
|  | assert(TokenOffset != StringRef::npos && | 
|  | "Completion token ^ wasn't found in Code."); | 
|  | std::string WithoutToken = Code.take_front(TokenOffset); | 
|  | WithoutToken += Code.drop_front(WithoutToken.size() + 1); | 
|  | assert(StringRef(WithoutToken).find('^') == StringRef::npos && | 
|  | "expected exactly one completion token ^ inside the code"); | 
|  |  | 
|  | auto Action = llvm::make_unique<CodeCompleteAction>( | 
|  | offsetToPosition(WithoutToken, TokenOffset), Results); | 
|  | clang::tooling::runToolOnCodeWithArgs(Action.release(), Code, {"-std=c++11"}, | 
|  | TestCCName); | 
|  | return Results; | 
|  | } | 
|  |  | 
|  | TEST(SemaCodeCompleteTest, VisitedNSForValidQualifiedId) { | 
|  | auto VisitedNS = runCodeCompleteOnCode(R"cpp( | 
|  | namespace ns1 {} | 
|  | namespace ns2 {} | 
|  | namespace ns3 {} | 
|  | namespace ns3 { namespace nns3 {} } | 
|  |  | 
|  | namespace foo { | 
|  | using namespace ns1; | 
|  | namespace ns4 {} // not visited | 
|  | namespace { using namespace ns2; } | 
|  | inline namespace bar { using namespace ns3::nns3; } | 
|  | } // foo | 
|  | namespace ns { foo::^ } | 
|  | )cpp"); | 
|  | EXPECT_THAT(VisitedNS, UnorderedElementsAre("foo", "ns1", "ns2", "ns3::nns3", | 
|  | "foo::(anonymous)")); | 
|  | } | 
|  |  | 
|  | TEST(SemaCodeCompleteTest, VisitedNSForInvalideQualifiedId) { | 
|  | auto VisitedNS = runCodeCompleteOnCode(R"cpp( | 
|  | namespace ns { foo::^ } | 
|  | )cpp"); | 
|  | EXPECT_TRUE(VisitedNS.empty()); | 
|  | } | 
|  |  | 
|  | TEST(SemaCodeCompleteTest, VisitedNSWithoutQualifier) { | 
|  | auto VisitedNS = runCodeCompleteOnCode(R"cpp( | 
|  | namespace n1 { | 
|  | namespace n2 { | 
|  | void f(^) {} | 
|  | } | 
|  | } | 
|  | )cpp"); | 
|  | EXPECT_THAT(VisitedNS, UnorderedElementsAre("n1", "n1::n2")); | 
|  | } | 
|  |  | 
|  | } // namespace |