/* This file is part of KDevelop Copyright 2006 Hamish Rodda Copyright 2007-2009 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "test_cppcodecompletion.h" #include #include #include #include #include #include #include #include #include #include #include #include "declarationbuilder.h" #include "usebuilder.h" #include "cppeditorintegrator.h" #include "dumptypes.h" #include "environmentmanager.h" #include "tokens.h" #include "parsesession.h" #include "rpp/preprocessor.h" #include "rpp/pp-engine.h" #include "rpp/pp-environment.h" #include "expressionvisitor.h" #include "expressionparser.h" #include "codecompletion/context.h" #include "codecompletion/helpers.h" #include "codecompletion/item.h" #include "codecompletion/implementationhelperitem.h" #include "cpppreprocessenvironment.h" #include #include "cppduchain/missingdeclarationproblem.h" #include "cppduchain/missingdeclarationassistant.h" #include "overloadresolutionhelper.h" #include #include #include #include #include #include #include #include #include #include #include using namespace KTextEditor; using namespace KDevelop; QTEST_KDEMAIN(TestCppCodeCompletion, GUI) QString testFile1 = "class Erna; struct Honk { int a,b; enum Enum { Number1, Number2 }; Erna& erna; operator int() {}; }; struct Pointer { Honk* operator ->() const {}; Honk& operator * () {}; }; Honk globalHonk; Honk honky; \n#define HONK Honk\n"; QString testFile2 = "struct Honk { int a,b; enum Enum { Number1, Number2 }; Erna& erna; operator int() {}; }; struct Erna { Erna( const Honk& honk ) {} }; struct Heinz : public Erna {}; Erna globalErna; Heinz globalHeinz; int globalInt; Heinz globalFunction(const Heinz& h ) {}; Erna globalFunction( const Erna& erna); Honk globalFunction( const Honk&, const Heinz& h ) {}; int globalFunction(int ) {}; HONK globalMacroHonk; struct GlobalClass { Heinz function(const Heinz& h ) {}; Erna function( const Erna& erna); }; GlobalClass globalClass;\n#undef HONK\n"; QString testFile3 = "struct A {}; struct B : public A {};"; QString testFile4 = "void test1() {}; class TestClass() { TestClass() {} };"; typedef CodeCompletionItemTester CompletionItemTester; #define TEST_FILE_PARSE_ONLY if (testFileParseOnly) QSKIP("Skip", SkipSingle); TestCppCodeCompletion::TestCppCodeCompletion() { testFileParseOnly = false; } void TestCppCodeCompletion::initTestCase() { AutoTestShell::init(QStringList() << "kdevcppsupport"); TestCore::initialize(Core::NoUi); Cpp::EnvironmentManager::init(); DUChain::self()->disablePersistentStorage(); typeInt = AbstractType::Ptr(new IntegralType(IntegralType::TypeInt)); addInclude( "testFile1.h", testFile1 ); addInclude( "testFile2.h", testFile2 ); addInclude( "testFile3.h", testFile3 ); } void TestCppCodeCompletion::cleanupTestCase() { TestCore::shutdown(); } Declaration* TestCppCodeCompletion::findDeclaration(DUContext* context, const Identifier& id, const CursorInRevision& position) { QList ret = context->findDeclarations(id, position); if (ret.count()) return ret.first(); return 0; } Declaration* TestCppCodeCompletion::findDeclaration(DUContext* context, const QualifiedIdentifier& id, const CursorInRevision& position) { QList ret = context->findDeclarations(id, position); if (ret.count()) return ret.first(); return 0; } void TestCppCodeCompletion::testCommentClearing() { QByteArray method = "struct a { int i; }; int foo() { a inst; }"; TopDUContext* top = parse(method, DumpNone); int ctxt = 2; DUChainWriteLocker lock(DUChain::lock()); CompletionItemTester test(top->childContexts()[ctxt], "//* \n inst."); QCOMPARE(test.names, QStringList() << "i"); CompletionItemTester test2(top->childContexts()[ctxt], "// \n inst."); QCOMPARE(test2.names, QStringList() << "i"); CompletionItemTester test3(top->childContexts()[ctxt], "/*//*/ inst."); QCOMPARE(test3.names, QStringList() << "i"); CompletionItemTester test4(top->childContexts()[ctxt], " ///*//*/ \n inst."); QCOMPARE(test4.names, QStringList() << "i"); CompletionItemTester test5(top->childContexts()[ctxt], "/*// inst."); QCOMPARE(test5.names, QStringList()); CompletionItemTester test6(top->childContexts()[ctxt], "// inst."); QCOMPARE(test6.names, QStringList()); CompletionItemTester test7(top->childContexts()[ctxt], "/*// \n/* // \n */ inst."); QCOMPARE(test7.names, QStringList() << "i"); CompletionItemTester test8(top->childContexts()[ctxt], "// \n /*// \n/* // \n */ /*/*/ inst."); QCOMPARE(test8.names, QStringList() << "i"); release(top); } void TestCppCodeCompletion::testExpressionBefore() { QString exp1 = "int x"; QCOMPARE(Cpp::expressionBefore(exp1, exp1.length()), 4); QString exp2 = "x = x()."; QCOMPARE(Cpp::expressionBefore(exp2, exp2.length()), 4); QString exp3 = "x = (x().)"; QCOMPARE(Cpp::expressionBefore(exp3, exp3.length()), 4); QString exp4 = "x = ((x().))"; QCOMPARE(Cpp::expressionBefore(exp4, exp4.length()), 4); QString exp5 = "x = asdfasdf"; QCOMPARE(Cpp::expressionBefore(exp5, exp5.length()), 4); QString exp6 = "++asdfasdf"; QCOMPARE(Cpp::expressionBefore(exp6, exp6.length()), 2); QString exp7 = "(asd)"; QCOMPARE(Cpp::expressionBefore(exp7, exp7.length()), 0); QString exp8 = "x = x[]->"; QCOMPARE(Cpp::expressionBefore(exp8, exp8.length()), 4); QString exp9 = "x = x()::"; QCOMPARE(Cpp::expressionBefore(exp9, exp9.length()), 4); QString exp10 = "x = x.y.z->x::a[]->h()[q]::S"; QCOMPARE(Cpp::expressionBefore(exp10, exp10.length()), 4); QString exp11 = "x >"; QCOMPARE(Cpp::expressionBefore(exp11, exp11.length()), 3); QString exp12 = "x = *(*y)"; QCOMPARE(Cpp::expressionBefore(exp12, exp12.length()), 5); QString exp13 = "q > xyz"; QCOMPARE(Cpp::expressionBefore(exp13, exp13.length()), 4); QString exp14 = "q>x()[asdf]."; QCOMPARE(Cpp::expressionBefore(exp14, exp14.length()), 2); QString exp15 = "x = y(\"(\")"; QCOMPARE(Cpp::expressionBefore(exp15, exp15.length()), 4); QString exp16 = "x = y<\"<\">"; QCOMPARE(Cpp::expressionBefore(exp16, exp16.length()), 4); QString exp17 = "x = y[z(\"[\")]"; QCOMPARE(Cpp::expressionBefore(exp17, exp17.length()), 4); QString exp18 = "x = y("; QCOMPARE(Cpp::expressionBefore(exp18, exp18.length()), 6); QString exp19 = "x = y["; QCOMPARE(Cpp::expressionBefore(exp19, exp19.length()), 6); QString exp20 = "x = y<"; QCOMPARE(Cpp::expressionBefore(exp20, exp20.length()), 6); QString exp21 = " "; QCOMPARE(Cpp::expressionBefore(exp21, exp21.length()), 1); QString exp22 = ""; QCOMPARE(Cpp::expressionBefore(exp22, exp22.length()), 0); QString exp23 = " "; QCOMPARE(Cpp::expressionBefore(exp23, exp23.length()), 3); } void TestCppCodeCompletion::testSpecialItems() { QByteArray method = "enum Color { Red = 0, Green = 1, Blue = 2 }; void test(Color c) { }"; TopDUContext* top = parse(method, DumpNone); int ctxt = 2; //There is duplication here, but in this case we want all the enum values added in their own group here //It's probably not worth it to go through the list and remove the previously added enums when in scope const QStringList enumGroupCompletions(QStringList() << "Red" << "Green" << "Blue"); DUChainWriteLocker lock(DUChain::lock()); CompletionItemTester test(top->childContexts()[ctxt], "c = "); qDebug() << "actual names:::" << test.names; QCOMPARE(test.names, QStringList() << "Color c =" << "c" << "Color" << "test" << "Red" << "Green" << "Blue" << enumGroupCompletions); CompletionItemTester test2(top->childContexts()[ctxt], "test("); QCOMPARE(test2.names, QStringList() << "test" << "c" << "Color" << "test" << "Red" << "Green" << "Blue" << enumGroupCompletions); CompletionItemTester test3(top->childContexts()[ctxt], "if (c == "); QCOMPARE(test3.names, QStringList() << "Color c ==" << "c" << "Color" << "test" << "Red" << "Green" << "Blue" << enumGroupCompletions); CompletionItemTester test4(top->childContexts()[ctxt], "if (c > "); QCOMPARE(test4.names, QStringList() << "Color c >" << "c" << "Color" << "test" << "Red" << "Green" << "Blue" << enumGroupCompletions); CompletionItemTester test5(top->childContexts()[ctxt], "c -= "); QCOMPARE(test5.names, QStringList() << "Color c -=" << "c" << "Color" << "test" << "Red" << "Green" << "Blue" << enumGroupCompletions); release(top); } void TestCppCodeCompletion::testOnlyShow() { QByteArray method = "template class T { }; namespace A { struct B {}; } struct C { }; int testMe() { }"; TopDUContext* top = parse(method, DumpAll); int fctxt = 5; int sctxt = 3; int nctxt = 2; DUChainWriteLocker lock(DUChain::lock()); CompletionItemTester implementationHelpers(top, "void "); QVERIFY(implementationHelpers.completionContext->onlyShow() == Cpp::CodeCompletionContext::ShowImplementationHelpers); CompletionItemTester showTypes(top->childContexts()[fctxt], "A::B* b = new "); QVERIFY(showTypes.completionContext->onlyShow() == Cpp::CodeCompletionContext::ShowTypes); CompletionItemTester showTypes2(top->childContexts()[fctxt], "A::B* b = const_cast< "); QVERIFY(showTypes2.completionContext->onlyShow() == Cpp::CodeCompletionContext::ShowTypes); CompletionItemTester showTypes3(top->childContexts()[fctxt], "A::B* b = static_cast< "); QVERIFY(showTypes3.completionContext->onlyShow() == Cpp::CodeCompletionContext::ShowTypes); CompletionItemTester showTypes4(top->childContexts()[fctxt], "A::B* b = dynamic_cast< "); QVERIFY(showTypes4.completionContext->onlyShow() == Cpp::CodeCompletionContext::ShowTypes); CompletionItemTester showTypes5(top->childContexts()[fctxt], "A::B* b = reinterpret_cast< "); QVERIFY(showTypes5.completionContext->onlyShow() == Cpp::CodeCompletionContext::ShowTypes); CompletionItemTester showTypes6(top->childContexts()[fctxt], "const "); QVERIFY(showTypes6.completionContext->onlyShow() == Cpp::CodeCompletionContext::ShowTypes); CompletionItemTester showTypes7(top, "typedef "); QVERIFY(showTypes7.completionContext->onlyShow() == Cpp::CodeCompletionContext::ShowTypes); CompletionItemTester showTypes8(top->childContexts()[sctxt], "public "); QVERIFY(showTypes8.completionContext->onlyShow() == Cpp::CodeCompletionContext::ShowTypes); CompletionItemTester showTypes9(top->childContexts()[sctxt], "protected "); QVERIFY(showTypes9.completionContext->onlyShow() == Cpp::CodeCompletionContext::ShowTypes); CompletionItemTester showTypes10(top->childContexts()[sctxt], "private "); QVERIFY(showTypes10.completionContext->onlyShow() == Cpp::CodeCompletionContext::ShowTypes); CompletionItemTester showTypes11(top->childContexts()[sctxt], "virtual "); QVERIFY(showTypes11.completionContext->onlyShow() == Cpp::CodeCompletionContext::ShowTypes); CompletionItemTester showTypes12(top->childContexts()[fctxt], "T<"); QVERIFY(showTypes12.completionContext->onlyShow() == Cpp::CodeCompletionContext::ShowTypes); CompletionItemTester showTypes13(top, ""); QVERIFY(showTypes13.completionContext->onlyShow() == Cpp::CodeCompletionContext::ShowTypes); CompletionItemTester showTypes14(top->childContexts()[sctxt], ""); QVERIFY(showTypes14.completionContext->onlyShow() == Cpp::CodeCompletionContext::ShowTypes); CompletionItemTester showTypes15(top->childContexts()[nctxt], ""); QVERIFY(showTypes15.completionContext->onlyShow() == Cpp::CodeCompletionContext::ShowTypes); CompletionItemTester dontShowTypes(top, "int i = "); QVERIFY(dontShowTypes.completionContext->onlyShow() == Cpp::CodeCompletionContext::ShowAll); CompletionItemTester dontShowTypes2(top->childContexts()[sctxt], "int i = "); QVERIFY(dontShowTypes2.completionContext->onlyShow() == Cpp::CodeCompletionContext::ShowAll); CompletionItemTester dontShowTypes3(top->childContexts()[nctxt], "int i = "); QVERIFY(dontShowTypes3.completionContext->onlyShow() == Cpp::CodeCompletionContext::ShowAll); CompletionItemTester dontShowTypes4(top, "int i = i +"); QVERIFY(dontShowTypes4.completionContext->onlyShow() == Cpp::CodeCompletionContext::ShowAll); CompletionItemTester dontShowTypes5(top->childContexts()[sctxt], "int i = i +"); QVERIFY(dontShowTypes5.completionContext->onlyShow() == Cpp::CodeCompletionContext::ShowAll); CompletionItemTester dontShowTypes6(top->childContexts()[nctxt], "int i = i +"); QVERIFY(dontShowTypes6.completionContext->onlyShow() == Cpp::CodeCompletionContext::ShowAll); release(top); } void TestCppCodeCompletion::testFriends() { QByteArray method = "class Friendly{};"; TopDUContext* top = parse(method, DumpNone); int ctxt = 0; DUChainWriteLocker lock(DUChain::lock()); CompletionItemTester test(top->childContexts()[ctxt], "friend "); QVERIFY(test.completionContext->isValid()); QVERIFY(test.completionContext->onlyShow() == Cpp::CodeCompletionContext::ShowTypes); CompletionItemTester test2(top->childContexts()[ctxt], "friend class "); QVERIFY(test2.completionContext->isValid()); QVERIFY(test2.completionContext->onlyShow() == Cpp::CodeCompletionContext::ShowTypes); CompletionItemTester test3(top->childContexts()[ctxt], "class "); QVERIFY(!test3.completionContext->isValid()); release(top); } void TestCppCodeCompletion::testInvalidContexts() { QByteArray method = "namespace A { struct B {}; } int testMe() { }"; TopDUContext* top = parse(method, DumpNone); int ctxt = 2; DUChainWriteLocker lock(DUChain::lock()); //TODO: these expressions should generally result in an invalid context, but // the ExpressionParser doesn't seem to think that "asdf" is an invalid exp. // Either the expressionParser should be fixed, or testContextValidity() in // context.cpp should be updated CompletionItemTester invalidExp(top->childContexts()[ctxt], "asdf->"); //Should be invalid (and is, but not as soon as it should be) QVERIFY(!invalidExp.completionContext->isValid()); CompletionItemTester invalidExp2(top->childContexts()[ctxt], "asdf."); //Should be invalid (and is, but not as soon as it should be) QVERIFY(!invalidExp2.completionContext->isValid()); CompletionItemTester invalidExp3(top->childContexts()[ctxt], "asdf::"); //Should be valid in case it's a namespace, but asdf should eval to false QVERIFY(invalidExp3.completionContext->isValid()); CompletionItemTester invalidExp31(top->childContexts()[ctxt], "A::"); //Should be valid as namespace, but A should evaluate to false QVERIFY(invalidExp31.completionContext->isValid()); CompletionItemTester invalidExp4(top->childContexts()[ctxt], "asdf("); //kept valid for MissingIncludeCompletion, but asdf should eval to false QVERIFY(invalidExp4.completionContext->parentContext()->isValid()); CompletionItemTester invalidExp5(top->childContexts()[ctxt], "asdf<"); //TODO: testContextValidity() says templateAccess should be kept valid even with invalid expression // as it could get MissingIncludeCompletion, but this is considered a "less than", since an invalid // expression will not create a templateAccess context QEXPECT_FAIL("", "Test mimics an unknown template expression, but there's no way to differentiate it from a less than operator with an unknown expression, which it gets considered to be. If it were a template, it would be valid.", Continue); QVERIFY(invalidExp5.completionContext->parentContext()->isValid()); CompletionItemTester invalidExp6(top->childContexts()[ctxt], "asdf &&"); QVERIFY(!invalidExp6.completionContext->parentContext()->isValid()); CompletionItemTester invalidExp7(top->childContexts()[ctxt], "void "); QVERIFY(!invalidExp7.completionContext->isValid()); CompletionItemTester invalidExp71(top->childContexts()[ctxt], "int* "); QVERIFY(!invalidExp71.completionContext->isValid()); CompletionItemTester invalidExp8(top->childContexts()[ctxt], "A::B &&"); QVERIFY(!invalidExp8.completionContext->parentContext()->isValid()); CompletionItemTester invalidExp9(top->childContexts()[ctxt], "."); QVERIFY(!invalidExp9.completionContext->isValid()); CompletionItemTester invalidExp10(top->childContexts()[ctxt], "->"); QVERIFY(!invalidExp10.completionContext->isValid()); CompletionItemTester invalidExp11(top->childContexts()[ctxt], "::"); QVERIFY(!invalidExp11.completionContext->isValid()); release(top); } void TestCppCodeCompletion::testMemberAccess() { QByteArray method = "template class T { public: T1 ta(); class U { public: class V{ }; }; };" "class X { public: X(){}; int a(int a, T b); int b;};" "T t;" "X* getX() { }" "class Z { public: Z() = delete; static int a(int b); };"; TopDUContext* top = parse(method, DumpNone); int ctxt = 4; DUChainWriteLocker lock(DUChain::lock()); CompletionItemTester testDot(top->childContexts()[ctxt], "t."); QCOMPARE(testDot.names, QStringList() << "ta"); QCOMPARE(testDot.completionContext->accessType(), Cpp::CodeCompletionContext::MemberAccess); CompletionItemTester testDot2(top->childContexts()[ctxt], "(*getX())."); QCOMPARE(testDot2.names, QStringList() << "a" << "b"); QCOMPARE(testDot2.completionContext->accessType(), Cpp::CodeCompletionContext::MemberAccess); CompletionItemTester testArrow(top->childContexts()[ctxt], "getX()->"); QCOMPARE(testArrow.names, QStringList() << "a" << "b"); QCOMPARE(testArrow.completionContext->accessType(), Cpp::CodeCompletionContext::ArrowMemberAccess); CompletionItemTester testArrow2(top->childContexts()[ctxt], "(&t)->"); QCOMPARE(testArrow2.names, QStringList() << "ta"); QCOMPARE(testArrow2.completionContext->accessType(), Cpp::CodeCompletionContext::ArrowMemberAccess); CompletionItemTester testColons(top->childContexts()[ctxt], "X::"); QCOMPARE(testColons.names, QStringList() << "X" << "a" << "b"); QCOMPARE(testColons.completionContext->accessType(), Cpp::CodeCompletionContext::StaticMemberChoose); CompletionItemTester testColons2(top->childContexts()[ctxt], "T::U::"); QCOMPARE(testColons2.names, QStringList() << "V"); QCOMPARE(testColons2.completionContext->accessType(), Cpp::CodeCompletionContext::StaticMemberChoose); CompletionItemTester testDeleted(top->childContexts()[ctxt], "Z::"); QCOMPARE(testDeleted.names, QStringList() << "a"); QCOMPARE(testDeleted.completionContext->accessType(), Cpp::CodeCompletionContext::StaticMemberChoose); release(top); } void TestCppCodeCompletion::testParentContexts() { //Binary operators (also parent contexts) are tested in testBinaryOperators QByteArray method = "template class Templ { T1 ta(); T2 tb(); }; class X { X(); int a(int a, Templ b); int b;}; X::X() { }"; TopDUContext* top = parse(method, DumpAll); int ctxt = 4; DUChainWriteLocker lock(DUChain::lock()); CompletionItemTester templateContext(top->childContexts()[ctxt], "Templ<"); QCOMPARE(templateContext.names, QStringList() << "Templ" << "Templ" << "X"); QCOMPARE(templateContext.completionContext->parentContext()->accessType(), Cpp::CodeCompletionContext::TemplateAccess); CompletionItemTester templateContext2(top->childContexts()[ctxt], "TemplparentContext()->accessType(), Cpp::CodeCompletionContext::TemplateAccess); CompletionItemTester templateContext3(top->childContexts()[ctxt], "Templ< int , int > t = new Templ<"); QCOMPARE(templateContext3.names, QStringList() << "Templ" << "Templ" << "X"); QCOMPARE(templateContext3.completionContext->parentContext()->accessType(), Cpp::CodeCompletionContext::TemplateAccess); CompletionItemTester templateContext4(top->childContexts()[ctxt], "Templ< int , int > t = new TemplparentContext()->accessType(), Cpp::CodeCompletionContext::TemplateAccess); CompletionItemTester templateContext5(top->childContexts()[ctxt], "Templ< int , Templ<"); QCOMPARE(templateContext5.names, QStringList() << "Templ" << "Templ" << "Templ" << "X"); QCOMPARE(templateContext5.completionContext->parentContext()->accessType(), Cpp::CodeCompletionContext::TemplateAccess); QCOMPARE(templateContext5.completionContext->parentContext()->parentContext()->accessType(), Cpp::CodeCompletionContext::TemplateAccess); CompletionItemTester notATemplate(top->childContexts()[ctxt], "if ( a > b < a > this->"); QCOMPARE(notATemplate.names, QStringList() << "a" << "b"); QCOMPARE(notATemplate.completionContext->parentContext()->accessType(), Cpp::CodeCompletionContext::BinaryOpFunctionCallAccess); CompletionItemTester functionContext(top->childContexts()[ctxt], "a("); QCOMPARE(functionContext.names, QStringList() << "a" << "X" << "a" << "b" << "Templ" << "this"); QCOMPARE(functionContext.completionContext->parentContext()->accessType(), Cpp::CodeCompletionContext::FunctionCallAccess); CompletionItemTester functionContext2(top->childContexts()[ctxt], "a(0, "); QCOMPARE(functionContext2.names, QStringList() << "a" << "X" << "a" << "b" << "Templ" << "this"); QCOMPARE(functionContext2.completionContext->parentContext()->accessType(), Cpp::CodeCompletionContext::FunctionCallAccess); CompletionItemTester functionContext3(top->childContexts()[ctxt], "a(a(100, "); QCOMPARE(functionContext3.names, QStringList() << "a" << "a" << "X" << "a" << "b" << "Templ" << "this"); QCOMPARE(functionContext3.completionContext->parentContext()->accessType(), Cpp::CodeCompletionContext::FunctionCallAccess); CompletionItemTester functionContext4(top->childContexts()[ctxt], "a(a(100, Templ), "); QCOMPARE(functionContext4.names, QStringList() << "a" << "X" << "a" << "b" << "Templ" << "this"); QCOMPARE(functionContext4.completionContext->parentContext()->accessType(), Cpp::CodeCompletionContext::FunctionCallAccess); //See also testKeywords CompletionItemTester returnContext(top->childContexts()[ctxt], "return "); QCOMPARE(returnContext.names, QStringList() << "X" << "a" << "b" << "Templ" << "this"); QCOMPARE(returnContext.completionContext->parentContext()->accessType(), Cpp::CodeCompletionContext::ReturnAccess); //See also testCaseContext release(top); } /* * A comment on expect-fails in following 3 tests: * "case" statement completion uses DUChainBase::createRangeMoving()->text() to get the switch'd expression * and parse its type. Unfortunately, PersistentRangeMoving class itself is not yet completed and doesn't work without an editor. * Hence the expression type cannot be resolved, which leads to 2 problems: * 1) best matches are not selected, so all items have match quality of 0; * 2) specialItemsForArgumentType() which is responsible for fetching enumerators from different scopes doesn't work either. */ void TestCppCodeCompletion::testCaseContext() { QByteArray method = "enum testEnum { foo, bar }; void test() { switch( testEnum ) { } }"; TopDUContext* top = parse(method, DumpNone); int ctxt = 2; int sctxt = 1; DUChainWriteLocker lock(DUChain::lock()); CompletionItemTester caseContext(top->childContexts()[ctxt]->childContexts()[sctxt], "case "); QCOMPARE(caseContext.names, QStringList() << "testEnum" << "foo" << "bar"); QCOMPARE(caseContext.completionContext->parentContext()->accessType(), Cpp::CodeCompletionContext::CaseAccess); QCOMPARE(caseContext.itemData("testEnum", KDevelop::CodeCompletionModel::MatchQuality).toInt(), 0); QEXPECT_FAIL("", "PersistentRangeMoving needs to be fixed", Continue); QCOMPARE(caseContext.itemData("foo", KDevelop::CodeCompletionModel::MatchQuality).toInt(), 10); QEXPECT_FAIL("", "PersistentRangeMoving needs to be fixed", Continue); QCOMPARE(caseContext.itemData("bar", KDevelop::CodeCompletionModel::MatchQuality).toInt(), 10); release(top); } void TestCppCodeCompletion::testCaseContextComplexExpression() { QByteArray method = "enum testEnum { foo, bar }; struct testStruct { testEnum e; }; void test(testStruct s) { switch (s.e) { } }"; TopDUContext* top = parse(method, DumpNone); int ctxt = 3; int sctxt = 1; DUChainWriteLocker lock(DUChain::lock()); CompletionItemTester caseContext(top->childContexts()[ctxt]->childContexts()[sctxt], "case "); QCOMPARE(caseContext.names, QStringList() << "testEnum" << "testStruct" << "foo" << "bar"); QCOMPARE(caseContext.completionContext->parentContext()->accessType(), Cpp::CodeCompletionContext::CaseAccess); QCOMPARE(caseContext.itemData("testEnum", KDevelop::CodeCompletionModel::MatchQuality).toInt(), 0); QCOMPARE(caseContext.itemData("testStruct", KDevelop::CodeCompletionModel::MatchQuality).toInt(), 0); QEXPECT_FAIL("", "PersistentRangeMoving needs to be fixed", Continue); QCOMPARE(caseContext.itemData("foo", KDevelop::CodeCompletionModel::MatchQuality).toInt(), 10); QEXPECT_FAIL("", "PersistentRangeMoving needs to be fixed", Continue); QCOMPARE(caseContext.itemData("bar", KDevelop::CodeCompletionModel::MatchQuality).toInt(), 10); release(top); } void TestCppCodeCompletion::testCaseContextDifferentScope() { QByteArray method = "struct testStruct { enum testEnum { foo, bar } e; }; void test(testStruct s) { switch (s.e) { } }"; TopDUContext* top = parse(method, DumpNone); int ctxt = 2; int sctxt = 1; DUChainWriteLocker lock(DUChain::lock()); CompletionItemTester caseContext(top->childContexts()[ctxt]->childContexts()[sctxt], "case "); QCOMPARE(caseContext.completionContext->parentContext()->accessType(), Cpp::CodeCompletionContext::CaseAccess); QEXPECT_FAIL("", "PersistentRangeMoving needs to be fixed", Continue); QCOMPARE(caseContext.names, QStringList() << "testStruct" << "testStruct::foo" << "testStruct::bar"); release(top); } void TestCppCodeCompletion::testCaseContextConstants() { QByteArray method = "enum testEnum { foo, bar };" "testEnum enum_nc; const testEnum enum_c; const testEnum enum_cc = foo;" "int int_nc; const int int_c; const int int_cc = 0;" "float float_nc; const float float_c; const float float_cc = 0.0;" "testEnum func_enum(); constexpr testEnum func_enum_cc();" "int func_int(); constexpr int func_int_cc();" "void func_void(); float func_float(); constexpr float func_float_cc();" "void testcase() { switch (enum_nc) { } }"; TopDUContext* top = parse(method, DumpNone); int sctxt = 1; DUChainWriteLocker lock(DUChain::lock()); CompletionItemTester caseContext(top->childContexts().last()->childContexts()[sctxt], "case "); QStringList names = caseContext.names; QVERIFY(names.contains("testEnum")); QVERIFY(!names.contains("enum_nc")); QVERIFY(!names.contains("enum_c")); QVERIFY( names.contains("enum_cc")); QVERIFY(!names.contains("int_nc")); QVERIFY(!names.contains("int_c")); QVERIFY( names.contains("int_cc")); QVERIFY(!names.contains("float_nc")); QVERIFY(!names.contains("float_c")); QVERIFY(!names.contains("float_cc")); QVERIFY(!names.contains("func_void")); QVERIFY(!names.contains("func_float")); QVERIFY(!names.contains("func_float_cc")); QEXPECT_FAIL("", "constexpr needs to be handled", Continue); QVERIFY(!names.contains("func_enum")); QVERIFY(names.contains("func_enum_cc")); QEXPECT_FAIL("", "constexpr needs to be handled", Continue); QVERIFY(!names.contains("func_int")); QVERIFY(names.contains("func_int_cc")); release (top); } void TestCppCodeCompletion::testUnaryOperators() { QByteArray method = "class X { X(); int a; int b;}; int x,*z; X::X() { }"; TopDUContext* top = parse(method, DumpAll); DUChainWriteLocker lock(DUChain::lock()); int ctxt = 2; CompletionItemTester plusplus(top->childContexts()[ctxt], "x *= ++"); QCOMPARE(plusplus.names, QStringList() << "int x *=" << "X" << "a" << "b" << "x" << "z" << "this"); CompletionItemTester plusplus2(top->childContexts()[ctxt], "x *= *z + ++"); QCOMPARE(plusplus2.names, QStringList() << "int* z +" << "X" << "a" << "b" << "x" << "z" << "this"); CompletionItemTester plusplus3(top->childContexts()[ctxt], "a + b = - ++"); QCOMPARE(plusplus3.names, QStringList() << "int b =" << "X" << "a" << "b" << "x" << "z" << "this"); CompletionItemTester plusplus4(top->childContexts()[ctxt], "x *= &*++"); QCOMPARE(plusplus4.names, QStringList() << "int x *=" << "X" << "a" << "b" << "x" << "z" << "this"); CompletionItemTester minusminus(top->childContexts()[ctxt], "x ~= --"); QCOMPARE(minusminus.names, QStringList() << "int x ~=" << "X" << "a" << "b" << "x" << "z" << "this"); CompletionItemTester minusminus2(top->childContexts()[ctxt], "x + *z + --"); QCOMPARE(minusminus2.names, QStringList() << "int* z +" << "X" << "a" << "b" << "x" << "z" << "this"); //TODO: should get "int b -" hint, but binary operators don't work behind paren CompletionItemTester minusminus3(top->childContexts()[ctxt], "a + b -(----"); QCOMPARE(minusminus3.names, QStringList() << "X" << "a" << "b" << "x" << "z" << "this"); CompletionItemTester minusminus4(top->childContexts()[ctxt], "x ^= ++--"); QCOMPARE(minusminus4.names, QStringList() << "int x ^=" << "X" << "a" << "b" << "x" << "z" << "this"); CompletionItemTester notop(top->childContexts()[ctxt], "!++"); QCOMPARE(notop.names, QStringList() << "X" << "a" << "b" << "x" << "z" << "this"); CompletionItemTester notop2(top->childContexts()[ctxt], "!a & !"); QCOMPARE(notop2.names, QStringList() << "int a &" << "X" << "a" << "b" << "x" << "z" << "this"); CompletionItemTester notop3(top->childContexts()[ctxt], "b != !"); QCOMPARE(notop3.names, QStringList() << "int b !=" << "X" << "a" << "b" << "x" << "z" << "this"); CompletionItemTester notop4(top->childContexts()[ctxt], "x *= ~!"); QCOMPARE(notop4.names, QStringList() << "int x *=" << "X" << "a" << "b" << "x" << "z" << "this"); CompletionItemTester bitnot(top->childContexts()[ctxt], "~--"); QCOMPARE(bitnot.names, QStringList() << "X" << "a" << "b" << "x" << "z" << "this"); CompletionItemTester bitnot2(top->childContexts()[ctxt], "++~"); QCOMPARE(bitnot2.names, QStringList() << "X" << "a" << "b" << "x" << "z" << "this"); CompletionItemTester bitnot3(top->childContexts()[ctxt], "b ~= ~"); QCOMPARE(bitnot3.names, QStringList() << "int b ~=" << "X" << "a" << "b" << "x" << "z" << "this"); CompletionItemTester bitnot4(top->childContexts()[ctxt], "a * ~~~"); QCOMPARE(bitnot4.names, QStringList() << "int a *" << "X" << "a" << "b" << "x" << "z" << "this"); CompletionItemTester plus(top->childContexts()[ctxt], "*z = +"); QCOMPARE(plus.names, QStringList() << "int* z =" << "X" << "a" << "b" << "x" << "z" << "this"); //TODO: same as minusminus3 CompletionItemTester plus2(top->childContexts()[ctxt], "a + b +(+"); QCOMPARE(plus2.names, QStringList() << "X" << "a" << "b" << "x" << "z" << "this"); CompletionItemTester plus3(top->childContexts()[ctxt], "b ~= +b + -+"); QCOMPARE(bitnot3.names, QStringList() << "int b ~=" << "X" << "a" << "b" << "x" << "z" << "this"); CompletionItemTester plus4(top->childContexts()[ctxt], "a * +"); QCOMPARE(plus4.names, QStringList() << "int a *" << "X" << "a" << "b" << "x" << "z" << "this"); //TODO: same as minusminus3 CompletionItemTester minus(top->childContexts()[ctxt], "*z -(-"); QCOMPARE(minus.names, QStringList() << "X" << "a" << "b" << "x" << "z" << "this"); CompletionItemTester minus2(top->childContexts()[ctxt], "a ^ -(-"); QCOMPARE(minus2.names, QStringList() << "int a ^" << "X" << "a" << "b" << "x" << "z" << "this"); CompletionItemTester minus3(top->childContexts()[ctxt], "b ~= -"); QCOMPARE(minus3.names, QStringList() << "int b ~=" << "X" << "a" << "b" << "x" << "z" << "this"); CompletionItemTester minus4(top->childContexts()[ctxt], "a * +-"); QCOMPARE(minus4.names, QStringList() << "int a *" << "X" << "a" << "b" << "x" << "z" << "this"); CompletionItemTester refderef1(top->childContexts()[ctxt], "**&(&"); QCOMPARE(refderef1.names, QStringList() << "X" << "a" << "b" << "x" << "z" << "this"); //TODO: same as minusminus3 CompletionItemTester refderef2(top->childContexts()[ctxt], "a & b & (&"); QCOMPARE(refderef2.names, QStringList() << "X" << "a" << "b" << "x" << "z" << "this"); CompletionItemTester refderef3(top->childContexts()[ctxt], "a * x * *z * *"); QCOMPARE(refderef3.names, QStringList() << "int* z *" << "X" << "a" << "b" << "x" << "z" << "this"); CompletionItemTester refderef4(top->childContexts()[ctxt], "b & b & *"); QCOMPARE(refderef4.names, QStringList() << "int b &" << "X" << "a" << "b" << "x" << "z" << "this"); release(top); } void TestCppCodeCompletion::testBinaryOperators() { QByteArray method = "class X { X(); int a; int b;}; X::X() { }"; TopDUContext* top = parse(method, DumpAll); DUChainWriteLocker lock(DUChain::lock()); CompletionItemTester gt(top->childContexts()[2], "if (a > "); QCOMPARE(gt.names, QStringList() << "int a >" << "X" << "a" << "b" << "this"); CompletionItemTester lt(top->childContexts()[2], "if (a < "); QCOMPARE(lt.names, QStringList() << "int a <" << "X" << "a" << "b" << "this"); CompletionItemTester plus(top->childContexts()[2], "if (a + "); QCOMPARE(plus.names, QStringList() << "int a +" << "X" << "a" << "b" << "this"); CompletionItemTester minus(top->childContexts()[2], "if (a - "); QCOMPARE(minus.names, QStringList() << "int a -" << "X" << "a" << "b" << "this"); CompletionItemTester multiply(top->childContexts()[2], "if (a * "); QCOMPARE(multiply.names, QStringList() << "int a *" << "X" << "a" << "b" << "this"); CompletionItemTester divide(top->childContexts()[2], "if (a / "); QCOMPARE(divide.names, QStringList() << "int a /" << "X" << "a" << "b" << "this"); CompletionItemTester modulus(top->childContexts()[2], "if (a % "); QCOMPARE(modulus.names, QStringList() << "int a %" << "X" << "a" << "b" << "this"); CompletionItemTester xorop(top->childContexts()[2], "if (a ^ "); QCOMPARE(xorop.names, QStringList() << "int a ^" << "X" << "a" << "b" << "this"); CompletionItemTester bitandop(top->childContexts()[2], "if (a & "); QCOMPARE(bitandop.names, QStringList() << "int a &" << "X" << "a" << "b" << "this"); CompletionItemTester bitorop(top->childContexts()[2], "if (a | "); QCOMPARE(bitorop.names, QStringList() << "int a |" << "X" << "a" << "b" << "this"); CompletionItemTester notequal(top->childContexts()[2], "if (a != "); QCOMPARE(notequal.names, QStringList() << "int a !=" << "X" << "a" << "b" << "this"); CompletionItemTester ltequal(top->childContexts()[2], "if (a <= "); QCOMPARE(ltequal.names, QStringList() << "int a <=" << "X" << "a" << "b" << "this"); CompletionItemTester gtequal(top->childContexts()[2], "if (a >= "); QCOMPARE(gtequal.names, QStringList() << "int a >=" << "X" << "a" << "b" << "this"); CompletionItemTester plusequal(top->childContexts()[2], "if (a += "); QCOMPARE(plusequal.names, QStringList() << "int a +=" << "X" << "a" << "b" << "this"); CompletionItemTester minusequal(top->childContexts()[2], "if (a -= "); QCOMPARE(minusequal.names, QStringList() << "int a -=" << "X" << "a" << "b" << "this"); CompletionItemTester multiplyequal(top->childContexts()[2], "if (a *= "); QCOMPARE(multiplyequal.names, QStringList() << "int a *=" << "X" << "a" << "b" << "this"); CompletionItemTester divideequal(top->childContexts()[2], "if (a /= "); QCOMPARE(divideequal.names, QStringList() << "int a /=" << "X" << "a" << "b" << "this"); CompletionItemTester modulusequal(top->childContexts()[2], "if (a %= "); QCOMPARE(modulusequal.names, QStringList() << "int a %=" << "X" << "a" << "b" << "this"); CompletionItemTester xorequal(top->childContexts()[2], "if (a ^= "); QCOMPARE(xorequal.names, QStringList() << "int a ^=" << "X" << "a" << "b" << "this"); CompletionItemTester bitandequal(top->childContexts()[2], "if (a &= "); QCOMPARE(bitandequal.names, QStringList() << "int a &=" << "X" << "a" << "b" << "this"); CompletionItemTester bitorequal(top->childContexts()[2], "if (a |= "); QCOMPARE(bitorequal.names, QStringList() << "int a |=" << "X" << "a" << "b" << "this"); CompletionItemTester bitnotequal(top->childContexts()[2], "if (a ~= "); QCOMPARE(bitnotequal.names, QStringList() << "int a ~=" << "X" << "a" << "b" << "this"); CompletionItemTester leftshift(top->childContexts()[2], "if (a << "); QCOMPARE(leftshift.names, QStringList() << "int a <<" << "X" << "a" << "b" << "this"); CompletionItemTester rightshift(top->childContexts()[2], "if (a >> "); QCOMPARE(rightshift.names, QStringList() << "int a >>" << "X" << "a" << "b" << "this"); CompletionItemTester leftshifteq(top->childContexts()[2], "if (a <<= "); QCOMPARE(leftshifteq.names, QStringList() << "int a <<=" << "X" << "a" << "b" << "this"); CompletionItemTester rightshifteq(top->childContexts()[2], "if (a >>= "); QCOMPARE(rightshifteq.names, QStringList() << "int a >>=" << "X" << "a" << "b" << "this"); CompletionItemTester equal(top->childContexts()[2], "if (a == "); QCOMPARE(equal.names, QStringList() << "int a ==" << "X" << "a" << "b" << "this"); CompletionItemTester andop(top->childContexts()[2], "if (a && "); QCOMPARE(andop.names, QStringList() << "int a &&" << "X" << "a" << "b" << "this"); CompletionItemTester orop(top->childContexts()[2], "if (a || "); QCOMPARE(orop.names, QStringList() << "int a ||" << "X" << "a" << "b" << "this"); CompletionItemTester bracket(top->childContexts()[2], "if (a[ "); QCOMPARE(bracket.names, QStringList() << "int a []" << "X" << "a" << "b" << "this"); CompletionItemTester assign(top->childContexts()[2], "if (a = "); QCOMPARE(assign.names, QStringList() << "int a =" << "X" << "a" << "b" << "this"); CompletionItemTester assign2(top->childContexts()[2], "X z = "); QCOMPARE(assign2.names, QStringList() << "class X =" << "X" << "a" << "b" << "this"); CompletionItemTester assign3(top->childContexts()[2], "X *z = new"); QCOMPARE(assign3.names, QStringList() << "class X =" << "X"); release(top); } void TestCppCodeCompletion::testDeclarationIsInitialization() { QByteArray method = "template class Templ { T1 ta(); T2 tb(); }; class X { X(); int a; int b;}; X::X() { }"; TopDUContext* top = parse(method, DumpAll); DUChainWriteLocker lock(DUChain::lock()); CompletionItemTester notActuallyADecl(top->childContexts()[4], "int g; if (a > this->"); QCOMPARE(notActuallyADecl.names, QStringList() << "int a >" << "a" << "b"); CompletionItemTester notActuallyADecl2(top->childContexts()[4], "int g; if (a < a > this->"); QCOMPARE(notActuallyADecl2.names, QStringList() << "int a >" << "a" << "b"); CompletionItemTester notTemplateEqOp(top->childContexts()[4], "int g = 1; int h ="); QCOMPARE(notTemplateEqOp.names, QStringList() << "int =" << "X" << "a" << "b" << "Templ" << "this"); CompletionItemTester isTemplateEqOp(top->childContexts()[4], "int g = 1; Templ h ="); QCOMPARE(isTemplateEqOp.names, QStringList() << "class Templ< int, int > =" << "X" << "a" << "b" << "Templ" << "this"); CompletionItemTester isTemplate(top->childContexts()[4], "int g = 1; Templ h("); QCOMPARE(isTemplate.names, QStringList() << "Templ< int, int >(" << "X" << "a" << "b" << "Templ" << "this"); CompletionItemTester isTemplate2(top->childContexts()[4], "Templ h("); QCOMPARE(isTemplate2.names, isTemplate.names); CompletionItemTester isTemplate3(top->childContexts()[4], "int g; Templ h("); QCOMPARE(isTemplate2.names, isTemplate.names); CompletionItemTester constructInit(top->childContexts()[4], "int g = 1; X myx("); QCOMPARE(constructInit.names, QStringList() << "X" << "X(" << "X" << "a" << "b" << "Templ" << "this"); CompletionItemTester ptr(top->childContexts()[4], "int g = 1; int* test ="); QCOMPARE(ptr.names, QStringList() << "int* =" << "X" << "a" << "b" << "Templ" << "this"); CompletionItemTester ptr2(top->childContexts()[4], "int g = 1; int** test ="); QCOMPARE(ptr2.names, QStringList() << "int** =" << "X" << "a" << "b" << "Templ" << "this"); release(top); } void TestCppCodeCompletion::testNoMemberAccess() { QByteArray test = "class MyClass{ public:\n int myint; };\n\n"; TopDUContext* context = parse(test, DumpNone); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(context->childContexts().count(), 1); CompletionItemTester testCase(context, "void "); //NoMemberAccess with non-empty valid-type expression QVERIFY(testCase.completionContext->isValid()); QCOMPARE(testCase.names, QStringList()); //Valid, but should not offer any completions in this case CompletionItemTester testCase1(context, "asdf "); //NoMemberAccess with non-empty invalid-type expression QVERIFY(!testCase1.completionContext->isValid()); CompletionItemTester testCase2(context, " "); //NoMemberAccess with empty expression QVERIFY(testCase2.completionContext->isValid()); QCOMPARE(testCase.names, QStringList()); //Theoretically should have "MyClass", but global completions aren't included CompletionItemTester testCase3(context, "public: "); QVERIFY(testCase2.completionContext->isValid()); CompletionItemTester testCase4(context, "protected: "); QVERIFY(testCase2.completionContext->isValid()); CompletionItemTester testCase5(context, "private: "); QVERIFY(testCase2.completionContext->isValid()); release(context); } void TestCppCodeCompletion::testFunctionImplementation() { //__hidden1 and _Hidden2 should not be visible in the code-completion, as their identifiers are reserved to C++ implementations and standard libraries. addInclude("myclass.h", "namespace mynamespace { class myclass { void students(); }; }; class __hidden1; int _Hidden2; "); QByteArray test = "#include \"myclass.h\"\nnamespace mynamespace { }"; TopDUContext* context = parse(test, DumpNone); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(context->childContexts().count(), 1); CompletionItemTester testCase(context->childContexts()[0]); QVERIFY(testCase.completionContext->isValid()); QCOMPARE(testCase.names, QStringList() << "mynamespace" << "myclass"); //TODO: If it ever becomes possible to test implementationhelpers, here it should be done release(context); } void TestCppCodeCompletion::testAliasDeclarationAccessPolicy() { QByteArray test = "namespace Base { int One; int Two; int Three };\ class List { public: using Base::One; protected: using Base::Two; private: using Base::Three; }; int main(List a) {}"; TopDUContext* context = parse(test, DumpNone); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(context->childContexts().count(), 4); CompletionItemTester testCase(context->childContexts()[3], "a."); QVERIFY(testCase.completionContext->isValid()); QCOMPARE(testCase.names, QStringList() << "One"); AliasDeclaration* aliasDeclOne = dynamic_cast(context->childContexts()[1]->localDeclarations()[0]); AliasDeclaration* aliasDeclTwo = dynamic_cast(context->childContexts()[1]->localDeclarations()[1]); AliasDeclaration* aliasDeclThree = dynamic_cast(context->childContexts()[1]->localDeclarations()[2]); QVERIFY(aliasDeclOne && aliasDeclTwo && aliasDeclThree); QVERIFY(aliasDeclOne->accessPolicy() == KDevelop::Declaration::Public); QVERIFY(aliasDeclTwo->accessPolicy() == KDevelop::Declaration::Protected); QVERIFY(aliasDeclThree->accessPolicy() == KDevelop::Declaration::Private); release(context); } void TestCppCodeCompletion::testKeywords() { QByteArray test = "struct Values { int Value1; int Value2; struct Sub { int SubValue; }; }; Values v; int test(int a) {}"; TopDUContext* context = parse(test, DumpNone); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(context->childContexts().count(), 3); int ctxt = 2; CompletionItemTester constcast(context->childContexts()[ctxt], "Values* y = const_cast<"); QVERIFY(constcast.completionContext->isValid()); QCOMPARE(constcast.names, QStringList() << "struct Values =" << "Values"); CompletionItemTester staticcast(context->childContexts()[ctxt], "Values* y = static_cast<"); QVERIFY(staticcast.completionContext->isValid()); QCOMPARE(staticcast.names, QStringList() << "struct Values =" << "Values"); CompletionItemTester dynamiccast(context->childContexts()[ctxt], "Values* y = dynamic_cast<"); QVERIFY(dynamiccast.completionContext->isValid()); QCOMPARE(dynamiccast.names, QStringList() << "struct Values =" << "Values"); CompletionItemTester reinterpretcast(context->childContexts()[ctxt], "Values* y = reinterpret_cast<"); QVERIFY(reinterpretcast.completionContext->isValid()); QCOMPARE(reinterpretcast.names, QStringList() << "struct Values =" << "Values"); CompletionItemTester testConst(context->childContexts()[ctxt], "const "); QVERIFY(testConst.completionContext->isValid()); QCOMPARE(testConst.names, QStringList() << "Values"); CompletionItemTester testTypedef(context->childContexts()[ctxt], "typedef "); QVERIFY(testTypedef.completionContext->isValid()); QCOMPARE(testTypedef.names, QStringList() << "Values"); CompletionItemTester testPublic(context->childContexts()[ctxt], "public "); QVERIFY(testPublic.completionContext->isValid()); QCOMPARE(testPublic.names, QStringList() << "Values"); CompletionItemTester testPublic2(context->childContexts()[0], "public: "); QVERIFY(testPublic2.completionContext->isValid()); QCOMPARE(testPublic2.names, QStringList() << "Sub" << "Values"); CompletionItemTester testFakePublic(context->childContexts()[ctxt], "fakepublic "); QVERIFY(!testFakePublic.completionContext->isValid()); CompletionItemTester testProtected(context->childContexts()[ctxt], "protected "); QVERIFY(testProtected.completionContext->isValid()); QCOMPARE(testProtected.names, QStringList() << "Values"); CompletionItemTester testProtected2(context->childContexts()[0], "protected: "); QVERIFY(testProtected2.completionContext->isValid()); QCOMPARE(testProtected2.names, QStringList() << "Sub" << "Values"); CompletionItemTester testFakeProtected(context->childContexts()[ctxt], "fakeprotected "); QVERIFY(!testFakeProtected.completionContext->isValid()); CompletionItemTester testPrivate(context->childContexts()[ctxt], "private "); QVERIFY(testPrivate.completionContext->isValid()); QCOMPARE(testPrivate.names, QStringList() << "Values"); CompletionItemTester testPrivate2(context->childContexts()[0], "private: "); QVERIFY(testPrivate2.completionContext->isValid()); QCOMPARE(testPrivate2.names, QStringList() << "Sub" << "Values"); CompletionItemTester testVirtual(context->childContexts()[ctxt], "virtual "); QVERIFY(testVirtual.completionContext->isValid()); QCOMPARE(testVirtual.names, QStringList() << "Values"); //TODO: fix ShowSignals, make this show only signals, and move emit tests to where it matters CompletionItemTester testEmit(context->childContexts()[ctxt], "emit "); QVERIFY(testEmit.completionContext->isValid()); QCOMPARE(testEmit.names, QStringList() << "a" << "Values" << "v" << "test"); CompletionItemTester testQEmit(context->childContexts()[ctxt], "Q_EMIT "); QVERIFY(testQEmit.completionContext->isValid()); QCOMPARE(testQEmit.names, QStringList() << "a" << "Values" << "v" << "test"); CompletionItemTester testCase(context->childContexts()[ctxt], "switch (a) { case "); QVERIFY(testCase.completionContext->isValid()); QCOMPARE(testCase.names, QStringList() << "Values" << "test"); CompletionItemTester testCase2(context->childContexts()[ctxt], "switch (a) { case v."); QVERIFY(testCase2.completionContext->isValid()); QCOMPARE(testCase2.names, QStringList()); CompletionItemTester testReturn(context->childContexts()[ctxt], "return "); QVERIFY(testReturn.completionContext->isValid()); QCOMPARE(testReturn.names, QStringList() << "return int" << "a" << "Values" << "v" << "test" << "v.Value1" << "v.Value2"); CompletionItemTester testReturn2(context->childContexts()[ctxt], "return v."); QVERIFY(testReturn2.completionContext->isValid()); QCOMPARE(testReturn2.names, QStringList() << "return int" << "Value1" << "Value2"); CompletionItemTester testNew(context->childContexts()[ctxt], "Values* b = new "); QVERIFY(testNew.completionContext->isValid()); QCOMPARE(testNew.names, QStringList() << "struct Values =" << "Values"); //TODO: make "new" onlyShow types in this case, and then skip it to create return context CompletionItemTester testNew2(context->childContexts()[ctxt], "Values::Sub* b = new Values::"); QVERIFY(testNew2.completionContext->isValid()); QCOMPARE(testNew2.names, QStringList() << "Value1" << "Value2" << "Sub" ); CompletionItemTester testFakeNew(context->childContexts()[ctxt], "mynew "); QVERIFY(!testFakeNew.completionContext->isValid()); CompletionItemTester testElse(context->childContexts()[ctxt], "if (a) {} else "); QVERIFY(testElse.completionContext->isValid()); QCOMPARE(testElse.names, QStringList() << "a" << "Values" << "v" << "test"); CompletionItemTester testElse2(context->childContexts()[ctxt], "if (a) {} else Values::"); QVERIFY(testElse2.completionContext->isValid()); QCOMPARE(testElse2.names, QStringList() << "Value1" << "Value2" << "Sub" ); CompletionItemTester testThrow(context->childContexts()[ctxt], "throw "); QVERIFY(testThrow.completionContext->isValid()); QCOMPARE(testThrow.names, QStringList() << "a" << "Values" << "v" << "test"); CompletionItemTester testThrow2(context->childContexts()[ctxt], "thow Values::"); QVERIFY(testThrow2.completionContext->isValid()); QCOMPARE(testThrow2.names, QStringList() << "Value1" << "Value2" << "Sub" ); CompletionItemTester testThrowNew(context->childContexts()[ctxt], "throw new"); QVERIFY(testThrowNew.completionContext->isValid()); QCOMPARE(testThrowNew.names, QStringList() << "Values"); //TODO: suboptimal results, see also testNew2 CompletionItemTester testThrowNew2(context->childContexts()[ctxt], "throw new Values::"); QVERIFY(testThrowNew2.completionContext->isValid()); QCOMPARE(testThrowNew2.names, QStringList() << "Value1" << "Value2" << "Sub"); CompletionItemTester testDelete(context->childContexts()[ctxt], "delete "); QVERIFY(testDelete.completionContext->isValid()); QCOMPARE(testDelete.names, QStringList() << "a" << "Values" << "v" << "test"); CompletionItemTester testDelete2(context->childContexts()[ctxt], "delete Values::"); QVERIFY(testDelete2.completionContext->isValid()); QCOMPARE(testDelete2.names, QStringList() << "Value1" << "Value2" << "Sub" ); CompletionItemTester testDelete3(context->childContexts()[ctxt], "delete [ ] "); QVERIFY(testDelete3.completionContext->isValid()); CompletionItemTester testFakeDelete(context->childContexts()[ctxt], "fakedelete "); QVERIFY(!testFakeDelete.completionContext->isValid()); CompletionItemTester testFakeDelete2(context->childContexts()[ctxt], "fakedelete [] "); QVERIFY(!testFakeDelete2.completionContext->isValid()); QCOMPARE(testDelete3.names, QStringList() << "a" << "Values" << "v" << "test"); release(context); } void TestCppCodeCompletion::testArgumentMatching() { { QByteArray test = "struct A{ int m;}; void test(int q) { A a; }"; TopDUContext* context = parse( test, DumpNone /*DumpDUChain | DumpAST */); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(context->childContexts().count(), 3); CompletionItemTester tester(context->childContexts()[2], "test(a."); QVERIFY(tester.completionContext->parentContext()); QCOMPARE(tester.completionContext->parentContext()->knownArgumentTypes().count(), 0); QCOMPARE(tester.completionContext->parentContext()->functionName(), QString("test")); bool abort = false; QCOMPARE(tester.completionContext->parentContext()->completionItems(abort).size(), 1); release(context); } { QByteArray test = "#define A(x) #x\n void test(char* a, char* b, int c) { } "; TopDUContext* context = parse( test, DumpNone /*DumpDUChain | DumpAST */); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(context->childContexts().count(), 2); CompletionItemTester tester(context->childContexts()[1], "test(\"hello\", A(a),"); QVERIFY(tester.completionContext->parentContext()); QCOMPARE(tester.completionContext->parentContext()->knownArgumentTypes().count(), 2); QVERIFY(tester.completionContext->parentContext()->knownArgumentTypes()[0].type.abstractType()); QVERIFY(tester.completionContext->parentContext()->knownArgumentTypes()[1].type.abstractType()); QCOMPARE(tester.completionContext->parentContext()->knownArgumentTypes()[0].type.abstractType()->toString(), QString("const char*")); QCOMPARE(tester.completionContext->parentContext()->knownArgumentTypes()[1].type.abstractType()->toString(), QString("const char*")); release(context); } } void TestCppCodeCompletion::testSubClassVisibility() { { QByteArray test = "typedef struct { int am; } A; void test() { A b; } "; TopDUContext* context = parse( test, DumpAll /*DumpDUChain | DumpAST */); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(context->childContexts().count(), 3); QCOMPARE(context->childContexts()[0]->localDeclarations().count(), 1); QCOMPARE(context->childContexts()[0]->localDeclarations()[0]->kind(), Declaration::Instance); QCOMPARE(CompletionItemTester(context->childContexts()[2], "b.").names, QStringList() << "am"); release(context); } { QByteArray test = "struct A { int am; struct B { int bm; }; }; void test() { A::B b; } "; TopDUContext* context = parse( test, DumpNone /*DumpDUChain | DumpAST */); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(context->childContexts().count(), 3); QCOMPARE(CompletionItemTester(context->childContexts()[2], "b.").names, QStringList() << "bm"); release(context); } { QByteArray test = "struct A { int am; struct B; }; struct A::B {int bm; }; void test() { A::B b; } "; TopDUContext* context = parse( test, DumpNone /*DumpDUChain | DumpAST */); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(context->childContexts().count(), 4); QCOMPARE(CompletionItemTester(context->childContexts()[3], "b.").names, QStringList() << "bm"); release(context); } } void TestCppCodeCompletion::testMacrosInCodeCompletion() { QByteArray test = "#define test foo\n #define testfunction(X) x\n #define test2 fee\n struct A {int mem;}; void fun() { A foo; A* fee;\n }"; TopDUContext* context = parse( test, DumpNone /*DumpDUChain | DumpAST */); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(context->childContexts().size(), 3); QCOMPARE(CompletionItemTester(context->childContexts()[2], "foo.", QString(), CursorInRevision(3, 0)).names, QStringList() << "mem"); QCOMPARE(CompletionItemTester(context->childContexts()[2], "test.", QString(), CursorInRevision(3, 0)).names, QStringList() << "mem"); QCOMPARE(CompletionItemTester(context->childContexts()[2], "fee->", QString(), CursorInRevision(3, 0)).names, QStringList() << "mem"); QCOMPARE(CompletionItemTester(context->childContexts()[2], "test2->", QString(), CursorInRevision(3, 0)).names, QStringList() << "mem"); QCOMPARE(CompletionItemTester(context->childContexts()[2], "testfunction2(", QString(), CursorInRevision(4, 0)).names.toSet(), QSet() << "testfunction2(" << "A" << "foo" << "fee"); QCOMPARE(CompletionItemTester(context->childContexts()[2], "testfunction(", QString(), CursorInRevision(4, 0)).names.toSet(), QSet() << "testfunction(" << "A" << "foo" << "fee"); release(context); } void TestCppCodeCompletion::testConstructorCompletion() { { QByteArray test = "class A {}; class Class : public A { Class(); int m_1; float m_2; char m_3; static int m_4; }; "; //74 TopDUContext* context = parse( test, DumpNone /*DumpDUChain | DumpAST */); DUChainWriteLocker lock(DUChain::lock()); { kDebug() << "TEST 2"; CompletionItemTester tester(context, "class Class { Class(); int m_1; float m_2; char m_3; }; Class::Class(int m1, float m2, char m3) : m_1(1), m_2(m2), "); //At first, only the members should be shown kDebug() << tester.names; QCOMPARE(tester.names, QStringList() << "m_3"); //m_1 should not be shown, because it is already initialized ///@todo Make sure that the item also inserts parens } { CompletionItemTester tester(context, "Class::Class(int m1, float m2, char m3) : "); //At first, only the members should be shown kDebug() << tester.names; QCOMPARE(tester.names, QStringList() << "A" << "m_1" << "m_2" << "m_3"); ///@todo Make sure that the item also inserts parens } { kDebug() << "TEST 3"; CompletionItemTester tester(context, "Class::Class(int m1, float m2, char m3) : m_1("); //At first, only the members should be shown QVERIFY(tester.names.size()); QVERIFY(tester.completionContext->parentContext()); //There must be a type-hinting context QVERIFY(tester.completionContext->parentContext()->parentContext()); //There must be a type-hinting context QVERIFY(!tester.completionContext->isConstructorInitialization()); QVERIFY(!tester.completionContext->parentContext()->isConstructorInitialization()); QVERIFY(tester.completionContext->parentContext()->accessType() == Cpp::CodeCompletionContext::FunctionCallAccess); QVERIFY(tester.completionContext->parentContext()->parentContext()->isConstructorInitialization()); } release(context); } } void TestCppCodeCompletion::testConstructorUsageCompletion_data() { QTest::addColumn("code"); // existing source code QTest::addColumn("args"); // completion arguments QTest::addColumn("not_args"); // forbidden completion arguments // only the default constructor is present QTest::newRow("only default") << "class A { A(); };" << (QStringList() << "()") << QStringList(); // the default constructor and one overload QTest::newRow("Default and int") << "class A { A(); A(int); };" << (QStringList() << "()" << "(int)") << QStringList(); // the default constructor and two overloads QTest::newRow("Default, int and float") << "class A { A(); A(int); A(float); };" << (QStringList() << "()" << "(int)" << "(float)") << QStringList(); // deleted default constructor and two overloads QTest::newRow("no default, int and float") << "class A { A() = delete; A(int); A(float); };" << (QStringList() << "(int)" << "(float)") << (QStringList() << "()"); // the default and copy constructors are present QTest::newRow("default and copy") << "class A { A(); A(const A&); };" << (QStringList() << "()" << "(const A&)") << QStringList(); // the default and copy constructors are deleted QTest::newRow("default and copy") << "class A { A() = delete; A(const A&) = delete; };" << QStringList() << (QStringList() << "()" << "(const A&)"); // the default and copy constructors are deleted, but another one is present QTest::newRow("default and copy") << "class A { A() = delete; A(const A&) = delete; A(int); };" << (QStringList() << "(int)") << (QStringList() << "()" << "(const A&)"); } void TestCppCodeCompletion::testConstructorUsageCompletion() { QFETCH(QString, code); QFETCH(QStringList, args); QFETCH(QStringList, not_args); TopDUContext* context = parse( code.toAscii(), DumpNone /*DumpDUChain | DumpAST */); DUChainWriteLocker lock(DUChain::lock()); CompletionItemTester tester(context, "void k() { A *a = new A("); QStringList foundargs; for (int i = 0; i < tester.items.count(); i++) foundargs << tester.itemData(i, KTextEditor::CodeCompletionModel::Arguments).toString(); qDebug() << foundargs << args << not_args; foreach(const QString &arg, args) QVERIFY(foundargs.contains(arg)); foreach(const QString &arg, not_args) QVERIFY(!foundargs.contains(arg)); release(context); } void TestCppCodeCompletion::testParentConstructor_data() { QTest::addColumn("code"); // existing source code QTest::addColumn("completion"); // completion contents // Parent has no constructor QTest::newRow("NoParentConstructor") << "class A {}; class B : public A { B (int i); };" << "B::B(int i) { }"; // Argument name mismatch - choose it anyway QTest::newRow("ArgNameMismatch") << "class A { A(int j) {} }; class B : public A { B(int i); };" << "B::B(int i): A(i) { }"; // Between two arguments with matching type, prefer one with matching name QTest::newRow("PreferNameMatch") << "class A { A(int i, int j) {} }; class B : public A { B(int j, int i); };" << "B::B(int j, int i): A(i, j) { }"; // Argument type mismatch - do not choose anything for this argument QTest::newRow("ArgTypeMismatch") << "class A { A(char i) {} }; class B : public A { B(int i); };" << "B::B(int i): A() { }"; // Some arguments match, others do not - leave the latter blank QTest::newRow("SomeArgumentsMatch") << "class A { A(int i, char j) {} }; class B : public A { B(int i, int j); };" << "B::B(int i, int j): A(i, ) { }"; // Parent class has multiple constructors - choose the best matching QTest::newRow("MultipleConstructors") << "class A { A(char c) {} A(int i, int j) {} }; class B : public A { B(int i); };" << "B::B(int i): A(i, i) { }"; QTest::newRow("MultipleConstructorsNoFullMatch") << "class A { A(char c, long l) {} A(int i, long l) {} }; class B : public A { B(int i); };" << "B::B(int i): A(i, ) { }"; // Omit call to default constructor if it is the best match QTest::newRow("BestMatchDefault") << "class A { A() {} A(char c) {} }; class B : public A { B(int i); };" << "B::B(int i) { }"; // Multiple parents, each has its own matches QTest::newRow("MultipleParents") << "class A { A(int i, char c) {} }; class B { B(char d, long l) {} }; class C : public A, public B { C(int i, long l, char c); };" << "C::C(int i, long int l, char c): A(i, c), B(c, l) { }"; // Last check QTest::newRow("ComplexCase") << "class A { A(int i, double d, char c) {} A(int i, double d, long l) {} };" "class B { B(double d, int j) {} B(char c) {} };" "class C { };" "class D : public A, public B, public C { D(short a1, double a2, long a3, int a4); };" << "D::D(short int a1, double a2, long int a3, int a4): A(a4, a2, a3), B(a2, a4) { }"; } void TestCppCodeCompletion::testParentConstructor() { QFETCH(QString, code); QFETCH(QString, completion); TopDUContext* context = parse(code.toAscii(), DumpNone); DUChainWriteLocker lock(DUChain::lock()); CompletionItemTester tester(context, "void"); // Force a function context Cpp::ImplementationHelperItem* constructorItem = dynamic_cast(tester.items[0].data()); QCOMPARE(constructorItem->insertionText().simplified(), completion.simplified()); release(context); } void TestCppCodeCompletion::testOverride_data() { QTest::addColumn("parentCode"); // code in parent class QTest::addColumn("prefix"); // completion prefix QTest::addColumn("completion"); // expected completion contents QTest::newRow("empty-prefix") << "virtual void foo();" << "" << "virtual void foo();"; QTest::newRow("public") << "virtual void foo();" << "public: " << "virtual void foo();"; QTest::newRow("protected") << "virtual void foo();" << "protected: " << "virtual void foo();"; QTest::newRow("private") << "virtual void foo();" << "private: " << "virtual void foo();"; } void TestCppCodeCompletion::testOverride() { QFETCH(QString, parentCode); QFETCH(QString, prefix); QFETCH(QString, completion); QString tmpl = QString(" class A {public: %1 };\n" " class B : public A { };").arg(parentCode); TopDUContext* context = parse(tmpl.toLocal8Bit(), DumpNone); DUChainWriteLocker lock; DUContext* BCtx = context->childContexts().last(); QCOMPARE(BCtx->localScopeIdentifier().toString(), QLatin1String("B")); CompletionItemTester tester(BCtx, prefix); bool found = false; foreach(CompletionItemTester::Item item, tester.items) { Cpp::ImplementationHelperItem* override = dynamic_cast(item.data()); if (!override) { continue; } QCOMPARE(override->m_type, Cpp::ImplementationHelperItem::Override); QCOMPARE(override->insertionText().simplified(), completion.simplified()); found = true; break; } QVERIFY(found); release(context); } void TestCppCodeCompletion::testOverrideDeleted() { const QByteArray tmpl("class A {public: virtual void foo() = delete; };\n" "class B : public A { };"); TopDUContext* context = parse(tmpl, DumpNone); DUChainWriteLocker lock; DUContext* BCtx = context->childContexts().last(); QCOMPARE(BCtx->localScopeIdentifier().toString(), QLatin1String("B")); CompletionItemTester tester(BCtx, ""); bool found = false; foreach(CompletionItemTester::Item item, tester.items) { Cpp::ImplementationHelperItem* override = dynamic_cast(item.data()); if (!override) { continue; } found = true; break; } QVERIFY(!found); release(context); } void TestCppCodeCompletion::testSignalSlotCompletion() { // By processing qobjectdefs.h, we make sure that the qt-specific macros are defined in the duchain through overriding (see setuphelpers.cpp) addInclude("/qobjectdefs.h", "#define signals\n#define slots\n#define Q_SIGNALS\n#define Q_SLOTS\n#define Q_PRIVATE_SLOT\n#define SIGNAL\n#define SLOT\n int n;\n"); addInclude("QObject.h", "#include \"/qobjectdefs.h\"\n class QObject { void connect(QObject* from, const char* signal, QObject* to, const char* slot); void connect(QObject* from, const char* signal, const char* slot); };"); QByteArray test("#include \"QObject.h\"\n class TE; class A : public QObject { public slots: void slot1(); void slot2(TE*); signals: void signal1(TE*, char);void signal2(); public: void test() { } private: Q_PRIVATE_SLOT(d,void slot3(TE*)) };"); TopDUContext* context = parse( test, DumpAll ); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(context->childContexts().count(), 1); QCOMPARE(context->childContexts()[0]->childContexts().count(), 8); CompletionItemTester(context->childContexts()[0]->childContexts()[5], "connect( this, "); QCOMPARE(CompletionItemTester(context->childContexts()[0]->childContexts()[5], "connect( this, ").names.toSet(), (QStringList() << "connect" << "signal1" << "signal2").toSet()); QCOMPARE(CompletionItemTester(context->childContexts()[0]->childContexts()[5], "connect( this, SIGNAL(").names.toSet(), (QStringList() << "connect" << "signal1" << "signal2").toSet()); kDebug() << "ITEMS:" << CompletionItemTester(context->childContexts()[0]->childContexts()[5], "connect( this, SIGNAL(signal2()), this, SLOT(").names; QCOMPARE(CompletionItemTester(context->childContexts()[0]->childContexts()[5], "connect( this, SIGNAL(signal2()), this, ").names.toSet(), (QStringList() << "connect" << "signal1" << "signal2" << "slot1" << "slot2" << "slot3" << "Connect to A::signal2 ()").toSet()); QCOMPARE(CompletionItemTester(context->childContexts()[0]->childContexts()[5], "connect( this, SIGNAL(signal2()), this, SIGNAL(").names.toSet(), (QStringList() << "connect" << "signal1" << "signal2" << "Connect to A::signal2 ()").toSet()); QCOMPARE(CompletionItemTester(context->childContexts()[0]->childContexts()[5], "connect( this, SIGNAL(signal2()), this, SLOT(").names.toSet(), (QStringList() << "connect" << "slot1" << "slot2" << "slot3" << "Connect to A::signal2 ()" << "signal2").toSet()); QVERIFY(((QStringList() << "connect" << "signal1" << "signal2" << "slot1" << "slot2" << "slot3" << "Connect to A::signal2 ()").toSet() - CompletionItemTester(context->childContexts()[0]->childContexts()[5], "connect( this, SIGNAL(signal2()), ").names.toSet()).isEmpty()); QVERIFY(((QStringList() << "connect" << "signal1" << "signal2" << "Connect to A::signal2 ()").toSet() - CompletionItemTester(context->childContexts()[0]->childContexts()[5], "connect( this, SIGNAL(signal2()), SIGNAL(").names.toSet()).isEmpty()); QVERIFY(((QStringList() << "connect" << "slot1" << "slot2" << "slot3"<< "Connect to A::signal2 ()").toSet() - CompletionItemTester(context->childContexts()[0]->childContexts()[5], "connect( this, SIGNAL(signal2()), SLOT(").names.toSet()).isEmpty()); Declaration* decl = context->childContexts().last()->findDeclarations(Identifier("slot3")).first(); QVERIFY(decl); QVERIFY(dynamic_cast(decl)); QVERIFY(dynamic_cast(decl)->accessPolicy() == Declaration::Private); QVERIFY(dynamic_cast(decl)->isSlot()); release(context); } void TestCppCodeCompletion::testSignalSlotExecution() { // By processing qobjectdefs.h, we make sure that the qt-specific macros are defined in the duchain through overriding (see setuphelpers.cpp) addInclude("/qobjectdefs.h", "#define signals\n#define slots\n#define Q_SIGNALS\n#define Q_SLOTS\n#define Q_PRIVATE_SLOT\n#define SIGNAL\n#define SLOT\n int n;\n"); addInclude("QObject.h", "#include \"/qobjectdefs.h\"\n class QObject { void connect(QObject* from, const char* signal, QObject* to, const char* slot); void connect(QObject* from, const char* signal, const char* slot); };"); QByteArray test("#include \"QObject.h\"" "\nclass A : public QObject { public slots: void slot1(); void slot2(void*);" "signals: void signal1(void*, char); void signal2(void*); " "\nvoid test() { connect( this, SIGNAL(signal2(void*)), SLOT() ); } };"); TopDUContext* top = parse( test, DumpNone ); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(top->childContexts().count(), 1); QCOMPARE(top->childContexts()[0]->childContexts().count(), 6); QVERIFY(top->problems().isEmpty()); DUContext* context = top->childContexts()[0]->childContexts()[5]; KTextEditor::Editor* editor = KTextEditor::EditorChooser::editor(); QVERIFY(editor); KTextEditor::Document* doc = editor->createDocument(this); QVERIFY(doc); doc->setText(test); KTextEditor::View* v = doc->createView(0); // Test 1: SIGNAL(signal2(void*)) parens balancing { doc->startEditing(); KTextEditor::Cursor c( 2, 36 ); v->setCursorPosition( c ); CompletionItemTester complCtx( context, "connect( this, SIGNAL(", "", CursorInRevision( c.line(), c.column() ) ); KSharedPtr item; for( int i = 0; i < complCtx.items.length(); ++i ) { if( complCtx.itemData( i ).toString() == "signal1" ) { item = complCtx.items.at( i ); } } QVERIFY( !item.isNull() ); item->execute( doc, Range( c, 0 ) ); QCOMPARE( doc->line( 2 ), QString("void test() { connect( this, SIGNAL(signal1(void*,char)), SLOT() ); } };") ); doc->endEditing(); } // Test 2: SLOT() parens balancing { doc->startEditing(); KTextEditor::Cursor c( 2, 58 ); v->setCursorPosition( c ); CompletionItemTester complCtx( context, "connect( this, SIGNAL(signal1(void*,char)), SLOT(", "", CursorInRevision( c.line(), c.column() ) ); KSharedPtr item; for( int i = 0; i < complCtx.items.length(); ++i ) { if( complCtx.itemData( i ).toString() == "slot2" ) { item = complCtx.items.at( i ); } } QVERIFY( !item.isNull() ); item->execute( doc, Range( c, 0 ) ); QCOMPARE( doc->line( 2 ), QString("void test() { connect( this, SIGNAL(signal1(void*,char)), SLOT(slot2(void*)) ); } };") ); doc->endEditing(); } // Test 3: Slot implementation helper: SLOT() parens balancing { doc->startEditing(); KTextEditor::Cursor c( 2, 58 ); v->setCursorPosition( c ); lock.unlock(); CompletionItemTester complCtx( context, "connect( this, SIGNAL(signal1(void*,char)), SLOT(", "slot3", CursorInRevision( c.line(), c.column() ) ); qDebug() << "TEST3 names: " << complCtx.names; KSharedPtr item; for( int i = 0; i < complCtx.items.length(); ++i ) { if( complCtx.itemData( i ).toString() == "slot3" ) { item = complCtx.items.at( i ); } } QVERIFY( !item.isNull() ); item->execute( doc, Range( c, 0 ) ); lock.lock(); QEXPECT_FAIL("", "Slot is not replaced because the engine fails to create the declaration.", Continue); QCOMPARE( doc->line( 2 ), QString("void test() { connect( this, SIGNAL(signal1(void*,char)), SLOT(slot3(void*)) ); } };") ); doc->endEditing(); } release(top); delete v; delete doc; } void TestCppCodeCompletion::testAssistant() { { QByteArray test = "#define A hallo(3) = 1\n void test() { A; bla = 5; }"; TopDUContext* context = parse( test, DumpAll ); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(context->problems().count(), 1); release(context); } { QByteArray test = "int n; class C { C() : member(n) {} }; }"; TopDUContext* context = parse( test, DumpAll ); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(context->problems().count(), 1); { KSharedPtr mdp( dynamic_cast(context->problems()[0].data()) ); QVERIFY(mdp); kDebug() << "problem:" << mdp->description(); QCOMPARE(mdp->type->containerContext.data(), context->childContexts()[0]); QCOMPARE(mdp->type->identifier().toString(), QString("member")); QVERIFY(mdp->type->assigned.type.isValid()); QCOMPARE(TypeUtils::removeConstants(mdp->type->assigned.type.abstractType(), context)->toString(), QString("int")); } release(context); } { QByteArray test = "class C {}; void test() {C c; c.value = 5; int i = c.value2; i = c.value3(); }"; TopDUContext* context = parse( test, DumpAll ); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(context->problems().count(), 3); { KSharedPtr mdp( dynamic_cast(context->problems()[0].data()) ); QVERIFY(mdp); kDebug() << "problem:" << mdp->description(); QCOMPARE(mdp->type->containerContext.data(), context->childContexts()[0]); QCOMPARE(mdp->type->identifier().toString(), QString("value")); QVERIFY(mdp->type->assigned.type.isValid()); QCOMPARE(TypeUtils::removeConstants(mdp->type->assigned.type.abstractType(), context)->toString(), QString("int")); QCOMPARE(context->childContexts().count(), 3); } { ///@todo Make this work as well /* KSharedPtr mdp( dynamic_cast(context->problems()[1].data()) ); QVERIFY(mdp); kDebug() << "problem:" << mdp->description(); QCOMPARE(mdp->type->containerContext.data(), context->childContexts()[0]); QCOMPARE(mdp->type->identifier().toString(), QString("value2")); QVERIFY(!mdp->type->assigned.type.isValid()); QVERIFY(mdp->type->convertedTo.type.isValid()); QCOMPARE(TypeUtils::removeConstants(mdp->type->convertedTo.type.abstractType())->toString(), QString("int")); QCOMPARE(context->childContexts().count(), 3);*/ } { KSharedPtr mdp( dynamic_cast(context->problems()[2].data()) ); QVERIFY(mdp); kDebug() << "problem:" << mdp->description(); QCOMPARE(mdp->type->containerContext.data(), context->childContexts()[0]); QCOMPARE(mdp->type->identifier().toString(), QString("value3")); QVERIFY(!mdp->type->assigned.type.isValid()); QVERIFY(mdp->type->convertedTo.type.isValid()); QCOMPARE(TypeUtils::removeConstants(mdp->type->convertedTo.type.abstractType(), context)->toString(), QString("int")); QCOMPARE(context->childContexts().count(), 3); } release(context); } { QByteArray test = "class C {}; void test() {C c; int valueName; c.functionName(valueName); }"; TopDUContext* context = parse( test, DumpAll ); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(context->problems().count(), 1); { KSharedPtr mdp( dynamic_cast(context->problems()[0].data()) ); QVERIFY(mdp); kDebug() << "problem:" << mdp->description(); QCOMPARE(mdp->type->containerContext.data(), context->childContexts()[0]); QCOMPARE(mdp->type->identifier().toString(), QString("functionName")); QVERIFY(!mdp->type->assigned.type.isValid()); QCOMPARE(mdp->type->arguments.count(), 1); ///@todo Use the value-names of values given to the function // QCOMPARE(mdp->type->arguments[0].second, QString("valueName")); QCOMPARE(context->childContexts().count(), 3); } release(context); } } void TestCppCodeCompletion::testImportTypedef() { { QByteArray test = "class Class { }; typedef Class Klass; class C : public Class { };"; TopDUContext* context = parse( test, DumpNone /*DumpDUChain | DumpAST */); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(context->childContexts().count(), 2); QCOMPARE(context->childContexts()[1]->importedParentContexts().count(), 1); QCOMPARE(context->childContexts()[1]->importedParentContexts()[0].context(context->topContext()), context->childContexts()[0]); } { QByteArray test = "class A { public: int m; }; template class C : public Base { };"; TopDUContext* context = parse( test, DumpNone /*DumpDUChain | DumpAST */); DUChainWriteLocker lock(DUChain::lock()); Declaration* CDecl = findDeclaration(context, QualifiedIdentifier("C")); QVERIFY(CDecl); QVERIFY(CDecl->internalContext()); QVector imports = CDecl->internalContext()->importedParentContexts(); QCOMPARE(imports.size(), 2); QVERIFY(imports[0].context(context)); QVERIFY(imports[1].context(context)); QCOMPARE(imports[0].context(context)->type(), DUContext::Template); QCOMPARE(imports[1].context(context)->type(), DUContext::Class); QCOMPARE(imports[1].context(context)->scopeIdentifier(true), QualifiedIdentifier("A")); } { QByteArray test = "class A { public: int m; }; template class C : public Base { }; typedef C TheBase; class B : public TheBase { }; class E : public B{ };"; TopDUContext* context = parse( test, DumpNone /*DumpDUChain | DumpAST */); DUChainWriteLocker lock(DUChain::lock()); Declaration* typeDef = findDeclaration(context, QualifiedIdentifier("TheBase")); QVERIFY(typeDef); QVERIFY(typeDef->isTypeAlias()); QVERIFY(typeDef->type()); Declaration* BDecl = findDeclaration(context, QualifiedIdentifier("B")); QVERIFY(BDecl); QCOMPARE(BDecl->internalContext()->importedParentContexts().size(), 1); QVERIFY(BDecl->internalContext()->importedParentContexts()[0].context(context)); } } void TestCppCodeCompletion::testPrivateVariableCompletion() { TEST_FILE_PARSE_ONLY QByteArray test = "class C {void test() {}; int i; };"; DUContext* context = parse( test, DumpAll /*DumpDUChain | DumpAST */); DUChainWriteLocker lock(DUChain::lock()); QVERIFY(context->type() != DUContext::Helper); QCOMPARE(context->childContexts().count(), 1); DUContext* CContext = context->childContexts()[0]; QCOMPARE(CContext->type(), DUContext::Class); QCOMPARE(CContext->childContexts().count(), 2); QCOMPARE(CContext->localDeclarations().count(), 2); DUContext* testContext = CContext->childContexts()[1]; QCOMPARE(testContext->type(), DUContext::Other ); QVERIFY(testContext->owner()); QCOMPARE(testContext->localScopeIdentifier(), QualifiedIdentifier("test")); lock.unlock(); CompletionItemTester tester(testContext); kDebug() << "names:" << tester.names; QCOMPARE(tester.names.toSet(), (QStringList() << "C" << "i" << "test" << "this").toSet()); lock.lock(); release(context); } void TestCppCodeCompletion::testCompletionPrefix() { TEST_FILE_PARSE_ONLY { QByteArray method("struct Test {int m;}; Test t;Test* t2;void test() {}"); TopDUContext* top = parse(method, DumpNone); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(top->childContexts().size(), 3); //Make sure the completion behind "for <" does not only show types QVERIFY(CompletionItemTester(top->childContexts()[2], ";for(int a = 0; a < t2->").names.contains("m")); QVERIFY(CompletionItemTester(top->childContexts()[2], ";for(int a = 0; a < ").names.contains("t2")); //Make sure that only types are shown as template parameters QVERIFY(!CompletionItemTester(top->childContexts()[2], "Test<").names.contains("t2")); QCOMPARE(CompletionItemTester(top->childContexts()[2], "if((t).").names, QStringList() << "m"); QCOMPARE(CompletionItemTester(top->childContexts()[2], "Test t(&t2->").names, QStringList() << "Test(" << "m"); QCOMPARE(CompletionItemTester(top->childContexts()[2], "Test(\"(\").").names, QStringList() << "m"); QCOMPARE(CompletionItemTester(top->childContexts()[2], "Test(\" \\\" quotedText( \\\" \").").names, QStringList() << "m"); QVERIFY(CompletionItemTester(top->childContexts()[2], ";int i = ").completionContext->parentContext()); QVERIFY(CompletionItemTester(top->childContexts()[2], ";int i ( ").completionContext->parentContext()); bool abort = false; QVERIFY(CompletionItemTester(top->childContexts()[2], ";int i = ").completionContext->parentContext()->completionItems(abort).size()); QVERIFY(CompletionItemTester(top->childContexts()[2], ";int i ( ").completionContext->parentContext()->completionItems(abort).size()); release(top); } } void TestCppCodeCompletion::testStringProblem() { TEST_FILE_PARSE_ONLY { QByteArray method("void test() {int i;};"); TopDUContext* top = parse(method, DumpNone); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(top->childContexts().count(), 2); CompletionItemTester tester(top->childContexts()[1],QString("bla url('\\\"');")); QCOMPARE(tester.names.toSet(), (QStringList() << "i" << "test").toSet());; release(top); } { QByteArray method("void test() {int i;};"); TopDUContext* top = parse(method, DumpNone); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(top->childContexts().count(), 2); CompletionItemTester tester(top->childContexts()[1],QString("bla url(\"http://wwww.bla.de/\");")); QCOMPARE(tester.names.toSet(), (QStringList() << "i" << "test").toSet());; release(top); } } void TestCppCodeCompletion::testInheritanceVisibility() { TEST_FILE_PARSE_ONLY QByteArray method("class A { public: class AMyClass {}; }; class B : protected A { public: class BMyClass {}; }; class C : private B{ public: class CMyClass {}; }; class D : public C { class DMyClass{}; }; "); TopDUContext* top = parse(method, DumpNone); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(top->childContexts().count(), 4); QCOMPARE(top->childContexts()[1]->type(), DUContext::Class); QVERIFY(top->childContexts()[1]->owner()); QVERIFY(Cpp::localClassFromCodeContext(top->childContexts()[1])); //From within B, MyClass is visible, because of the protected inheritance QCOMPARE(top->childContexts()[1]->localDeclarations().size(), 1); QVERIFY(!Cpp::isAccessible(top, dynamic_cast(top->childContexts()[0]->localDeclarations()[0]), top, top->childContexts()[1])); QCOMPARE(CompletionItemTester(top->childContexts()[1], "A::").names, QStringList() << "AMyClass"); QCOMPARE(CompletionItemTester(top->childContexts()[1]).names.toSet(), QSet() << "BMyClass" << "AMyClass" << "A" << "B" ); QCOMPARE(CompletionItemTester(top, "A::").names, QStringList() << "AMyClass"); kDebug() << "list:" << CompletionItemTester(top, "B::").names << CompletionItemTester(top, "A::").names.size(); QCOMPARE(CompletionItemTester(top, "B::").names, QStringList() << "BMyClass"); QCOMPARE(CompletionItemTester(top->childContexts()[2]).names.toSet(), QSet() << "CMyClass" << "BMyClass" << "AMyClass" << "C" << "B" << "A"); QCOMPARE(CompletionItemTester(top, "C::").names.toSet(), QSet() << "CMyClass"); QCOMPARE(CompletionItemTester(top->childContexts()[3]).names.toSet(), QSet() << "DMyClass" << "CMyClass" << "D" << "C" << "B" << "A"); QCOMPARE(CompletionItemTester(top, "D::").names.toSet(), QSet() << "CMyClass" ); //DMyClass is private } void TestCppCodeCompletion::testConstVisibility() { TEST_FILE_PARSE_ONLY QByteArray method("struct test { void f(); void e() const; }; int main() { const test a; } void test::e() const { }"); TopDUContext* top = parse(method, DumpNone); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(top->childContexts().count(), 5); kDebug() << "list:" << CompletionItemTester(top->childContexts()[2], "a.").names << CompletionItemTester(top->childContexts()[2], "a.").names.size(); QCOMPARE(CompletionItemTester(top->childContexts()[2], "a.").names.toSet(), QSet() << "e"); kDebug() << "list:" << CompletionItemTester(top->childContexts()[4], "").names << CompletionItemTester(top->childContexts()[4], "").names.size(); QCOMPARE(CompletionItemTester(top->childContexts()[4], "").names.toSet(), QSet() << "e" << "test" << "main" << "this"); } void TestCppCodeCompletion::testConstOverloadVisibility() { // see also: https://bugs.kde.org/show_bug.cgi?id=267877 TEST_FILE_PARSE_ONLY QByteArray method("struct test { test foo(); const test& foo() const; void bar() const; void asdf(); };\n" "int main() { const test testConst; test testNonConst; }"); TopDUContext* top = parse(method, DumpNone); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(top->childContexts().count(), 3); QCOMPARE(top->childContexts().first()->localDeclarations().size(), 4); CompletionItemTester tester = CompletionItemTester(top->childContexts()[2], "testConst."); QCOMPARE(tester.names.size(), 2); // non-const foo() should be hidden by overloaded const version QVERIFY(!tester.containsDeclaration( top->childContexts().first()->localDeclarations().at(0) )); // const& foo() const QVERIFY(tester.containsDeclaration( top->childContexts().first()->localDeclarations().at(1) )); // bar is always shown (not overloaded and const) QVERIFY(tester.containsDeclaration( top->childContexts().first()->localDeclarations().at(2) )); // foo is hidden since it's non-const QVERIFY(!tester.containsDeclaration( top->childContexts().first()->localDeclarations().at(3) )); tester = CompletionItemTester(top->childContexts()[2], "testNonConst."); QCOMPARE(tester.names.size(), 3); // non-const foo() QVERIFY(tester.containsDeclaration( top->childContexts().first()->localDeclarations().at(0) )); // const& foo() const hidden by overloaded non-const foo QVERIFY(!tester.containsDeclaration( top->childContexts().first()->localDeclarations().at(1) )); // bar is always shown (not overloaded and const) QVERIFY(tester.containsDeclaration( top->childContexts().first()->localDeclarations().at(2) )); // foo is shown here as well QVERIFY(tester.containsDeclaration( top->childContexts().first()->localDeclarations().at(3) )); } void TestCppCodeCompletion::testFriendVisibility() { TEST_FILE_PARSE_ONLY QByteArray method("class A { class PrivateClass {}; friend class B; }; class B{};"); TopDUContext* top = parse(method, DumpNone); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(top->childContexts().count(), 2); //No type within A, so there should be no items QCOMPARE(CompletionItemTester(top->childContexts()[1], "A::").names, QStringList() << "PrivateClass"); } void TestCppCodeCompletion::testLocalUsingNamespace() { TEST_FILE_PARSE_ONLY { QByteArray method("namespace Fuu { int test0(); }; namespace Foo { using namespace Fuu; int test() {} } void Bar() { using namespace Foo; int b = test(); }"); TopDUContext* top = parse(method, DumpAll); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(top->childContexts().count(), 4); QCOMPARE(top->childContexts()[1]->localDeclarations().size(), 2); QCOMPARE(top->childContexts()[3]->localDeclarations().size(), 2); QVERIFY(top->childContexts()[1]->localDeclarations()[1]->uses().size()); QVERIFY(top->childContexts()[3]->findLocalDeclarations(KDevelop::globalImportIdentifier(), KDevelop::CursorInRevision::invalid(), 0, KDevelop::AbstractType::Ptr(), KDevelop::DUContext::NoFiltering).size()); // QVERIFY(top->childContexts()[2]->findDeclarations(KDevelop::globalImportIdentifier).size()); QVERIFY(CompletionItemTester(top->childContexts()[3]).names.contains("test")); QVERIFY(CompletionItemTester(top->childContexts()[3]).names.contains("test0")); // QVERIFY(CompletionItemTester(top->childContexts()[3], "Foo::").names.contains("test0")); release(top); } } void TestCppCodeCompletion::testTemplateFunction() { QByteArray method("template void test(A i); void t() { }"); TopDUContext* top = parse(method, DumpNone); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(top->childContexts().count(), 4); { CompletionItemTester tester1(top->childContexts()[3], "test<"); QVERIFY(tester1.completionContext->parentContext()); CompletionItemTester tester2 = tester1.parent(); QCOMPARE(tester2.items.size(), 1); Cpp::NormalDeclarationCompletionItem* item = dynamic_cast(tester2.items[0].data()); QVERIFY(item); QVERIFY(item->completingTemplateParameters()); } { kDebug() << "second test"; CompletionItemTester tester1(top->childContexts()[3], "test("); QVERIFY(tester1.completionContext->parentContext()); CompletionItemTester tester2 = tester1.parent(); QCOMPARE(tester2.items.size(), 1); Cpp::NormalDeclarationCompletionItem* item = dynamic_cast(tester2.items[0].data()); QVERIFY(item); QVERIFY(!item->completingTemplateParameters()); QVERIFY(tester2.completionContext->matchTypes().size() == 1); QVERIFY(tester2.completionContext->matchTypes()[0].type()); } release(top); } void TestCppCodeCompletion::testTemplateArguments() { QByteArray method("template struct I; typedef I II; template struct Test { T t; }; "); TopDUContext* top = parse(method, DumpNone); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(top->childContexts().count(), 3); QVERIFY(findDeclaration(top, QualifiedIdentifier("II"))); Declaration* decl = findDeclaration(top, QualifiedIdentifier("Test::t")); QVERIFY(decl); QVERIFY(decl->abstractType()); QVERIFY(decl->type()); //Since II is not template-dependent, the type should have stayed a TypeAliasType QCOMPARE(Identifier(decl->abstractType()->toString()), Identifier("II")); release(top); } void TestCppCodeCompletion::testCompletionBehindTypedeffedConstructor() { QByteArray method("template struct A { A(T); int m; }; typedef A TInt; void test() {}"); TopDUContext* top = parse(method, DumpAll); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(top->childContexts().count(), 4); QCOMPARE(top->childContexts()[1]->localDeclarations().size(), 2); //Member completion // NOTE: constructor A is not listed, as you can't call the constructor in this way QCOMPARE(CompletionItemTester(top->childContexts()[3], "A().").names.toSet(), (QStringList() << QString("m")).toSet()); QCOMPARE(CompletionItemTester(top->childContexts()[3], "TInt().").names.toSet(), (QStringList() << QString("m")).toSet()); //Argument-hints kDebug() << CompletionItemTester(top->childContexts()[3], "TInt(").parent().names; QVERIFY(CompletionItemTester(top->childContexts()[3], "TInt(").parent().names.contains("A")); QVERIFY(CompletionItemTester(top->childContexts()[3], "TInt ti(").parent().names.contains("A")); release(top); } void TestCppCodeCompletion::testCompletionInExternalClassDefinition() { { QByteArray method("class A { class Q; class B; }; class A::B {class C;}; class A::B::C{void test(); }; void A::B::test() {}; void A::B::C::test() {}"); TopDUContext* top = parse(method, DumpAll); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(top->childContexts().count(), 7); QCOMPARE(top->childContexts()[1]->childContexts().count(), 1); QVERIFY(CompletionItemTester(top->childContexts()[1]->childContexts()[0]).names.contains("Q")); QVERIFY(CompletionItemTester(top->childContexts()[2]->childContexts()[0]).names.contains("Q")); QVERIFY(CompletionItemTester(top->childContexts()[3]).names.contains("Q")); QVERIFY(CompletionItemTester(top->childContexts()[5]).names.contains("Q")); release(top); } } void TestCppCodeCompletion::testTemplateMemberAccess() { { QByteArray method("template struct I; template class Test { public: typedef I It; }; template struct I { }; Test::It test;"); TopDUContext* top = parse(method, DumpNone); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(top->localDeclarations().count(), 4); AbstractType::Ptr type = TypeUtils::unAliasedType(top->localDeclarations()[3]->abstractType()); QVERIFY(type); IdentifiedType* identified = dynamic_cast(type.unsafeData()); QVERIFY(identified); QVERIFY(!identified->declarationId().isDirect()); QString specializationString = IndexedInstantiationInformation(identified->declarationId().specialization()).information().toString(); QCOMPARE(specializationString, QString("")); QCOMPARE(top->localDeclarations()[3]->abstractType()->toString().remove(' '), QString("Test::It")); QCOMPARE(TypeUtils::unAliasedType(top->localDeclarations()[3]->abstractType())->toString().remove(' '), QString("I")); lock.unlock(); parse(method, DumpNone, 0, KUrl(), top); lock.lock(); QCOMPARE(top->localDeclarations().count(), 4); QVERIFY(top->localDeclarations()[3]->abstractType()); QCOMPARE(TypeUtils::unAliasedType(top->localDeclarations()[3]->abstractType())->toString().remove(' '), QString("I")); release(top); } { QByteArray method("template class Test { public: T member; typedef T Data; enum { Value = 3 }; }; typedef Test IntTest; void test() { IntTest tv; int i = Test::Value; }"); TopDUContext* top = parse(method, DumpNone); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(CompletionItemTester(top->childContexts()[3], "Test::").names.toSet(), QSet() << "Data" << "Value" << "member"); lock.unlock(); parse(method, DumpNone, 0, KUrl(), top); lock.lock(); QCOMPARE(top->childContexts().count(), 4); QCOMPARE(top->childContexts()[3]->type(), DUContext::Other); QCOMPARE(CompletionItemTester(top->childContexts()[3], "IntTest::").names.toSet(), QSet() << "Data" << "Value" << "member"); QCOMPARE(CompletionItemTester(top->childContexts()[3], "Test::").names.toSet(), QSet() << "Data" << "Value" << "member"); QCOMPARE(CompletionItemTester(top->childContexts()[3], "tv.").names.toSet(), QSet() << "member"); release(top); } } void TestCppCodeCompletion::testNamespaceCompletion() { QByteArray method("namespace A { class m; namespace Q {}; }; namespace A { class n; int q; }"); TopDUContext* top = parse(method, DumpNone); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(top->localDeclarations().count(), 2); QCOMPARE(top->childContexts().count(), 2); QCOMPARE(top->localDeclarations()[0]->identifier(), Identifier("A")); QCOMPARE(top->localDeclarations()[1]->identifier(), Identifier("A")); QCOMPARE(top->localDeclarations()[0]->kind(), Declaration::Namespace); QCOMPARE(top->localDeclarations()[1]->kind(), Declaration::Namespace); QVERIFY(!top->localDeclarations()[0]->abstractType()); QVERIFY(!top->localDeclarations()[1]->abstractType()); QCOMPARE(top->localDeclarations()[0]->internalContext(), top->childContexts()[0]); QCOMPARE(top->localDeclarations()[1]->internalContext(), top->childContexts()[1]); QCOMPARE(CompletionItemTester(top).names, QStringList() << "A"); QCOMPARE(CompletionItemTester(top->childContexts()[1], "A::").names.toSet(), QSet() << "m" << "n" << "Q"); QCOMPARE(CompletionItemTester(top).itemData("A", KTextEditor::CodeCompletionModel::Prefix).toString(), QString("namespace")); release(top); } void TestCppCodeCompletion::testNamespaceAliasCompletion() { QByteArray method("namespace A { class C_A1; class C_A2; namespace Q { class C_Q1; class C_Q2; }; }; " "namespace B = A; " // direct import of a namespace "namespace C = B; " // indirect import through another alias ); TopDUContext* top = parse(method, DumpNone); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(top->localDeclarations().count(), 3); QCOMPARE(top->childContexts().count(), 1); QCOMPARE(top->localDeclarations()[0]->identifier(), Identifier("A")); QCOMPARE(top->localDeclarations()[1]->identifier(), Identifier("B")); QCOMPARE(top->localDeclarations()[2]->identifier(), Identifier("C")); QCOMPARE(top->localDeclarations()[0]->kind(), Declaration::Namespace); QCOMPARE(top->localDeclarations()[1]->kind(), Declaration::NamespaceAlias); QCOMPARE(top->localDeclarations()[2]->kind(), Declaration::NamespaceAlias); QVERIFY(!top->localDeclarations()[0]->abstractType()); QVERIFY(!top->localDeclarations()[1]->abstractType()); QVERIFY(!top->localDeclarations()[2]->abstractType()); QCOMPARE(top->localDeclarations()[0]->internalContext(), top->childContexts()[0]); QCOMPARE(CompletionItemTester(top).names.toSet(), QSet() << "A" << "B" << "C"); QCOMPARE(CompletionItemTester(top->childContexts()[0], "A::").names.toSet(), QSet() << "C_A1" << "C_A2" << "Q"); QCOMPARE(CompletionItemTester(top->childContexts()[0], "B::").names.toSet(), QSet() << "C_A1" << "C_A2" << "Q"); QCOMPARE(CompletionItemTester(top->childContexts()[0], "C::").names.toSet(), QSet() << "C_A1" << "C_A2" << "Q"); QCOMPARE(CompletionItemTester(top).itemData("A", KTextEditor::CodeCompletionModel::Prefix).toString(), QString("namespace")); release(top); } void TestCppCodeCompletion::testNamespaceAliasCycleCompletion() { QByteArray method("namespace A { class C_A1; class C_A2; namespace Q { class C_Q1; class C_Q2; }; }; " "namespace B = A; namespace A = B; "); TopDUContext* top = parse(method, DumpNone); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(top->localDeclarations().count(), 3); QCOMPARE(top->childContexts().count(), 1); QCOMPARE(top->localDeclarations()[0]->identifier(), Identifier("A")); QCOMPARE(top->localDeclarations()[1]->identifier(), Identifier("B")); QCOMPARE(top->localDeclarations()[2]->identifier(), Identifier("A")); QCOMPARE(top->localDeclarations()[0]->kind(), Declaration::Namespace); QCOMPARE(top->localDeclarations()[1]->kind(), Declaration::NamespaceAlias); QCOMPARE(top->localDeclarations()[2]->kind(), Declaration::NamespaceAlias); QVERIFY(!top->localDeclarations()[0]->abstractType()); QVERIFY(!top->localDeclarations()[1]->abstractType()); QVERIFY(!top->localDeclarations()[2]->abstractType()); QCOMPARE(top->localDeclarations()[0]->internalContext(), top->childContexts()[0]); QCOMPARE(CompletionItemTester(top).names.toSet(), QSet() << "A" << "B"); QCOMPARE(CompletionItemTester(top->childContexts()[0], "A::").names.toSet(), QSet() << "C_A1" << "C_A2" << "Q"); QCOMPARE(CompletionItemTester(top->childContexts()[0], "B::").names.toSet(), QSet() << "C_A1" << "C_A2" << "Q"); QCOMPARE(CompletionItemTester(top).itemData("A", KTextEditor::CodeCompletionModel::Prefix).toString(), QString("namespace")); release(top); } void TestCppCodeCompletion::testAfterNamespace() { QByteArray method("void foo(); namespace asdf { namespace foobar {} }"); TopDUContext* top = parse(method, DumpNone); DUChainWriteLocker lock; CompletionItemTester tester(top, "namespace"); QCOMPARE(tester.names, QStringList() << "asdf"); release(top); } void TestCppCodeCompletion::testIndirectImports() { { addInclude("testIndirectImportsHeader1.h", "class C {};"); addInclude("testIndirectImportsHeader2.h", "template class D : public T {};"); QByteArray method("#include \"testIndirectImportsHeader2.h\"\n#include \"testIndirectImportsHeader1.h\"\n typedef D Base; class MyClass : public C, public Base {}; "); TopDUContext* top = parse(method, DumpNone); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(top->importedParentContexts().size(), 2); QCOMPARE(top->childContexts().count(), 1); QCOMPARE(top->childContexts()[0]->importedParentContexts().count(), 2); QVERIFY(!top->childContexts()[0]->importedParentContexts()[0].isDirect()); //Should not be direct, since it crosses a header QVERIFY(!top->childContexts()[0]->importedParentContexts()[0].indirectDeclarationId().isDirect()); //Should not be direct, since it crosses a header QVERIFY(!top->childContexts()[0]->importedParentContexts()[1].isDirect()); //Should not be direct, since it crosses a header QVERIFY(!top->childContexts()[0]->importedParentContexts()[1].indirectDeclarationId().isDirect()); //Should not be direct, since it crosses a header DUContext* import1 = top->childContexts()[0]->importedParentContexts()[1].context(top); QVERIFY(import1); QCOMPARE(import1->importedParentContexts().count(), 2); //The template-context is also imported QVERIFY(!import1->importedParentContexts()[1].isDirect()); //Should not be direct, since it crosses a header QVERIFY(!import1->importedParentContexts()[1].indirectDeclarationId().isDirect()); //Should not be direct, since it crosses a header DUContext* import2 = import1->importedParentContexts()[0].context(top); QVERIFY(import2); QCOMPARE(import2->importedParentContexts().count(), 0); release(top); } } void TestCppCodeCompletion::testSameNamespace() { { addInclude("testSameNamespaceClassHeader.h", "namespace A {\n class B\n {\n \n};\n \n}"); QByteArray method("#include \"testSameNamespaceClassHeader.h\"\n namespace A {\n namespace AA {\n};\n };\n"); TopDUContext* top = parse(method, DumpNone); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(top->importedParentContexts().size(), 1); QVERIFY(!top->parentContext()); QCOMPARE(top->childContexts().count(), 1); QCOMPARE(top->childContexts()[0]->childContexts().count(), 1); { kDebug() << CompletionItemTester(top->childContexts()[0]).names; QCOMPARE(CompletionItemTester(top->childContexts()[0]).names.toSet(), QSet() << "B" << "A" << "AA"); QCOMPARE(CompletionItemTester(top->childContexts()[0]->childContexts()[0]).names.toSet(), QSet() << "B" << "A" << "AA"); } release(top); } { // 0 1 2 3 4 5 6 7 // 0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012 QByteArray method("namespace A { class C { }; void test2() {} } namespace A { void test() { } class C {};}"); TopDUContext* top = parse(method, DumpNone); DUChainWriteLocker lock(DUChain::lock()); QVERIFY(!top->parentContext()); QCOMPARE(top->childContexts().count(), 2); QCOMPARE(top->childContexts()[1]->childContexts().count(), 3); QCOMPARE(top->childContexts()[1]->localDeclarations().count(), 2); FunctionDefinition* funDef = dynamic_cast(top->childContexts()[1]->localDeclarations()[0]); QVERIFY(!funDef->hasDeclaration()); // lock.unlock(); { kDebug() << CompletionItemTester(top->childContexts()[1]->childContexts()[2]).names; QCOMPARE(CompletionItemTester(top->childContexts()[1]->childContexts()[2]).names.toSet(), QSet() << "C" << "A"); QCOMPARE(CompletionItemTester(top->childContexts()[1]).names.toSet(), QSet() << "C" << "A"); QCOMPARE(CompletionItemTester(top->childContexts()[1]->childContexts()[1]).names.toSet(), QSet() << "C" << "test2" << "test" << "A"); } release(top); } } void TestCppCodeCompletion::testUnnamedNamespace() { TEST_FILE_PARSE_ONLY // 0 1 2 3 4 5 6 7 // 0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012 QByteArray method("namespace {int a;} namespace { int b; }; void test() {a = 3;}"); TopDUContext* top = parse(method, DumpNone); DUChainWriteLocker lock(DUChain::lock()); QVERIFY(!top->parentContext()); QCOMPARE(top->childContexts().count(), 4); QCOMPARE(top->localDeclarations().count(), 3); kDebug() << top->localDeclarations()[0]->range().castToSimpleRange().textRange(); QCOMPARE(top->localDeclarations()[0]->range().castToSimpleRange().textRange(), KTextEditor::Range(0, 10, 0, 10)); QVERIFY(findDeclaration(top, QualifiedIdentifier("a"))); QVERIFY(findDeclaration(top, QualifiedIdentifier("b"))); QVERIFY(findDeclaration(top, QualifiedIdentifier("a"))->uses().size()); PersistentSymbolTable::FilteredDeclarationIterator decls = KDevelop::PersistentSymbolTable::self().getFilteredDeclarations(QualifiedIdentifier(Cpp::unnamedNamespaceIdentifier().identifier()), top->recursiveImportIndices()); QVERIFY(decls); QCOMPARE(top->findLocalDeclarations(Cpp::unnamedNamespaceIdentifier().identifier()).size(), 2); QCOMPARE(top->findDeclarations(QualifiedIdentifier(Cpp::unnamedNamespaceIdentifier().identifier())).size(), 2); // lock.unlock(); { Cpp::CodeCompletionContext::Ptr cptr( new Cpp::CodeCompletionContext(DUContextPointer(top), "; ", QString(), top->range().end) ); bool abort = false; typedef KSharedPtr Item; QList items = cptr->completionItems(abort); foreach(Item i, items) { Cpp::NormalDeclarationCompletionItem* decItem = dynamic_cast(i.data()); QVERIFY(decItem); kDebug() << decItem->declaration()->toString(); kDebug() << i->data(fakeModel().index(0, KTextEditor::CodeCompletionModel::Name), Qt::DisplayRole, 0).toString(); } //Have been filtered out, because only types are shown from the global scope QCOMPARE(items.count(), 0); //C, test, and i } { Cpp::CodeCompletionContext::Ptr cptr( new Cpp::CodeCompletionContext(DUContextPointer(top->childContexts()[3]), "; ", QString(), top->range().end) ); bool abort = false; typedef KSharedPtr Item; QList items = cptr->completionItems(abort); foreach(Item i, items) { Cpp::NormalDeclarationCompletionItem* decItem = dynamic_cast(i.data()); QVERIFY(decItem); kDebug() << decItem->declaration()->toString(); kDebug() << i->data(fakeModel().index(0, KTextEditor::CodeCompletionModel::Name), Qt::DisplayRole, 0).toString(); } QCOMPARE(items.count(), 3); //b, a, and test } // lock.lock(); release(top); } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void TestCppCodeCompletion::testCompletionContext() { TEST_FILE_PARSE_ONLY QByteArray test = "#include \"testFile1.h\"\n"; test += "#include \"testFile2.h\"\n"; test += "void test() { }"; DUContext* context = parse( test, DumpNone /*DumpDUChain | DumpAST */); DUChainWriteLocker lock(DUChain::lock()); QVERIFY(context->childContexts().count()); DUContext* testContext = context->childContexts()[0]; QCOMPARE( testContext->type(), DUContext::Function ); lock.unlock(); { ///Test whether a recursive function-call context is created correctly Cpp::CodeCompletionContext::Ptr cptr( new Cpp::CodeCompletionContext(DUContextPointer(DUContextPointer(context)), "; globalFunction(globalFunction(globalHonk, ", QString(), CursorInRevision::invalid() ) ); Cpp::CodeCompletionContext& c(*cptr); QVERIFY( c.isValid() ); QVERIFY( c.accessType() == Cpp::CodeCompletionContext::NoMemberAccess ); QVERIFY( !c.memberAccessContainer().isValid() ); //globalHonk is of type Honk. Check whether all matching functions were rated correctly by the overload-resolution. //The preferred parent-function in the list should be "Honk globalFunction( const Honk&, const Heinz& h )", because the first argument maches globalHonk Cpp::CodeCompletionContext* function = c.parentContext(); QVERIFY(function); QVERIFY(function->accessType() == Cpp::CodeCompletionContext::FunctionCallAccess); QVERIFY(!function->functions().isEmpty()); lock.lock(); for( Cpp::CodeCompletionContext::FunctionList::const_iterator it = function->functions().begin(); it != function->functions().end(); ++it ) kDebug(9007) << (*it).function.declaration()->toString() << ((*it).function.isViable() ? QString("(viable)") : QString("(not viable)")) ; lock.unlock(); QCOMPARE(function->functions().size(), 4); QVERIFY(function->functions()[0].function.isViable()); //Because Honk has a conversion-function to int, globalFunction(int) is yet viable(although it can take only 1 parameter) QVERIFY(function->functions()[1].function.isViable()); //Because Erna has a constructor that takes "const Honk&", globalFunction(Erna) is yet viable(although it can take only 1 parameter) QVERIFY(function->functions()[2].function.isViable()); //Because a value of type Honk is given, 2 globalFunction's are not viable QVERIFY(!function->functions()[3].function.isViable()); function = function->parentContext(); QVERIFY(function); QVERIFY(function->accessType() == Cpp::CodeCompletionContext::FunctionCallAccess); QVERIFY(!function->functions().isEmpty()); QVERIFY(!function->parentContext()); QVERIFY(function->functions().size() == 4); //Because no arguments were given, all functions are viable QVERIFY(function->functions()[0].function.isViable()); QVERIFY(function->functions()[1].function.isViable()); QVERIFY(function->functions()[2].function.isViable()); QVERIFY(function->functions()[3].function.isViable()); } { ///The context is a function, and there is no prefix-expression, so it should be normal completion. DUContextPointer contPtr(context); Cpp::CodeCompletionContext c(contPtr, "{", QString(), CursorInRevision::invalid() ); QVERIFY( c.isValid() ); QVERIFY( c.accessType() == Cpp::CodeCompletionContext::NoMemberAccess ); QVERIFY( !c.memberAccessContainer().isValid() ); } lock.lock(); release(context); } void TestCppCodeCompletion::testTypeConversion() { TEST_FILE_PARSE_ONLY QByteArray test = "#include \"testFile1.h\"\n"; test += "#include \"testFile2.h\"\n"; test += "#include \"testFile3.h\"\n"; test += "int n;\n"; test += "void test() { }\n"; DUContext* context = parse( test, DumpNone /*DumpDUChain | DumpAST */); DUChainWriteLocker lock(DUChain::lock()); DUContext* testContext = context->childContexts()[0]; QCOMPARE( testContext->type(), DUContext::Function ); QVERIFY(findDeclaration( testContext, QualifiedIdentifier("Heinz") )); QVERIFY(findDeclaration( testContext, QualifiedIdentifier("Erna") )); Declaration* decl = findDeclaration( testContext, QualifiedIdentifier("Erna") ); QVERIFY(decl); QVERIFY(decl->logicalInternalContext(context->topContext())); QVERIFY(findDeclaration( testContext, QualifiedIdentifier("Honk") )); QVERIFY(findDeclaration( testContext, QualifiedIdentifier("A") )); QVERIFY(findDeclaration( testContext, QualifiedIdentifier("B") )); QVERIFY(findDeclaration( testContext, QualifiedIdentifier("test") )); QVERIFY(findDeclaration( testContext, QualifiedIdentifier("n") )); AbstractType::Ptr Heinz = findDeclaration( testContext, QualifiedIdentifier("Heinz") )->abstractType(); AbstractType::Ptr Erna = findDeclaration( testContext, QualifiedIdentifier("Erna") )->abstractType(); AbstractType::Ptr Honk = findDeclaration( testContext, QualifiedIdentifier("Honk") )->abstractType(); AbstractType::Ptr A = findDeclaration( testContext, QualifiedIdentifier("A") )->abstractType(); AbstractType::Ptr B = findDeclaration( testContext, QualifiedIdentifier("B") )->abstractType(); AbstractType::Ptr n = findDeclaration( testContext, QualifiedIdentifier("n") )->abstractType(); QVERIFY(n); { FunctionType::Ptr test = findDeclaration( testContext, QualifiedIdentifier("test") )->type(); QVERIFY(test); Cpp::TypeConversion conv(context->topContext()); QVERIFY(!conv.implicitConversion(test->returnType()->indexed(), Heinz->indexed(), false)); QVERIFY(!conv.implicitConversion(Heinz->indexed(), test->returnType()->indexed(), false)); QVERIFY(!conv.implicitConversion(test->returnType()->indexed(), n->indexed(), false)); QVERIFY(!conv.implicitConversion(n->indexed(), test->returnType()->indexed(), false)); } //lock.unlock(); { ///Test whether a recursive function-call context is created correctly Cpp::TypeConversion conv(context->topContext()); QVERIFY( !conv.implicitConversion(Honk->indexed(), Heinz->indexed()) ); QVERIFY( conv.implicitConversion(Honk->indexed(), typeInt->indexed()) ); //Honk has operator int() QVERIFY( conv.implicitConversion(Honk->indexed(), Erna->indexed()) ); //Erna has constructor that takes Honk QVERIFY( !conv.implicitConversion(Erna->indexed(), Heinz->indexed()) ); ///@todo reenable once base-classes are parsed correctly //QVERIFY( conv.implicitConversion(B, A) ); //B is based on A, so there is an implicit copy-constructor that creates A from B //QVERIFY( conv.implicitConversion(Heinz, Erna) ); //Heinz is based on Erna, so there is an implicit copy-constructor that creates Erna from Heinz } //lock.lock(); release(context); } KDevelop::IndexedType toReference(IndexedType t) { ReferenceType::Ptr refType( new ReferenceType); refType->setBaseType(t.abstractType()); return refType->indexed(); } KDevelop::IndexedType toPointer(IndexedType t) { PointerType::Ptr refType( new PointerType); refType->setBaseType(t.abstractType()); return refType->indexed(); } void TestCppCodeCompletion::testTypeConversion2() { { QByteArray test = "class A {}; class B {public: explicit B(const A&); explicit B(const int&){}; private: operator A() const {}; }; class C : public B{private: C(B) {}; };"; TopDUContext* context = parse( test, DumpAll /*DumpDUChain | DumpAST */); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(context->localDeclarations().size(), 3); QVERIFY(context->localDeclarations()[0]->internalContext()); QCOMPARE(context->localDeclarations()[1]->internalContext()->localDeclarations().count(), 3); ClassFunctionDeclaration* classFun = dynamic_cast(context->localDeclarations()[1]->internalContext()->localDeclarations()[0]); QVERIFY(classFun); QVERIFY(classFun->isExplicit()); classFun = dynamic_cast(context->localDeclarations()[1]->internalContext()->localDeclarations()[1]); QVERIFY(classFun); QVERIFY(classFun->isExplicit()); Cpp::TypeConversion conv(context); QVERIFY( !conv.implicitConversion(context->localDeclarations()[2]->indexedType(), context->localDeclarations()[0]->indexedType()) ); QVERIFY( conv.implicitConversion(context->localDeclarations()[2]->indexedType(), context->localDeclarations()[1]->indexedType()) ); QVERIFY( !conv.implicitConversion(context->localDeclarations()[1]->indexedType(), context->localDeclarations()[2]->indexedType()) ); QVERIFY( !conv.implicitConversion(context->localDeclarations()[1]->indexedType(), context->localDeclarations()[0]->indexedType()) ); QVERIFY( !conv.implicitConversion(context->localDeclarations()[0]->indexedType(), context->localDeclarations()[1]->indexedType()) ); QVERIFY( !conv.implicitConversion(toReference(context->localDeclarations()[2]->indexedType()), toReference(context->localDeclarations()[0]->indexedType()) )); QVERIFY( conv.implicitConversion(toReference(context->localDeclarations()[2]->indexedType()), toReference(context->localDeclarations()[1]->indexedType()) )); QVERIFY( !conv.implicitConversion(toReference(context->localDeclarations()[1]->indexedType()), toReference(context->localDeclarations()[2]->indexedType()) )); QVERIFY( !conv.implicitConversion(toReference(context->localDeclarations()[1]->indexedType()), toReference(context->localDeclarations()[0]->indexedType()) )); QVERIFY( !conv.implicitConversion(toReference(context->localDeclarations()[0]->indexedType()), toReference(context->localDeclarations()[1]->indexedType()) )); QVERIFY( !conv.implicitConversion(toPointer(context->localDeclarations()[2]->indexedType()), toPointer(context->localDeclarations()[0]->indexedType()) )); QVERIFY( conv.implicitConversion(toPointer(context->localDeclarations()[2]->indexedType()), toPointer(context->localDeclarations()[1]->indexedType()) )); QVERIFY( !conv.implicitConversion(toPointer(context->localDeclarations()[1]->indexedType()), toPointer(context->localDeclarations()[2]->indexedType()) )); QVERIFY( !conv.implicitConversion(toPointer(context->localDeclarations()[1]->indexedType()), toPointer(context->localDeclarations()[0]->indexedType()) )); QVERIFY( !conv.implicitConversion(toPointer(context->localDeclarations()[0]->indexedType()), toPointer(context->localDeclarations()[1]->indexedType()) )); release(context); } { QByteArray test = "const char** b; char** c; char** const d; char* const * e; char f; const char q; "; TopDUContext* context = parse( test, DumpNone /*DumpDUChain | DumpAST */); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(context->localDeclarations().size(), 6); Cpp::TypeConversion conv(context); QVERIFY( !conv.implicitConversion(context->localDeclarations()[0]->indexedType(), context->localDeclarations()[1]->indexedType()) ); PointerType::Ptr fromPointer = context->localDeclarations()[1]->indexedType().type< PointerType>(); QVERIFY(fromPointer); QVERIFY( !(fromPointer->modifiers() & AbstractType::ConstModifier)); QVERIFY( conv.implicitConversion(context->localDeclarations()[1]->indexedType(), context->localDeclarations()[0]->indexedType()) ); QVERIFY( conv.implicitConversion(context->localDeclarations()[1]->indexedType(), context->localDeclarations()[2]->indexedType()) ); QVERIFY( !conv.implicitConversion(context->localDeclarations()[0]->indexedType(), context->localDeclarations()[2]->indexedType()) ); QVERIFY( conv.implicitConversion(context->localDeclarations()[2]->indexedType(), context->localDeclarations()[0]->indexedType()) ); QVERIFY( conv.implicitConversion(context->localDeclarations()[2]->indexedType(), context->localDeclarations()[1]->indexedType()) ); QVERIFY( !conv.implicitConversion(context->localDeclarations()[3]->indexedType(), context->localDeclarations()[0]->indexedType()) ); QVERIFY( !conv.implicitConversion(context->localDeclarations()[3]->indexedType(), context->localDeclarations()[1]->indexedType()) ); QVERIFY( !conv.implicitConversion(context->localDeclarations()[0]->indexedType(), context->localDeclarations()[3]->indexedType()) ); QVERIFY( conv.implicitConversion(context->localDeclarations()[1]->indexedType(), context->localDeclarations()[3]->indexedType()) ); QVERIFY( !conv.implicitConversion(context->localDeclarations()[3]->indexedType(), context->localDeclarations()[1]->indexedType()) ); QVERIFY( conv.implicitConversion(context->localDeclarations()[4]->indexedType(), context->localDeclarations()[5]->indexedType()) ); QVERIFY( conv.implicitConversion(context->localDeclarations()[5]->indexedType(), context->localDeclarations()[4]->indexedType()) ); release(context); } { QByteArray test = "class A {}; class C {}; enum M { Em }; template class B{ public:B(T t); }; "; TopDUContext* context = parse( test, DumpNone /*DumpDUChain | DumpAST */); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(context->localDeclarations().size(), 4); QCOMPARE(context->childContexts().size(), 5); Cpp::TypeConversion conv(context); Declaration* decl = findDeclaration(context, QualifiedIdentifier("B")); QVERIFY(decl); kDebug() << decl->toString(); QVERIFY( conv.implicitConversion(context->localDeclarations()[0]->indexedType(), decl->indexedType()) ); decl = findDeclaration(context, QualifiedIdentifier("B")); QVERIFY(decl); kDebug() << decl->toString(); QCOMPARE(context->childContexts()[2]->localDeclarations().size(), 1); QVERIFY( conv.implicitConversion(context->childContexts()[2]->localDeclarations()[0]->indexedType(), decl->indexedType()) ); release(context); } } void TestCppCodeCompletion::testInclude() { TEST_FILE_PARSE_ONLY addInclude("file1.h", "#include \"testFile1.h\"\n#include \"testFile2.h\"\n"); QByteArray test = "#include \"file1.h\" \n struct Cont { operator int() {}; }; void test( int c = 5 ) { this->test( Cont(), 1, 5.5, 6); }; HONK undefinedHonk;"; DUContext* c = parse( test, DumpNone /*DumpDUChain | DumpAST */); DUChainWriteLocker lock(DUChain::lock()); QVERIFY(c->topContext()->usingImportsCache()); Declaration* decl = findDeclaration(c, QualifiedIdentifier("globalHeinz")); QVERIFY(decl); QVERIFY(decl->abstractType()); QCOMPARE(decl->abstractType()->toString(), QString("Heinz")); decl = findDeclaration(c, QualifiedIdentifier("globalErna")); QVERIFY(decl); QVERIFY(decl->abstractType()); QCOMPARE(decl->abstractType()->toString(), QString("Erna")); decl = findDeclaration(c, QualifiedIdentifier("globalInt")); QVERIFY(decl); QVERIFY(decl->abstractType()); QCOMPARE(decl->abstractType()->toString(), QString("int")); decl = findDeclaration(c, QualifiedIdentifier("Honk")); QVERIFY(decl); QVERIFY(decl->abstractType()); QCOMPARE(decl->abstractType()->toString(), QString("Honk")); decl = findDeclaration(c, QualifiedIdentifier("honky")); QVERIFY(decl); QVERIFY(decl->abstractType()); QCOMPARE(decl->abstractType()->toString(), QString("Honk")); decl = findDeclaration(c, QualifiedIdentifier("globalHonk")); QVERIFY(decl); QVERIFY(decl->abstractType()); QCOMPARE(decl->abstractType()->toString(), QString("Honk")); decl = findDeclaration(c, QualifiedIdentifier("globalMacroHonk")); QVERIFY(decl); QVERIFY(decl->abstractType()); QCOMPARE(decl->abstractType()->toString(), QString("Honk")); ///HONK was #undef'ed in testFile2, so this must be unresolved. decl = findDeclaration(c, QualifiedIdentifier("undefinedHonk")); QVERIFY(decl); QVERIFY(decl->abstractType().cast()); Cpp::ExpressionParser parser; ///The following test tests the expression-parser, but it is here because the other tests depend on it lock.unlock(); Cpp::ExpressionEvaluationResult result = parser.evaluateExpression( "globalHonk.erna", DUContextPointer( c ) ); lock.lock(); QVERIFY(result.isValid()); QVERIFY(result.isInstance); QVERIFY(result.type); QCOMPARE(result.type.abstractType()->toString(), QString("Erna&")); ///Test overload-resolution lock.unlock(); result = parser.evaluateExpression( "globalClass.function(globalHeinz)", DUContextPointer(c)); lock.lock(); QVERIFY(result.isValid()); QVERIFY(result.isInstance); QVERIFY(result.type); QCOMPARE(result.type.abstractType()->toString(), QString("Heinz")); lock.unlock(); result = parser.evaluateExpression( "globalClass.function(globalHonk.erna)", DUContextPointer(c)); lock.lock(); QVERIFY(result.isValid()); QVERIFY(result.isInstance); QVERIFY(result.type); QCOMPARE(result.type.abstractType()->toString(), QString("Erna")); //No matching function for type Honk. Since the expression-parser is not set to "strict", it returns any found function with the right name. lock.unlock(); result = parser.evaluateExpression( "globalClass.function(globalHonk)", DUContextPointer(c)); lock.lock(); QVERIFY(result.isValid()); QVERIFY(result.isInstance); QVERIFY(result.type); //QCOMPARE(result.type.abstractType()->toString(), QString("Heinz")); lock.unlock(); result = parser.evaluateExpression( "globalFunction(globalHeinz)", DUContextPointer(c)); lock.lock(); QVERIFY(result.isValid()); QVERIFY(result.isInstance); QVERIFY(result.type); QCOMPARE(result.type.abstractType()->toString(), QString("Heinz")); lock.unlock(); result = parser.evaluateExpression( "globalFunction(globalHonk.erna)", DUContextPointer(c)); lock.lock(); QVERIFY(result.isValid()); QVERIFY(result.isInstance); QVERIFY(result.type); QCOMPARE(result.type.abstractType()->toString(), QString("Erna")); release(c); } void TestCppCodeCompletion::testUpdateChain() { TEST_FILE_PARSE_ONLY { QByteArray text("#define Q_FOREACH(variable, container) for (QForeachContainer<__typeof__(container)> _container_(container); !_container_.brk && _container_.i != _container_.e; __extension__ ({ ++_container_.brk; ++_container_.i; })) for (variable = *_container_.i;; __extension__ ({--_container_.brk; break;})) \nvoid test() { Q_FOREACH(int a, b) { int i; } }"); TopDUContext* top = parse( text, DumpAll ); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(top->childContexts().count(), 2); QCOMPARE(top->childContexts()[1]->childContexts().count(), 2); QCOMPARE(top->childContexts()[1]->childContexts()[1]->childContexts().count(), 2); QCOMPARE(top->childContexts()[1]->childContexts()[1]->childContexts()[1]->localDeclarations().count(), 1); IndexedDeclaration decl(top->childContexts()[1]->childContexts()[1]->childContexts()[1]->localDeclarations()[0]); QVERIFY(decl.data()); QCOMPARE(decl.data()->identifier().toString(), QString("i")); parse(text, DumpNone, 0, KUrl(), top); QVERIFY(decl.data()); //Make sure the declaration has been updated, and not deleted release(top); } } void TestCppCodeCompletion::testHeaderSections() { TEST_FILE_PARSE_ONLY /** * Make sure that the ends of header-sections are recognized correctly * */ addInclude( "someHeader.h", "\n" ); addInclude( "otherHeader.h", "\n" ); IncludeFileList includes; IndexedString turl("ths.h"); QCOMPARE(preprocess(turl, "#include \"someHeader.h\"\nHello", includes, 0, true), QString("\n")); QCOMPARE(includes.count(), 1); includes.clear(); QCOMPARE(preprocess(turl, "#include \"someHeader.h\"\nHello", includes, 0, false), QString("\nHello")); QCOMPARE(includes.count(), 1); includes.clear(); QCOMPARE(preprocess(turl, "#include \"someHeader.h\"\n#include \"otherHeader.h\"\nHello", includes, 0, false), QString("\n\nHello")); QCOMPARE(includes.count(), 2); includes.clear(); QCOMPARE(preprocess(turl, "#include \"someHeader.h\"\n#include \"otherHeader.h\"\nHello", includes, 0, true), QString("\n\n")); QCOMPARE(includes.count(), 2); includes.clear(); QCOMPARE(preprocess(turl, "#ifndef GUARD\n#define GUARD\n#include \"someHeader.h\"\nHello\n#endif", includes, 0, true), QString("\n\n\n")); QCOMPARE(includes.count(), 1); includes.clear(); QCOMPARE(preprocess(turl, "#ifndef GUARD\n#define GUARD\n#include \"someHeader.h\"\nHello\n#endif", includes, 0, false), QString("\n\n\nHello\n")); QCOMPARE(includes.count(), 1); includes.clear(); } void TestCppCodeCompletion::testForwardDeclaration() { addInclude( "testdeclaration.h", "class Test{ };" ); QByteArray method("#include \"testdeclaration.h\"\n class Test; "); DUContext* top = parse(method, DumpNone); DUChainWriteLocker lock(DUChain::lock()); Declaration* decl = findDeclaration(top, Identifier("Test"), top->range().end); QVERIFY(decl); QVERIFY(decl->abstractType()); AbstractType::Ptr t(decl->abstractType()); QVERIFY(dynamic_cast(t.unsafeData())); QVERIFY(!decl->isForwardDeclaration()); release(top); } void TestCppCodeCompletion::testUsesThroughMacros() { { QByteArray method("int x;\n#define TEST(X) X\ny = TEST(x);"); DUContext* top = parse(method, DumpNone); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(top->localDeclarations().count(), 1); QCOMPARE(top->localDeclarations()[0]->uses().count(), 1); QCOMPARE(top->localDeclarations()[0]->uses().begin()->count(), 1); QCOMPARE(top->localDeclarations()[0]->uses().begin()->at(0).start.column, 9); QCOMPARE(top->localDeclarations()[0]->uses().begin()->at(0).end.column, 10); } { ///2 uses of x, that go through the macro TEST(..), and effectively are in line 2 column 5. QByteArray method("int x;\n#define TEST(X) void test() { int z = X; int q = X; }\nTEST(x)"); kDebug() << method; DUContext* top = parse(method, DumpNone); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(top->localDeclarations().count(), 2); QCOMPARE(top->localDeclarations()[0]->uses().count(), 1); //Since uses() returns unique uses, and both uses of x in TEST(x) have the same range, //only one use is returned. QCOMPARE(top->localDeclarations()[0]->uses().begin()->count(), 1); RangeInRevision range1(top->localDeclarations()[0]->uses().begin()->at(0)); QCOMPARE(range1.start.line, 2); QCOMPARE(range1.end.line, 2); QCOMPARE(range1.start.column, 5); QCOMPARE(range1.end.column, 6); } } void TestCppCodeCompletion::testMacroIncludeDirectives() { addInclude( "macroincludedirectivetest1.h", "class Test1{ };" ); addInclude( "macro includedirectivetest2.h", "class Test1{ };" ); { QByteArray method("#define TEST macroincludedirectivetest1.h \n #define TEST_HPP \n #include TEST_HPP\n"); DUContext* top = parse(method, DumpNone); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(top->importedParentContexts().size(), 1); release(top); } { QByteArray method("#define TEST \"macroincludedirectivetest1.h\" \n #include TEST\n"); DUContext* top = parse(method, DumpNone); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(top->importedParentContexts().size(), 1); release(top); } { QByteArray method("#define TEST \n #include TEST\n"); DUContext* top = parse(method, DumpNone); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(top->importedParentContexts().size(), 1); release(top); } { QByteArray method("#include \"macro includedirectivetest2.h\"\n"); DUContext* top = parse(method, DumpNone); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(top->importedParentContexts().size(), 1); release(top); } { QByteArray method("#define TEST \"macro includedirectivetest2.h\" \n #include TEST\n"); DUContext* top = parse(method, DumpNone); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(top->importedParentContexts().size(), 1); release(top); } } void TestCppCodeCompletion::testAcrossHeaderReferences() { addInclude( "acrossheader1.h", "class Test{ };" ); addInclude( "acrossheader2.h", "Test t;" ); QByteArray method("#include \"acrossheader1.h\"\n#include \"acrossheader2.h\"\n"); DUContext* top = parse(method, DumpNone); DUChainWriteLocker lock(DUChain::lock()); Declaration* decl = findDeclaration(top, Identifier("t"), top->range().end); QVERIFY(decl); QVERIFY(decl->abstractType()); AbstractType::Ptr t(decl->abstractType()); QVERIFY(dynamic_cast(t.unsafeData())); release(top); } void TestCppCodeCompletion::testAcrossHeaderTemplateResolution() { addInclude("acrossheaderresolution1.h", "class C {}; namespace std { template class A { }; }"); addInclude("acrossheaderresolution2.h", "namespace std { template class B { typedef A Type; }; }"); QByteArray method("#include \"acrossheaderresolution1.h\"\n#include \"acrossheaderresolution2.h\"\n std::B::Type t;"); DUContext* top = parse(method, DumpNone); DUChainWriteLocker lock(DUChain::lock()); Declaration* decl = findDeclaration(top, QualifiedIdentifier("t"), top->range().end); QVERIFY(decl); QCOMPARE(QualifiedIdentifier(TypeUtils::unAliasedType(decl->abstractType())->toString()), QualifiedIdentifier("std::A")); release(top); } void TestCppCodeCompletion::testAcrossHeaderTemplateReferences() { addInclude( "acrossheader1.h", "class Dummy { }; template class Test{ };" ); addInclude( "acrossheader2.h", "template > class Test2 : public Test{ Test bm; };" ); QByteArray method("#include \"acrossheader1.h\"\n#include \"acrossheader2.h\"\n "); DUContext* top = parse(method, DumpNone); DUChainWriteLocker lock(DUChain::lock()); { kDebug() << "top is" << top; Declaration* decl = findDeclaration(top, QualifiedIdentifier("Dummy"), top->range().end); QVERIFY(decl); QVERIFY(decl->abstractType()); AbstractType::Ptr t(decl->abstractType()); QVERIFY(dynamic_cast(t.unsafeData())); QCOMPARE(decl->abstractType()->toString(), QString("Dummy")); } { Declaration* decl = findDeclaration(top, QualifiedIdentifier("Test2::B2"), top->range().end); QVERIFY(decl); QVERIFY(decl->abstractType()); AbstractType::Ptr t(decl->abstractType()); QVERIFY(dynamic_cast(t.unsafeData())); QCOMPARE(decl->abstractType()->toString(), QString("Test< Dummy >")); } { Declaration* decl = findDeclaration(top, QualifiedIdentifier("Test2::bm"), top->range().end); QVERIFY(decl); QVERIFY(decl->abstractType()); AbstractType::Ptr t(decl->abstractType()); QVERIFY(dynamic_cast(t.unsafeData())); QCOMPARE(decl->abstractType()->toString(), QString("Test< Dummy >")); } { ClassDeclaration* decl = dynamic_cast(findDeclaration(top, QualifiedIdentifier("Test2"), top->range().end)); QVERIFY(decl); QVERIFY(decl->abstractType()); CppClassType::Ptr classType = decl->abstractType().cast(); QVERIFY(classType); QCOMPARE(decl->baseClassesSize(), 1u); QVERIFY(decl->baseClasses()[0].baseClass); CppClassType::Ptr parentClassType = decl->baseClasses()[0].baseClass.type(); QVERIFY(parentClassType); QCOMPARE(parentClassType->toString(), QString("Test< Dummy >")); } release(top); } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void TestCppCodeCompletion::release(DUContext* top) { //EditorIntegrator::releaseTopRange(top->textRangePtr()); if(dynamic_cast(top)) DUChain::self()->removeDocumentChain(static_cast(top)); //delete top; } void TestCppCodeCompletion::addInclude( const QString& identity, const QString& text ) { fakeIncludes[identity] = text; } //Only for debugging QString print(const Cpp::ReferenceCountedStringSet& set) { QString ret; bool first = true; Cpp::ReferenceCountedStringSet::Iterator it(set.iterator()); while(it) { if(!first) ret += ", "; first = false; ret += (*it).str(); ++it; } return ret; } //Only for debugging QStringList toStringList( const Cpp::ReferenceCountedStringSet& set ) { QStringList ret; Cpp::ReferenceCountedStringSet::Iterator it(set.iterator()); while(it) { ret << (*it).str(); ++it; } ret.sort(); return ret; } QStringList splitSorted(const QString& str) { QStringList ret = str.split("\n"); ret.sort(); return ret; } void TestCppCodeCompletion::testEmptyMacroArguments() { QString test("#define merge(prefix, suffix) prefix ## suffix\n void merge(test1, ) () { } void merge(, test2) () { }"); DUChainWriteLocker l(DUChain::lock()); TopDUContext* ctx = parse(test.toUtf8()); QCOMPARE(ctx->localDeclarations().count(), 2); QCOMPARE(ctx->localDeclarations()[0]->identifier().toString(), QString("test1")); QCOMPARE(ctx->localDeclarations()[1]->identifier().toString(), QString("test2")); } void TestCppCodeCompletion::testMacroExpansionRanges() { { QString test("#define TEST(X) int allabamma; \nTEST(C)\n"); DUChainWriteLocker l(DUChain::lock()); TopDUContext* ctx = parse(test.toUtf8()); QCOMPARE(ctx->localDeclarations().count(), 1); kDebug() << ctx->localDeclarations()[0]->range().castToSimpleRange().textRange(); //kDebug() << ctx->localDeclarations()[1]->range().castToSimpleRange().textRange(); QCOMPARE(ctx->localDeclarations()[0]->range().castToSimpleRange().textRange(), KTextEditor::Range(1, 7, 1, 7)); //Because the macro TEST was expanded out of its physical range, the Declaration is collapsed. // QCOMPARE(ctx->localDeclarations()[1]->range().castToSimpleRange().textRange(), KTextEditor::Range(1, 10, 1, 11)); //kDebug() << "Range:" << ctx->localDeclarations()[0]->range().castToSimpleRange().textRange(); } { QString test("#define A(X) bbbbbb\nint A(0);\n"); DUChainWriteLocker l(DUChain::lock()); TopDUContext* ctx = parse(test.toUtf8()); QCOMPARE(ctx->localDeclarations().count(), 1); kDebug() << ctx->localDeclarations()[0]->range().castToSimpleRange().textRange(); QCOMPARE(ctx->localDeclarations()[0]->range().castToSimpleRange().textRange(), KTextEditor::Range(1, 8, 1, 8)); //Because the macro TEST was expanded out of its physical range, the Declaration is collapsed. } { QString test("#define TEST namespace NS{int a;int b;int c;int d;int q;} class A{}; \nTEST; int a; int b; int c; int d;int e;int f;int g;int h;\n"); DUChainWriteLocker l(DUChain::lock()); TopDUContext* ctx = parse(test.toUtf8()); QCOMPARE(ctx->localDeclarations().count(), 10); QCOMPARE(ctx->localDeclarations()[1]->range().castToSimpleRange().textRange(), KTextEditor::Range(1, 4, 1, 4)); //Because the macro TEST was expanded out of its physical range, the Declaration is collapsed. QCOMPARE(ctx->localDeclarations()[2]->range().castToSimpleRange().textRange(), KTextEditor::Range(1, 10, 1, 11)); } { //The range of the merged declaration name should be trimmed to the end of the macro invocation QString test("#define TEST(X) class X ## Class {};\nTEST(Hallo)\n"); DUChainWriteLocker l(DUChain::lock()); TopDUContext* ctx = parse(test.toUtf8()); QCOMPARE(ctx->localDeclarations().count(), 1); QCOMPARE(ctx->localDeclarations()[0]->range().castToSimpleRange().textRange(), KTextEditor::Range(1, 5, 1, 11)); } { //The range of the merged declaration name should be within macro invocation even when the order of merging is different from the order of formal parameters QString test("#define TEST(X, Y) class Y ## X {};\nTEST(Hello, World)\n"); DUChainWriteLocker l(DUChain::lock()); TopDUContext* ctx = parse(test.toUtf8()); QCOMPARE(ctx->localDeclarations().count(), 1); QCOMPARE(ctx->localDeclarations()[0]->range().castToSimpleRange().textRange(), KTextEditor::Range(1, 12, 1, 18)); } { //The range of the merged declaration name should be collapsed if it does not start with a macro parameter QString test("#define TEST(X) class Hallo ## X {};\nTEST(Class)\n"); DUChainWriteLocker l(DUChain::lock()); TopDUContext* ctx = parse(test.toUtf8()); QCOMPARE(ctx->localDeclarations().count(), 1); QCOMPARE(ctx->localDeclarations()[0]->range().castToSimpleRange().textRange(), KTextEditor::Range(1, 11, 1, 11)); } } void TestCppCodeCompletion::testTimeMacro() { QString test("const char* str = __TIME__;"); DUChainWriteLocker l(DUChain::lock()); TopDUContext* ctx = parse(test.toUtf8()); QVERIFY(ctx->problems().isEmpty()); QCOMPARE(ctx->localDeclarations().count(), 1); } void TestCppCodeCompletion::testDateMacro() { QString test("const char* str = __DATE__;"); DUChainWriteLocker l(DUChain::lock()); TopDUContext* ctx = parse(test.toUtf8()); QVERIFY(ctx->problems().isEmpty()); QCOMPARE(ctx->localDeclarations().count(), 1); } void TestCppCodeCompletion::testFileMacro() { QString test("const char* str = __FILE__;"); DUChainWriteLocker l(DUChain::lock()); TopDUContext* ctx = parse(test.toUtf8()); QVERIFY(ctx->problems().isEmpty()); QCOMPARE(ctx->localDeclarations().count(), 1); } void TestCppCodeCompletion::testNaiveMatching() { return; Cpp::EnvironmentManager::self()->setMatchingLevel(Cpp::EnvironmentManager::Naive); { addInclude("recursive_test_1.h", "#include \"recursive_test_2.h\"\nint i1;\n"); addInclude("recursive_test_2.h", "#include \"recursive_test_1.h\"\nint i2;\n"); TopDUContext* test1 = parse(QByteArray("#include \"recursive_test_1.h\"\n"), DumpNone); DUChainWriteLocker l(DUChain::lock()); QCOMPARE(test1->recursiveImportIndices().count(), 3u); QCOMPARE(test1->importedParentContexts().count(), 1); QCOMPARE(test1->importedParentContexts()[0].indexedContext().context()->importedParentContexts().count(), 1); QCOMPARE(test1->importedParentContexts()[0].indexedContext().context()->importedParentContexts()[0].indexedContext().context()->importedParentContexts().count(), 1); QCOMPARE(test1->importedParentContexts()[0].indexedContext().context()->importedParentContexts()[0].indexedContext().context()->importedParentContexts()[0].indexedContext().context()->importedParentContexts().count(), 1); Cpp::EnvironmentFile* envFile1 = dynamic_cast(test1->parsingEnvironmentFile().data()); QVERIFY(envFile1); QVERIFY(envFile1->headerGuard().isEmpty()); release(test1); } } void TestCppCodeCompletion::testHeaderGuards() { { TopDUContext* test1 = parse(QByteArray("#ifndef GUARD\n#define GUARD\nint x = 5; \n#endif\n#define BLA\n"), DumpNone); DUChainWriteLocker l(DUChain::lock()); Cpp::EnvironmentFile* envFile1 = dynamic_cast(test1->parsingEnvironmentFile().data()); QVERIFY(envFile1); QVERIFY(envFile1->headerGuard().isEmpty()); release(test1); } { TopDUContext* test1 = parse(QByteArray("#ifndef GUARD\n#define GUARD\nint x = 5;\n#ifndef GUARD\n#define GUARD\n#endif\n#if defined(TEST)\n int q = 4;#endif\n#endif\n"), DumpNone); DUChainWriteLocker l(DUChain::lock()); Cpp::EnvironmentFile* envFile1 = dynamic_cast(test1->parsingEnvironmentFile().data()); QVERIFY(envFile1); QCOMPARE(envFile1->headerGuard().str(), QString("GUARD")); release(test1); } { TopDUContext* test1 = parse(QByteArray("int x;\n#ifndef GUARD\n#define GUARD\nint x = 5; \n#endif\n"), DumpNone); DUChainWriteLocker l(DUChain::lock()); Cpp::EnvironmentFile* envFile1 = dynamic_cast(test1->parsingEnvironmentFile().data()); QVERIFY(envFile1); QVERIFY(envFile1->headerGuard().isEmpty()); release(test1); } { TopDUContext* test1 = parse(QByteArray("#define X\n#ifndef GUARD\n#define GUARD\nint x = 5; \n#endif\n"), DumpNone); DUChainWriteLocker l(DUChain::lock()); Cpp::EnvironmentFile* envFile1 = dynamic_cast(test1->parsingEnvironmentFile().data()); QVERIFY(envFile1); QVERIFY(envFile1->headerGuard().isEmpty()); release(test1); } { TopDUContext* test1 = parse(QByteArray("#ifndef GUARD\n#define GUARD\nint x = 5; \n#endif\nint o;\n"), DumpNone); DUChainWriteLocker l(DUChain::lock()); Cpp::EnvironmentFile* envFile1 = dynamic_cast(test1->parsingEnvironmentFile().data()); QVERIFY(envFile1); QVERIFY(envFile1->headerGuard().isEmpty()); release(test1); } } void TestCppCodeCompletion::testEnvironmentMatching() { { CppPreprocessEnvironment::setRecordOnlyImportantString(false); addInclude("deep2.h", "#ifdef WANT_DEEP\nint x;\n#undef WANT_DEEP\n#endif\n"); addInclude("deep1.h", "#define WANT_DEEP\n#include \"deep2.h\"\n"); TopDUContext* test1 = parse(QByteArray("#include \"deep1.h\""), DumpNone); Cpp::EnvironmentFile* envFile1 = dynamic_cast(test1->parsingEnvironmentFile().data()); DUChainWriteLocker lock(DUChain::lock()); QVERIFY(envFile1); QCOMPARE(envFile1->definedMacroNames().set().count(), 0u); QCOMPARE(envFile1->definedMacros().set().count(), 0u); QCOMPARE(envFile1->usedMacros().set().count(), 0u); } addInclude("h1.h", "#ifndef H1_H \n#define H1_H \n class H1 {};\n #else \n class H1_Already_Defined {}; \n#endif"); addInclude("h1_user.h", "#ifndef H1_USER \n#define H1_USER \n#include \"h1.h\" \nclass H1User {}; \n#endif\n"); { TopDUContext* test1 = parse(QByteArray("#include \"h1.h\" \n#include \"h1_user.h\"\n\nclass Honk {};"), DumpNone); //We test here, whether the environment-manager re-combines h1_user.h so it actually contains a definition of class H1. //In the version parsed in test1, H1_H was already defined, so the h1.h parsed into h1_user.h was parsed to contain H1_Already_Defined. TopDUContext* test2 = parse(QByteArray("#include \"h1_user.h\"\n"), DumpNone); DUChainWriteLocker lock(DUChain::lock()); QVERIFY(test1->parsingEnvironmentFile()); QVERIFY(test2->parsingEnvironmentFile()); Cpp::EnvironmentFile* envFile1 = dynamic_cast(test1->parsingEnvironmentFile().data()); Cpp::EnvironmentFile* envFile2 = dynamic_cast(test2->parsingEnvironmentFile().data()); QVERIFY(envFile1); QVERIFY(envFile2); QCOMPARE(envFile1->usedMacros().set().count(), 0u); QCOMPARE(envFile2->usedMacros().set().count(), 0u); QVERIFY(findDeclaration( test1, Identifier("H1") )); QCOMPARE( envFile1->contentStartLine(), 3 ); } { //Test shadowing of strings through #undefs addInclude("stringset_test1.h", "String1 s1;\n#undef String2\n String2 s2;"); addInclude("stringset_test2.h", "String1 s1;\n#undef String2\n String2 s2;"); { TopDUContext* top = parse(QByteArray("#include \"stringset_test1.h\"\n"), DumpNone); DUChainWriteLocker lock(DUChain::lock()); QVERIFY(top->parsingEnvironmentFile()); Cpp::EnvironmentFile* envFile = dynamic_cast(top->parsingEnvironmentFile().data()); QVERIFY(envFile); kDebug() << "url" << envFile->url().str(); QCOMPARE(envFile->usedMacros().set().count(), 0u); QCOMPARE(toStringList(envFile->strings()), splitSorted("String1\ns1\ns2")); //The #undef protects String2, so it cannot be affected from outside } { TopDUContext* top = parse(QByteArray("#define String1\n#include \"stringset_test1.h\"\nString2 String1;"), DumpNone); //Both String1 and String2 are shadowed. String1 by the macro, and String2 by the #undef in stringset_test1.h DUChainWriteLocker lock(DUChain::lock()); QVERIFY(top->parsingEnvironmentFile()); Cpp::EnvironmentFile* envFile = dynamic_cast(top->parsingEnvironmentFile().data()); QVERIFY(envFile); //String1 is shadowed by the macro-definition, so it is not a string that can be affected from outside. QCOMPARE(toStringList(envFile->strings()), splitSorted("s1\ns2")); QCOMPARE(toStringList(envFile->usedMacroNames()), QStringList()); //No macros from outside were used QCOMPARE(envFile->definedMacros().set().count(), 1u); QCOMPARE(envFile->usedMacros().set().count(), 0u); QCOMPARE(top->importedParentContexts().count(), 1); TopDUContext* top2 = dynamic_cast(top->importedParentContexts()[0].context(0)); QVERIFY(top2); Cpp::EnvironmentFile* envFile2 = dynamic_cast(top2->parsingEnvironmentFile().data()); QVERIFY(envFile2); QCOMPARE(envFile2->definedMacros().set().count(), 0u); QCOMPARE(toStringList(envFile2->usedMacroNames()), QStringList("String1")); //stringset_test1.h used the Macro String1 from outside QCOMPARE(toStringList(envFile2->strings()), splitSorted("String1\ns1\ns2")); } { TopDUContext* top = parse(QByteArray("#define String1\n#undef String1\n#include \"stringset_test1.h\""), DumpNone); DUChainWriteLocker lock(DUChain::lock()); QVERIFY(top->parsingEnvironmentFile()); Cpp::EnvironmentFile* envFile = dynamic_cast(top->parsingEnvironmentFile().data()); QVERIFY(envFile); QCOMPARE(envFile->definedMacros().set().count(), 0u); QCOMPARE(envFile->usedMacros().set().count(), 0u); //String1 is shadowed by the macro-definition, so it is not a string that can be affected from outside. kDebug() << toStringList(envFile->strings()) << splitSorted("s1\ns2"); QCOMPARE(toStringList(envFile->strings()), splitSorted("s1\ns2")); QCOMPARE(toStringList(envFile->usedMacroNames()), QStringList()); //No macros from outside were used QCOMPARE(top->importedParentContexts().count(), 1); TopDUContext* top2 = dynamic_cast(top->importedParentContexts()[0].context(0)); QVERIFY(top2); Cpp::EnvironmentFile* envFile2 = dynamic_cast(top2->parsingEnvironmentFile().data()); QVERIFY(envFile2); QCOMPARE(envFile2->definedMacros().set().count(), 0u); QCOMPARE(toStringList(envFile2->usedMacroNames()), QStringList()); //stringset_test1.h used the Macro String1 from outside. However it is an undef-macro, so it does not appear in usedMacroNames() and usedMacros() QCOMPARE(envFile2->usedMacros().set().count(), (unsigned int)0); QCOMPARE(toStringList(envFile2->strings()), splitSorted("String1\ns1\ns2")); } { addInclude("usingtest1.h", "#define HONK\nMACRO m\n#undef HONK2\n"); TopDUContext* top = parse(QByteArray("#define MACRO meh\nint MACRO;\n#include \"usingtest1.h\"\n"), DumpNone); DUChainWriteLocker lock(DUChain::lock()); QVERIFY(top->parsingEnvironmentFile()); Cpp::EnvironmentFile* envFile = dynamic_cast(top->parsingEnvironmentFile().data()); QVERIFY(envFile); QCOMPARE(envFile->definedMacros().set().count(), 2u); QCOMPARE(envFile->unDefinedMacroNames().set().count(), 1u); QCOMPARE(envFile->usedMacros().set().count(), 0u); QCOMPARE(envFile->usedMacroNames().set().count(), 0u); kDebug() << toStringList(envFile->strings()) ; QCOMPARE(envFile->strings().count(), 3u); //meh, m, int QCOMPARE(top->importedParentContexts().count(), 1); TopDUContext* top2 = dynamic_cast(top->importedParentContexts()[0].context(0)); QVERIFY(top2); Cpp::EnvironmentFile* envFile2 = dynamic_cast(top2->parsingEnvironmentFile().data()); QVERIFY(envFile2); QCOMPARE(envFile2->definedMacros().set().count(), 1u); QCOMPARE(envFile2->unDefinedMacroNames().set().count(), 1u); QCOMPARE(envFile2->usedMacros().set().count(), 1u); QCOMPARE(envFile2->usedMacroNames().set().count(), 1u); kDebug() << toStringList(envFile2->strings()) ; QCOMPARE(envFile2->strings().count(), 3u); //meh(from macro), MACRO, m } } /* addInclude( "envmatch_header1.h", "#include \"envmatch_header2.h\"\n class SomeName; #define SomeName SomeAlternativeName" ); addInclude( "envmatch_header2.h", "#ifndef SOMEDEF\n #define SOMEDEF\n#endif\n" ); QByteArray method1("#include \"envmatch_header1.h\""); QByteArray method2("#include \"envmatch_header1.h\""); QByteArray method3("#include \"envmatch_header1.h\"\n#include \"envmatch_header1.h\""); DUContext* top1 = parse(method1, DumpNone); DUContext* top2 = parse(method1, DumpNone); DUContext* top3 = parse(method1, DumpNone); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(top1->importedParentContexts().count(), 1); QCOMPARE(top2->importedParentContexts().count(), 1); // QCOMPARE(top3->importedParentContexts().count(), 2); QCOMPARE(top1->importedParentContexts()[0], top2->importedParentContexts()[1]);*/ } void TestCppCodeCompletion::testPreprocessor() { TEST_FILE_PARSE_ONLY IncludeFileList includes; { QString a = "#define Q(c) c; char* q = #c; \n Q(int i;\n char* c = \"a\";)\n"; QString preprocessed = preprocess(IndexedString(), a, includes); kDebug() << "preprocessed:" << preprocessed; QVERIFY(preprocessed.contains("\"int i;\\n char* c = \\\"a\\\";")); //The newline must have been escaped correctly, and the string as well TopDUContext* top = parse(a.toLocal8Bit(), DumpNone); DUChainWriteLocker lock(DUChain::lock()); kDebug() << top->localDeclarations()[0]->identifier().toString(); QCOMPARE(top->localDeclarations().count(), 3); QCOMPARE(top->localDeclarations()[0]->range().start.line, 1); QCOMPARE(top->localDeclarations()[1]->range().start.line, 2); QCOMPARE(top->localDeclarations()[2]->range().start.line, 2); } { QString a = "#undef __attribute__\n__attribute__((visibility(\"default\")))"; QString preprocessed = preprocess(IndexedString(), a, includes); kDebug() << "preprocessed:" << preprocessed; QVERIFY(!preprocessed.contains ("__attribute__")); } { QString a = "#ifdef __attribute__\npassed\n#else\nfailed\n#endif"; QString preprocessed = preprocess(IndexedString(), a, includes); kDebug() << "preprocessed: " << preprocessed; QVERIFY(!preprocessed.contains("failed")); QVERIFY(preprocessed.contains("passed")); } { QString a = "#define Q(c) c ## ULL \n void test() {int i = Q(0x5);}"; QString preprocessed = preprocess(IndexedString(), a, includes); kDebug() << "preprocessed:" << preprocessed; TopDUContext* top = parse(a.toLocal8Bit(), DumpNone); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(top->childContexts().count(), 2); QCOMPARE(top->childContexts()[1]->localDeclarations().count(), 1); } { QString a = "#define MA(x) T a\n #define MB(x) T\n #define MC(X) int\n #define MD(X) c\n template struct A {}; template struct T {}; int main(int argc, char ** argv) { MA(A); A b; MC(a)MD(b); MC(a)d; }"; QString preprocessed = preprocess(IndexedString(), a, includes); kDebug() << "preprocessed:" << preprocessed; TopDUContext* top = parse(a.toUtf8(), DumpAll); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(top->childContexts().count(), 6); QCOMPARE(top->childContexts()[5]->localDeclarations().count(), 4); } #ifdef TEST_MACRO_EXPANSION_ORDER //Not working yet {//No macro-expansion should happen on the first layer of a macro-call QString preprocessed = preprocess(IndexedString(), "#define VAL_KIND A \n#define DO_CAT_I(a, b) a ## b \n#define DO_CAT(a, b) DO_CAT_I(a, b) \nint DO_CAT(Value_, VAL_KIND); \nint DO_CAT_I(Value_, VAL_KIND);\n int VAL_KIND;\nint DO_CAT(VAL_KIND, _Value);\nint DO_CAT(VAL_KIND, _Value);\n", includes); kDebug() << preprocessed; TopDUContext* top = parse(preprocessed.toUtf8(), DumpNone); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(top->localDeclarations().count(), 5); QCOMPARE(top->localDeclarations()[0]->identifier(), Identifier("Value_A")); QCOMPARE(top->localDeclarations()[1]->identifier(), Identifier("Value_VAL_KIND")); QCOMPARE(top->localDeclarations()[1]->identifier(), Identifier("A")); QCOMPARE(top->localDeclarations()[0]->identifier(), Identifier("A_Value")); QCOMPARE(top->localDeclarations()[0]->identifier(), Identifier("VAL_KIND_Value")); } #endif {//Test macro redirection QString test = preprocess(IndexedString(), "#define M1(X) X ## _m1 \n#define M2(X) M ## X\n#define M3 M2\n#define M4 M3 \nM4(1)(hallo)", includes); kDebug() << test; QCOMPARE(test.trimmed(), QString("hallo_m1")); } {//Test replacement of merged preprocessor function calls TopDUContext* top = parse(QByteArray("#define MACRO_1(X) X ## _fromMacro1 \n#define A(pred, n) MACRO_ ## n(pred) \n#define D(X,Y) A(X ## Y, 1) \nint D(a,ba);"), DumpNone); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(top->localDeclarations().count(), 1); QCOMPARE(top->localDeclarations()[0]->identifier(), Identifier("aba_fromMacro1")); } {//Test merging TopDUContext* top = parse(QByteArray("#define D(X,Y) X ## Y \nint D(a,ba);"), DumpNone); IncludeFileList includes; kDebug() << preprocess(IndexedString("somefile"), "#define D(X,Y) X ## Y \nint D(a,ba);", includes); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(top->localDeclarations().count(), 1); QCOMPARE(top->localDeclarations()[0]->identifier(), Identifier("aba")); } { TopDUContext* top = parse(QByteArray("#define MERGE(a, b) a ## b \n#define MERGE_WITH_PARENS(par) MERGE ## par \nint MERGE_WITH_PARENS((int, B));"), DumpNone); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(top->localDeclarations().count(), 1); QCOMPARE(top->localDeclarations()[0]->identifier(), Identifier("intB")); } {//Test simple #if TopDUContext* top = parse(QByteArray("#define X\n#if defined(X)\nint xDefined;\n#endif\n#if !defined(X)\nint xNotDefined;\n#endif\n#if (!defined(X))\nint xNotDefined2;\n#endif"), DumpNone); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(top->localDeclarations().count(), 1); QCOMPARE(top->localDeclarations()[0]->identifier(), Identifier("xDefined")); } {//Test simple #if TopDUContext* top = parse(QByteArray("#if defined(X)\nint xDefined;\n#endif\n#if !defined(X)\nint xNotDefined;\n#endif\n#if (!defined(X))\nint xNotDefined2;\n#endif"), DumpNone); DUChainWriteLocker lock(DUChain::lock()); QVERIFY(top->localDeclarations().count() >= 1); QCOMPARE(top->localDeclarations()[0]->identifier(), Identifier("xNotDefined")); QCOMPARE(top->localDeclarations().count(), 2); QCOMPARE(top->localDeclarations()[1]->identifier(), Identifier("xNotDefined2")); } {//Test multi-line definitions TopDUContext* top = parse(QByteArray("#define X \\\nint i;\\\nint o;\nX;\n"), DumpNone); IncludeFileList includes; DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(top->localDeclarations().count(), 2); } {//Test multi-line definitions TopDUContext* top = parse(QByteArray("#define THROUGH_DEFINE(X) X\nclass B {\nclass C{\n};\nC* cPcPcPcPcPcPcPcPcP;\n};\nB* bP;\nvoid test() {\nTHROUGH_DEFINE(bP->cPcPcPcPcPcPcPcPcP);\n}\n"), DumpNone); IncludeFileList includes; DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(top->childContexts().count(), 3); QCOMPARE(top->childContexts()[0]->localDeclarations().count(), 2); QCOMPARE(top->childContexts()[0]->localDeclarations()[1]->uses().size(), 1); QCOMPARE(top->childContexts()[0]->localDeclarations()[1]->uses().begin()->count(), 1); QCOMPARE(top->childContexts()[0]->localDeclarations()[1]->uses().begin()->at(0).start.column, 19); QCOMPARE(top->childContexts()[0]->localDeclarations()[1]->uses().begin()->at(0).end.column, 37); } {//Test merging TopDUContext* top = parse(QByteArray("#define D(X,Y) X ## Y \nint D(a,ba);"), DumpNone); IncludeFileList includes; kDebug() << preprocess(IndexedString("somefile"), "#define D(X,Y) X ## Y \nint D(a,ba);", includes); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(top->localDeclarations().count(), 1); QCOMPARE(top->localDeclarations()[0]->identifier(), Identifier("aba")); } {//Test merging TopDUContext* top = parse(QByteArray("#define A(x) int x;\n#define B(name) A(bo ## name)\nB(Hallo)"), DumpNone); IncludeFileList includes; DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(top->localDeclarations().count(), 1); QCOMPARE(top->localDeclarations()[0]->identifier(), Identifier("boHallo")); } {//Test __builtin_offsetof TopDUContext* top = parse(QByteArray("typedef struct a { int i; } t_a; int o = __builtin_offsetof(t_a, i);"), DumpNone); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(top->localDeclarations().count(), 3); } {//Test __builtin_offsetof with a struct type (make sure spaces are handled correctly by the macro) TopDUContext* top = parse(QByteArray("struct a { int i; }; int o = __builtin_offsetof(struct a, i);"), DumpNone); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(top->localDeclarations().count(), 2); } { QString parsed = preprocess(IndexedString("somefile"), "#define NC(...) __VA_ARGS__\nNC(bla,bla)\n", includes) .replace(QRegExp("[\n\t ]+"), ""); QEXPECT_FAIL("", "Variadic macros unsupported", Continue); QCOMPARE(parsed, QString("bla,bla")); // fallback: at least don't fail parsing altogether, see https://bugs.kde.org/show_bug.cgi?id=308556 QCOMPARE(parsed, QString()); parsed = preprocess(IndexedString("somefile"), "#define PUT_BETWEEN(x,y) x y x\n#define NC(...) __VA_ARGS__\nPUT_BETWEEN(NC(pair), c)\n", includes) .replace(QRegExp("[\n\t ]+"), " ").trimmed(); QEXPECT_FAIL("", "Variadic macros unsupported", Continue); QCOMPARE(parsed, QString("pair c pair")); } } void TestCppCodeCompletion::testArgumentList() { QMap codeToArgList; codeToArgList.insert("void foo(int arg[]){}", "(int arg[])"); codeToArgList.insert("void foo(int arg[1]){}", "(int arg[1])"); codeToArgList.insert("void foo(int arg[][1]){}", "(int arg[][1])"); codeToArgList.insert("void foo(int arg[1][1]){}", "(int arg[1][1])"); codeToArgList.insert("void foo(int arg[][1][1]){}", "(int arg[][1][1])"); codeToArgList.insert("void foo(void){}", "(void)"); codeToArgList.insert("void foo(int){}", "(int)"); QMap< QByteArray, QString >::const_iterator it = codeToArgList.constBegin(); while (it != codeToArgList.constEnd()){ qDebug() << "input function is:" << it.key(); TopDUContext* top = parse(it.key(), DumpNone); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(top->localDeclarations().size(), 1); CompletionItemTester complCtx(top, ""); Cpp::NormalDeclarationCompletionItem item(DeclarationPointer(top->localDeclarations().first()), KSharedPtr(complCtx.completionContext.data())); QString ret; Cpp::createArgumentList(item, ret, 0); QCOMPARE(ret, it.value()); release(top); ++it; } } void TestCppCodeCompletion::testStaticMethods() { QByteArray code("struct A {\n" " public: static void myPublicStatic() {}\n" " protected: static void myProtectedStatic() {}\n" " private: static void myPrivateStatic() {}\n" " public: void myPublicNonStatic() {}\n" " protected: void myProtectedNonStatic() {}\n" " private: void myPrivateNonStatic() {}\n" "};\n" "A a; int main() { return 0;}"); TopDUContext* top = parse(code, DumpNone); DUChainWriteLocker lock(DUChain::lock()); QVERIFY(top->problems().isEmpty()); QCOMPARE(top->childContexts().size(), 3); { // in body of main CompletionItemTester complCtx(top->childContexts().last(), "a."); QVERIFY(complCtx.completionContext->isValid()); QCOMPARE(complCtx.names, QStringList() << "myPublicStatic" << "myPublicNonStatic"); } { // in body of main CompletionItemTester complCtx(top->childContexts().last(), "A::"); QVERIFY(complCtx.completionContext->isValid()); QEXPECT_FAIL("", "non-static functions don't get filtered. comment in context.cpp: ///@todo what NOT to show on static member choose? Actually we cannot hide all non-static functions, because of function-pointers", Continue); QCOMPARE(complCtx.names, QStringList() << "myPublicStatic"); // fails and gets skipped // this is a fallback to verify the current behavior QCOMPARE(complCtx.names, QStringList() << "myPublicStatic" << "myPublicNonStatic"); } { // in body of myPrivate CompletionItemTester complCtx(top->childContexts().first()->childContexts().last(), "this->"); QVERIFY(complCtx.completionContext->isValid()); QCOMPARE(complCtx.names, QStringList() << "myPublicStatic" << "myProtectedStatic" << "myPrivateStatic" << "myPublicNonStatic" << "myProtectedNonStatic" << "myPrivateNonStatic"); } { // in body of myPrivate CompletionItemTester complCtx(top->childContexts().first()->childContexts().last(), "A::"); QVERIFY(complCtx.completionContext->isValid()); QEXPECT_FAIL("", "non-static functions don't get filtered. comment in context.cpp: ///@todo what NOT to show on static member choose? Actually we cannot hide all non-static functions, because of function-pointers", Continue); QCOMPARE(complCtx.names, QStringList() << "myPublicStatic" << "myProtectedStatic" << "myPrivateStatic"); // fails and gets skipped // this is a fallback to verify the current behavior QCOMPARE(complCtx.names, QStringList() << "myPublicStatic" << "myProtectedStatic" << "myPrivateStatic" << "myPublicNonStatic" << "myProtectedNonStatic" << "myPrivateNonStatic"); } release(top); } void TestCppCodeCompletion::testStringInComment_data() { QTest::addColumn("prefix"); QTest::newRow("cpp comment") << QString("/* \" */\n"); QTest::newRow("c comment") << QString("// \"\n"); } void TestCppCodeCompletion::testStringInComment() { QByteArray code(""); TopDUContext* top = parse(code, DumpNone); DUChainWriteLocker lock(DUChain::lock()); QVERIFY(top->problems().isEmpty()); QFETCH(QString, prefix); CompletionItemTester complCtx(top, prefix); QVERIFY(complCtx.completionContext->isValid()); } void TestCppCodeCompletion::testProperties() { QByteArray code( "class A{\n" " __qt_property__(int myProp READ myProp WRITE setMyProp);\n" "public:\n" " int myProp() const;\n" // actual getter " void setMyProp(int);\n" // actual setter "};\n" " A aStack; A* aHeap = new A; int main() {}"); TopDUContext* top = parse(code, DumpNone); DUChainWriteLocker lock; QVERIFY(top->problems().isEmpty()); QVector declarations = top->childContexts().first()->localDeclarations(); QCOMPARE(declarations.count(), 2); Declaration* getter = declarations.first(); QVERIFY(getter->isFunctionDeclaration()); Declaration* setter = declarations.last(); QVERIFY(getter->isFunctionDeclaration()); DUContext* ctx = top->childContexts().last(); QCOMPARE(ctx->type(), DUContext::Other); { CompletionItemTester complCtx(ctx, "aStack."); QVERIFY(complCtx.completionContext->isValid()); QCOMPARE(complCtx.completionContext->accessType(), Cpp::CodeCompletionContext::MemberAccess); QCOMPARE(complCtx.completionContext->onlyShow(), Cpp::CodeCompletionContext::ShowAll); QVERIFY(complCtx.containsDeclaration(getter)); QVERIFY(complCtx.containsDeclaration(setter)); } { CompletionItemTester complCtx(ctx, "aHeap->"); QVERIFY(complCtx.completionContext->isValid()); QCOMPARE(complCtx.completionContext->accessType(), Cpp::CodeCompletionContext::ArrowMemberAccess); QCOMPARE(complCtx.completionContext->onlyShow(), Cpp::CodeCompletionContext::ShowAll); QVERIFY(complCtx.containsDeclaration(getter)); QVERIFY(complCtx.containsDeclaration(setter)); } release(top); } void TestCppCodeCompletion::testAnonStruct() { QByteArray code("void foo() { struct { int a; } myStruct; }"); TopDUContext* top = parse(code, DumpNone); DUChainWriteLocker lock; QVERIFY(top->problems().isEmpty()); CompletionItemTester complCtx(top->childContexts().last(), ""); QVERIFY(!complCtx.names.contains("")); release(top); } void TestCppCodeCompletion::testOverrideCtor() { QByteArray code("class A { A(int) {} }; class B : public A { B(float) {} }; class C : public B { C(float) {} };"); TopDUContext* top = parse(code, DumpNone); DUChainWriteLocker lock; QVERIFY(top->problems().isEmpty()); Declaration* aCtor = top->childContexts().at(0)->localDeclarations().first(); Declaration* bCtor = top->childContexts().at(1)->localDeclarations().first(); { // in B we should see A's ctor CompletionItemTester complCtx(top->childContexts().at(1), ""); QVERIFY(complCtx.containsDeclaration(aCtor)); QVERIFY(!complCtx.containsDeclaration(bCtor)); } { // in C we should _only_ see B's ctor. Since it's already overridden, nothing should be shown. CompletionItemTester complCtx(top->childContexts().at(2), ""); QVERIFY(!complCtx.containsDeclaration(aCtor)); QVERIFY(!complCtx.containsDeclaration(bCtor)); } release(top); } void TestCppCodeCompletion::testFilterVoid() { QByteArray code("struct S{}; void foo() {}"); TopDUContext* top = parse(code, DumpNone); DUChainWriteLocker lock; QVERIFY(top->problems().isEmpty()); // filter void here foreach(const QString& s, QStringList() << "int i = " << "S* sPtr = ") { CompletionItemTester complCtx(top->childContexts().last(), s); QVERIFY(!complCtx.containsDeclaration(top->localDeclarations().last())); } // but not here CompletionItemTester complCtx(top->childContexts().last(), "void (*fPtr)() ="); QVERIFY(complCtx.containsDeclaration(top->localDeclarations().last())); release(top); } void TestCppCodeCompletion::testExecuteKeepWord_data() { QTest::addColumn("code"); QTest::addColumn("expectedCode"); QTest::newRow("replaceWord") << "\nstruct Foo { };\nFoo f;\nfbar();" << "f();"; QTest::newRow("structfunction") << "\nstruct Foo { int bar() { return 0; } };\nFoo f;\nfbar();" << "f.bar();"; QTest::newRow("pointer") << "\nstruct Foo { int bar() { return 0; } };\nFoo *f;\nfbar();" << "f->bar();"; QTest::newRow("reference") << "\nstruct Foo { int bar() { return 0; } };\nFoo &f;\nfbar();" << "f.bar();"; QTest::newRow("smartPtr") << "template struct SmartPointer { T* operator->() {return 0;}};\nstruct Foo { int bar() { return 0; } };\nSmartPointer f;\nfbar();" << "f->bar();"; QTest::newRow("enum") << "\nclass f { enum Foo { xy, xz }; };\n\nfFoo x;" << "f::Foo x;"; QTest::newRow("staticfunction") << "struct f { static void bar() {} };\n\n\nfbar();" << "f::bar();"; } void TestCppCodeCompletion::testExecuteKeepWord() { QFETCH(QString, code); TopDUContext* top = parse(code.toAscii(), DumpAll); KTextEditor::Editor* editor = KTextEditor::EditorChooser::editor(); QVERIFY(editor); KTextEditor::Document* doc = editor->createDocument(this); QVERIFY(doc); doc->setText(code); doc->startEditing(); KTextEditor::View *v = doc->createView(0); v->setCursorPosition(KTextEditor::Cursor(3, 1)); DUChainWriteLocker lock; QVERIFY(top->problems().isEmpty()); CompletionItemTester complCtx(top->childContexts().last(), ""); KSharedPtr item; for(int i=0; iexecute(doc, KTextEditor::Range(3, 0, 3, 4)); QFETCH(QString, expectedCode); QCOMPARE(doc->line(3), expectedCode); doc->endEditing(); release(top); delete v; delete doc; } //BEGIN: Helper class TestPreprocessor : public rpp::Preprocessor { public: TestCppCodeCompletion* cc; IncludeFileList& included; rpp::pp* pp; bool stopAfterHeaders; Cpp::EnvironmentFilePointer environmentFile; TestPreprocessor( TestCppCodeCompletion* _cc, IncludeFileList& _included, bool _stopAfterHeaders ) : cc(_cc), included(_included), pp(0), stopAfterHeaders(_stopAfterHeaders) { } rpp::Stream* sourceNeeded(QString& fileName, rpp::Preprocessor::IncludeType /*type*/, int sourceLine, bool /*skipCurrentPath*/) { QMap::const_iterator it = cc->fakeIncludes.constFind(fileName); if( it != cc->fakeIncludes.constEnd() || !pp ) { kDebug(9007) << "parsing included file \"" << fileName << "\""; included << LineContextPair( dynamic_cast(cc->parse( (*it).toUtf8(), TestCppCodeCompletion::DumpNone, pp, KUrl(it.key()))), sourceLine ); } else { kDebug(9007) << "could not find include-file \"" << fileName << "\""; } return 0; } void setPp( rpp::pp* _pp ) { pp = _pp; } virtual void headerSectionEnded(rpp::Stream& stream) { if( environmentFile ) environmentFile->setContentStartLine( stream.originalInputPosition().line ); if(stopAfterHeaders) stream.toEnd(); } virtual void foundHeaderGuard(rpp::Stream& /*stream*/, KDevelop::IndexedString guardName) { environmentFile->setHeaderGuard(guardName); } }; QString TestCppCodeCompletion::preprocess( const IndexedString& url, const QString& text, IncludeFileList& included, rpp::pp* parent, bool stopAfterHeaders, KSharedPtr* paramEnvironmentFile, rpp::LocationTable** returnLocationTable, PreprocessedContents* targetContents ) { TestPreprocessor ppc( this, included, stopAfterHeaders ); rpp::pp preprocessor(&ppc); ppc.setPp( &preprocessor ); KSharedPtr environmentFile; if( paramEnvironmentFile && *paramEnvironmentFile ) environmentFile = *paramEnvironmentFile; else environmentFile = Cpp::EnvironmentFilePointer( new Cpp::EnvironmentFile( IndexedString(url.str()), 0 ) ); ppc.environmentFile = environmentFile; if( paramEnvironmentFile ) *paramEnvironmentFile = environmentFile; CppPreprocessEnvironment* currentEnvironment = new CppPreprocessEnvironment( environmentFile ); preprocessor.setEnvironment( currentEnvironment ); currentEnvironment->setEnvironmentFile( environmentFile ); if( parent ) preprocessor.environment()->swapMacros(parent->environment()); else currentEnvironment->merge(CppUtils::standardMacros()); PreprocessedContents contents = preprocessor.processFile(url.str(), text.toUtf8()); if(targetContents) *targetContents = contents; QString result = QString::fromUtf8(stringFromContents(contents)); if (returnLocationTable) *returnLocationTable = preprocessor.environment()->takeLocationTable(); currentEnvironment->finishEnvironment(); if( parent ) { preprocessor.environment()->swapMacros(parent->environment()); static_cast(parent->environment())->environmentFile()->merge(*environmentFile); } return result; } TopDUContext* TestCppCodeCompletion::parse(const QByteArray& unit, DumpAreas dump, rpp::pp* parent, KUrl _identity, TopDUContext* update) { if (dump) kDebug(9007) << "==== Beginning new test case...:" << endl << unit; ParseSession* session = new ParseSession(); ; static int testNumber = 0; IndexedString url(QString("file:///internal/%1").arg(testNumber++)); if( !_identity.isEmpty() ) url = IndexedString(_identity); IncludeFileList included; QList temporaryIncluded; rpp::LocationTable* locationTable; Cpp::EnvironmentFilePointer file; PreprocessedContents contents; preprocess( url, QString::fromUtf8(unit), included, parent, false, &file, &locationTable, &contents ); session->setContents( contents, locationTable ); if( parent ) { //Temporarily insert all files parsed previously by the parent, so forward-declarations can be resolved etc. TestPreprocessor* testPreproc = dynamic_cast(parent->preprocessor()); if( testPreproc ) { foreach( LineContextPair include, testPreproc->included ) { if( !containsContext( included, include.context ) ) { included.push_front( include ); temporaryIncluded << include.context; } } } else { kDebug(9007) << "PROBLEM"; } } Parser parser(&control); TranslationUnitAST* ast = parser.parse(session); ast->session = session; if (dump & DumpAST) { kDebug(9007) << "===== AST:"; cppDumper.dump(ast, session); } DeclarationBuilder definitionBuilder(session); TopDUContext* top = definitionBuilder.buildDeclarations(file, ast, &included, ReferencedTopDUContext(update)); UseBuilder useBuilder(session); useBuilder.buildUses(ast); foreach(KDevelop::ProblemPointer problem, useBuilder.problems()) { DUChainWriteLocker lock(DUChain::lock()); top->addProblem(problem); } if (dump & DumpDUChain) { kDebug(9007) << "===== DUChain:"; DUChainWriteLocker lock(DUChain::lock()); KDevelop::dumpDUContext(top); } if( parent ) { //Remove temporarily inserted files parsed previously by the parent DUChainWriteLocker lock(DUChain::lock()); TestPreprocessor* testPreproc = dynamic_cast(parent->preprocessor()); if( testPreproc ) { foreach( DUContext* context, temporaryIncluded ) top->removeImportedParentContext( context ); } else { kDebug(9007) << "PROBLEM"; } } if (dump) kDebug(9007) << "===== Finished test case."; delete session; return top; } void TestCppCodeCompletion::testCompletedIncludeFilePath() { KTempDir tempDir; QDir dir(tempDir.name()); QString innerDirName = "directoryabcde"; dir.mkdir(innerDirName); QDir innerDir(tempDir.name() + innerDirName); QString filename = "xxxxx.h"; QFile file(innerDir.absoluteFilePath(filename)); QVERIFY(file.open(QIODevice::ReadWrite)); QList includeItems = CppUtils::allFilesInIncludePath(QString(tempDir.name() + "source.cpp"), true, innerDirName, QStringList() << tempDir.name()); QCOMPARE(includeItems.size(), 1); QCOMPARE(includeItems[0].basePath, KUrl(innerDir.absolutePath())); } /** * Check that there are no multiple include completion items referring to the same file */ void TestCppCodeCompletion::testMultipleIncludeCompletionItems() { KTempDir tempDir; QDir dir(tempDir.name()); QString innerDirName1 = "directory1abcde"; dir.mkdir(innerDirName1); QString innerDirName2 = "directory2abcde"; dir.mkdir(innerDirName2); QDir innerDir1(tempDir.name() + innerDirName1); QString filename = "xxxxx.h"; QFile file(innerDir1.absoluteFilePath(filename)); QVERIFY(file.open(QIODevice::ReadWrite)); QList includeItems = CppUtils::allFilesInIncludePath(innerDir1.absoluteFilePath("source.cpp"), true, QString("../" + innerDirName1), QStringList() << QString(tempDir.name() + innerDirName1) << QString(tempDir.name() + innerDirName2)); QCOMPARE(includeItems.size(), 1); QCOMPARE(includeItems[0].basePath, KUrl(innerDir1.absolutePath())); } void TestCppCodeCompletion::testAfterVisibility_data() { QTest:: addColumn("vis"); QTest::newRow("public") << "public:"; QTest::newRow("protected") << "public:"; QTest::newRow("private") << "public:"; // happens e.g. after Q_OBJECT QTest::newRow("private-private") << "private:private:"; QTest::newRow("private-public") << "private:public:"; QTest::newRow("private-protected") << "private:protected:"; } void TestCppCodeCompletion::testAfterVisibility() { QByteArray code("struct b { virtual void foo(); }; struct c : public b {};"); TopDUContext* top = parse(code, DumpNone); DUChainWriteLocker lock; QVERIFY(top->problems().isEmpty()); QFETCH(QString, vis); CompletionItemTester complCtx(top->childContexts().last(), vis); QVERIFY(complCtx.completionContext->isValid()); QVERIFY(complCtx.containsDeclaration(top->findDeclarations(QualifiedIdentifier("b::foo")).first())); release(top); } void TestCppCodeCompletion::testNoQuadrupleColon() { QByteArray code("namespace Foobar { static int var; }\n main() { Foobar::var; }"); TopDUContext* top = parse(code, DumpNone); DUChainWriteLocker lock; QVERIFY(top->problems().isEmpty()); CompletionItemTester tester(top->childContexts().last()); QVERIFY(tester.completionContext->isValid()); KSharedPtr item; for( int i = 0; i < tester.items.length(); ++i ) { if( tester.itemData( i ).toString() == "Foobar" ) { item = tester.items.at( i ); } } QVERIFY( !item.isNull() ); KTextEditor::Editor* editor = KTextEditor::EditorChooser::editor(); QVERIFY(editor); KTextEditor::Document* doc = editor->createDocument(this); QVERIFY(doc); // verify it adds the "::" when the doc is empty doc->setText(""); KTextEditor::View* v = doc->createView(0); doc->startEditing(); KTextEditor::Cursor c( 0, 0 ); v->setCursorPosition( c ); item->execute( doc, Range( c, 0 ) ); QCOMPARE( doc->line( 0 ), QString("Foobar::") ); // verify it doesn't when there's already a "::" doc->setText("::var;"); v->setCursorPosition( c ); item->execute( doc, Range( c, 0 ) ); QCOMPARE( doc->line( 0 ), QString("Foobar::var;") ); doc->endEditing(); } void TestCppCodeCompletion::testLookaheadMatches_data() { QTest::addColumn("insert"); // inserted code QTest::addColumn("completions"); // completions offered QStringList all; all << "m_one" << "m_two" << "m_smartOne" << "m_access" << "ThreeTwoOne" << "One" << "Two" << "OneSmartPointer" << "Access" << "OneTwoThree" << "this"; QTest::newRow("Function Arg") << "m_smartOne.setOne(" << (QStringList() << "m_two.hatPointer" << "m_smartOne.operator->" << "setOne" << all); QTest::newRow("Smart Pointer") << "int foo = " << (QStringList() << "int =" << "m_one.alsoRan" << "m_two.meToo" << "m_smartOne->alsoRan" << "m_access.publicMember" << all); QTest::newRow("Type Conversions") << "m_smartOne = " << (QStringList() << "OneSmartPointer m_smartOne =" << "m_two.hatPointer" << all ); QTest::newRow("Assignment") << "m_one = " << (QStringList() << "One m_one =" << "m_two.hat" << all ); QTest::newRow("Equality") << "m_one == " << (QStringList() << "m_two.hat" << all ); QTest::newRow("ReturnAccess") << "return" << (QStringList() << "return int" << "m_one.alsoRan" << "m_two.meToo" << "m_smartOne->alsoRan" << "m_access.publicMember" << all ); QTest::newRow("No Lookahead") << "One::NoLookahead test = " << (QStringList() << "One::NoLookahead NoLookahead =" << "NO" << "CAN" << "SEE" << all ); } void TestCppCodeCompletion::testLookaheadMatches() { QByteArray test = "struct One { enum NoLookahead { NO, CAN, SEE, }; int alsoRan; typedef int myInt; };" "struct Two { One hat; One *hatPointer; int meToo(); };" "struct OneSmartPointer { OneSmartPointer(One*) {}; void setOne(One*); One* operator->() const {} };" "class Access{ int privateMember; public: int publicMember; };" "struct OneTwoThree { One m_one; Two m_two; OneSmartPointer m_smartOne; Access m_access; int ThreeTwoOne() { } };"; QFETCH(QString, insert); QFETCH(QStringList, completions); TopDUContext* top = parse(test, DumpNone); DUChainWriteLocker lock(DUChain::lock()); DUContext *testContext = top->childContexts()[4]->childContexts()[1]; CompletionItemTester tester(testContext, insert); qDebug() << tester.names << "VERSUS" << completions; QCOMPARE(tester.names.toSet(), completions.toSet()); release(top); } void TestCppCodeCompletion::testMemberAccessInstance() { QByteArray test = "struct foo{}; int main() {}"; TopDUContext* top = parse(test, DumpNone); DUChainWriteLocker lock(DUChain::lock()); CompletionItemTester tester(top->childContexts()[2], "foo."); QCOMPARE(tester.names, QStringList()); release(top); } void TestCppCodeCompletion::testNestedInlineNamespace() { QByteArray test = "namespace a { inline namespace b { void foo(); } } int main() {}"; TopDUContext* top = parse(test, DumpAll); DUChainWriteLocker lock; CompletionItemTester tester(top->childContexts()[2], "a::"); QCOMPARE(tester.names, QStringList() << "b" << "foo" ); release(top); } void TestCppCodeCompletion::testDuplicatedNamespace() { // see also: https://bugs.kde.org/show_bug.cgi?id=328803 QByteArray test( "namespace foo { const int bar = 1; }\n" "namespace foo { const int asdf = 1; }\n" "namespace foo { }\n" "int main() {}\n" ); TopDUContext* top = parse(test, DumpNone); DUChainWriteLocker lock; CompletionItemTester tester(top->childContexts().last(), "foo::"); //TODO: the sort-order is apparently undefined... qSort(tester.names); QCOMPARE(tester.names, QStringList() << "asdf" << "bar" ); release(top); } #include "test_cppcodecompletion.moc"