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"