1 /* This file is part of KDevelop
2 
3     SPDX-FileCopyrightText: 2010 Niko Sams <niko.sams@gmail.com>
4     SPDX-FileCopyrightText: 2011 Milian Wolff <mail@milianw.de>
5 
6     SPDX-License-Identifier: LGPL-2.0-only
7 */
8 
9 #include "duchain_multiplefiles.h"
10 
11 #include <QtTest>
12 
13 #include <tests/testcore.h>
14 #include <interfaces/ilanguagecontroller.h>
15 #include <interfaces/icompletionsettings.h>
16 #include <language/backgroundparser/backgroundparser.h>
17 #include <tests/testproject.h>
18 #include <tests/testfile.h>
19 #include <language/duchain/declaration.h>
20 #include <language/duchain/problem.h>
21 #include <language/duchain/types/integraltype.h>
22 
23 QTEST_MAIN(Php::TestDUChainMultipleFiles)
24 
25 using namespace KDevelop;
26 using namespace Php;
27 
initTestCase()28 void TestDUChainMultipleFiles::initTestCase()
29 {
30     DUChainTestBase::initTestCase();
31     TestCore* core = dynamic_cast<TestCore*>(ICore::self());
32     Q_ASSERT(core);
33     m_projectController = new TestProjectController(core);
34     core->setProjectController(m_projectController);
35 }
36 
testImportsGlobalFunction()37 void TestDUChainMultipleFiles::testImportsGlobalFunction()
38 {
39     TopDUContext::Features features = TopDUContext::VisibleDeclarationsAndContexts;
40 
41     TestProject* project = new TestProject;
42     m_projectController->closeAllProjects();
43     m_projectController->addProject(project);
44 
45     TestFile f1(QStringLiteral("<? function foo() {}"), QStringLiteral("php"), project);
46     f1.parse(features);
47     QVERIFY(f1.waitForParsed());
48 
49     TestFile f2(QStringLiteral("<? foo();"), QStringLiteral("php"), project);
50     f2.parse(features);
51     QVERIFY(f2.waitForParsed());
52 
53     DUChainWriteLocker lock(DUChain::lock());
54     QVERIFY(f1.topContext());
55     QVERIFY(f2.topContext());
56     QVERIFY(f2.topContext()->imports(f1.topContext(), CursorInRevision(0, 0)));
57 }
58 
testImportsBaseClassNotYetParsed()59 void TestDUChainMultipleFiles::testImportsBaseClassNotYetParsed()
60 {
61     TopDUContext::Features features = TopDUContext::VisibleDeclarationsAndContexts;
62 
63     TestProject* project = new TestProject;
64     m_projectController->closeAllProjects();
65     m_projectController->addProject(project);
66 
67     TestFile f2(QStringLiteral("<? class B extends A {}"), QStringLiteral("php"), project);
68     f2.parse(features);
69 
70     TestFile f1(QStringLiteral("<? class A {}"), QStringLiteral("php"), project);
71     f1.parse(features, 100); //low priority, to make sure f2 is parsed first
72 
73     QVERIFY(f1.waitForParsed());
74     QTest::qWait(100);
75 
76     DUChainWriteLocker lock(DUChain::lock());
77     QVERIFY(f2.topContext()->imports(f1.topContext(), CursorInRevision(0, 0)));
78     QVERIFY(ICore::self()->languageController()->backgroundParser()->queuedCount() == 0);
79 }
80 
testNonExistingBaseClass()81 void TestDUChainMultipleFiles::testNonExistingBaseClass()
82 {
83     TopDUContext::Features features = TopDUContext::VisibleDeclarationsAndContexts;
84 
85     TestProject* project = new TestProject;
86     m_projectController->closeAllProjects();
87     m_projectController->addProject(project);
88 
89     TestFile f1(QStringLiteral("<? class B extends A {}"), QStringLiteral("php"), project);
90     f1.parse(features);
91     QVERIFY(f1.waitForParsed());
92 
93     //there must not be a re-enqueued parsejob
94     QVERIFY(ICore::self()->languageController()->backgroundParser()->queuedCount() == 0);
95 }
96 
testImportsGlobalFunctionNotYetParsed()97 void TestDUChainMultipleFiles::testImportsGlobalFunctionNotYetParsed()
98 {
99     TopDUContext::Features features = TopDUContext::VisibleDeclarationsAndContexts;
100 
101     TestProject* project = new TestProject;
102     m_projectController->closeAllProjects();
103     m_projectController->addProject(project);
104 
105     TestFile f2(QStringLiteral("<? foo2();"), QStringLiteral("php"), project);
106     f2.parse(features);
107 
108     TestFile f1(QStringLiteral("<? function foo2() {}"), QStringLiteral("php"), project);
109     f1.parse(features, 100); //low priority, to make sure f2 is parsed first
110 
111     QVERIFY(f2.waitForParsed());
112     QTest::qWait(100);
113 
114     DUChainWriteLocker lock(DUChain::lock());
115     QVERIFY(f2.topContext()->imports(f1.topContext(), CursorInRevision(0, 0)));
116 
117 }
118 
testNonExistingGlobalFunction()119 void TestDUChainMultipleFiles::testNonExistingGlobalFunction()
120 {
121     TopDUContext::Features features = TopDUContext::VisibleDeclarationsAndContexts;
122 
123     TestProject* project = new TestProject;
124     m_projectController->closeAllProjects();
125     m_projectController->addProject(project);
126 
127     TestFile f2(QStringLiteral("<? foo3();"), QStringLiteral("php"), project);
128     f2.parse(features);
129 
130     QVERIFY(f2.waitForParsed());
131      //there must not be a re-enqueued parsejob
132     QVERIFY(ICore::self()->languageController()->backgroundParser()->queuedCount() == 0);
133 }
134 
testImportsStaticFunctionNotYetParsed()135 void TestDUChainMultipleFiles::testImportsStaticFunctionNotYetParsed()
136 {
137     TopDUContext::Features features = TopDUContext::VisibleDeclarationsAndContexts;
138 
139     TestProject* project = new TestProject;
140     m_projectController->closeAllProjects();
141     m_projectController->addProject(project);
142 
143     TestFile f2(QStringLiteral("<? C::foo();"), QStringLiteral("php"), project);
144     f2.parse(features);
145 
146     TestFile f1(QStringLiteral("<? class C { public static function foo() {} }"), QStringLiteral("php"), project);
147     f1.parse(features, 100); //low priority, to make sure f2 is parsed first
148 
149     QVERIFY(f2.waitForParsed());
150     QTest::qWait(100);
151 
152     DUChainWriteLocker lock(DUChain::lock());
153     QVERIFY(f2.topContext()->imports(f1.topContext(), CursorInRevision(0, 0)));
154 }
155 
testNonExistingStaticFunction()156 void TestDUChainMultipleFiles::testNonExistingStaticFunction()
157 {
158     TopDUContext::Features features = TopDUContext::VisibleDeclarationsAndContexts;
159 
160     TestProject* project = new TestProject;
161     m_projectController->closeAllProjects();
162     m_projectController->addProject(project);
163 
164     TestFile f2(QStringLiteral("<? D::foo();"), QStringLiteral("php"), project);
165     f2.parse(features);
166 
167     QVERIFY(f2.waitForParsed());
168      //there must not be a re-enqueued parsejob
169     QVERIFY(ICore::self()->languageController()->backgroundParser()->queuedCount() == 0);
170 }
171 
testForeachImportedIdentifier()172 void TestDUChainMultipleFiles::testForeachImportedIdentifier()
173 {
174     // see https://bugs.kde.org/show_bug.cgi?id=269369
175 
176     TopDUContext::Features features = TopDUContext::VisibleDeclarationsAndContexts;
177 
178     TestProject* project = new TestProject;
179     m_projectController->closeAllProjects();
180     m_projectController->addProject(project);
181 
182     // build dependency
183     TestFile f1(QStringLiteral("<? class SomeIterator implements Countable, Iterator { }"), QStringLiteral("php"), project);
184     f1.parse(features);
185     QVERIFY(f1.waitForParsed());
186 
187     TestFile f2(QStringLiteral("<?\n"
188                 "class A {\n"
189                 "  public function foo() { $i = $this->bar(); foreach($i as $a => $b) {} } \n"
190                 "  public function bar() { $a = new SomeIterator(); return $a; }\n"
191                 " }\n"), QStringLiteral("php"), project);
192 
193     for(int i = 0; i < 2; ++i) {
194         if (i > 0) {
195             features = static_cast<TopDUContext::Features>(features | TopDUContext::ForceUpdate);
196         }
197         f2.parse(features);
198         QVERIFY(f2.waitForParsed());
199         QTest::qWait(100);
200 
201         DUChainWriteLocker lock(DUChain::lock());
202         QCOMPARE(f2.topContext()->childContexts().size(), 1);
203         DUContext* ACtx = f2.topContext()->childContexts().first();
204         QVERIFY(ACtx);
205         QCOMPARE(ACtx->childContexts().size(), 4);
206         Declaration* iDec = ACtx->childContexts().at(1)->localDeclarations().first();
207         QVERIFY(iDec);
208         Declaration* SomeIteratorDec = f1.topContext()->localDeclarations().first();
209         QVERIFY(SomeIteratorDec);
210         if (i == 0) {
211             QEXPECT_FAIL("", "needs a full two-pass (i.e. get rid of PreDeclarationBuilder)", Continue);
212         }
213         QVERIFY(iDec->abstractType()->equals(SomeIteratorDec->abstractType().constData()));
214         QVERIFY(f2.topContext()->imports(f1.topContext(), CursorInRevision(0, 0)));
215     }
216 }
217 
testUpdateForeach()218 void TestDUChainMultipleFiles::testUpdateForeach()
219 {
220     TopDUContext::Features features = TopDUContext::AllDeclarationsContextsAndUses;
221 
222     TestProject* project = new TestProject;
223     m_projectController->closeAllProjects();
224     m_projectController->addProject(project);
225 
226     TestFile f(QStringLiteral("<?\n$k = null;\nforeach(array() as $i => $k) {}\n"), QStringLiteral("php"), project);
227 
228     f.parse(features);
229     QVERIFY(f.waitForParsed());
230     QVERIFY(f.topContext());
231 
232     {
233         DUChainWriteLocker lock;
234         QVERIFY(f.topContext()->problems().isEmpty());
235         QCOMPARE(f.topContext()->findDeclarations(Identifier("k")).count(), 1);
236         Declaration* kDec = f.topContext()->findDeclarations(Identifier(QStringLiteral("k"))).first();
237         QCOMPARE(kDec->rangeInCurrentRevision().start().line(), 1);
238         QCOMPARE(kDec->rangeInCurrentRevision().start().column(), 0);
239         QCOMPARE(kDec->uses().count(), 1);
240         QCOMPARE(kDec->uses().begin()->count(), 1);
241         QCOMPARE(kDec->uses().begin()->begin()->start.line, 2);
242     }
243 
244     // delete $k = null; line
245     f.setFileContents(QStringLiteral("<?\nforeach(array() as $i => $k) {}\n"));
246     f.parse(static_cast<TopDUContext::Features>(features | TopDUContext::ForceUpdate));
247     QVERIFY(f.waitForParsed());
248     QVERIFY(f.topContext());
249 
250     {
251         DUChainWriteLocker lock;
252         QVERIFY(f.topContext()->problems().isEmpty());
253         QCOMPARE(f.topContext()->findDeclarations(Identifier("k")).count(), 1);
254         Declaration* kDec = f.topContext()->findDeclarations(Identifier(QStringLiteral("k"))).first();
255         QCOMPARE(kDec->rangeInCurrentRevision().start().line(), 1);
256         QCOMPARE(kDec->rangeInCurrentRevision().start().column(), 25);
257         QCOMPARE(kDec->uses().count(), 0);
258     }
259 }
260 
testTodoExtractorReparse()261 void TestDUChainMultipleFiles::testTodoExtractorReparse()
262 {
263     TestFile file(QStringLiteral("<?php\n$foo = new bar();\n// TODO\n$foo->baz();"), QStringLiteral("php"));
264 
265     QVERIFY(KDevelop::ICore::self()->languageController()->completionSettings()->todoMarkerWords().contains("TODO"));
266 
267     auto features = TopDUContext::AllDeclarationsContextsAndUses;
268 
269     for (int i = 0; i < 2; ++i) {
270         if (i == 1) {
271             file.setFileContents(QStringLiteral("<?php\n$foo = new bar();\n// TODO\n$foo->asdf();"));
272             features = static_cast<TopDUContext::Features>(features | TopDUContext::ForceUpdate);
273         }
274 
275         file.parse(features);
276         QVERIFY(file.waitForParsed());
277 
278         DUChainReadLocker lock;
279         auto top = file.topContext();
280         QVERIFY(top);
281         QCOMPARE(top->problems().size(), 1);
282         QCOMPARE(top->problems().at(0)->description(), QString("TODO"));
283         QCOMPARE(top->problems().at(0)->range(), RangeInRevision(2, 3, 2, 7));
284     }
285 }
286 
testIteratorForeachReparse()287 void TestDUChainMultipleFiles::testIteratorForeachReparse() {
288     TestFile file(QStringLiteral("<?php\n/*\n\n/*\n*/\nforeach (new A() as $a) {}\nclass A implements Iterator {\npublic function current() { return 0; }\n}"), QStringLiteral("php"));
289 
290     auto features = TopDUContext::AllDeclarationsAndContexts;
291 
292     for (int i = 0; i < 2; ++i) {
293         if (i == 1) {
294             file.setFileContents(QStringLiteral("<?php\n/*\n*/\n\n/*\n*/\nforeach (new A() as $a) {}\nclass A implements Iterator {\npublic function current() { return 0; }\n}"));
295             features = static_cast<TopDUContext::Features>(features | TopDUContext::ForceUpdate);
296         }
297 
298         file.parse(features);
299         QVERIFY(file.waitForParsed());
300 
301         DUChainReadLocker lock;
302         auto top = file.topContext();
303         QVERIFY(top);
304         QVERIFY(top->localDeclarations().size() == 2);
305         QCOMPARE(top->localDeclarations().at(0)->qualifiedIdentifier(), QualifiedIdentifier("a"));
306 
307         IntegralType::Ptr type = top->localDeclarations().at(0)->type<IntegralType>();
308         QVERIFY(type);
309         //Should actually parse as an TypeInt, but this does not work.
310         QVERIFY(type->dataType() == IntegralType::TypeMixed);
311     }
312 }
313