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