File indexing completed on 2024-05-19 04:36:55

0001 /*
0002     SPDX-FileCopyrightText: 2008 Niko Sams <niko.sams@gmail.com>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-only
0005 */
0006 
0007 #include "test_completion.h"
0008 
0009 #include <QtTest>
0010 
0011 #include <language/duchain/parsingenvironment.h>
0012 #include <language/duchain/duchain.h>
0013 #include <language/duchain/duchainlock.h>
0014 #include <language/duchain/topducontext.h>
0015 #include <language/duchain/declaration.h>
0016 #include <language/duchain/duchainpointer.h>
0017 #include <language/duchain/codemodel.h>
0018 #include <language/codecompletion/codecompletiontesthelper.h>
0019 #include <language/duchain/types/alltypes.h>
0020 
0021 #include "../../duchain/types/structuretype.h"
0022 #include "../../duchain/declarations/functiondeclaration.h"
0023 
0024 #include "context.h"
0025 #include "item.h"
0026 #include "helpers.h"
0027 #include "model.h"
0028 
0029 using namespace KTextEditor;
0030 using namespace KDevelop;
0031 
0032 QTEST_MAIN(Php::TestCompletion)
0033 
0034 namespace Php
0035 {
0036 
0037 class TestCodeCompletionModel : public CodeCompletionModel
0038 {
0039 public:
0040     using CodeCompletionModel::foundDeclarations;
0041     //normally set by worker, but in test we don't have a worker
0042     void foundDeclarations(QList<KDevelop::CompletionTreeItemPointer> items, CodeCompletionContext* completionContext)
0043     {
0044         beginResetModel();
0045         m_completionItems.clear();
0046         foreach(const CompletionTreeItemPointer &i, items) {
0047             m_completionItems << QExplicitlySharedDataPointer<CompletionTreeElement>(i);
0048         }
0049         m_completionContext = KDevelop::CodeCompletionContext::Ptr(completionContext);
0050         endResetModel();
0051     }
0052 };
0053 
0054 /**
0055  * declaration of class A with a number of completion items
0056  *
0057  * also introduces a instance of class A named $instA;
0058  */
0059 const QByteArray testClassA(
0060     "class A {"
0061     // start non-static
0062     // public
0063     " public function pubf() {}"             // at(0)
0064     " public $pub;"                          // at(1)
0065     // protected
0066     " protected function protf() {}"         // at(2)
0067     " protected $prot;"                      // at(3)
0068     // private
0069     " private function privf() {}"           // at(4)
0070     " private $priv;"                        // at(5)
0071     // start static
0072     // public
0073     " static public function spubf() {}"     // at(6)
0074     " static public $spub;"                  // at(7)
0075     // const == static public
0076     " const c = 0;"                          // at(8)
0077     // protected
0078     " static protected function sprotf() {}" // at(9)
0079     " static protected $sprot;"              // at(10)
0080     // private
0081     " static private function sprivf() {}"   // at(11)
0082     " static private $spriv;"                // at(12)
0083     "} $instA = new A; "
0084 );
0085 
0086 /**
0087  * declaration of class B which extends class A
0088  * B has one new public member function
0089  *
0090  * also introduces a instance of class B named $instB;
0091  */
0092 const QByteArray testClassB(
0093     "class B extends A {"
0094     "public function __construct(){}" // at(0)
0095     "} $instB = new B; "
0096 );
0097 
0098 class TestCodeCompletionContext : public CodeCompletionContext
0099 {
0100 public:
0101     TestCodeCompletionContext(KDevelop::DUContextPointer context, const QString& text, const QString& followingText, const CursorInRevision &position, int depth = 0)
0102         : CodeCompletionContext(context, text, followingText, position, depth) { }
0103 protected:
0104     QList<QSet<IndexedString> > completionFiles() override {
0105         QList<QSet<IndexedString> > ret;
0106         QSet<IndexedString> set;
0107         set << IndexedString("file:///internal/projecttest0");
0108         set << IndexedString("file:///internal/projecttest1");
0109         ret << set;
0110         return ret;
0111     }
0112 };
0113 
0114 typedef CodeCompletionItemTester<TestCodeCompletionContext> BasePhpCompletionTester;
0115 
0116 /**
0117  * Automatically prepent the test string with "<?php " when it does not start with "<?" already-
0118  * If we would not do that the Tokenizer in the code-completion would not work (always T_INLINE_HTML).
0119  */
0120 class PhpCompletionTester : public BasePhpCompletionTester
0121 {
0122 public:
0123     PhpCompletionTester(DUContext* context, QString text = QStringLiteral("; "), QString followingText = {}, CursorInRevision position = CursorInRevision::invalid())
0124         : BasePhpCompletionTester(context, text.startsWith(QLatin1String("<?")) ? text : text.prepend("<?php "), followingText, position)
0125     {
0126 
0127     }
0128 };
0129 
0130 TestCompletion::TestCompletion()
0131 {
0132 }
0133 
0134 void TestCompletion::dumpCompletionItems(QList<CompletionTreeItemPointer> items)
0135 {
0136     qDebug() << items.count() << "completion items:";
0137     foreach(const CompletionTreeItemPointer &item, items) {
0138         qDebug() << item->declaration()->toString();
0139     }
0140 }
0141 
0142 void TestCompletion::publicObjectCompletion()
0143 {
0144     TopDUContext* top = parse("<?php " + testClassA, DumpNone);
0145     DUChainReleaser releaseTop(top);
0146     DUChainWriteLocker lock(DUChain::lock());
0147 
0148     PhpCompletionTester tester(top, QStringLiteral("$blah; $instA->"));
0149 
0150     QCOMPARE(tester.completionContext->memberAccessOperation(), CodeCompletionContext::MemberAccess);
0151 
0152     QCOMPARE(tester.names, QStringList() << "pubf" << "pub");
0153 }
0154 void TestCompletion::publicStaticObjectCompletion()
0155 {
0156     TopDUContext* top = parse("<?php " + testClassA, DumpNone);
0157     DUChainReleaser releaseTop(top);
0158     DUChainWriteLocker lock(DUChain::lock());
0159 
0160     PhpCompletionTester tester(top, QStringLiteral("$blah; A::"));
0161 
0162     QCOMPARE(tester.completionContext->memberAccessOperation(), CodeCompletionContext::StaticMemberAccess);
0163 
0164     QCOMPARE(tester.names, QStringList() << "spubf" << "$spub" << "c");
0165 }
0166 void TestCompletion::privateObjectCompletion()
0167 {
0168     TopDUContext* top = parse("<?php " + testClassA, DumpNone);
0169     DUChainReleaser releaseTop(top);
0170     DUChainWriteLocker lock(DUChain::lock());
0171 
0172 
0173     DUContext* funContext = top->childContexts().first()->localDeclarations().first()->internalContext();
0174     PhpCompletionTester tester(funContext, QStringLiteral("$this->"));
0175 
0176     QCOMPARE(tester.completionContext->memberAccessOperation(), CodeCompletionContext::MemberAccess);
0177 
0178     QCOMPARE(tester.names, QStringList() << "pubf" << "pub" << "protf" << "prot" << "privf" << "priv");
0179 }
0180 void TestCompletion::privateStaticObjectCompletion()
0181 {
0182     TopDUContext* top = parse("<?php " + testClassA, DumpNone);
0183     DUChainReleaser releaseTop(top);
0184     DUChainWriteLocker lock(DUChain::lock());
0185 
0186     DUContext* funContext = top->childContexts().first()->localDeclarations().first()->internalContext();
0187 
0188     {
0189     PhpCompletionTester tester(funContext, QStringLiteral("self::"));
0190 
0191     QCOMPARE(tester.completionContext->memberAccessOperation(), CodeCompletionContext::StaticMemberAccess);
0192 
0193     QCOMPARE(tester.names, QStringList() << "spubf" << "$spub" << "c" << "sprotf" << "$sprot" << "sprivf" << "$spriv");
0194     }
0195     {
0196     PhpCompletionTester tester(funContext, QStringLiteral("static::"));
0197 
0198     QCOMPARE(tester.completionContext->memberAccessOperation(), CodeCompletionContext::StaticMemberAccess);
0199 
0200     QCOMPARE(tester.names, QStringList() << "spubf" << "$spub" << "c" << "sprotf" << "$sprot" << "sprivf" << "$spriv");
0201     }
0202 }
0203 void TestCompletion::protectedObjectCompletion()
0204 {
0205     TopDUContext* top = parse("<?php " + testClassA + testClassB, DumpNone);
0206     DUChainReleaser releaseTop(top);
0207     DUChainWriteLocker lock(DUChain::lock());
0208 
0209     DUContext* funContext = top->childContexts().at(1)->localDeclarations().first()->internalContext();
0210 
0211     {
0212         PhpCompletionTester tester(funContext, QStringLiteral("$this->"));
0213 
0214         QCOMPARE(tester.completionContext->memberAccessOperation(), CodeCompletionContext::MemberAccess);
0215 
0216         QCOMPARE(tester.names, QStringList() << "__construct" << "pubf" << "pub" << "protf" << "prot");
0217     }
0218 
0219     {
0220         PhpCompletionTester tester(funContext, {});
0221 
0222         QCOMPARE(tester.completionContext->memberAccessOperation(), CodeCompletionContext::NoMemberAccess);
0223 
0224         qDebug() << tester.names;
0225 
0226         QVERIFY(tester.names.contains("$this->__construct"));
0227         QVERIFY(tester.names.contains("$this->pubf"));
0228         QVERIFY(tester.names.contains("$this->pub"));
0229         QVERIFY(tester.names.contains("self::spubf"));
0230         QVERIFY(tester.names.contains("self::$spub"));
0231         QVERIFY(tester.names.contains("$this->protf"));
0232         QVERIFY(tester.names.contains("$this->prot"));
0233         QVERIFY(tester.names.contains("self::sprotf"));
0234         QVERIFY(tester.names.contains("self::$sprot"));
0235         QVERIFY(tester.names.contains("self::c"));
0236         QVERIFY(!tester.names.contains("self::sprivf"));
0237         QVERIFY(!tester.names.contains("self::$spriv"));
0238         QVERIFY(!tester.names.contains("$this->privf"));
0239         QVERIFY(!tester.names.contains("$this->$priv"));
0240     }
0241 }
0242 void TestCompletion::protectedStaticObjectCompletion()
0243 {
0244     TopDUContext* top = parse("<?php " + testClassA + testClassB, DumpNone);
0245     DUChainReleaser releaseTop(top);
0246     DUChainWriteLocker lock(DUChain::lock());
0247 
0248     DUContext* funContext = top->childContexts().at(1)->localDeclarations().first()->internalContext();
0249     PhpCompletionTester tester(funContext, QStringLiteral("self::"));
0250 
0251     QCOMPARE(tester.completionContext->memberAccessOperation(), CodeCompletionContext::StaticMemberAccess);
0252 
0253     QCOMPARE(tester.names, QStringList() << "spubf" << "$spub" << "c" << "sprotf" << "$sprot");
0254 }
0255 
0256 void TestCompletion::methodCall()
0257 {
0258     //                 0         1         2         3         4         5         6         7
0259     //                 01234567890123456789012345678901234567890123456789012345678901234567890123456789
0260     QByteArray method("<? class A { public function foo(A $a, $b = null) {} } $i = new A();");
0261 
0262     TopDUContext* top = parse(method, DumpAll);
0263     DUChainReleaser releaseTop(top);
0264     DUChainWriteLocker lock(DUChain::lock());
0265 
0266     {
0267         PhpCompletionTester tester(top, QStringLiteral("$blah; $i->foo("));
0268         QCOMPARE(tester.completionContext->memberAccessOperation(), CodeCompletionContext::NoMemberAccess);
0269         QVERIFY(tester.completionContext->parentContext());
0270         QCOMPARE(tester.completionContext->parentContext()->memberAccessOperation(),
0271                  CodeCompletionContext::FunctionCallAccess);
0272 
0273         CompletionTreeItemPointer item = searchDeclaration(tester.items, top->childContexts().at(0)->localDeclarations().at(0));
0274         QVERIFY(item);
0275         NormalDeclarationCompletionItem* item2 = dynamic_cast<NormalDeclarationCompletionItem*>(item.data());
0276 
0277         QString ret;
0278         createArgumentList(*item2, ret, nullptr);
0279         QCOMPARE(ret, QString("(A $a, null $b = null)"));
0280     }
0281     {
0282         PhpCompletionTester tester(top, QStringLiteral("blah; $i->foo(new A(), "));
0283         QVERIFY(searchDeclaration(tester.items, top->childContexts().at(0)->localDeclarations().at(0)));
0284     }
0285 }
0286 
0287 void TestCompletion::functionCall()
0288 {
0289     //                 0         1         2         3         4         5         6         7
0290     //                 01234567890123456789012345678901234567890123456789012345678901234567890123456789
0291     QByteArray method("<? $outside = 1; class A {} function foo(A $a, $b = null) {}");
0292 
0293     TopDUContext* top = parse(method, DumpAll);
0294     DUChainReleaser releaseTop(top);
0295     DUChainWriteLocker lock(DUChain::lock());
0296     PhpCompletionTester tester(top, QStringLiteral("blah; foo("));
0297     QVERIFY(tester.completionContext->parentContext());
0298 
0299     QVERIFY(tester.completionContext->parentContext());
0300     QVERIFY(!tester.completionContext->parentContext()->parentContext());
0301     QCOMPARE(tester.completionContext->parentContext()->memberAccessOperation(),
0302              CodeCompletionContext::FunctionCallAccess);
0303 
0304     QVERIFY(searchDeclaration(tester.items, top->localDeclarations().at(1)));
0305 }
0306 
0307 void TestCompletion::nestedFunctionCall_data()
0308 {
0309     QTest::addColumn<QString>("text");
0310 
0311     QTest::newRow("nested") << QStringLiteral("bar(foo(");
0312     QTest::newRow("nested prev arg") << QStringLiteral("bar(1, foo(");
0313     QTest::newRow("nested prev func call") << QStringLiteral("bar(foo(1), foo(");
0314     QTest::newRow("nested prev arg comma") << QStringLiteral("bar(1, bar(1, ");
0315     QTest::newRow("nested prev func comma") << QStringLiteral("bar(1, bar(foo(1), ");
0316 }
0317 
0318 void TestCompletion::nestedFunctionCall()
0319 {
0320     QFETCH(QString, text);
0321 
0322     //                 0         1         2         3         4         5         6         7
0323     //                 01234567890123456789012345678901234567890123456789012345678901234567890123456789
0324     QByteArray method("<? function foo($a) {return 1;} function bar($b, $c) {return 2;}");
0325 
0326     TopDUContext* top = parse(method, DumpNone);
0327     DUChainReleaser releaseTop(top);
0328     DUChainWriteLocker lock;
0329 
0330     PhpCompletionTester tester(top, text);
0331     QVERIFY(tester.completionContext->parentContext());
0332     QVERIFY(tester.completionContext->parentContext()->parentContext());
0333     QVERIFY(!tester.completionContext->parentContext()->parentContext()->parentContext());
0334     QCOMPARE(tester.completionContext->parentContext()->memberAccessOperation(),
0335              CodeCompletionContext::FunctionCallAccess);
0336     QCOMPARE(tester.completionContext->parentContext()->parentContext()->memberAccessOperation(),
0337              CodeCompletionContext::FunctionCallAccess);
0338 
0339     QVERIFY(searchDeclaration(tester.items, top->localDeclarations().at(0)));
0340     QVERIFY(searchDeclaration(tester.items, top->localDeclarations().at(1)));
0341 }
0342 
0343 void TestCompletion::newObjectFromOtherFile()
0344 {
0345 
0346     TopDUContext* addTop = parseAdditionalFile(IndexedString("/duchaintest/foo.php"), "<?php class Foo { function bar() {} } ");
0347     DUChainReleaser releaseAddTop(addTop);
0348 
0349     //                 0         1         2         3         4         5         6         7
0350     //                 01234567890123456789012345678901234567890123456789012345678901234567890123456789
0351     QByteArray method("<? $a = new Foo(); ");
0352 
0353     TopDUContext* top = parse(method, DumpAll);
0354     DUChainReleaser releaseTop(top);
0355     DUChainWriteLocker lock(DUChain::lock());
0356 
0357     PhpCompletionTester tester(top, QStringLiteral("blah; $a->"));
0358     QCOMPARE(tester.items.count(), 1);
0359     QCOMPARE(tester.items.first()->declaration().data(), addTop->childContexts().first()->localDeclarations().first());
0360 }
0361 
0362 void TestCompletion::constantFromOtherFile()
0363 {
0364     TopDUContext* addTop = parseAdditionalFile(
0365         IndexedString("file:///internal/projecttest0"),
0366         "<?php define('FIND_ME', 1); $dontFindMe = 1; "
0367     );
0368     DUChainReleaser releaseAddTop(addTop);
0369 
0370     //                 0         1         2         3         4         5         6         7
0371     //                 01234567890123456789012345678901234567890123456789012345678901234567890123456789
0372     QByteArray method("<? ");
0373     TopDUContext* top = parse(method, DumpAll);
0374     DUChainReleaser releaseTop(top);
0375     DUChainWriteLocker lock(DUChain::lock());
0376 
0377     QCOMPARE(addTop->localDeclarations().size(), 2);
0378     Declaration* findMe = addTop->localDeclarations().first();
0379     Declaration* dontFindMe = addTop->localDeclarations().last();
0380 
0381     PhpCompletionTester tester(top, {});
0382     QVERIFY(searchDeclaration(tester.items, findMe));
0383     QVERIFY(!searchDeclaration(tester.items, dontFindMe));
0384 }
0385 
0386 void TestCompletion::baseClass()
0387 {
0388     QByteArray method("<? class A { public $avar; } class B extends A { public $bvar; } $a = new A(); $b = new B(); ");
0389 
0390     TopDUContext* top = parse(method, DumpAll);
0391     DUChainReleaser releaseTop(top);
0392     DUChainWriteLocker lock(DUChain::lock());
0393 
0394     {
0395         PhpCompletionTester tester(top, QStringLiteral("$a->"));
0396         QCOMPARE(tester.names, QStringList() << "avar");
0397     }
0398 
0399     {
0400         PhpCompletionTester tester(top, QStringLiteral("$b->"));
0401         QCOMPARE(tester.names, QStringList() << "bvar" << "avar");
0402     }
0403 }
0404 
0405 void TestCompletion::extendsFromOtherFile()
0406 {
0407 
0408     TopDUContext* addTop = parseAdditionalFile(IndexedString("/duchaintest/foo.php"), "<?php class A { public $avar; } ");
0409     DUChainReleaser releaseAddTop(addTop);
0410     //                 0         1         2         3         4         5         6         7
0411     //                 01234567890123456789012345678901234567890123456789012345678901234567890123456789
0412     QByteArray method("<? class B extends A { public $bvar; } $b = new B();");
0413 
0414     TopDUContext* top = parse(method, DumpAll);
0415     DUChainReleaser releaseTop(top);
0416     DUChainWriteLocker lock(DUChain::lock());
0417 
0418     PhpCompletionTester tester(top, QStringLiteral("$b->"));
0419     QCOMPARE(tester.items.count(), 2);
0420     QCOMPARE(tester.items.at(1)->declaration().data(), addTop->childContexts().first()->localDeclarations().first());
0421     QCOMPARE(tester.items.at(0)->declaration().data(), top->childContexts().first()->localDeclarations().first());
0422 }
0423 
0424 
0425 void TestCompletion::globalClassFromOtherFile()
0426 {
0427 
0428     TopDUContext* addTop = parseAdditionalFile(IndexedString("/duchaintest/foo.php"), "<?php class A { } ");
0429     DUChainReleaser releaseAddTop(addTop);
0430     //                 0         1         2         3         4         5         6         7
0431     //                 01234567890123456789012345678901234567890123456789012345678901234567890123456789
0432     QByteArray method("<? ");
0433 
0434     TopDUContext* top = parse(method, DumpAll);
0435     DUChainReleaser releaseTop(top);
0436     DUChainWriteLocker lock(DUChain::lock());
0437     /*
0438         PhpCompletionTester tester(top, "new ");
0439         QVERIFY(searchDeclaration(tester.items, addTop->localDeclarations().first()));
0440     */
0441 }
0442 
0443 void TestCompletion::codeModel()
0444 {
0445     DUChainWriteLocker lock(DUChain::lock());
0446     uint count;
0447     const CodeModelItem* items;
0448 
0449     CodeModel::self().addItem(IndexedString("file:///foo"), QualifiedIdentifier(QStringLiteral("identifier")), CodeModelItem::Class);
0450 
0451     CodeModel::self().items(IndexedString("file:///foo"), count, items);
0452     bool found = false;
0453     for (uint i = 0;i < count;++i) {
0454         if (items[0].id.identifier() == QualifiedIdentifier(QStringLiteral("identifier"))) {
0455             found = true;
0456             QCOMPARE(items[i].kind, CodeModelItem::Class);
0457         }
0458     }
0459     QVERIFY(found);
0460 }
0461 
0462 void TestCompletion::projectFileClass()
0463 {
0464     TopDUContext* addTop = parseAdditionalFile(IndexedString("file:///internal/projecttest0"), "<? class B { function invisible() {} } ");
0465     DUChainReleaser releaseAddTop(addTop);
0466 
0467     //                 0         1         2         3         4         5         6         7
0468     //                 01234567890123456789012345678901234567890123456789012345678901234567890123456789
0469     TopDUContext* top = parse("<?php class foo { function bar() {} }", DumpNone, QUrl(QStringLiteral("file:///internal/projecttest1")));
0470     DUChainReleaser releaseTop(top);
0471 
0472     DUChainWriteLocker lock(DUChain::lock());
0473 
0474     {
0475         // outside of class foo
0476         PhpCompletionTester tester(top, QStringLiteral("<?php "));
0477         QVERIFY(searchDeclaration(tester.items, addTop->localDeclarations().first()));
0478     }
0479     {
0480         // inside of class foo, i.e. in its bar() method
0481         PhpCompletionTester tester(top->childContexts().first()->childContexts().first(), QStringLiteral("<?php "));
0482 
0483         qDebug() << tester.names;
0484         // we want to see the class
0485         QVERIFY(searchDeclaration(tester.items, addTop->localDeclarations().first()));
0486         // but not its methods
0487         QVERIFY(!searchDeclaration(tester.items, addTop->childContexts().first()->localDeclarations().first()));
0488     }
0489 }
0490 
0491 
0492 void TestCompletion::variable()
0493 {
0494     //                 0         1         2         3         4         5         6         7
0495     //                 01234567890123456789012345678901234567890123456789012345678901234567890123456789
0496     QByteArray method("<? class A {  } $a = new A();");
0497 
0498     TopDUContext* top = parse(method, DumpNone);
0499     DUChainReleaser releaseTop(top);
0500     DUChainWriteLocker lock(DUChain::lock());
0501 
0502     PhpCompletionTester tester(top, {});
0503     QCOMPARE(tester.completionContext->memberAccessOperation(), CodeCompletionContext::NoMemberAccess);
0504 
0505     QVERIFY(searchDeclaration(tester.items, top->localDeclarations().at(1)));
0506 }
0507 
0508 void TestCompletion::nameNormalVariable()
0509 {
0510     //                 0         1         2         3         4         5         6         7
0511     //                 01234567890123456789012345678901234567890123456789012345678901234567890123456789
0512     QByteArray method("<? $abc = 0; $arr = array(); define('def', 0); class ghi {} ");
0513 
0514     TopDUContext* top = parse(method, DumpAll);
0515     DUChainReleaser releaseTop(top);
0516     DUChainWriteLocker lock(DUChain::lock());
0517 
0518     PhpCompletionTester tester(top, {});
0519     QCOMPARE(tester.completionContext->memberAccessOperation(), CodeCompletionContext::NoMemberAccess);
0520 
0521     foreach(const QString &id, QStringList() << "ghi" << "def" << "$abc" << "$arr") {
0522         QVERIFY(tester.names.contains(id, Qt::CaseSensitive));
0523     }
0524 }
0525 
0526 void TestCompletion::nameClassMember()
0527 {
0528     //                 0         1         2         3         4         5         6         7
0529     //                 01234567890123456789012345678901234567890123456789012345678901234567890123456789
0530     QByteArray method("<? class A { public $abc = 0; } $b = new A;  ");
0531 
0532     TopDUContext* top = parse(method, DumpAll);
0533     DUChainReleaser releaseTop(top);
0534     DUChainWriteLocker lock(DUChain::lock());
0535 
0536     PhpCompletionTester tester(top, QStringLiteral("$b->"));
0537 
0538     auto *model = new TestCodeCompletionModel;
0539     model->foundDeclarations(tester.items, tester.completionContext.data());
0540 
0541     QCOMPARE(tester.completionContext->memberAccessOperation(), CodeCompletionContext::MemberAccess);
0542 
0543     CompletionTreeItemPointer itm = searchDeclaration(tester.items, top->childContexts().first()->localDeclarations().first());
0544     QVERIFY(itm);
0545     QCOMPARE(itm->data(model->index(0, Php::CodeCompletionModel::Name), Qt::DisplayRole, model).toString(),
0546              QString("abc"));
0547 
0548     //don't delete model as its constructor does bad things (quit the current thread - we don't want that in test)
0549     //TODO find better solution that doesn't leak
0550 }
0551 
0552 void TestCompletion::exceptions()
0553 {
0554     //                 0         1         2         3         4         5         6         7
0555     //                 01234567890123456789012345678901234567890123456789012345678901234567890123456789
0556     QByteArray method("<? class MyExcpt extends Exception {} $excpt = new MyExcpt(); ");
0557 
0558     TopDUContext* top = parse(method, DumpNone);
0559     DUChainReleaser releaseTop(top);
0560     DUChainWriteLocker lock(DUChain::lock());
0561 
0562     {
0563         PhpCompletionTester tester(top, QStringLiteral("throw "));
0564         QCOMPARE(tester.completionContext->memberAccessOperation(), CodeCompletionContext::ExceptionInstanceChoose);
0565         QCOMPARE(tester.items.count(), 1);
0566         QVERIFY(searchDeclaration(tester.items, top->localDeclarations().at(1)));
0567     }
0568 
0569     {
0570         PhpCompletionTester tester(top, QStringLiteral("throw new "));
0571         QCOMPARE(tester.completionContext->memberAccessOperation(), CodeCompletionContext::ExceptionChoose);
0572         QCOMPARE(tester.items.count(), 2);
0573         QVERIFY(searchDeclaration(tester.items, top->localDeclarations().at(0)));
0574     }
0575 
0576     {
0577         PhpCompletionTester tester(top, QStringLiteral("try { } catch("));
0578         QCOMPARE(tester.completionContext->memberAccessOperation(), CodeCompletionContext::ExceptionChoose);
0579         QCOMPARE(tester.items.count(), 2);
0580         QVERIFY(searchDeclaration(tester.items, top->localDeclarations().at(0)));
0581     }
0582 }
0583 
0584 void TestCompletion::exceptionOtherFile()
0585 {
0586     TopDUContext* addTop = parseAdditionalFile(IndexedString("file:///internal/projecttest0"),
0587         "<?php class MyExcptOtherFile extends Exception {} class MyClass {}");
0588     DUChainReleaser releaseAddTop(addTop);
0589     //                 0         1         2         3         4         5         6         7
0590     //                 01234567890123456789012345678901234567890123456789012345678901234567890123456789
0591     QByteArray method("<? ");
0592 
0593     TopDUContext* top = parse(method, DumpNone);
0594     DUChainReleaser releaseTop(top);
0595     DUChainWriteLocker lock(DUChain::lock());
0596 
0597     {
0598         PhpCompletionTester tester(top, QStringLiteral("throw new "));
0599         QCOMPARE(tester.completionContext->memberAccessOperation(), CodeCompletionContext::ExceptionChoose);
0600         QCOMPARE(tester.items.count(), 2);
0601         QVERIFY(searchDeclaration(tester.items, addTop->localDeclarations().at(0)));
0602     }
0603 
0604 }
0605 
0606 void TestCompletion::abstractMethods()
0607 {
0608     //                 0         1         2         3         4         5         6         7
0609     //                 01234567890123456789012345678901234567890123456789012345678901234567890123456789
0610     QByteArray method("<? abstract class A {  abstract function foo(); function bar(){} }");
0611 
0612     TopDUContext* top = parse(method, DumpNone);
0613     DUChainReleaser releaseTop(top);
0614     DUChainWriteLocker lock(DUChain::lock());
0615 
0616     DUContext* funContext = top->childContexts().first()->localDeclarations().last()->internalContext();
0617     PhpCompletionTester tester(funContext, QStringLiteral("$this->"));
0618     QCOMPARE(tester.names, QStringList() << "foo" << "bar");
0619 }
0620 
0621 void TestCompletion::interfaceMethods()
0622 {
0623     //                 0         1         2         3         4         5         6         7
0624     //                 01234567890123456789012345678901234567890123456789012345678901234567890123456789
0625     QByteArray method("<? interface A {  function foo(); } class B implements A { function bar(){} }");
0626 
0627     TopDUContext* top = parse(method, DumpNone);
0628     DUChainReleaser releaseTop(top);
0629     DUChainWriteLocker lock(DUChain::lock());
0630 
0631     DUContext* funContext = top->childContexts().last()->localDeclarations().first()->internalContext();
0632     PhpCompletionTester tester(funContext, QStringLiteral("$this->"));
0633     QCOMPARE(tester.names, QStringList() << "bar" << "foo");
0634 }
0635 
0636 void TestCompletion::interfaceMethods2()
0637 {
0638     //                 0         1         2         3         4         5         6         7
0639     //                 01234567890123456789012345678901234567890123456789012345678901234567890123456789
0640     QByteArray method("<? interface A {  function foo(); } /** @var A **/ $a = x(); ");
0641 
0642     TopDUContext* top = parse(method, DumpNone);
0643     DUChainReleaser releaseTop(top);
0644     DUChainWriteLocker lock(DUChain::lock());
0645 
0646     DUContext* funContext = top;
0647     PhpCompletionTester tester(funContext, QStringLiteral("$a->"));
0648     QCOMPARE(tester.names, QStringList() << "foo");
0649 }
0650 
0651 void TestCompletion::implementMethods()
0652 {
0653     //                 0         1         2         3         4         5         6         7
0654     //                 01234567890123456789012345678901234567890123456789012345678901234567890123456789
0655     QByteArray method("<? interface A { function foo(); } class B implements A {  }");
0656 
0657     TopDUContext* top = parse(method, DumpNone);
0658     DUChainReleaser releaseTop(top);
0659     DUChainWriteLocker lock(DUChain::lock());
0660 
0661     // context of class B
0662     DUContext* classContext = top->childContexts().last();
0663     {
0664         PhpCompletionTester tester(classContext, QStringLiteral("{"));
0665         QStringList compItems;
0666         compItems << QStringLiteral("foo");
0667         compItems << QStringLiteral("const");
0668         compItems << QStringLiteral("final");
0669         compItems << QStringLiteral("function");
0670         compItems << QStringLiteral("public");
0671         compItems << QStringLiteral("private");
0672         compItems << QStringLiteral("protected");
0673         compItems << QStringLiteral("static");
0674         compItems << QStringLiteral("var");
0675         compItems.sort();
0676         tester.names.sort();
0677         QCOMPARE(tester.names, compItems);
0678     }
0679 
0680     //TODO: verify actual completion text
0681 }
0682 
0683 void TestCompletion::overrideMethods()
0684 {
0685     //                 0         1         2         3         4         5         6         7
0686     //                 01234567890123456789012345678901234567890123456789012345678901234567890123456789
0687     QByteArray method("<? class A { function a(){} final function b(){}  } class B extends A {  }");
0688 
0689     TopDUContext* top = parse(method, DumpNone);
0690     DUChainReleaser releaseTop(top);
0691     DUChainWriteLocker lock(DUChain::lock());
0692 
0693     // context of class B
0694     DUContext* classContext = top->childContexts().last();
0695     {
0696         PhpCompletionTester tester(classContext, QStringLiteral("{"));
0697         QStringList compItems;
0698         compItems << QStringLiteral("a");
0699         compItems << QStringLiteral("const");
0700         compItems << QStringLiteral("final");
0701         compItems << QStringLiteral("function");
0702         compItems << QStringLiteral("public");
0703         compItems << QStringLiteral("private");
0704         compItems << QStringLiteral("protected");
0705         compItems << QStringLiteral("static");
0706         compItems << QStringLiteral("var");
0707         compItems.sort();
0708         tester.names.sort();
0709         QCOMPARE(tester.names, compItems);
0710     }
0711     {
0712         PhpCompletionTester tester(classContext, QStringLiteral("public static"));
0713         QStringList compItems;
0714         compItems << QStringLiteral("final");
0715         compItems << QStringLiteral("function");
0716         compItems.sort();
0717         tester.names.sort();
0718         QCOMPARE(tester.names, compItems);
0719     }
0720     {
0721         PhpCompletionTester tester(classContext, QStringLiteral("private function"));
0722         QVERIFY(tester.items.isEmpty());
0723     }
0724     {
0725         PhpCompletionTester tester(classContext, QStringLiteral("final public "));
0726         QStringList compItems;
0727         compItems << QStringLiteral("a");
0728         compItems << QStringLiteral("function");
0729         compItems << QStringLiteral("static");
0730         compItems.sort();
0731         tester.names.sort();
0732         QCOMPARE(tester.names, compItems);
0733     }
0734 
0735     //TODO: verify actual completion text
0736 }
0737 
0738 void TestCompletion::overrideVars()
0739 {
0740     //                 0         1         2         3         4         5         6         7
0741     //                 01234567890123456789012345678901234567890123456789012345678901234567890123456789
0742     QByteArray method("<? class A { protected $x;  } class B extends A {  }");
0743 
0744     TopDUContext* top = parse(method, DumpNone);
0745     DUChainReleaser releaseTop(top);
0746     DUChainWriteLocker lock(DUChain::lock());
0747 
0748     // context of class B
0749     DUContext* classContext = top->childContexts().last();
0750     {
0751         PhpCompletionTester tester(classContext, QStringLiteral("{"));
0752         QStringList compItems;
0753         compItems << QStringLiteral("x");
0754         compItems << QStringLiteral("const");
0755         compItems << QStringLiteral("final");
0756         compItems << QStringLiteral("function");
0757         compItems << QStringLiteral("public");
0758         compItems << QStringLiteral("private");
0759         compItems << QStringLiteral("protected");
0760         compItems << QStringLiteral("static");
0761         compItems << QStringLiteral("var");
0762         compItems.sort();
0763         tester.names.sort();
0764         QCOMPARE(tester.names, compItems);
0765     }
0766 }
0767 
0768 void TestCompletion::inArray()
0769 {
0770     TopDUContext* top = parse("", DumpNone);
0771     DUChainReleaser releaseTop(top);
0772     DUChainWriteLocker lock(DUChain::lock());
0773 
0774     PhpCompletionTester tester(top, QStringLiteral("<?php a = array(1, "));
0775     QVERIFY(tester.items.count() > 0);
0776 
0777     // TODO: compare to global completion list
0778 }
0779 
0780 void TestCompletion::verifyExtendsOrImplements(const QString &codeStr, const QString &completionStr,
0781         ClassDeclarationData::ClassType type,
0782         const CursorInRevision& cursor,
0783         QStringList forbiddenIdentifiers)
0784 {
0785     if (cursor.isValid()) {
0786         qDebug() << codeStr.mid(0, cursor.column) + completionStr + '|' + codeStr.mid(cursor.column);
0787     } else {
0788         qDebug() << codeStr + completionStr + '|';
0789     }
0790     TopDUContext *top = parse(codeStr.toUtf8(), DumpNone);
0791     DUChainReleaser releaseTop(top);
0792 
0793     DUContext *ctx;
0794     if (cursor.isValid()) {
0795         DUChainWriteLocker lock(DUChain::lock());
0796         ctx = top->findContextAt(cursor);
0797         QVERIFY(ctx);
0798         QVERIFY(ctx->owner());
0799         QVERIFY(dynamic_cast<ClassDeclaration*>(ctx->owner()));
0800     } else {
0801         ctx = top;
0802     }
0803 
0804     PhpCompletionTester tester(ctx, completionStr);
0805 
0806     QVERIFY(!tester.items.isEmpty());
0807     // make sure the items are unique
0808     QCOMPARE(tester.names.size(), QSet(tester.names.cbegin(), tester.names.cend()).size());
0809     foreach(const CompletionTreeItemPointer &item, tester.items) {
0810         ClassDeclaration* klass = dynamic_cast<ClassDeclaration*>(item->declaration().data());
0811         QVERIFY(klass);
0812         QVERIFY(klass->classModifier() != ClassDeclarationData::Final);
0813         QCOMPARE(klass->classType(), type);
0814 
0815         if (!forbiddenIdentifiers.isEmpty()) {
0816             QVERIFY(! forbiddenIdentifiers.contains(item->declaration()->identifier().toString()));
0817         }
0818     }
0819 }
0820 
0821 void TestCompletion::newExtends()
0822 {
0823     verifyExtendsOrImplements(QStringLiteral("<?php "), QStringLiteral("class test extends "),
0824                               ClassDeclarationData::Class,
0825                               CursorInRevision::invalid(),
0826                               QStringList() << QStringLiteral("test"));
0827 
0828     verifyExtendsOrImplements(QStringLiteral("<?php "), QStringLiteral("interface test extends "),
0829                               ClassDeclarationData::Interface,
0830                               CursorInRevision::invalid(),
0831                               QStringList() << QStringLiteral("test"));
0832 
0833     verifyExtendsOrImplements(QStringLiteral("<?php interface blub{} "), QStringLiteral("interface test extends blub, "),
0834                               ClassDeclarationData::Interface,
0835                               CursorInRevision::invalid(),
0836                               QStringList() << QStringLiteral("test") << QStringLiteral("blub"));
0837 }
0838 
0839 void TestCompletion::updateExtends()
0840 {
0841     //                         0         1         2         3         4         5
0842     //                         012345678901234567890123456789012345678901234567890123456789
0843     verifyExtendsOrImplements(QStringLiteral("<?php class test {}"), QStringLiteral("class test extends "),
0844                               ClassDeclarationData::Class,
0845                               CursorInRevision(0, 16),
0846                               QStringList() << QStringLiteral("test"));
0847 
0848     //                         0         1         2         3         4         5
0849     //                         012345678901234567890123456789012345678901234567890123456789
0850     verifyExtendsOrImplements(QStringLiteral("<?php interface test {}"), QStringLiteral("interface test extends "),
0851                               ClassDeclarationData::Interface,
0852                               CursorInRevision(0, 20),
0853                               QStringList() << QStringLiteral("test"));
0854 
0855     //                         0         1         2         3         4         5
0856     //                         012345678901234567890123456789012345678901234567890123456789
0857     verifyExtendsOrImplements(QStringLiteral("<?php interface blub{} interface test extends blub {}"),
0858                               QStringLiteral("interface test extends blub,bar, "),
0859                               ClassDeclarationData::Interface,
0860                               CursorInRevision(0, 50),
0861                               QStringList() << QStringLiteral("test") << QStringLiteral("blub"));
0862 }
0863 
0864 void TestCompletion::newImplements()
0865 {
0866     verifyExtendsOrImplements(QStringLiteral("<?php "), QStringLiteral("class test implements "),
0867                               ClassDeclarationData::Interface,
0868                               CursorInRevision::invalid(),
0869                               QStringList() << QStringLiteral("test"));
0870     verifyExtendsOrImplements(QStringLiteral("<?php interface blub{}"), QStringLiteral(" class test implements blub, "),
0871                               ClassDeclarationData::Interface,
0872                               CursorInRevision::invalid(),
0873                               QStringList() << QStringLiteral("test") << QStringLiteral("blub"));
0874 }
0875 
0876 void TestCompletion::updateImplements()
0877 {
0878     //                         0         1         2         3         4         5
0879     //                         012345678901234567890123456789012345678901234567890123456789
0880     verifyExtendsOrImplements(QStringLiteral("<?php class test {}"), QStringLiteral("class test implements "),
0881                               ClassDeclarationData::Interface,
0882                               CursorInRevision(0, 16),
0883                               QStringList() << QStringLiteral("test"));
0884 
0885     //                         0         1         2         3         4         5
0886     //                         012345678901234567890123456789012345678901234567890123456789
0887     verifyExtendsOrImplements(QStringLiteral("<?php interface blub{} class test implements blub {}"),
0888                               QStringLiteral("class test implements blub, "),
0889                               ClassDeclarationData::Interface,
0890                               CursorInRevision(0, 49),
0891                               QStringList() << QStringLiteral("test") << QStringLiteral("blub"));
0892 }
0893 
0894 void TestCompletion::avoidCircularInheritance()
0895 {
0896     verifyExtendsOrImplements(QStringLiteral("<?php interface blub{} interface bar extends blub{}"),
0897                               QStringLiteral("interface test extends bar, "),
0898                               ClassDeclarationData::Interface,
0899                               CursorInRevision::invalid(),
0900                               QStringList() << QStringLiteral("test") << QStringLiteral("blub") << QStringLiteral("bar"));
0901 
0902     verifyExtendsOrImplements(QStringLiteral("<?php interface blub{} interface bar extends blub{}"),
0903                               QStringLiteral("class test implements bar, "),
0904                               ClassDeclarationData::Interface,
0905                               CursorInRevision::invalid(),
0906                               QStringList() << QStringLiteral("blub") << QStringLiteral("bar"));
0907 }
0908 
0909 
0910 
0911 void TestCompletion::unsureType()
0912 {
0913     QByteArray method("<? class A { public $vA; } class B { public $vB; } function foo() { return new A; return new B; } $f = foo(); ");
0914 
0915     TopDUContext* top = parse(method, DumpAll);
0916     DUChainReleaser releaseTop(top);
0917     DUChainWriteLocker lock(DUChain::lock());
0918 
0919     PhpCompletionTester tester(top, QStringLiteral("$f->"));
0920     QCOMPARE(tester.completionContext->memberAccessOperation(), CodeCompletionContext::MemberAccess);
0921 
0922     qDebug() << tester.names;
0923     foreach(const QString &id, QStringList() << "vA" << "vB") {
0924         QVERIFY(tester.names.contains(id, Qt::CaseSensitive));
0925     }
0926 }
0927 
0928 void TestCompletion::completionAfterComments()
0929 {
0930     TopDUContext* top = parse("<?php\n", DumpAll);
0931     DUChainReleaser releaseTop(top);
0932     DUChainWriteLocker lock(DUChain::lock());
0933 
0934     foreach ( const QString &code, QStringList() << "# asdf\n"
0935                                     << "// asdf\n"
0936                                     << "/* */" )
0937     {
0938         PhpCompletionTester tester(top, code);
0939 
0940         qDebug() << tester.names;
0941         QVERIFY(tester.completionContext->isValid());
0942         QVERIFY(tester.items.count() > 0);
0943     }
0944 
0945     // TODO: compare to global completion list
0946 }
0947 
0948 void TestCompletion::completionInComments()
0949 {
0950     TopDUContext* top = parse("<?php\n", DumpNone);
0951     DUChainReleaser releaseTop(top);
0952     DUChainWriteLocker lock(DUChain::lock());
0953 
0954     foreach ( const QString &code, QStringList() << "# "
0955                                     << "// " << "/* " )
0956     {
0957         PhpCompletionTester tester(top, code);
0958         QVERIFY(!tester.completionContext->isValid());
0959     }
0960 }
0961 
0962 void TestCompletion::phpStartTag()
0963 {
0964     // some context with a function (or anything else for that matter) starting with "php" substring
0965     TopDUContext* top = parse("<?php function php_test() {} \n", DumpAll);
0966     DUChainReleaser releaseTop(top);
0967     DUChainWriteLocker lock(DUChain::lock());
0968 
0969     foreach ( const QString &code, QStringList() << "p" << "ph" << "php" ) {
0970         PhpCompletionTester tester(top, QStringLiteral("<?"), code);
0971 
0972         QVERIFY(tester.items.isEmpty());
0973     }
0974 
0975     PhpCompletionTester tester(top, QStringLiteral("<?php "));
0976 
0977     QVERIFY(!tester.items.isEmpty());
0978 }
0979 
0980 void TestCompletion::outsidePhpContext()
0981 {
0982     TopDUContext* top = parse("<?php $var = 1; ?>=", DumpDUChain);
0983     DUChainReleaser releaseTop(top);
0984     DUChainWriteLocker lock(DUChain::lock());
0985 
0986     PhpCompletionTester tester(top, QStringLiteral("<?php $var = 1; ?>="));
0987 
0988     QVERIFY(tester.items.isEmpty());
0989 }
0990 
0991 void TestCompletion::nonGlobalInFunction()
0992 {
0993     TopDUContext* top = parse("<?php $outside = 1; function test() {}", DumpDUChain);
0994     DUChainReleaser releaseTop(top);
0995     DUChainWriteLocker lock(DUChain::lock());
0996 
0997     PhpCompletionTester tester(top->childContexts().first(), {});
0998 
0999     QList<Declaration*> decs = top->findLocalDeclarations(Identifier(QStringLiteral("outside")));
1000     QCOMPARE(decs.count(), 1);
1001     QVERIFY(!searchDeclaration(tester.items, decs.first()));
1002 }
1003 
1004 void TestCompletion::fileCompletion()
1005 {
1006     TopDUContext* top = parse("<?php ", DumpNone);
1007     DUChainReleaser releaseTop(top);
1008     DUChainWriteLocker lock(DUChain::lock());
1009 
1010     ///TODO: somehow offer files and check whether they work with relative sub-paths
1011     ///TODO: make sure items after dirname(__FILE__) or similar start with a /
1012     foreach ( const QString& code, QStringList() << "include \"" << "include_once \"" << "require_once \""
1013                                                  << "require \"" << "include ( \""
1014                                                  << "include dirname(__FILE__) . \"/"
1015                                                  << "include dirname(__FILE__) . '/"
1016                                                  << "include ( dirname(__FILE__) . \"/"
1017                                                  << "include '" << "include ( '"
1018                                                  << "include ( dirname(__FILE__) . '/"
1019                                                  << "include __DIR__ . \"/"
1020                                                  << "include __DIR__ . '/"
1021                                                  << "include ( __DIR__ . \"/"
1022                                                  << "include ( __DIR__ . '/" )
1023     {
1024         qDebug() << code;
1025         PhpCompletionTester tester(top, code);
1026         QCOMPARE(tester.completionContext->memberAccessOperation(), CodeCompletionContext::FileChoose);
1027     }
1028 }
1029 
1030 void TestCompletion::instanceof()
1031 {
1032     TopDUContext* top = parse("<?php interface A{} class B{} abstract class C{} final class D{}", DumpNone);
1033     DUChainReleaser releaseTop(top);
1034     DUChainWriteLocker lock(DUChain::lock());
1035 
1036     PhpCompletionTester tester(top, QStringLiteral("$a instanceof "));
1037 
1038     foreach ( const QString& name, QStringList() << "a" << "b" << "c" << "d" ) {
1039         qDebug() << name;
1040         QList<Declaration*> decs = top->findLocalDeclarations(Identifier(name));
1041         QCOMPARE(decs.size(), 1);
1042         ClassDeclaration* cdec = dynamic_cast<ClassDeclaration*>(decs.first());
1043         QVERIFY(cdec);
1044         QVERIFY(searchDeclaration(tester.items, cdec));
1045     }
1046 
1047     foreach ( const CompletionTreeItemPointer &item, tester.items ) {
1048         QVERIFY(dynamic_cast<ClassDeclaration*>(item->declaration().data()));
1049     }
1050 }
1051 
1052 void TestCompletion::afterFunctionArg()
1053 {
1054     TopDUContext* top = parse("<?php class A{ var $b; } $a = new A;", DumpNone);
1055     DUChainReleaser releaseTop(top);
1056     DUChainWriteLocker lock(DUChain::lock());
1057 
1058     foreach ( const QString &code, QStringList() << "if ($a->" << "while ($a->" << "foobar($a->" ) {
1059         qDebug() << code;
1060         PhpCompletionTester tester(top, code);
1061         QCOMPARE(tester.names.size(), 1);
1062         QCOMPARE(tester.names.first(), QString("b"));
1063     }
1064 }
1065 
1066 void TestCompletion::functionBeforeDeclaration()
1067 {
1068     //                 0         1         2         3         4         5         6         7
1069     //                 01234567890123456789012345678901234567890123456789012345678901234567890123456789
1070     TopDUContext* top = parse("<?php  function test() {}", DumpNone);
1071     DUChainReleaser releaseTop(top);
1072     DUChainWriteLocker lock(DUChain::lock());
1073 
1074     QCOMPARE(top->localDeclarations().size(), 1);
1075     PhpCompletionTester tester(top, {}, {}, CursorInRevision(0, 3));
1076     // function _should_ be found
1077     QVERIFY(searchDeclaration(tester.items, top->localDeclarations().first()));
1078 }
1079 
1080 void TestCompletion::classBeforeDeclaration()
1081 {
1082     //                 0         1         2         3         4         5         6         7
1083     //                 01234567890123456789012345678901234567890123456789012345678901234567890123456789
1084     TopDUContext* top = parse("<?php  function test() {}", DumpNone);
1085     DUChainReleaser releaseTop(top);
1086     DUChainWriteLocker lock(DUChain::lock());
1087 
1088     QCOMPARE(top->localDeclarations().size(), 1);
1089     PhpCompletionTester tester(top, {}, {}, CursorInRevision(0, 3));
1090     // class _should_ be found
1091     QVERIFY(searchDeclaration(tester.items, top->localDeclarations().first()));
1092 }
1093 
1094 void TestCompletion::constantBeforeDeclaration()
1095 {
1096     //                 0         1         2         3         4         5         6         7
1097     //                 01234567890123456789012345678901234567890123456789012345678901234567890123456789
1098     TopDUContext* top = parse("<?php  define('TEST', 1);", DumpNone);
1099     DUChainReleaser releaseTop(top);
1100     DUChainWriteLocker lock(DUChain::lock());
1101 
1102     QCOMPARE(top->localDeclarations().size(), 1);
1103     PhpCompletionTester tester(top, {}, {}, CursorInRevision(0, 3));
1104     // constant should _not_ be found
1105     QVERIFY(!searchDeclaration(tester.items, top->localDeclarations().first()));
1106 }
1107 
1108 void TestCompletion::variableBeforeDeclaration()
1109 {
1110     //                 0         1         2         3         4         5         6         7
1111     //                 01234567890123456789012345678901234567890123456789012345678901234567890123456789
1112     TopDUContext* top = parse("<?php  $test = 1;", DumpNone);
1113     DUChainReleaser releaseTop(top);
1114     DUChainWriteLocker lock(DUChain::lock());
1115 
1116     QCOMPARE(top->localDeclarations().size(), 1);
1117     PhpCompletionTester tester(top, {}, {}, CursorInRevision(0, 3));
1118     // variable should _not_ be found
1119     QVERIFY(!searchDeclaration(tester.items, top->localDeclarations().first()));
1120 }
1121 
1122 void TestCompletion::functionArguments()
1123 {
1124     //                 0         1         2         3         4         5         6         7
1125     //                 01234567890123456789012345678901234567890123456789012345678901234567890123456789
1126     TopDUContext* top = parse("<?php function foo($asdf, $bar) {}", DumpNone);
1127     DUChainReleaser releaseTop(top);
1128     DUChainWriteLocker lock(DUChain::lock());
1129 
1130     Declaration* fDec = top->localDeclarations().first();
1131     QVERIFY(fDec);
1132     FunctionType::Ptr fType = fDec->type<FunctionType>();
1133     QVERIFY(fType);
1134 
1135     // params
1136     QVector< Declaration* > args = top->childContexts().first()->localDeclarations();
1137     QCOMPARE(args.size(), 2);
1138 
1139     PhpCompletionTester tester(top->childContexts().last(), {});
1140     // should get two local and the func itself
1141     QVERIFY(searchDeclaration(tester.items, fDec));
1142     foreach( Declaration* dec, args ) {
1143         qDebug() << dec->toString();
1144         QVERIFY(searchDeclaration(tester.items, dec));
1145     }
1146 }
1147 
1148 void TestCompletion::referencedClass()
1149 {
1150     //                 0         1         2         3         4         5         6         7
1151     //                 01234567890123456789012345678901234567890123456789012345678901234567890123456789
1152     TopDUContext* top = parse("<?php " + testClassA + " function foo(A &$arg) {}", DumpNone);
1153     DUChainReleaser releaseTop(top);
1154     DUChainWriteLocker lock(DUChain::lock());
1155 
1156     QList<Declaration*> decs = top->findDeclarations(Identifier(QStringLiteral("a")));
1157     QCOMPARE(decs.size(), 1);
1158 
1159     ClassDeclaration* aDec = dynamic_cast<ClassDeclaration*>(decs.first());
1160     QVERIFY(aDec);
1161 
1162     decs = top->findDeclarations(Identifier(QStringLiteral("foo")));
1163     QCOMPARE(decs.size(), 1);
1164 
1165     FunctionDeclaration* funcDec = dynamic_cast<FunctionDeclaration*>(decs.first());
1166     QVERIFY(funcDec);
1167     QVERIFY(funcDec->internalContext());
1168     QVERIFY(funcDec->internalFunctionContext());
1169     QVERIFY(funcDec->internalContext()->imports(funcDec->internalFunctionContext()));
1170 
1171     PhpCompletionTester tester(funcDec->internalContext(), QStringLiteral("$arg->"));
1172     QVERIFY(tester.completionContext->memberAccessOperation() == CodeCompletionContext::MemberAccess);
1173     QCOMPARE(tester.names, QStringList() << "pubf" << "pub");
1174 }
1175 
1176 void TestCompletion::ctorCall()
1177 {
1178     //                 0         1         2         3         4         5         6         7
1179     //                 01234567890123456789012345678901234567890123456789012345678901234567890123456789
1180     QByteArray method("<? class A { /** @param string $bar **/ public function __construct($bar) {} }\n"
1181                       "class B { /** @param bool $asdf **/ public function B($asdf) {} }");
1182 
1183     TopDUContext* top = parse(method, DumpAll);
1184     DUChainReleaser releaseTop(top);
1185     DUChainWriteLocker lock(DUChain::lock());
1186 
1187     Declaration* aCtor = top->childContexts().first()->localDeclarations().first();
1188     Declaration* bCtor = top->childContexts().last()->localDeclarations().first();
1189 
1190     {
1191         PhpCompletionTester tester(top, QStringLiteral("new A("));
1192         QCOMPARE(tester.completionContext->memberAccessOperation(), CodeCompletionContext::NoMemberAccess);
1193         QVERIFY(tester.completionContext->parentContext());
1194         QCOMPARE(tester.completionContext->parentContext()->memberAccessOperation(),
1195                  CodeCompletionContext::FunctionCallAccess);
1196 
1197         CompletionTreeItemPointer item = searchDeclaration(tester.items, aCtor);
1198         QVERIFY(item);
1199         NormalDeclarationCompletionItem* item2 = dynamic_cast<NormalDeclarationCompletionItem*>(item.data());
1200 
1201         QString ret;
1202         createArgumentList(*item2, ret, nullptr);
1203         QCOMPARE(ret, QString("(string $bar)"));
1204     }
1205     {
1206         PhpCompletionTester tester(top, QStringLiteral("new B("));
1207         QCOMPARE(tester.completionContext->memberAccessOperation(), CodeCompletionContext::NoMemberAccess);
1208         QVERIFY(tester.completionContext->parentContext());
1209         QCOMPARE(tester.completionContext->parentContext()->memberAccessOperation(),
1210                  CodeCompletionContext::FunctionCallAccess);
1211 
1212         CompletionTreeItemPointer item = searchDeclaration(tester.items, bCtor);
1213         QVERIFY(item);
1214         NormalDeclarationCompletionItem* item2 = dynamic_cast<NormalDeclarationCompletionItem*>(item.data());
1215 
1216         QString ret;
1217         createArgumentList(*item2, ret, nullptr);
1218         QCOMPARE(ret, QString("(bool $asdf)"));
1219     }
1220 }
1221 
1222 void TestCompletion::chainedCalling()
1223 {
1224     //                 0         1         2         3         4         5         6         7
1225     //                 01234567890123456789012345678901234567890123456789012345678901234567890123456789
1226     TopDUContext* top = parse("<?php class a { function b() { return new a; } } $a = new a;", DumpNone);
1227     DUChainReleaser releaseTop(top);
1228     DUChainWriteLocker lock(DUChain::lock());
1229 
1230     PhpCompletionTester tester(top, QStringLiteral("$a->b()->"));
1231     QVERIFY(tester.completionContext->memberAccessOperation() == CodeCompletionContext::MemberAccess);
1232     QCOMPARE(tester.names, QStringList() << "b");
1233 }
1234 
1235 void TestCompletion::funcCallInConditional()
1236 {
1237     //                 0         1         2         3         4         5         6         7
1238     //                 01234567890123456789012345678901234567890123456789012345678901234567890123456789
1239     QByteArray method("<? function asdf($a, $b = 1) {}");
1240 
1241     TopDUContext* top = parse(method, DumpNone);
1242     DUChainReleaser releaseTop(top);
1243     DUChainWriteLocker lock(DUChain::lock());
1244 
1245     {
1246         PhpCompletionTester tester(top, QStringLiteral("if ( !empty($_POST['answer']) && asdf("));
1247         QCOMPARE(tester.completionContext->memberAccessOperation(), CodeCompletionContext::NoMemberAccess);
1248         QVERIFY(tester.completionContext->parentContext());
1249         QCOMPARE(tester.completionContext->parentContext()->memberAccessOperation(),
1250                  CodeCompletionContext::FunctionCallAccess);
1251 
1252         CompletionTreeItemPointer item = searchDeclaration(tester.items, top->localDeclarations().at(0));
1253         QVERIFY(item);
1254         NormalDeclarationCompletionItem* item2 = dynamic_cast<NormalDeclarationCompletionItem*>(item.data());
1255 
1256         QString ret;
1257         createArgumentList(*item2, ret, nullptr);
1258         QCOMPARE(ret, QString("(mixed $a, int $b = 1)"));
1259     }
1260 }
1261 
1262 void TestCompletion::namespaces()
1263 {
1264     //                 0         1         2         3         4         5         6         7
1265     //                 01234567890123456789012345678901234567890123456789012345678901234567890123456789
1266     QByteArray method("<? namespace foo\\bar {}\n");
1267 
1268     TopDUContext* top = parse(method, DumpNone);
1269     DUChainReleaser releaseTop(top);
1270     DUChainWriteLocker lock;
1271 
1272     {
1273         PhpCompletionTester tester(top, QStringLiteral("namespace "));
1274         QCOMPARE(tester.completionContext->memberAccessOperation(), CodeCompletionContext::NamespaceChoose);
1275         QVERIFY(!tester.completionContext->parentContext());
1276 
1277         QCOMPARE(tester.names, QStringList() << "foo");
1278     }
1279 }
1280 
1281 void TestCompletion::inNamespace()
1282 {
1283     //                 0         1         2         3         4         5         6         7
1284     //                 01234567890123456789012345678901234567890123456789012345678901234567890123456789
1285     QByteArray method("<? namespace foo { function bar() {} }\n"
1286                       "   namespace yxc { function qwe() {} }\n" );
1287 
1288     TopDUContext* top = parse(method, DumpNone);
1289     DUChainReleaser releaseTop(top);
1290     DUChainWriteLocker lock;
1291 
1292     {
1293         PhpCompletionTester tester(top->childContexts().at(0), {});
1294         QCOMPARE(tester.completionContext->memberAccessOperation(), CodeCompletionContext::NoMemberAccess);
1295         QVERIFY(!tester.completionContext->parentContext());
1296 
1297         QVERIFY(searchDeclaration(tester.items, top->localDeclarations().first()));
1298         QVERIFY(searchDeclaration(tester.items, top->childContexts().first()->localDeclarations().first()));
1299         QVERIFY(searchDeclaration(tester.items, top->localDeclarations().last()));
1300         QVERIFY(!searchDeclaration(tester.items, top->childContexts().last()->localDeclarations().first()));
1301     }
1302     {
1303         PhpCompletionTester tester(top->childContexts().at(0), QStringLiteral("\\"));
1304         QCOMPARE(tester.completionContext->memberAccessOperation(), CodeCompletionContext::BackslashAccess);
1305         QVERIFY(!tester.completionContext->parentContext());
1306 
1307         QCOMPARE(tester.items.count(), 2);
1308         QVERIFY(searchDeclaration(tester.items, top->localDeclarations().first()));
1309         QVERIFY(!searchDeclaration(tester.items, top->childContexts().first()->localDeclarations().first()));
1310         QVERIFY(searchDeclaration(tester.items, top->localDeclarations().last()));
1311         QVERIFY(!searchDeclaration(tester.items, top->childContexts().last()->localDeclarations().first()));
1312     }
1313     {
1314         PhpCompletionTester tester(top->childContexts().at(0), QStringLiteral("\\foo\\"));
1315         QCOMPARE(tester.completionContext->memberAccessOperation(), CodeCompletionContext::BackslashAccess);
1316         QVERIFY(!tester.completionContext->parentContext());
1317 
1318         QCOMPARE(tester.items.count(), 1);
1319         QVERIFY(!searchDeclaration(tester.items, top->localDeclarations().first()));
1320         QVERIFY(searchDeclaration(tester.items, top->childContexts().first()->localDeclarations().first()));
1321         QVERIFY(!searchDeclaration(tester.items, top->localDeclarations().last()));
1322         QVERIFY(!searchDeclaration(tester.items, top->childContexts().last()->localDeclarations().first()));
1323     }
1324     {
1325         PhpCompletionTester tester(top->childContexts().at(0), QStringLiteral("\\yxc\\"));
1326         QCOMPARE(tester.completionContext->memberAccessOperation(), CodeCompletionContext::BackslashAccess);
1327         QVERIFY(!tester.completionContext->parentContext());
1328 
1329         QCOMPARE(tester.items.count(), 1);
1330         QVERIFY(!searchDeclaration(tester.items, top->localDeclarations().first()));
1331         QVERIFY(!searchDeclaration(tester.items, top->childContexts().first()->localDeclarations().first()));
1332         QVERIFY(!searchDeclaration(tester.items, top->localDeclarations().last()));
1333         QVERIFY(searchDeclaration(tester.items, top->childContexts().last()->localDeclarations().first()));
1334     }
1335 }
1336 
1337 void TestCompletion::closures()
1338 {
1339     //                 0         1         2         3         4         5         6         7
1340     //                 01234567890123456789012345678901234567890123456789012345678901234567890123456789
1341     QByteArray method("<? $l = function($a) {};\n" );
1342 
1343     TopDUContext* top = parse(method, DumpNone);
1344     QVERIFY(top);
1345     DUChainReleaser releaseTop(top);
1346     DUChainWriteLocker lock;
1347 
1348     Declaration* l = top->localDeclarations().first();
1349     Declaration* c = top->localDeclarations().last();
1350     {
1351         PhpCompletionTester tester(top, {});
1352         QVERIFY(tester.containsDeclaration(l));
1353         QVERIFY(!tester.containsDeclaration(c));
1354     }
1355     {
1356         PhpCompletionTester tester(top, QStringLiteral("$l("));
1357         QVERIFY(tester.containsDeclaration(l));
1358         QVERIFY(!tester.containsDeclaration(c));
1359 
1360         QVERIFY(tester.completionContext->parentContext());
1361         QVERIFY(!tester.completionContext->parentContext()->parentContext());
1362         QCOMPARE(tester.completionContext->parentContext()->memberAccessOperation(),
1363                  CodeCompletionContext::FunctionCallAccess);
1364     }
1365 }
1366 
1367 }
1368 
1369 #include "moc_test_completion.cpp"