diff --git a/duchain/tests/duchain_multiplefiles.cpp b/duchain/tests/duchain_multiplefiles.cpp index 09237dd..2856521 100644 --- a/duchain/tests/duchain_multiplefiles.cpp +++ b/duchain/tests/duchain_multiplefiles.cpp @@ -1,324 +1,320 @@ /* This file is part of KDevelop Copyright 2010 Niko Sams Copyright 2011 Milian Wolff This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "duchain_multiplefiles.h" #include #include #include #include #include #include #include #include #include #include QTEST_MAIN(Php::TestDUChainMultipleFiles) using namespace KDevelop; using namespace Php; void TestDUChainMultipleFiles::initTestCase() { DUChainTestBase::initTestCase(); TestCore* core = dynamic_cast(ICore::self()); Q_ASSERT(core); m_projectController = new TestProjectController(core); core->setProjectController(m_projectController); } void TestDUChainMultipleFiles::testImportsGlobalFunction() { TopDUContext::Features features = TopDUContext::VisibleDeclarationsAndContexts; TestProject* project = new TestProject; m_projectController->closeAllProjects(); m_projectController->addProject(project); TestFile f1(QStringLiteral("imports(f1.topContext(), CursorInRevision(0, 0))); } void TestDUChainMultipleFiles::testImportsBaseClassNotYetParsed() { TopDUContext::Features features = TopDUContext::VisibleDeclarationsAndContexts; TestProject* project = new TestProject; m_projectController->closeAllProjects(); m_projectController->addProject(project); TestFile f2(QStringLiteral("imports(f1.topContext(), CursorInRevision(0, 0))); QVERIFY(ICore::self()->languageController()->backgroundParser()->queuedCount() == 0); } void TestDUChainMultipleFiles::testNonExistingBaseClass() { TopDUContext::Features features = TopDUContext::VisibleDeclarationsAndContexts; TestProject* project = new TestProject; m_projectController->closeAllProjects(); m_projectController->addProject(project); TestFile f1(QStringLiteral("languageController()->backgroundParser()->queuedCount() == 0); } void TestDUChainMultipleFiles::testImportsGlobalFunctionNotYetParsed() { TopDUContext::Features features = TopDUContext::VisibleDeclarationsAndContexts; TestProject* project = new TestProject; m_projectController->closeAllProjects(); m_projectController->addProject(project); TestFile f2(QStringLiteral("imports(f1.topContext(), CursorInRevision(0, 0))); - } void TestDUChainMultipleFiles::testNonExistingGlobalFunction() { TopDUContext::Features features = TopDUContext::VisibleDeclarationsAndContexts; TestProject* project = new TestProject; m_projectController->closeAllProjects(); m_projectController->addProject(project); TestFile f2(QStringLiteral("languageController()->backgroundParser()->queuedCount() == 0); } void TestDUChainMultipleFiles::testImportsStaticFunctionNotYetParsed() { TopDUContext::Features features = TopDUContext::VisibleDeclarationsAndContexts; TestProject* project = new TestProject; m_projectController->closeAllProjects(); m_projectController->addProject(project); TestFile f2(QStringLiteral("imports(f1.topContext(), CursorInRevision(0, 0))); } void TestDUChainMultipleFiles::testNonExistingStaticFunction() { TopDUContext::Features features = TopDUContext::VisibleDeclarationsAndContexts; TestProject* project = new TestProject; m_projectController->closeAllProjects(); m_projectController->addProject(project); TestFile f2(QStringLiteral("languageController()->backgroundParser()->queuedCount() == 0); } void TestDUChainMultipleFiles::testForeachImportedIdentifier() { // see https://bugs.kde.org/show_bug.cgi?id=269369 TopDUContext::Features features = TopDUContext::VisibleDeclarationsAndContexts; TestProject* project = new TestProject; m_projectController->closeAllProjects(); m_projectController->addProject(project); // build dependency TestFile f1(QStringLiteral("bar(); foreach($i as $a => $b) {} } \n" " public function bar() { $a = new SomeIterator(); return $a; }\n" " }\n"), QStringLiteral("php"), project); for(int i = 0; i < 2; ++i) { if (i > 0) { features = static_cast(features | TopDUContext::ForceUpdate); } f2.parse(features); QVERIFY(f2.waitForParsed()); QTest::qWait(100); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(f2.topContext()->childContexts().size(), 1); DUContext* ACtx = f2.topContext()->childContexts().first(); QVERIFY(ACtx); QCOMPARE(ACtx->childContexts().size(), 4); Declaration* iDec = ACtx->childContexts().at(1)->localDeclarations().first(); QVERIFY(iDec); Declaration* SomeIteratorDec = f1.topContext()->localDeclarations().first(); QVERIFY(SomeIteratorDec); if (i == 0) { QEXPECT_FAIL("", "needs a full two-pass (i.e. get rid of PreDeclarationBuilder)", Continue); } QVERIFY(iDec->abstractType()->equals(SomeIteratorDec->abstractType().constData())); QVERIFY(f2.topContext()->imports(f1.topContext(), CursorInRevision(0, 0))); } } void TestDUChainMultipleFiles::testUpdateForeach() { TopDUContext::Features features = TopDUContext::AllDeclarationsContextsAndUses; TestProject* project = new TestProject; m_projectController->closeAllProjects(); m_projectController->addProject(project); TestFile f(QStringLiteral(" $k) {}\n"), QStringLiteral("php"), project); f.parse(features); QVERIFY(f.waitForParsed()); QVERIFY(f.topContext()); { DUChainWriteLocker lock; QVERIFY(f.topContext()->problems().isEmpty()); QCOMPARE(f.topContext()->findDeclarations(Identifier("k")).count(), 1); Declaration* kDec = f.topContext()->findDeclarations(Identifier(QStringLiteral("k"))).first(); QCOMPARE(kDec->rangeInCurrentRevision().start().line(), 1); QCOMPARE(kDec->rangeInCurrentRevision().start().column(), 0); QCOMPARE(kDec->uses().count(), 1); QCOMPARE(kDec->uses().begin()->count(), 1); QCOMPARE(kDec->uses().begin()->begin()->start.line, 2); } // delete $k = null; line f.setFileContents(QStringLiteral(" $k) {}\n")); f.parse(static_cast(features | TopDUContext::ForceUpdate)); QVERIFY(f.waitForParsed()); QVERIFY(f.topContext()); { DUChainWriteLocker lock; QVERIFY(f.topContext()->problems().isEmpty()); QCOMPARE(f.topContext()->findDeclarations(Identifier("k")).count(), 1); Declaration* kDec = f.topContext()->findDeclarations(Identifier(QStringLiteral("k"))).first(); QCOMPARE(kDec->rangeInCurrentRevision().start().line(), 1); QCOMPARE(kDec->rangeInCurrentRevision().start().column(), 25); QCOMPARE(kDec->uses().count(), 0); } } void TestDUChainMultipleFiles::testTodoExtractorReparse() { TestFile file(QStringLiteral("baz();"), QStringLiteral("php")); QVERIFY(KDevelop::ICore::self()->languageController()->completionSettings()->todoMarkerWords().contains("TODO")); auto features = TopDUContext::AllDeclarationsContextsAndUses; for (int i = 0; i < 2; ++i) { if (i == 1) { file.setFileContents(QStringLiteral("asdf();")); features = static_cast(features | TopDUContext::ForceUpdate); } file.parse(features); QVERIFY(file.waitForParsed()); DUChainReadLocker lock; auto top = file.topContext(); QVERIFY(top); QCOMPARE(top->problems().size(), 1); QCOMPARE(top->problems().at(0)->description(), QString("TODO")); QCOMPARE(top->problems().at(0)->range(), RangeInRevision(2, 3, 2, 7)); } } void TestDUChainMultipleFiles::testIteratorForeachReparse() { TestFile file(QStringLiteral("(features | TopDUContext::ForceUpdate); } file.parse(features); QVERIFY(file.waitForParsed()); DUChainReadLocker lock; auto top = file.topContext(); QVERIFY(top); QVERIFY(top->localDeclarations().size() == 2); QCOMPARE(top->localDeclarations().at(0)->qualifiedIdentifier(), QualifiedIdentifier("a")); IntegralType::Ptr type = top->localDeclarations().at(0)->type(); QVERIFY(type); //Should actually parse as an TypeInt, but this does not work. QVERIFY(type->dataType() == IntegralType::TypeMixed); } } diff --git a/phpparsejob.cpp b/phpparsejob.cpp index c980378..5b39308 100644 --- a/phpparsejob.cpp +++ b/phpparsejob.cpp @@ -1,250 +1,250 @@ /***************************************************************************** * Copyright (c) 2007 Piyush verma * * Copyright (c) 2008 Niko Sams * * Copyright (c) 2010 Milian Wolff * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * *****************************************************************************/ #include "phpparsejob.h" #include #include #include #include #include #include #include #include #include #include #include #include "editorintegrator.h" #include "parsesession.h" #include "phplanguagesupport.h" #include "phpdebugvisitor.h" #include "duchain/builders/declarationbuilder.h" #include "duchain/builders/usebuilder.h" #include "duchain/helper.h" #include "phpducontext.h" #include "phpdebug.h" #include #include #include #include using namespace KDevelop; namespace Php { ParseJob::ParseJob(const IndexedString& url, ILanguageSupport* languageSupport) : KDevelop::ParseJob(url, languageSupport) , m_parentJob(nullptr) { } ParseJob::~ParseJob() { } LanguageSupport* ParseJob::php() const { return dynamic_cast(languageSupport()); } void ParseJob::run(ThreadWeaver::JobPointer /*self*/, ThreadWeaver::Thread * /*thread*/) { if (document() != internalFunctionFile()) { // make sure we loaded the internal file already const auto &phpSupport = languageSupport(); static std::once_flag once; std::call_once(once, [phpSupport] { qCDebug(PHP) << "Initializing internal function file" << internalFunctionFile(); ParseJob internalJob(internalFunctionFile(), phpSupport); internalJob.setMinimumFeatures(TopDUContext::AllDeclarationsAndContexts); internalJob.run({}, nullptr); Q_ASSERT(internalJob.success()); }); } UrlParseLock urlLock(document()); if (!(minimumFeatures() & Resheduled) && !isUpdateRequired(phpLanguageString())) { return; } qCDebug(PHP) << "parsing" << document().str(); KDevelop::ProblemPointer p = readContents(); if (p) { //TODO: associate problem with topducontext return abortJob();; } ParseSession session; //TODO: support different charsets session.setContents(QString::fromUtf8(contents().contents)); session.setCurrentDocument(document()); // 2) parse StartAst* ast = nullptr; bool matched = session.parse(&ast); if (abortRequested() || ICore::self()->shuttingDown()) { return abortJob(); } KDevelop::ReferencedTopDUContext toUpdate; { KDevelop::DUChainReadLocker duchainlock(KDevelop::DUChain::lock()); toUpdate = KDevelop::DUChainUtils::standardContextForUrl(document().toUrl()); } KDevelop::TopDUContext::Features newFeatures = minimumFeatures(); if (toUpdate) newFeatures = (KDevelop::TopDUContext::Features)(newFeatures | toUpdate->features()); //Remove update-flags like 'Recursive' or 'ForceUpdate' newFeatures = static_cast(newFeatures & KDevelop::TopDUContext::AllDeclarationsContextsUsesAndAST); if (matched) { if (abortRequested()) { return abortJob(); } EditorIntegrator editor(&session); QReadLocker parseLock(php()->parseLock()); DeclarationBuilder builder(&editor); KDevelop::ReferencedTopDUContext chain = builder.build(document(), ast, toUpdate); if (abortRequested()) { return abortJob(); } setDuChain(chain); bool hadUnresolvedIdentifiers = builder.hadUnresolvedIdentifiers(); if ( newFeatures & TopDUContext::AllDeclarationsContextsAndUses && document() != internalFunctionFile() ) { UseBuilder useBuilder(&editor); useBuilder.buildUses(ast); if (useBuilder.hadUnresolvedIdentifiers()) hadUnresolvedIdentifiers = true; } if (hadUnresolvedIdentifiers) { - if (!(minimumFeatures() & Resheduled) && KDevelop::ICore::self()->languageController()->backgroundParser()->queuedCount()) { + if (!(minimumFeatures() & Resheduled)) { // Need to create new parse job with lower priority qCDebug(PHP) << "Reschedule file " << document().str() << "for parsing"; KDevelop::TopDUContext::Features feat = static_cast( minimumFeatures() | KDevelop::TopDUContext::VisibleDeclarationsAndContexts | Resheduled ); int priority = qMin(parsePriority()+100, (int)KDevelop::BackgroundParser::WorstPriority); KDevelop::ICore::self()->languageController()->backgroundParser() ->addDocument(document(), feat, priority); } else { // We haven't resolved all identifiers, but by now, we don't expect to qCDebug(PHP) << "Builder found unresolved identifiers when they should have been resolved! (if there was no coding error)"; } } if (abortRequested()) { return abortJob(); } if (abortRequested()) { return abortJob(); } { DUChainWriteLocker lock(DUChain::lock()); foreach(const ProblemPointer &p, session.problems()) { chain->addProblem(p); } chain->setFeatures(newFeatures); ParsingEnvironmentFilePointer file = chain->parsingEnvironmentFile(); file->setModificationRevision(contents().modification); DUChain::self()->updateContextEnvironment( chain->topContext(), file.data() ); } highlightDUChain(); } else { ReferencedTopDUContext top; DUChainWriteLocker lock; { top = DUChain::self()->chainForDocument(document()); } if (top) { ///NOTE: if we clear the imported parent contexts, autocompletion of built-in PHP stuff won't work! //top->clearImportedParentContexts(); top->parsingEnvironmentFile()->clearModificationRevisions(); top->clearProblems(); } else { ParsingEnvironmentFile *file = new ParsingEnvironmentFile(document()); file->setLanguage(phpLanguageString()); top = new TopDUContext(document(), RangeInRevision(0, 0, INT_MAX, INT_MAX), file); DUChain::self()->addDocumentChain(top); } foreach(const ProblemPointer &p, session.problems()) { top->addProblem(p); } setDuChain(top); qCDebug(PHP) << "===Failed===" << document().str(); } DUChain::self()->emitUpdateReady(document(), duChain()); } void ParseJob::setParentJob(ParseJob *job) { m_parentJob = job; } bool ParseJob::hasParentDocument(const IndexedString &doc) { if (document() == doc) return true; if (!m_parentJob) return false; if (m_parentJob->document() == doc) return true; return m_parentJob->hasParentDocument(doc); } ProblemPointer ParseJob::createProblem(const QString &description, AstNode* node, EditorIntegrator * editor, IProblem::Source source, IProblem::Severity severity) { ProblemPointer p(new Problem()); p->setSource(source); p->setSeverity(severity); p->setDescription(description); p->setFinalLocation(DocumentRange(document(), editor->findRange(node).castToSimpleRange())); qCDebug(PHP) << p->description(); return p; } } // kate: space-indent on; indent-width 4; tab-width 4; replace-tabs on; auto-insert-doxygen on