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