| //=== 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 |