1 /*
2     SPDX-FileCopyrightText: 2008 Niko Sams <niko.sams@gmail.com>
3 
4     SPDX-License-Identifier: LGPL-2.0-only
5 */
6 
7 #include "test_completion.h"
8 
9 #include <QtTest>
10 
11 #include <language/duchain/parsingenvironment.h>
12 #include <language/duchain/duchain.h>
13 #include <language/duchain/duchainlock.h>
14 #include <language/duchain/topducontext.h>
15 #include <language/duchain/declaration.h>
16 #include <language/duchain/duchainpointer.h>
17 #include <language/duchain/codemodel.h>
18 #include <language/codecompletion/codecompletiontesthelper.h>
19 #include <language/duchain/types/alltypes.h>
20 
21 #include "../../duchain/types/structuretype.h"
22 #include "../../duchain/declarations/functiondeclaration.h"
23 
24 #include "context.h"
25 #include "item.h"
26 #include "helpers.h"
27 #include "model.h"
28 
29 using namespace KTextEditor;
30 using namespace KDevelop;
31 
32 QTEST_MAIN(Php::TestCompletion)
33 
34 namespace Php
35 {
36 
37 class TestCodeCompletionModel : public CodeCompletionModel
38 {
39 public:
40     using CodeCompletionModel::foundDeclarations;
41     //normally set by worker, but in test we don't have a worker
foundDeclarations(QList<KDevelop::CompletionTreeItemPointer> items,CodeCompletionContext * completionContext)42     void foundDeclarations(QList<KDevelop::CompletionTreeItemPointer> items, CodeCompletionContext* completionContext)
43     {
44         beginResetModel();
45         m_completionItems.clear();
46         foreach(const CompletionTreeItemPointer &i, items) {
47             m_completionItems << QExplicitlySharedDataPointer<CompletionTreeElement>(i);
48         }
49         m_completionContext = KDevelop::CodeCompletionContext::Ptr(completionContext);
50         endResetModel();
51     }
52 };
53 
54 /**
55  * declaration of class A with a number of completion items
56  *
57  * also introduces a instance of class A named $instA;
58  */
59 const QByteArray testClassA(
60     "class A {"
61     // start non-static
62     // public
63     " public function pubf() {}"             // at(0)
64     " public $pub;"                          // at(1)
65     // protected
66     " protected function protf() {}"         // at(2)
67     " protected $prot;"                      // at(3)
68     // private
69     " private function privf() {}"           // at(4)
70     " private $priv;"                        // at(5)
71     // start static
72     // public
73     " static public function spubf() {}"     // at(6)
74     " static public $spub;"                  // at(7)
75     // const == static public
76     " const c = 0;"                          // at(8)
77     // protected
78     " static protected function sprotf() {}" // at(9)
79     " static protected $sprot;"              // at(10)
80     // private
81     " static private function sprivf() {}"   // at(11)
82     " static private $spriv;"                // at(12)
83     "} $instA = new A; "
84 );
85 
86 /**
87  * declaration of class B which extends class A
88  * B has one new public member function
89  *
90  * also introduces a instance of class B named $instB;
91  */
92 const QByteArray testClassB(
93     "class B extends A {"
94     "public function __construct(){}" // at(0)
95     "} $instB = new B; "
96 );
97 
98 class TestCodeCompletionContext : public CodeCompletionContext
99 {
100 public:
TestCodeCompletionContext(KDevelop::DUContextPointer context,const QString & text,const QString & followingText,const CursorInRevision & position,int depth=0)101     TestCodeCompletionContext(KDevelop::DUContextPointer context, const QString& text, const QString& followingText, const CursorInRevision &position, int depth = 0)
102         : CodeCompletionContext(context, text, followingText, position, depth) { }
103 protected:
completionFiles()104     QList<QSet<IndexedString> > completionFiles() override {
105         QList<QSet<IndexedString> > ret;
106         QSet<IndexedString> set;
107         set << IndexedString("file:///internal/projecttest0");
108         set << IndexedString("file:///internal/projecttest1");
109         ret << set;
110         return ret;
111     }
112 };
113 
114 typedef CodeCompletionItemTester<TestCodeCompletionContext> BasePhpCompletionTester;
115 
116 /**
117  * Automatically prepent the test string with "<?php " when it does not start with "<?" already-
118  * If we would not do that the Tokenizer in the code-completion would not work (always T_INLINE_HTML).
119  */
120 class PhpCompletionTester : public BasePhpCompletionTester
121 {
122 public:
PhpCompletionTester(DUContext * context,QString text=QStringLiteral ("; "),QString followingText={},CursorInRevision position=CursorInRevision::invalid ())123     PhpCompletionTester(DUContext* context, QString text = QStringLiteral("; "), QString followingText = {}, CursorInRevision position = CursorInRevision::invalid())
124         : BasePhpCompletionTester(context, text.startsWith(QLatin1String("<?")) ? text : text.prepend("<?php "), followingText, position)
125     {
126 
127     }
128 };
129 
TestCompletion()130 TestCompletion::TestCompletion()
131 {
132 }
133 
dumpCompletionItems(QList<CompletionTreeItemPointer> items)134 void TestCompletion::dumpCompletionItems(QList<CompletionTreeItemPointer> items)
135 {
136     qDebug() << items.count() << "completion items:";
137     foreach(const CompletionTreeItemPointer &item, items) {
138         qDebug() << item->declaration()->toString();
139     }
140 }
141 
publicObjectCompletion()142 void TestCompletion::publicObjectCompletion()
143 {
144     TopDUContext* top = parse("<?php " + testClassA, DumpNone);
145     DUChainReleaser releaseTop(top);
146     DUChainWriteLocker lock(DUChain::lock());
147 
148     PhpCompletionTester tester(top, QStringLiteral("$blah; $instA->"));
149 
150     QCOMPARE(tester.completionContext->memberAccessOperation(), CodeCompletionContext::MemberAccess);
151 
152     QCOMPARE(tester.names, QStringList() << "pubf" << "pub");
153 }
publicStaticObjectCompletion()154 void TestCompletion::publicStaticObjectCompletion()
155 {
156     TopDUContext* top = parse("<?php " + testClassA, DumpNone);
157     DUChainReleaser releaseTop(top);
158     DUChainWriteLocker lock(DUChain::lock());
159 
160     PhpCompletionTester tester(top, QStringLiteral("$blah; A::"));
161 
162     QCOMPARE(tester.completionContext->memberAccessOperation(), CodeCompletionContext::StaticMemberAccess);
163 
164     QCOMPARE(tester.names, QStringList() << "spubf" << "$spub" << "c");
165 }
privateObjectCompletion()166 void TestCompletion::privateObjectCompletion()
167 {
168     TopDUContext* top = parse("<?php " + testClassA, DumpNone);
169     DUChainReleaser releaseTop(top);
170     DUChainWriteLocker lock(DUChain::lock());
171 
172 
173     DUContext* funContext = top->childContexts().first()->localDeclarations().first()->internalContext();
174     PhpCompletionTester tester(funContext, QStringLiteral("$this->"));
175 
176     QCOMPARE(tester.completionContext->memberAccessOperation(), CodeCompletionContext::MemberAccess);
177 
178     QCOMPARE(tester.names, QStringList() << "pubf" << "pub" << "protf" << "prot" << "privf" << "priv");
179 }
privateStaticObjectCompletion()180 void TestCompletion::privateStaticObjectCompletion()
181 {
182     TopDUContext* top = parse("<?php " + testClassA, DumpNone);
183     DUChainReleaser releaseTop(top);
184     DUChainWriteLocker lock(DUChain::lock());
185 
186     DUContext* funContext = top->childContexts().first()->localDeclarations().first()->internalContext();
187 
188     {
189     PhpCompletionTester tester(funContext, QStringLiteral("self::"));
190 
191     QCOMPARE(tester.completionContext->memberAccessOperation(), CodeCompletionContext::StaticMemberAccess);
192 
193     QCOMPARE(tester.names, QStringList() << "spubf" << "$spub" << "c" << "sprotf" << "$sprot" << "sprivf" << "$spriv");
194     }
195     {
196     PhpCompletionTester tester(funContext, QStringLiteral("static::"));
197 
198     QCOMPARE(tester.completionContext->memberAccessOperation(), CodeCompletionContext::StaticMemberAccess);
199 
200     QCOMPARE(tester.names, QStringList() << "spubf" << "$spub" << "c" << "sprotf" << "$sprot" << "sprivf" << "$spriv");
201     }
202 }
protectedObjectCompletion()203 void TestCompletion::protectedObjectCompletion()
204 {
205     TopDUContext* top = parse("<?php " + testClassA + testClassB, DumpNone);
206     DUChainReleaser releaseTop(top);
207     DUChainWriteLocker lock(DUChain::lock());
208 
209     DUContext* funContext = top->childContexts().at(1)->localDeclarations().first()->internalContext();
210 
211     {
212         PhpCompletionTester tester(funContext, QStringLiteral("$this->"));
213 
214         QCOMPARE(tester.completionContext->memberAccessOperation(), CodeCompletionContext::MemberAccess);
215 
216         QCOMPARE(tester.names, QStringList() << "__construct" << "pubf" << "pub" << "protf" << "prot");
217     }
218 
219     {
220         PhpCompletionTester tester(funContext, {});
221 
222         QCOMPARE(tester.completionContext->memberAccessOperation(), CodeCompletionContext::NoMemberAccess);
223 
224         qDebug() << tester.names;
225 
226         QVERIFY(tester.names.contains("$this->__construct"));
227         QVERIFY(tester.names.contains("$this->pubf"));
228         QVERIFY(tester.names.contains("$this->pub"));
229         QVERIFY(tester.names.contains("self::spubf"));
230         QVERIFY(tester.names.contains("self::$spub"));
231         QVERIFY(tester.names.contains("$this->protf"));
232         QVERIFY(tester.names.contains("$this->prot"));
233         QVERIFY(tester.names.contains("self::sprotf"));
234         QVERIFY(tester.names.contains("self::$sprot"));
235         QVERIFY(tester.names.contains("self::c"));
236         QVERIFY(!tester.names.contains("self::sprivf"));
237         QVERIFY(!tester.names.contains("self::$spriv"));
238         QVERIFY(!tester.names.contains("$this->privf"));
239         QVERIFY(!tester.names.contains("$this->$priv"));
240     }
241 }
protectedStaticObjectCompletion()242 void TestCompletion::protectedStaticObjectCompletion()
243 {
244     TopDUContext* top = parse("<?php " + testClassA + testClassB, DumpNone);
245     DUChainReleaser releaseTop(top);
246     DUChainWriteLocker lock(DUChain::lock());
247 
248     DUContext* funContext = top->childContexts().at(1)->localDeclarations().first()->internalContext();
249     PhpCompletionTester tester(funContext, QStringLiteral("self::"));
250 
251     QCOMPARE(tester.completionContext->memberAccessOperation(), CodeCompletionContext::StaticMemberAccess);
252 
253     QCOMPARE(tester.names, QStringList() << "spubf" << "$spub" << "c" << "sprotf" << "$sprot");
254 }
255 
methodCall()256 void TestCompletion::methodCall()
257 {
258     //                 0         1         2         3         4         5         6         7
259     //                 01234567890123456789012345678901234567890123456789012345678901234567890123456789
260     QByteArray method("<? class A { public function foo(A $a, $b = null) {} } $i = new A();");
261 
262     TopDUContext* top = parse(method, DumpAll);
263     DUChainReleaser releaseTop(top);
264     DUChainWriteLocker lock(DUChain::lock());
265 
266     {
267         PhpCompletionTester tester(top, QStringLiteral("$blah; $i->foo("));
268         QCOMPARE(tester.completionContext->memberAccessOperation(), CodeCompletionContext::NoMemberAccess);
269         QVERIFY(tester.completionContext->parentContext());
270         QCOMPARE(tester.completionContext->parentContext()->memberAccessOperation(),
271                  CodeCompletionContext::FunctionCallAccess);
272 
273         CompletionTreeItemPointer item = searchDeclaration(tester.items, top->childContexts().at(0)->localDeclarations().at(0));
274         QVERIFY(item);
275         NormalDeclarationCompletionItem* item2 = dynamic_cast<NormalDeclarationCompletionItem*>(item.data());
276 
277         QString ret;
278         createArgumentList(*item2, ret, nullptr);
279         QCOMPARE(ret, QString("(A $a, null $b = null)"));
280     }
281     {
282         PhpCompletionTester tester(top, QStringLiteral("blah; $i->foo(new A(), "));
283         QVERIFY(searchDeclaration(tester.items, top->childContexts().at(0)->localDeclarations().at(0)));
284     }
285 }
286 
functionCall()287 void TestCompletion::functionCall()
288 {
289     //                 0         1         2         3         4         5         6         7
290     //                 01234567890123456789012345678901234567890123456789012345678901234567890123456789
291     QByteArray method("<? $outside = 1; class A {} function foo(A $a, $b = null) {}");
292 
293     TopDUContext* top = parse(method, DumpAll);
294     DUChainReleaser releaseTop(top);
295     DUChainWriteLocker lock(DUChain::lock());
296     PhpCompletionTester tester(top, QStringLiteral("blah; foo("));
297     QVERIFY(tester.completionContext->parentContext());
298 
299     QVERIFY(tester.completionContext->parentContext());
300     QVERIFY(!tester.completionContext->parentContext()->parentContext());
301     QCOMPARE(tester.completionContext->parentContext()->memberAccessOperation(),
302              CodeCompletionContext::FunctionCallAccess);
303 
304     QVERIFY(searchDeclaration(tester.items, top->localDeclarations().at(1)));
305 }
306 
nestedFunctionCall_data()307 void TestCompletion::nestedFunctionCall_data()
308 {
309     QTest::addColumn<QString>("text");
310 
311     QTest::newRow("nested") << QStringLiteral("bar(foo(");
312     QTest::newRow("nested prev arg") << QStringLiteral("bar(1, foo(");
313     QTest::newRow("nested prev func call") << QStringLiteral("bar(foo(1), foo(");
314     QTest::newRow("nested prev arg comma") << QStringLiteral("bar(1, bar(1, ");
315     QTest::newRow("nested prev func comma") << QStringLiteral("bar(1, bar(foo(1), ");
316 }
317 
nestedFunctionCall()318 void TestCompletion::nestedFunctionCall()
319 {
320     QFETCH(QString, text);
321 
322     //                 0         1         2         3         4         5         6         7
323     //                 01234567890123456789012345678901234567890123456789012345678901234567890123456789
324     QByteArray method("<? function foo($a) {return 1;} function bar($b, $c) {return 2;}");
325 
326     TopDUContext* top = parse(method, DumpNone);
327     DUChainReleaser releaseTop(top);
328     DUChainWriteLocker lock;
329 
330     PhpCompletionTester tester(top, text);
331     QVERIFY(tester.completionContext->parentContext());
332     QVERIFY(tester.completionContext->parentContext()->parentContext());
333     QVERIFY(!tester.completionContext->parentContext()->parentContext()->parentContext());
334     QCOMPARE(tester.completionContext->parentContext()->memberAccessOperation(),
335              CodeCompletionContext::FunctionCallAccess);
336     QCOMPARE(tester.completionContext->parentContext()->parentContext()->memberAccessOperation(),
337              CodeCompletionContext::FunctionCallAccess);
338 
339     QVERIFY(searchDeclaration(tester.items, top->localDeclarations().at(0)));
340     QVERIFY(searchDeclaration(tester.items, top->localDeclarations().at(1)));
341 }
342 
newObjectFromOtherFile()343 void TestCompletion::newObjectFromOtherFile()
344 {
345 
346     TopDUContext* addTop = parseAdditionalFile(IndexedString("/duchaintest/foo.php"), "<?php class Foo { function bar() {} } ");
347     DUChainReleaser releaseAddTop(addTop);
348 
349     //                 0         1         2         3         4         5         6         7
350     //                 01234567890123456789012345678901234567890123456789012345678901234567890123456789
351     QByteArray method("<? $a = new Foo(); ");
352 
353     TopDUContext* top = parse(method, DumpAll);
354     DUChainReleaser releaseTop(top);
355     DUChainWriteLocker lock(DUChain::lock());
356 
357     PhpCompletionTester tester(top, QStringLiteral("blah; $a->"));
358     QCOMPARE(tester.items.count(), 1);
359     QCOMPARE(tester.items.first()->declaration().data(), addTop->childContexts().first()->localDeclarations().first());
360 }
361 
constantFromOtherFile()362 void TestCompletion::constantFromOtherFile()
363 {
364     TopDUContext* addTop = parseAdditionalFile(
365         IndexedString("file:///internal/projecttest0"),
366         "<?php define('FIND_ME', 1); $dontFindMe = 1; "
367     );
368     DUChainReleaser releaseAddTop(addTop);
369 
370     //                 0         1         2         3         4         5         6         7
371     //                 01234567890123456789012345678901234567890123456789012345678901234567890123456789
372     QByteArray method("<? ");
373     TopDUContext* top = parse(method, DumpAll);
374     DUChainReleaser releaseTop(top);
375     DUChainWriteLocker lock(DUChain::lock());
376 
377     QCOMPARE(addTop->localDeclarations().size(), 2);
378     Declaration* findMe = addTop->localDeclarations().first();
379     Declaration* dontFindMe = addTop->localDeclarations().last();
380 
381     PhpCompletionTester tester(top, {});
382     QVERIFY(searchDeclaration(tester.items, findMe));
383     QVERIFY(!searchDeclaration(tester.items, dontFindMe));
384 }
385 
baseClass()386 void TestCompletion::baseClass()
387 {
388     QByteArray method("<? class A { public $avar; } class B extends A { public $bvar; } $a = new A(); $b = new B(); ");
389 
390     TopDUContext* top = parse(method, DumpAll);
391     DUChainReleaser releaseTop(top);
392     DUChainWriteLocker lock(DUChain::lock());
393 
394     {
395         PhpCompletionTester tester(top, QStringLiteral("$a->"));
396         QCOMPARE(tester.names, QStringList() << "avar");
397     }
398 
399     {
400         PhpCompletionTester tester(top, QStringLiteral("$b->"));
401         QCOMPARE(tester.names, QStringList() << "bvar" << "avar");
402     }
403 }
404 
extendsFromOtherFile()405 void TestCompletion::extendsFromOtherFile()
406 {
407 
408     TopDUContext* addTop = parseAdditionalFile(IndexedString("/duchaintest/foo.php"), "<?php class A { public $avar; } ");
409     DUChainReleaser releaseAddTop(addTop);
410     //                 0         1         2         3         4         5         6         7
411     //                 01234567890123456789012345678901234567890123456789012345678901234567890123456789
412     QByteArray method("<? class B extends A { public $bvar; } $b = new B();");
413 
414     TopDUContext* top = parse(method, DumpAll);
415     DUChainReleaser releaseTop(top);
416     DUChainWriteLocker lock(DUChain::lock());
417 
418     PhpCompletionTester tester(top, QStringLiteral("$b->"));
419     QCOMPARE(tester.items.count(), 2);
420     QCOMPARE(tester.items.at(1)->declaration().data(), addTop->childContexts().first()->localDeclarations().first());
421     QCOMPARE(tester.items.at(0)->declaration().data(), top->childContexts().first()->localDeclarations().first());
422 }
423 
424 
globalClassFromOtherFile()425 void TestCompletion::globalClassFromOtherFile()
426 {
427 
428     TopDUContext* addTop = parseAdditionalFile(IndexedString("/duchaintest/foo.php"), "<?php class A { } ");
429     DUChainReleaser releaseAddTop(addTop);
430     //                 0         1         2         3         4         5         6         7
431     //                 01234567890123456789012345678901234567890123456789012345678901234567890123456789
432     QByteArray method("<? ");
433 
434     TopDUContext* top = parse(method, DumpAll);
435     DUChainReleaser releaseTop(top);
436     DUChainWriteLocker lock(DUChain::lock());
437     /*
438         PhpCompletionTester tester(top, "new ");
439         QVERIFY(searchDeclaration(tester.items, addTop->localDeclarations().first()));
440     */
441 }
442 
codeModel()443 void TestCompletion::codeModel()
444 {
445     DUChainWriteLocker lock(DUChain::lock());
446     uint count;
447     const CodeModelItem* items;
448 
449     CodeModel::self().addItem(IndexedString("file:///foo"), QualifiedIdentifier(QStringLiteral("identifier")), CodeModelItem::Class);
450 
451     CodeModel::self().items(IndexedString("file:///foo"), count, items);
452     bool found = false;
453     for (uint i = 0;i < count;++i) {
454         if (items[0].id.identifier() == QualifiedIdentifier(QStringLiteral("identifier"))) {
455             found = true;
456             QCOMPARE(items[i].kind, CodeModelItem::Class);
457         }
458     }
459     QVERIFY(found);
460 }
461 
projectFileClass()462 void TestCompletion::projectFileClass()
463 {
464     TopDUContext* addTop = parseAdditionalFile(IndexedString("file:///internal/projecttest0"), "<? class B { function invisible() {} } ");
465     DUChainReleaser releaseAddTop(addTop);
466 
467     //                 0         1         2         3         4         5         6         7
468     //                 01234567890123456789012345678901234567890123456789012345678901234567890123456789
469     TopDUContext* top = parse("<?php class foo { function bar() {} }", DumpNone, QUrl(QStringLiteral("file:///internal/projecttest1")));
470     DUChainReleaser releaseTop(top);
471 
472     DUChainWriteLocker lock(DUChain::lock());
473 
474     {
475         // outside of class foo
476         PhpCompletionTester tester(top, QStringLiteral("<?php "));
477         QVERIFY(searchDeclaration(tester.items, addTop->localDeclarations().first()));
478     }
479     {
480         // inside of class foo, i.e. in its bar() method
481         PhpCompletionTester tester(top->childContexts().first()->childContexts().first(), QStringLiteral("<?php "));
482 
483         qDebug() << tester.names;
484         // we want to see the class
485         QVERIFY(searchDeclaration(tester.items, addTop->localDeclarations().first()));
486         // but not its methods
487         QVERIFY(!searchDeclaration(tester.items, addTop->childContexts().first()->localDeclarations().first()));
488     }
489 }
490 
491 
variable()492 void TestCompletion::variable()
493 {
494     //                 0         1         2         3         4         5         6         7
495     //                 01234567890123456789012345678901234567890123456789012345678901234567890123456789
496     QByteArray method("<? class A {  } $a = new A();");
497 
498     TopDUContext* top = parse(method, DumpNone);
499     DUChainReleaser releaseTop(top);
500     DUChainWriteLocker lock(DUChain::lock());
501 
502     PhpCompletionTester tester(top, {});
503     QCOMPARE(tester.completionContext->memberAccessOperation(), CodeCompletionContext::NoMemberAccess);
504 
505     QVERIFY(searchDeclaration(tester.items, top->localDeclarations().at(1)));
506 }
507 
nameNormalVariable()508 void TestCompletion::nameNormalVariable()
509 {
510     //                 0         1         2         3         4         5         6         7
511     //                 01234567890123456789012345678901234567890123456789012345678901234567890123456789
512     QByteArray method("<? $abc = 0; $arr = array(); define('def', 0); class ghi {} ");
513 
514     TopDUContext* top = parse(method, DumpAll);
515     DUChainReleaser releaseTop(top);
516     DUChainWriteLocker lock(DUChain::lock());
517 
518     PhpCompletionTester tester(top, {});
519     QCOMPARE(tester.completionContext->memberAccessOperation(), CodeCompletionContext::NoMemberAccess);
520 
521     foreach(const QString &id, QStringList() << "ghi" << "def" << "$abc" << "$arr") {
522         QVERIFY(tester.names.contains(id, Qt::CaseSensitive));
523     }
524 }
525 
nameClassMember()526 void TestCompletion::nameClassMember()
527 {
528     //                 0         1         2         3         4         5         6         7
529     //                 01234567890123456789012345678901234567890123456789012345678901234567890123456789
530     QByteArray method("<? class A { public $abc = 0; } $b = new A;  ");
531 
532     TopDUContext* top = parse(method, DumpAll);
533     DUChainReleaser releaseTop(top);
534     DUChainWriteLocker lock(DUChain::lock());
535 
536     PhpCompletionTester tester(top, QStringLiteral("$b->"));
537 
538     auto *model = new TestCodeCompletionModel;
539     model->foundDeclarations(tester.items, tester.completionContext.data());
540 
541     QCOMPARE(tester.completionContext->memberAccessOperation(), CodeCompletionContext::MemberAccess);
542 
543     CompletionTreeItemPointer itm = searchDeclaration(tester.items, top->childContexts().first()->localDeclarations().first());
544     QVERIFY(itm);
545     QCOMPARE(itm->data(model->index(0, Php::CodeCompletionModel::Name), Qt::DisplayRole, model).toString(),
546              QString("abc"));
547 
548     //don't delete model as its constructor does bad things (quit the current thread - we don't want that in test)
549     //TODO find better solution that doesn't leak
550 }
551 
exceptions()552 void TestCompletion::exceptions()
553 {
554     //                 0         1         2         3         4         5         6         7
555     //                 01234567890123456789012345678901234567890123456789012345678901234567890123456789
556     QByteArray method("<? class MyExcpt extends Exception {} $excpt = new MyExcpt(); ");
557 
558     TopDUContext* top = parse(method, DumpNone);
559     DUChainReleaser releaseTop(top);
560     DUChainWriteLocker lock(DUChain::lock());
561 
562     {
563         PhpCompletionTester tester(top, QStringLiteral("throw "));
564         QCOMPARE(tester.completionContext->memberAccessOperation(), CodeCompletionContext::ExceptionInstanceChoose);
565         QCOMPARE(tester.items.count(), 1);
566         QVERIFY(searchDeclaration(tester.items, top->localDeclarations().at(1)));
567     }
568 
569     {
570         PhpCompletionTester tester(top, QStringLiteral("throw new "));
571         QCOMPARE(tester.completionContext->memberAccessOperation(), CodeCompletionContext::ExceptionChoose);
572         QCOMPARE(tester.items.count(), 2);
573         QVERIFY(searchDeclaration(tester.items, top->localDeclarations().at(0)));
574     }
575 
576     {
577         PhpCompletionTester tester(top, QStringLiteral("try { } catch("));
578         QCOMPARE(tester.completionContext->memberAccessOperation(), CodeCompletionContext::ExceptionChoose);
579         QCOMPARE(tester.items.count(), 2);
580         QVERIFY(searchDeclaration(tester.items, top->localDeclarations().at(0)));
581     }
582 }
583 
exceptionOtherFile()584 void TestCompletion::exceptionOtherFile()
585 {
586     TopDUContext* addTop = parseAdditionalFile(IndexedString("file:///internal/projecttest0"),
587         "<?php class MyExcptOtherFile extends Exception {} class MyClass {}");
588     DUChainReleaser releaseAddTop(addTop);
589     //                 0         1         2         3         4         5         6         7
590     //                 01234567890123456789012345678901234567890123456789012345678901234567890123456789
591     QByteArray method("<? ");
592 
593     TopDUContext* top = parse(method, DumpNone);
594     DUChainReleaser releaseTop(top);
595     DUChainWriteLocker lock(DUChain::lock());
596 
597     {
598         PhpCompletionTester tester(top, QStringLiteral("throw new "));
599         QCOMPARE(tester.completionContext->memberAccessOperation(), CodeCompletionContext::ExceptionChoose);
600         QCOMPARE(tester.items.count(), 2);
601         QVERIFY(searchDeclaration(tester.items, addTop->localDeclarations().at(0)));
602     }
603 
604 }
605 
abstractMethods()606 void TestCompletion::abstractMethods()
607 {
608     //                 0         1         2         3         4         5         6         7
609     //                 01234567890123456789012345678901234567890123456789012345678901234567890123456789
610     QByteArray method("<? abstract class A {  abstract function foo(); function bar(){} }");
611 
612     TopDUContext* top = parse(method, DumpNone);
613     DUChainReleaser releaseTop(top);
614     DUChainWriteLocker lock(DUChain::lock());
615 
616     DUContext* funContext = top->childContexts().first()->localDeclarations().last()->internalContext();
617     PhpCompletionTester tester(funContext, QStringLiteral("$this->"));
618     QCOMPARE(tester.names, QStringList() << "foo" << "bar");
619 }
620 
interfaceMethods()621 void TestCompletion::interfaceMethods()
622 {
623     //                 0         1         2         3         4         5         6         7
624     //                 01234567890123456789012345678901234567890123456789012345678901234567890123456789
625     QByteArray method("<? interface A {  function foo(); } class B implements A { function bar(){} }");
626 
627     TopDUContext* top = parse(method, DumpNone);
628     DUChainReleaser releaseTop(top);
629     DUChainWriteLocker lock(DUChain::lock());
630 
631     DUContext* funContext = top->childContexts().last()->localDeclarations().first()->internalContext();
632     PhpCompletionTester tester(funContext, QStringLiteral("$this->"));
633     QCOMPARE(tester.names, QStringList() << "bar" << "foo");
634 }
635 
interfaceMethods2()636 void TestCompletion::interfaceMethods2()
637 {
638     //                 0         1         2         3         4         5         6         7
639     //                 01234567890123456789012345678901234567890123456789012345678901234567890123456789
640     QByteArray method("<? interface A {  function foo(); } /** @var A **/ $a = x(); ");
641 
642     TopDUContext* top = parse(method, DumpNone);
643     DUChainReleaser releaseTop(top);
644     DUChainWriteLocker lock(DUChain::lock());
645 
646     DUContext* funContext = top;
647     PhpCompletionTester tester(funContext, QStringLiteral("$a->"));
648     QCOMPARE(tester.names, QStringList() << "foo");
649 }
650 
implementMethods()651 void TestCompletion::implementMethods()
652 {
653     //                 0         1         2         3         4         5         6         7
654     //                 01234567890123456789012345678901234567890123456789012345678901234567890123456789
655     QByteArray method("<? interface A { function foo(); } class B implements A {  }");
656 
657     TopDUContext* top = parse(method, DumpNone);
658     DUChainReleaser releaseTop(top);
659     DUChainWriteLocker lock(DUChain::lock());
660 
661     // context of class B
662     DUContext* classContext = top->childContexts().last();
663     {
664         PhpCompletionTester tester(classContext, QStringLiteral("{"));
665         QStringList compItems;
666         compItems << QStringLiteral("foo");
667         compItems << QStringLiteral("const");
668         compItems << QStringLiteral("final");
669         compItems << QStringLiteral("function");
670         compItems << QStringLiteral("public");
671         compItems << QStringLiteral("private");
672         compItems << QStringLiteral("protected");
673         compItems << QStringLiteral("static");
674         compItems << QStringLiteral("var");
675         compItems.sort();
676         tester.names.sort();
677         QCOMPARE(tester.names, compItems);
678     }
679 
680     //TODO: verify actual completion text
681 }
682 
overrideMethods()683 void TestCompletion::overrideMethods()
684 {
685     //                 0         1         2         3         4         5         6         7
686     //                 01234567890123456789012345678901234567890123456789012345678901234567890123456789
687     QByteArray method("<? class A { function a(){} final function b(){}  } class B extends A {  }");
688 
689     TopDUContext* top = parse(method, DumpNone);
690     DUChainReleaser releaseTop(top);
691     DUChainWriteLocker lock(DUChain::lock());
692 
693     // context of class B
694     DUContext* classContext = top->childContexts().last();
695     {
696         PhpCompletionTester tester(classContext, QStringLiteral("{"));
697         QStringList compItems;
698         compItems << QStringLiteral("a");
699         compItems << QStringLiteral("const");
700         compItems << QStringLiteral("final");
701         compItems << QStringLiteral("function");
702         compItems << QStringLiteral("public");
703         compItems << QStringLiteral("private");
704         compItems << QStringLiteral("protected");
705         compItems << QStringLiteral("static");
706         compItems << QStringLiteral("var");
707         compItems.sort();
708         tester.names.sort();
709         QCOMPARE(tester.names, compItems);
710     }
711     {
712         PhpCompletionTester tester(classContext, QStringLiteral("public static"));
713         QStringList compItems;
714         compItems << QStringLiteral("final");
715         compItems << QStringLiteral("function");
716         compItems.sort();
717         tester.names.sort();
718         QCOMPARE(tester.names, compItems);
719     }
720     {
721         PhpCompletionTester tester(classContext, QStringLiteral("private function"));
722         QVERIFY(tester.items.isEmpty());
723     }
724     {
725         PhpCompletionTester tester(classContext, QStringLiteral("final public "));
726         QStringList compItems;
727         compItems << QStringLiteral("a");
728         compItems << QStringLiteral("function");
729         compItems << QStringLiteral("static");
730         compItems.sort();
731         tester.names.sort();
732         QCOMPARE(tester.names, compItems);
733     }
734 
735     //TODO: verify actual completion text
736 }
737 
overrideVars()738 void TestCompletion::overrideVars()
739 {
740     //                 0         1         2         3         4         5         6         7
741     //                 01234567890123456789012345678901234567890123456789012345678901234567890123456789
742     QByteArray method("<? class A { protected $x;  } class B extends A {  }");
743 
744     TopDUContext* top = parse(method, DumpNone);
745     DUChainReleaser releaseTop(top);
746     DUChainWriteLocker lock(DUChain::lock());
747 
748     // context of class B
749     DUContext* classContext = top->childContexts().last();
750     {
751         PhpCompletionTester tester(classContext, QStringLiteral("{"));
752         QStringList compItems;
753         compItems << QStringLiteral("x");
754         compItems << QStringLiteral("const");
755         compItems << QStringLiteral("final");
756         compItems << QStringLiteral("function");
757         compItems << QStringLiteral("public");
758         compItems << QStringLiteral("private");
759         compItems << QStringLiteral("protected");
760         compItems << QStringLiteral("static");
761         compItems << QStringLiteral("var");
762         compItems.sort();
763         tester.names.sort();
764         QCOMPARE(tester.names, compItems);
765     }
766 }
767 
inArray()768 void TestCompletion::inArray()
769 {
770     TopDUContext* top = parse("", DumpNone);
771     DUChainReleaser releaseTop(top);
772     DUChainWriteLocker lock(DUChain::lock());
773 
774     PhpCompletionTester tester(top, QStringLiteral("<?php a = array(1, "));
775     QVERIFY(tester.items.count() > 0);
776 
777     // TODO: compare to global completion list
778 }
779 
verifyExtendsOrImplements(const QString & codeStr,const QString & completionStr,ClassDeclarationData::ClassType type,const CursorInRevision & cursor,QStringList forbiddenIdentifiers)780 void TestCompletion::verifyExtendsOrImplements(const QString &codeStr, const QString &completionStr,
781         ClassDeclarationData::ClassType type,
782         const CursorInRevision& cursor,
783         QStringList forbiddenIdentifiers)
784 {
785     if (cursor.isValid()) {
786         qDebug() << codeStr.mid(0, cursor.column) + completionStr + '|' + codeStr.mid(cursor.column);
787     } else {
788         qDebug() << codeStr + completionStr + '|';
789     }
790     TopDUContext *top = parse(codeStr.toUtf8(), DumpNone);
791     DUChainReleaser releaseTop(top);
792 
793     DUContext *ctx;
794     if (cursor.isValid()) {
795         DUChainWriteLocker lock(DUChain::lock());
796         ctx = top->findContextAt(cursor);
797         QVERIFY(ctx);
798         QVERIFY(ctx->owner());
799         QVERIFY(dynamic_cast<ClassDeclaration*>(ctx->owner()));
800     } else {
801         ctx = top;
802     }
803 
804     PhpCompletionTester tester(ctx, completionStr);
805 
806     QVERIFY(!tester.items.isEmpty());
807     // make sure the items are unique
808     QCOMPARE(tester.names.size(), tester.names.toSet().size());
809     foreach(const CompletionTreeItemPointer &item, tester.items) {
810         ClassDeclaration* klass = dynamic_cast<ClassDeclaration*>(item->declaration().data());
811         QVERIFY(klass);
812         QVERIFY(klass->classModifier() != ClassDeclarationData::Final);
813         QCOMPARE(klass->classType(), type);
814 
815         if (!forbiddenIdentifiers.isEmpty()) {
816             QVERIFY(! forbiddenIdentifiers.contains(item->declaration()->identifier().toString()));
817         }
818     }
819 }
820 
newExtends()821 void TestCompletion::newExtends()
822 {
823     verifyExtendsOrImplements(QStringLiteral("<?php "), QStringLiteral("class test extends "),
824                               ClassDeclarationData::Class,
825                               CursorInRevision::invalid(),
826                               QStringList() << QStringLiteral("test"));
827 
828     verifyExtendsOrImplements(QStringLiteral("<?php "), QStringLiteral("interface test extends "),
829                               ClassDeclarationData::Interface,
830                               CursorInRevision::invalid(),
831                               QStringList() << QStringLiteral("test"));
832 
833     verifyExtendsOrImplements(QStringLiteral("<?php interface blub{} "), QStringLiteral("interface test extends blub, "),
834                               ClassDeclarationData::Interface,
835                               CursorInRevision::invalid(),
836                               QStringList() << QStringLiteral("test") << QStringLiteral("blub"));
837 }
838 
updateExtends()839 void TestCompletion::updateExtends()
840 {
841     //                         0         1         2         3         4         5
842     //                         012345678901234567890123456789012345678901234567890123456789
843     verifyExtendsOrImplements(QStringLiteral("<?php class test {}"), QStringLiteral("class test extends "),
844                               ClassDeclarationData::Class,
845                               CursorInRevision(0, 16),
846                               QStringList() << QStringLiteral("test"));
847 
848     //                         0         1         2         3         4         5
849     //                         012345678901234567890123456789012345678901234567890123456789
850     verifyExtendsOrImplements(QStringLiteral("<?php interface test {}"), QStringLiteral("interface test extends "),
851                               ClassDeclarationData::Interface,
852                               CursorInRevision(0, 20),
853                               QStringList() << QStringLiteral("test"));
854 
855     //                         0         1         2         3         4         5
856     //                         012345678901234567890123456789012345678901234567890123456789
857     verifyExtendsOrImplements(QStringLiteral("<?php interface blub{} interface test extends blub {}"),
858                               QStringLiteral("interface test extends blub,bar, "),
859                               ClassDeclarationData::Interface,
860                               CursorInRevision(0, 50),
861                               QStringList() << QStringLiteral("test") << QStringLiteral("blub"));
862 }
863 
newImplements()864 void TestCompletion::newImplements()
865 {
866     verifyExtendsOrImplements(QStringLiteral("<?php "), QStringLiteral("class test implements "),
867                               ClassDeclarationData::Interface,
868                               CursorInRevision::invalid(),
869                               QStringList() << QStringLiteral("test"));
870     verifyExtendsOrImplements(QStringLiteral("<?php interface blub{}"), QStringLiteral(" class test implements blub, "),
871                               ClassDeclarationData::Interface,
872                               CursorInRevision::invalid(),
873                               QStringList() << QStringLiteral("test") << QStringLiteral("blub"));
874 }
875 
updateImplements()876 void TestCompletion::updateImplements()
877 {
878     //                         0         1         2         3         4         5
879     //                         012345678901234567890123456789012345678901234567890123456789
880     verifyExtendsOrImplements(QStringLiteral("<?php class test {}"), QStringLiteral("class test implements "),
881                               ClassDeclarationData::Interface,
882                               CursorInRevision(0, 16),
883                               QStringList() << QStringLiteral("test"));
884 
885     //                         0         1         2         3         4         5
886     //                         012345678901234567890123456789012345678901234567890123456789
887     verifyExtendsOrImplements(QStringLiteral("<?php interface blub{} class test implements blub {}"),
888                               QStringLiteral("class test implements blub, "),
889                               ClassDeclarationData::Interface,
890                               CursorInRevision(0, 49),
891                               QStringList() << QStringLiteral("test") << QStringLiteral("blub"));
892 }
893 
avoidCircularInheritance()894 void TestCompletion::avoidCircularInheritance()
895 {
896     verifyExtendsOrImplements(QStringLiteral("<?php interface blub{} interface bar extends blub{}"),
897                               QStringLiteral("interface test extends bar, "),
898                               ClassDeclarationData::Interface,
899                               CursorInRevision::invalid(),
900                               QStringList() << QStringLiteral("test") << QStringLiteral("blub") << QStringLiteral("bar"));
901 
902     verifyExtendsOrImplements(QStringLiteral("<?php interface blub{} interface bar extends blub{}"),
903                               QStringLiteral("class test implements bar, "),
904                               ClassDeclarationData::Interface,
905                               CursorInRevision::invalid(),
906                               QStringList() << QStringLiteral("blub") << QStringLiteral("bar"));
907 }
908 
909 
910 
unsureType()911 void TestCompletion::unsureType()
912 {
913     QByteArray method("<? class A { public $vA; } class B { public $vB; } function foo() { return new A; return new B; } $f = foo(); ");
914 
915     TopDUContext* top = parse(method, DumpAll);
916     DUChainReleaser releaseTop(top);
917     DUChainWriteLocker lock(DUChain::lock());
918 
919     PhpCompletionTester tester(top, QStringLiteral("$f->"));
920     QCOMPARE(tester.completionContext->memberAccessOperation(), CodeCompletionContext::MemberAccess);
921 
922     qDebug() << tester.names;
923     foreach(const QString &id, QStringList() << "vA" << "vB") {
924         QVERIFY(tester.names.contains(id, Qt::CaseSensitive));
925     }
926 }
927 
completionAfterComments()928 void TestCompletion::completionAfterComments()
929 {
930     TopDUContext* top = parse("<?php\n", DumpAll);
931     DUChainReleaser releaseTop(top);
932     DUChainWriteLocker lock(DUChain::lock());
933 
934     foreach ( const QString &code, QStringList() << "# asdf\n"
935                                     << "// asdf\n"
936                                     << "/* */" )
937     {
938         PhpCompletionTester tester(top, code);
939 
940         qDebug() << tester.names;
941         QVERIFY(tester.completionContext->isValid());
942         QVERIFY(tester.items.count() > 0);
943     }
944 
945     // TODO: compare to global completion list
946 }
947 
completionInComments()948 void TestCompletion::completionInComments()
949 {
950     TopDUContext* top = parse("<?php\n", DumpNone);
951     DUChainReleaser releaseTop(top);
952     DUChainWriteLocker lock(DUChain::lock());
953 
954     foreach ( const QString &code, QStringList() << "# "
955                                     << "// " << "/* " )
956     {
957         PhpCompletionTester tester(top, code);
958         QVERIFY(!tester.completionContext->isValid());
959     }
960 }
961 
phpStartTag()962 void TestCompletion::phpStartTag()
963 {
964     // some context with a function (or anything else for that matter) starting with "php" substring
965     TopDUContext* top = parse("<?php function php_test() {} \n", DumpAll);
966     DUChainReleaser releaseTop(top);
967     DUChainWriteLocker lock(DUChain::lock());
968 
969     foreach ( const QString &code, QStringList() << "p" << "ph" << "php" ) {
970         PhpCompletionTester tester(top, QStringLiteral("<?"), code);
971 
972         QVERIFY(tester.items.isEmpty());
973     }
974 
975     PhpCompletionTester tester(top, QStringLiteral("<?php "));
976 
977     QVERIFY(!tester.items.isEmpty());
978 }
979 
outsidePhpContext()980 void TestCompletion::outsidePhpContext()
981 {
982     TopDUContext* top = parse("<?php $var = 1; ?>=", DumpDUChain);
983     DUChainReleaser releaseTop(top);
984     DUChainWriteLocker lock(DUChain::lock());
985 
986     PhpCompletionTester tester(top, QStringLiteral("<?php $var = 1; ?>="));
987 
988     QVERIFY(tester.items.isEmpty());
989 }
990 
nonGlobalInFunction()991 void TestCompletion::nonGlobalInFunction()
992 {
993     TopDUContext* top = parse("<?php $outside = 1; function test() {}", DumpDUChain);
994     DUChainReleaser releaseTop(top);
995     DUChainWriteLocker lock(DUChain::lock());
996 
997     PhpCompletionTester tester(top->childContexts().first(), {});
998 
999     QList<Declaration*> decs = top->findLocalDeclarations(Identifier(QStringLiteral("outside")));
1000     QCOMPARE(decs.count(), 1);
1001     QVERIFY(!searchDeclaration(tester.items, decs.first()));
1002 }
1003 
fileCompletion()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 
instanceof()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 
afterFunctionArg()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 
functionBeforeDeclaration()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 
classBeforeDeclaration()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 
constantBeforeDeclaration()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 
variableBeforeDeclaration()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 
functionArguments()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 
referencedClass()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 
ctorCall()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 
chainedCalling()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 
funcCallInConditional()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 
namespaces()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 
inNamespace()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 
closures()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 
1370