diff --git a/docs/kcm_kdevphpdocs.desktop.cmake b/docs/kcm_kdevphpdocs.desktop.cmake index 7efbdb1..64954b7 100644 --- a/docs/kcm_kdevphpdocs.desktop.cmake +++ b/docs/kcm_kdevphpdocs.desktop.cmake @@ -1,40 +1,39 @@ [Desktop Entry] Icon=application-x-php Type=Service ServiceTypes=KCModule X-KDE-ModuleType=Library X-KDE-Library=kdevplatform/@KDEV_PLUGIN_VERSION@/kcm/kcm_kdevphpdocs X-KDE-FactoryName=kdevphpdocs_config X-KDE-ParentApp=kdevplatform X-KDE-ParentComponents=kdevplatform,kdevphpdocs X-KDE-CfgDlgHierarchy=DOCUMENTATION X-KDE-Weight=150 Name=PHP Documentation Name[ca]=Documentació de PHP Name[ca@valencia]=Documentació de PHP Name[cs]=Dokumentace PHP Name[de]=PHP-Dokumentation -Name[el]=PHP τεκμηρίωση Name[en_GB]=PHP Documentation Name[es]=Documentación de PHP Name[et]=PHP dokumentatsioon Name[fi]=PHP-dokumentaatio Name[fr]=Documentation PHP Name[gl]=Documentación de PHP Name[it]=Documentazione PHP Name[ko]=PHP 문서 Name[nb]=PHP-dokumentasjon Name[nl]=PHP-documentatie Name[pl]=Dokumentacja PHP Name[pt]=Documentação de PHP Name[pt_BR]=Documentação de PHP Name[ru]=Документация к PHP Name[sk]=Dokumentácia PHP Name[sl]=Dokumentacija za PHP Name[sv]=PHP-dokumentation Name[tr]=PHP Belgelendirmesi Name[uk]=Документація з PHP Name[x-test]=xxPHP Documentationxx Name[zh_CN]=PHP 文档 diff --git a/docs/kdevphpdocs.json b/docs/kdevphpdocs.json index 2e18c43..167786a 100644 --- a/docs/kdevphpdocs.json +++ b/docs/kdevphpdocs.json @@ -1,69 +1,69 @@ { "KPlugin": { "Authors": [ { "Name": "Milian Wolff", "Name[x-test]": "xxMilian Wolffxx" } ], "Category": "Documentation", "Description": "This plugin integrates PHP.net online documentation.", "Description[ca@valencia]": "Aquest connector integra la documentació en línia de PHP.net.", "Description[ca]": "Aquest connector integra la documentació en línia de PHP.net.", "Description[de]": "Dieses Modul integriert die Online-Dokumentation von PHP.net.", "Description[es]": "Este complemento integra la documentación en línea de PHP.net.", + "Description[et]": "See plugin lõimib PHP veebidokumentatsiooni.", "Description[fi]": "Tämä liitännäinen integroi PHP.net-dokumentaation.", "Description[fr]": "Ce module intègre la documentation en ligne de PHP.net.", - "Description[gl]": "Este complemento integra a documentación con conexión de PHP.net.", + "Description[gl]": "Esta extensión integra a documentación en liña de PHP.net.", "Description[it]": "Questa estensione integra la documentazione online di PHP.net.", - "Description[ko]": "PHP.net 온라인 문서를 통합합니다.", "Description[nl]": "Deze plugin integreert PHP.net online-documentatie.", "Description[pl]": "Wtyczka ta integruje dokumentację internetową PHP.net.", "Description[pt]": "Este 'plugin' integra-se com a documentação 'online' do PHP.net.", "Description[pt_BR]": "Este plugin integra a documentação online do PHP.net.", "Description[sk]": "Tento plugin integruje online dokumentáciu PHP.net.", "Description[sl]": "Ta vstavek vključuje spletno dokumentacijo za PHP.", "Description[sv]": "Insticksprogrammet integrerar Internet-dokumentationen från PHP.net.", "Description[tr]": "Bu eklenti, PHP çevrimiçi belgelendirmesini entegre eder.", "Description[uk]": "За допомогою цього додатка можна інтегрувати документацію PHP.net.", "Description[x-test]": "xxThis plugin integrates PHP.net online documentation.xx", "Description[zh_CN]": "此插件集成了 PHP.net 的在线文档。", "Icon": "application-x-php", "Id": "kdevphpdocs", "License": "GPL", "Name": "PHP Documentation", "Name[ca@valencia]": "Documentació de PHP", "Name[ca]": "Documentació de PHP", "Name[cs]": "Dokumentace PHP", "Name[de]": "PHP-Dokumentation", "Name[es]": "Documentación de PHP", + "Name[et]": "PHP dokumentatsioon", "Name[fi]": "PHP-dokumentaatio", "Name[fr]": "Documentation PHP", "Name[gl]": "Documentación de PHP", "Name[it]": "Documentazione PHP", - "Name[ko]": "PHP 문서", "Name[nl]": "PHP-documentatie", "Name[pl]": "Dokumentacja PHP", "Name[pt]": "Documentação de PHP", "Name[pt_BR]": "Documentação de PHP", "Name[ru]": "Документация к PHP", "Name[sk]": "Dokumentácia PHP", "Name[sl]": "Dokumentacija za PHP", "Name[sv]": "PHP-dokumentation", "Name[tr]": "PHP Belgelendirmesi", "Name[uk]": "Документація з PHP", "Name[x-test]": "xxPHP Documentationxx", "Name[zh_CN]": "PHP 文档", "ServiceTypes": [ "KDevelop/Plugin" ] }, "X-KDevelop-Category": "Global", "X-KDevelop-IRequired": [ "org.kdevelop.PHPSupport" ], "X-KDevelop-Interfaces": [ "org.kdevelop.IDocumentationProvider" ], "X-KDevelop-Mode": "GUI" } diff --git a/docs/phpdocsplugin.cpp b/docs/phpdocsplugin.cpp index d49d869..ca84fad 100644 --- a/docs/phpdocsplugin.cpp +++ b/docs/phpdocsplugin.cpp @@ -1,224 +1,224 @@ /* This file is part of the KDevelop PHP Documentation Plugin Copyright 2012 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) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. 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 "phpdocsplugin.h" #include "phpdocsmodel.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "phpdocsdebug.h" #include "phpdocumentation.h" #include "phpdocssettings.h" using namespace KDevelop; K_PLUGIN_FACTORY_WITH_JSON(PhpDocsFactory, "kdevphpdocs.json", registerPlugin();) PhpDocsPlugin::PhpDocsPlugin(QObject* parent, const QVariantList& args) : IPlugin(QStringLiteral("kdevphpdocs"), parent) , m_model(new PhpDocsModel(this)) { Q_UNUSED(args); readConfig(); KSettings::Dispatcher::registerComponent( QStringLiteral("kdevphpdocs_config"), this, "readConfig" ); } PhpDocsPlugin::~PhpDocsPlugin() { } QString PhpDocsPlugin::name() const { return QStringLiteral("PHP"); } QIcon PhpDocsPlugin::icon() const { static QIcon icon = QIcon::fromTheme(QStringLiteral("application-x-php")); return icon; } void PhpDocsPlugin::readConfig() { // since PhpDocsSettings pointer in this plugin is distinct from the one in the KCM // we have to trigger reading manually PhpDocsSettings::self()->load(); } ///TODO: possibly return multiple filenames (i.e. fallbacks) when doing local lookups QString PhpDocsPlugin::getDocumentationFilename( KDevelop::Declaration* dec, const bool& isLocal ) const { QString fname; //TODO: most of the SPL stuff is not found for me in the deb package php-doc // => check newer documentation or give a fallback to ref.spl.html if ( ClassFunctionDeclaration* fdec = dynamic_cast( dec ) ) { // class methods -> php.net/CLASS.METHOD // local: either CLASS.METHOD.html or function.CLASS-METHOD.html... really sucks :-/ // for now, use the latter... if ( dec->context() && dec->context()->type() == DUContext::Class && dec->context()->owner() ) { QString className = dec->context()->owner()->identifier().toString(); if ( !isLocal ) { fname = className + '.' + fdec->identifier().toString(); } else { if ( fdec->isConstructor() ) { fname = QStringLiteral("construct"); } else if ( fdec->isDestructor() ) { fname = QStringLiteral("destruct"); } else { fname = fdec->identifier().toString(); } //TODO: CLASS.METHOD.html e.g. for xmlreader etc. pp. fname = "function." + className + '-' + fname; } } } else if ( dynamic_cast(dec) ) { fname = "class." + dec->identifier().toString(); } else if ( dynamic_cast(dec) ) { fname = "function." + dec->identifier().toString(); } // check for superglobals / reserved variables else if ( dec->identifier() == Identifier(QStringLiteral("GLOBALS")) || dec->identifier() == Identifier(QStringLiteral("php_errormsg")) || dec->identifier() == Identifier(QStringLiteral("HTTP_RAW_POST_DATA")) || dec->identifier() == Identifier(QStringLiteral("http_response_header")) || dec->identifier() == Identifier(QStringLiteral("argc")) || dec->identifier() == Identifier(QStringLiteral("argv")) || dec->identifier() == Identifier(QStringLiteral("_GET")) || dec->identifier() == Identifier(QStringLiteral("_POST")) || dec->identifier() == Identifier(QStringLiteral("_FILES")) || dec->identifier() == Identifier(QStringLiteral("_REQUEST")) || dec->identifier() == Identifier(QStringLiteral("_SESSION")) || dec->identifier() == Identifier(QStringLiteral("_ENV")) || dec->identifier() == Identifier(QStringLiteral("_COOKIE")) ) { if ( isLocal ) { fname = QStringLiteral("reserved.variables.") + dec->identifier().toString().remove('_'); } else { fname = dec->identifier().toString(); } } qCDebug(DOCS) << fname; if ( !fname.isEmpty() && isLocal ) { fname = fname.toLower(); fname.replace('_', '-'); fname.append(".html"); } return fname; } IDocumentation::Ptr PhpDocsPlugin::documentationForDeclaration( Declaration* dec ) const { if ( dec ) { DUChainReadLocker lock( DUChain::lock() ); // filter non global or non-php declarations if ( dec->topContext()->url() != m_model->internalFunctionFile() ) { return {}; } QUrl url = PhpDocsSettings::phpDocLocation(); qCDebug(DOCS) << url; QString file = getDocumentationFilename( dec, url.isLocalFile() ); if ( file.isEmpty() ) { qCDebug(DOCS) << "no documentation pattern found for" << dec->toString(); return {}; } url.setPath( url.path() + '/' + file); if ( url.isLocalFile() && !QFile::exists( url.toLocalFile() ) ) { qCDebug(DOCS) << "bad path" << url << "for documentation of" << dec->toString() << " - aborting"; return {}; } qCDebug(DOCS) << "php documentation located at " << url << "for" << dec->toString(); - return documentationForUrl(url, dec->qualifiedIdentifier().toString(), dec->comment()); + return documentationForUrl(url, dec->qualifiedIdentifier().toString()); } return {}; } QAbstractListModel* PhpDocsPlugin::indexModel() const { return m_model; } IDocumentation::Ptr PhpDocsPlugin::documentationForIndex(const QModelIndex& index) const { return documentationForDeclaration(static_cast( index.data(PhpDocsModel::DeclarationRole).value().data() )); } void PhpDocsPlugin::loadUrl(const QUrl& url) const { qCDebug(DOCS) << "loading URL" << url; auto doc = documentationForUrl(url, QString()); ICore::self()->documentationController()->showDocumentation(doc); } void PhpDocsPlugin::showDocumentation(const QUrl& url) { auto doc = documentationForUrl(url, url.toString()); ICore::self()->documentationController()->showDocumentation(doc); } IDocumentation::Ptr PhpDocsPlugin::documentationForUrl(const QUrl& url, const QString& name, const QByteArray& description) const { return IDocumentation::Ptr(new PhpDocumentation( url, name, description, const_cast(this))); } IDocumentation::Ptr PhpDocsPlugin::homePage() const { QUrl url = PhpDocsSettings::phpDocLocation(); if ( url.isLocalFile() ) { url.setPath(url.path() + "/index.html"); } else { url.setPath(url.path() + "/manual"); } return documentationForUrl(url, i18n("PHP Documentation")); } #include "phpdocsplugin.moc" diff --git a/duchain/expressionvisitor.cpp b/duchain/expressionvisitor.cpp index 7e8cae9..4f48000 100644 --- a/duchain/expressionvisitor.cpp +++ b/duchain/expressionvisitor.cpp @@ -1,910 +1,1018 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2008 Niko Sams * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library 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 Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "expressionvisitor.h" #include "parsesession.h" #include "editorintegrator.h" #include "helper.h" #include "declarations/variabledeclaration.h" #include "declarations/classdeclaration.h" #include #include #include #include #include #include #include #include #include "duchaindebug.h" #define ifDebug(x) using namespace KDevelop; namespace Php { ExpressionVisitor::ExpressionVisitor(EditorIntegrator* editor) : m_editor(editor), m_createProblems(false), m_offset(CursorInRevision::invalid()), m_currentContext(nullptr), m_isAssignmentExpressionEqual(false), m_inDefine(false) { } DeclarationPointer ExpressionVisitor::processVariable(VariableIdentifierAst* variable) { Q_ASSERT(m_currentContext); CursorInRevision position = m_editor->findPosition(variable->variable, EditorIntegrator::BackEdge); if ( m_offset.isValid() ) { position.line += m_offset.line; position.column += m_offset.column; } DeclarationPointer ret; Identifier identifier = identifierForNode(variable).last(); ifDebug(qCDebug(DUCHAIN) << "processing variable" << identifier.toString() << position.castToSimpleCursor();) DUChainReadLocker lock; if (identifier.nameEquals(Identifier(QStringLiteral("this")))) { if (m_currentContext->parentContext() && m_currentContext->parentContext()->type() == DUContext::Class && m_currentContext->parentContext()->owner()) { ret = m_currentContext->parentContext()->owner(); } } else { //DontSearchInParent-flag because (1) in Php global variables aren't available in function //context and (2) a function body consists of a single context (so this is no problem) ret = findVariableDeclaration(m_currentContext, identifier, position, DUContext::DontSearchInParent); } if (!ret && m_currentContext->type() == DUContext::Namespace) { ret = findVariableDeclaration(m_currentContext, identifier, position, DUContext::NoSearchFlags); } if (!ret) { //look for a function argument ///TODO: why doesn't m_currentContext->findDeclarations() work? /// evaluate if the stuff below is fast enough (faster?) than findDeclarations() ///see r1028306 foreach(const DUContext::Import &import, m_currentContext->importedParentContexts() ) { if ( !import.isDirect() || import.position > position ) { continue; } DUContext* ctx = import.context(m_currentContext->topContext()); if ( ctx->type() == DUContext::Function ) { QList args = ctx->findLocalDeclarations(identifier); if ( !args.isEmpty() ) { ret = args.first(); break; } } } } if (!ret) { //look for a superglobal variable foreach(Declaration* dec, m_currentContext->topContext()->findDeclarations(identifier, position)) { VariableDeclaration* varDec = dynamic_cast(dec); if (varDec && varDec->isSuperglobal()) { ret = dec; break; } } } lock.unlock(); if ( !m_isAssignmentExpressionEqual || identifier.nameEquals( Identifier(QStringLiteral("this")) ) // might be something like $s = $s . $s; in which case we have to add a use for the first $s || (ret && ret->range().end < position) ) { // also don't report uses for the place of declaration if (!ret || ret->range().end != position) { usingDeclaration(variable, ret); } } ifDebug(qCDebug(DUCHAIN) << "found declaration:" << (ret ? ret->toString() : QString("no declaration found"));) return ret; } void ExpressionVisitor::visitNode(AstNode *node) { if (node && node->ducontext) { m_currentContext = node->ducontext; } Q_ASSERT(m_currentContext); DefaultVisitor::visitNode(node); } void ExpressionVisitor::visitAssignmentExpression(AssignmentExpressionAst *node) { if (node->assignmentExpressionEqual) { m_isAssignmentExpressionEqual = true; } visitNode(node->expression); m_isAssignmentExpressionEqual = false; visitNode(node->assignmentExpressionEqual); visitNode(node->assignmentExpression); if (node->operation == OperationPlus || node->operation == OperationMinus || node->operation == OperationMul || node->operation == OperationDiv || node->operation == OperationExp) { m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeInt))); } else if (node->operation == OperationConcat) { m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeString))); } } void ExpressionVisitor::visitArrayIndexSpecifier(ArrayIndexSpecifierAst* node) { DefaultVisitor::visitArrayIndexSpecifier(node); // it's an array item but we don't support it really, so just assign type mixed and be done m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeMixed))); } void ExpressionVisitor::visitCompoundVariableWithSimpleIndirectReference(CompoundVariableWithSimpleIndirectReferenceAst *node) { if (node->variable) { m_result.setDeclaration(processVariable(node->variable)); } DefaultVisitor::visitCompoundVariableWithSimpleIndirectReference(node); } void ExpressionVisitor::visitVariable(VariableAst* node) { if ( node->variablePropertiesSequence && node->variablePropertiesSequence->front() && node->variablePropertiesSequence->front()->element && node->variablePropertiesSequence->front()->element->variableProperty && node->variablePropertiesSequence->front()->element->variableProperty->objectProperty ) { // make sure we mark $foo as a use in $foo->... bool isAssignmentExpressionEqual = m_isAssignmentExpressionEqual; m_isAssignmentExpressionEqual = false; DefaultVisitor::visitVariable(node); m_isAssignmentExpressionEqual = isAssignmentExpressionEqual; } else { DefaultVisitor::visitVariable(node); } } void ExpressionVisitor::visitVarExpression(VarExpressionAst *node) { DefaultVisitor::visitVarExpression(node); if (node->isGenerator != -1) { DeclarationPointer generatorDecl = findDeclarationImport(ClassDeclarationType, QualifiedIdentifier("generator")); if (generatorDecl) { m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeMixed))); if (hasCurrentClosureReturnType()) { FunctionType::Ptr closureType = currentClosureReturnType().cast(); closureType->setReturnType(generatorDecl->abstractType()); } } } } void ExpressionVisitor::visitVarExpressionNewObject(VarExpressionNewObjectAst *node) { DefaultVisitor::visitVarExpressionNewObject(node); - if (node->className->staticIdentifier != -1) { + if (node->classNameReference->className && node->classNameReference->className->staticIdentifier != -1) { static const QualifiedIdentifier id(QStringLiteral("static")); DeclarationPointer dec = findDeclarationImport(ClassDeclarationType, id); - usingDeclaration(node->className, dec); + usingDeclaration(node->classNameReference->className, dec); m_result.setDeclaration(dec); - } else if (node->className->identifier) { - const QualifiedIdentifier id = identifierForNamespace(node->className->identifier, m_editor); + } else if (node->classNameReference->className && node->classNameReference->className->identifier) { + const QualifiedIdentifier id = identifierForNamespace(node->classNameReference->className->identifier, m_editor); DeclarationPointer dec = findDeclarationImport(ClassDeclarationType, id); - usingDeclaration(node->className->identifier->namespaceNameSequence->back()->element, dec); - buildNamespaceUses(node->className->identifier, id); + usingDeclaration(node->classNameReference->className->identifier->namespaceNameSequence->back()->element, dec); + buildNamespaceUses(node->classNameReference->className->identifier, id); m_result.setDeclaration(dec); } } void ExpressionVisitor::visitVarExpressionArray(VarExpressionArrayAst *node) { DefaultVisitor::visitVarExpressionArray(node); m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeArray))); } void ExpressionVisitor::visitClosure(ClosureAst* node) { auto* closureType = new FunctionType; closureType->setReturnType(AbstractType::Ptr(new IntegralType(IntegralType::TypeVoid))); openClosureReturnType(AbstractType::Ptr(closureType)); if (node->functionBody) { visitInnerStatementList(node->functionBody); } if (node->returnType && node->returnType->typehint && isClassTypehint(node->returnType->typehint, m_editor)) { NamespacedIdentifierAst* objectType = node->returnType->typehint->genericType; QualifiedIdentifier id = identifierForNamespace(objectType, m_editor); DeclarationPointer dec = findDeclarationImport(ClassDeclarationType, id); usingDeclaration(objectType->namespaceNameSequence->back()->element, dec); buildNamespaceUses(objectType, id); } //Override found type with return typehint or phpdoc return typehint AbstractType::Ptr type = returnType(node->returnType, {}, m_editor, m_currentContext); if (type) { closureType->setReturnType(type); } if (node->parameters->parametersSequence) { const KDevPG::ListNode< ParameterAst* >* it = node->parameters->parametersSequence->front(); forever { AbstractType::Ptr type = parameterType(it->element, {}, m_editor, m_currentContext); closureType->addArgument(type); if (it->element->parameterType && it->element->parameterType->typehint && isClassTypehint(it->element->parameterType->typehint, m_editor)) { NamespacedIdentifierAst* objectType = it->element->parameterType->typehint->genericType; QualifiedIdentifier id = identifierForNamespace(objectType, m_editor); DeclarationPointer dec = findDeclarationImport(ClassDeclarationType, id); usingDeclaration(objectType->namespaceNameSequence->back()->element, dec); buildNamespaceUses(objectType, id); } if (it->element->defaultValue) { visitExpr(it->element->defaultValue); } if ( it->hasNext() ) { it = it->next; } else { break; } } } if (node->lexicalVars && node->lexicalVars->lexicalVarsSequence) { const KDevPG::ListNode< LexicalVarAst* >* it = node->lexicalVars->lexicalVarsSequence->front(); DUChainWriteLocker lock; forever { DeclarationPointer found; foreach(Declaration* dec, m_currentContext->findDeclarations(identifierForNode(it->element->variable))) { if (dec->kind() == Declaration::Instance) { found = dec; break; } } usingDeclaration(it->element->variable, found); if ( it->hasNext() ) { it = it->next; } else { break; } } } m_result.setType(AbstractType::Ptr(closureType)); closeClosureReturnType(); } void ExpressionVisitor::visitFunctionCallParameterList( FunctionCallParameterListAst* node ) { QList decs = m_result.allDeclarations(); AbstractType::Ptr type = m_result.type(); DefaultVisitor::visitFunctionCallParameterList( node ); m_result.setDeclarations(decs); m_result.setType(type); } void ExpressionVisitor::visitFunctionCallParameterListElement(FunctionCallParameterListElementAst* node) { DefaultVisitor::visitFunctionCallParameterListElement(node); if (m_inDefine) m_inDefine = false; //reset after first parameter passed, the second argument can be a class name } void ExpressionVisitor::visitFunctionCall(FunctionCallAst* node) { if (node->stringFunctionNameOrClass && !node->stringFunctionName && !node->varFunctionName) { QualifiedIdentifier id = identifierForNamespace(node->stringFunctionNameOrClass, m_editor); if (id.toString(RemoveExplicitlyGlobalPrefix) == QLatin1String("define") && node->stringParameterList && node->stringParameterList->parametersSequence && node->stringParameterList->parametersSequence->count() > 0) { //in a define() call the first argument is the constant name. we don't want to look for a class name to build uses m_inDefine = true; } } DefaultVisitor::visitFunctionCall(node); m_inDefine = false; if (node->stringFunctionNameOrClass) { if (node->stringFunctionName) { //static function call foo::bar() DUContext* context = findClassContext(node->stringFunctionNameOrClass); if (context) { DUChainReadLocker lock(DUChain::lock()); QualifiedIdentifier methodName(stringForNode(node->stringFunctionName).toLower()); m_result.setDeclarations(context->findDeclarations(methodName)); lock.unlock(); if (!m_result.allDeclarations().isEmpty()) { usingDeclaration(node->stringFunctionName, m_result.allDeclarations().last()); FunctionType::Ptr function = m_result.allDeclarations().last()->type(); if (function) { m_result.setType(function->returnType()); } else { m_result.setType(AbstractType::Ptr()); } } } else { m_result.setHadUnresolvedIdentifiers(true); usingDeclaration(node->stringFunctionName, DeclarationPointer()); m_result.setType(AbstractType::Ptr()); } } else if (node->varFunctionName) { //static function call foo::$bar() } else if (node->expr) { //static function call foo::{expr}() const QualifiedIdentifier id = identifierForNamespace(node->stringFunctionNameOrClass, m_editor); DeclarationPointer dec = findDeclarationImport(ClassDeclarationType, id); usingDeclaration(node->stringFunctionNameOrClass->namespaceNameSequence->back()->element, dec); buildNamespaceUses(node->stringFunctionNameOrClass, id); m_result.setDeclaration(dec); } else { //global function call foo(); QualifiedIdentifier id = identifierForNamespace(node->stringFunctionNameOrClass, m_editor); DeclarationPointer dec = findDeclarationImport(FunctionDeclarationType, id); if (!dec) { id.setExplicitlyGlobal(true); dec = findDeclarationImport(FunctionDeclarationType, id); } ifDebug(qCDebug(DUCHAIN) << "function call of" << (dec ? dec->toString() : QString("function not found"));) m_result.setDeclaration(dec); usingDeclaration(node->stringFunctionNameOrClass->namespaceNameSequence->back()->element, dec); buildNamespaceUses(node->stringFunctionNameOrClass, id); if (dec) { FunctionType::Ptr function = dec->type(); if (function) { m_result.setType(function->returnType()); } else { m_result.setType(AbstractType::Ptr()); } } else { m_result.setHadUnresolvedIdentifiers(true); } } } } ///TODO: DUContext pointer? DUContext* ExpressionVisitor::findClassContext(IdentifierAst* className) { DUContext* context = nullptr; DeclarationPointer declaration = findDeclarationImport(ClassDeclarationType, className); usingDeclaration(className, declaration); if (declaration) { DUChainReadLocker lock(DUChain::lock()); context = declaration->internalContext(); if (!context && m_currentContext->parentContext() && m_currentContext->parentContext()->localScopeIdentifier() == declaration->qualifiedIdentifier()) { //className is currentClass (internalContext is not yet set) context = m_currentContext->parentContext(); } } return context; } ///TODO: DUContext pointer? DUContext* ExpressionVisitor::findClassContext(NamespacedIdentifierAst* className) { DUContext* context = nullptr; const QualifiedIdentifier id = identifierForNamespace(className, m_editor); DeclarationPointer declaration = findDeclarationImport(ClassDeclarationType, id); usingDeclaration(className->namespaceNameSequence->back()->element, declaration); buildNamespaceUses(className, id); if (declaration) { DUChainReadLocker lock(DUChain::lock()); context = declaration->internalContext(); if (!context && m_currentContext->parentContext() && m_currentContext->parentContext()->localScopeIdentifier() == declaration->qualifiedIdentifier()) { //className is currentClass (internalContext is not yet set) context = m_currentContext->parentContext(); } } return context; } void ExpressionVisitor::visitConstantOrClassConst(ConstantOrClassConstAst *node) { DefaultVisitor::visitConstantOrClassConst(node); if (node->classConstant) { //class constant Foo::BAR DUContext* context = findClassContext(node->constant); if (context) { DUChainReadLocker lock(DUChain::lock()); m_result.setDeclarations(context->findDeclarations(Identifier(m_editor->parseSession()->symbol(node->classConstant)))); lock.unlock(); if (!m_result.allDeclarations().isEmpty()) { usingDeclaration(node->classConstant, m_result.allDeclarations().last()); } else { usingDeclaration(node->classConstant, DeclarationPointer()); } if (!stringForNode(node->classConstant).compare(QLatin1String("class"), Qt::CaseInsensitive)) { m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeString))); } } else { m_result.setType(AbstractType::Ptr()); } } else { QString str(stringForNode(node->constant).toLower()); if (str == QLatin1String("true") || str == QLatin1String("false")) { m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeBoolean))); } else if (str == QLatin1String("null")) { m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeNull))); } else { //constant (created with declare('foo', 'bar')) or const Foo = 1; QualifiedIdentifier id = identifierForNamespace(node->constant, m_editor, true); DeclarationPointer declaration = findDeclarationImport(ConstantDeclarationType, id); if (!declaration) { id.setExplicitlyGlobal(true); declaration = findDeclarationImport(ConstantDeclarationType, id); } if (!declaration) { ///TODO: is this really wanted? //it could also be a global function call, without () declaration = findDeclarationImport(FunctionDeclarationType, id); } m_result.setDeclaration(declaration); usingDeclaration(node->constant->namespaceNameSequence->back()->element, declaration); buildNamespaceUses(node->constant, id); } } } void ExpressionVisitor::visitScalar(ScalarAst *node) { DefaultVisitor::visitScalar(node); if (node->commonScalar) { uint type = IntegralType::TypeVoid; switch (node->commonScalar->scalarType) { case ScalarTypeInt: type = IntegralType::TypeInt; break; case ScalarTypeFloat: type = IntegralType::TypeFloat; break; case ScalarTypeString: type = IntegralType::TypeString; break; } m_result.setType(AbstractType::Ptr(new IntegralType(type))); } else if (node->varname != -1) { //STRING_VARNAME-Token, probably the type of varname should be used m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeString))); } else if (node->encapsList) { m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeString))); } if (!m_inDefine && node->commonScalar && node->commonScalar->scalarType == ScalarTypeString) { QString str = m_editor->parseSession()->symbol(node->commonScalar); QRegExp exp("^['\"]([A-Za-z0-9_]+)['\"]$"); if (exp.exactMatch(str)) { //that *could* be a class name QualifiedIdentifier id(exp.cap(1).toLower()); DeclarationPointer declaration = findDeclarationImport(ClassDeclarationType, id); if (declaration) { usingDeclaration(node->commonScalar, declaration); } else { m_result.setHadUnresolvedIdentifiers(true); } } } } void ExpressionVisitor::visitStaticScalar(StaticScalarAst *node) { if (node->ducontext) { m_currentContext = node->ducontext; } Q_ASSERT(m_currentContext); DefaultVisitor::visitStaticScalar(node); uint type = IntegralType::TypeVoid; if (node->value) { switch (node->value->scalarType) { case ScalarTypeInt: type = IntegralType::TypeInt; break; case ScalarTypeFloat: type = IntegralType::TypeFloat; break; case ScalarTypeString: type = IntegralType::TypeString; break; } } else if (node->plusValue || node->minusValue) { type = IntegralType::TypeInt; } else if (node->array != -1) { type = IntegralType::TypeArray; } if (type != IntegralType::TypeVoid) { m_result.setType(AbstractType::Ptr(new IntegralType(type))); } } void ExpressionVisitor::visitEncapsVar(EncapsVarAst *node) { DefaultVisitor::visitEncapsVar(node); if (node->variable) { // handle $foo DeclarationPointer dec = processVariable(node->variable); if (dec && node->propertyIdentifier) { // handle property in $foo->bar DeclarationPointer foundDec; DUChainReadLocker lock(DUChain::lock()); if ( StructureType::Ptr structType = dec->type() ) { if ( ClassDeclaration* cdec = dynamic_cast(structType->declaration(m_currentContext->topContext())) ) { ///TODO: share code with visitVariableProperty DUContext* ctx = cdec->internalContext(); if (!ctx && m_currentContext->parentContext()) { if (m_currentContext->parentContext()->localScopeIdentifier() == cdec->qualifiedIdentifier()) { //class is currentClass (internalContext is not yet set) ctx = m_currentContext->parentContext(); } } if (ctx) { foreach( Declaration* pdec, ctx->findDeclarations(identifierForNode(node->propertyIdentifier)) ) { if ( !pdec->isFunctionDeclaration() ) { foundDec = pdec; break; } } } } } lock.unlock(); usingDeclaration(node->propertyIdentifier, foundDec); } } } void ExpressionVisitor::visitVariableProperty(VariablePropertyAst *node) { ifDebug(qCDebug(DUCHAIN) << "node:" << m_editor->parseSession()->symbol(node) << (node->isFunctionCall != -1 ? QString("is function call") : QString("is no function call"));) if (node->objectProperty && node->objectProperty->objectDimList) { //handle $foo->bar() and $foo->baz, $foo is m_result.type() AbstractType::Ptr type = m_result.type(); //If the variable type is unsure, try to see if it contains a StructureType. If so, use that // (since the other types do not allow accessing properties) if (type && type.cast()) { UnsureType::Ptr unsureType = type.cast(); int numStructureType = 0; StructureType::Ptr structureType; for (unsigned int i = 0; itypesSize(); ++i) { StructureType::Ptr subType = unsureType->types()[i].type(); if (subType) { structureType = subType; ++numStructureType; } } //Only use the found structureType if there's exactly *one* such type if (numStructureType == 1) { Q_ASSERT(structureType); type = AbstractType::Ptr(structureType); } } if (type && StructureType::Ptr::dynamicCast(type)) { DUChainReadLocker lock(DUChain::lock()); Declaration* declaration = StructureType::Ptr::staticCast(type)->declaration(m_currentContext->topContext()); if (declaration) { ifDebug(qCDebug(DUCHAIN) << "parent:" << declaration->toString();) DUContext* context = declaration->internalContext(); if (!context && m_currentContext->parentContext()) { if (m_currentContext->parentContext()->localScopeIdentifier() == declaration->qualifiedIdentifier()) { //class is currentClass (internalContext is not yet set) context = m_currentContext->parentContext(); } } if (context) { QualifiedIdentifier propertyId; if ( node->isFunctionCall != -1 ) { propertyId = QualifiedIdentifier(stringForNode(node->objectProperty->objectDimList->variableName->name).toLower()); } else { propertyId = identifierForNode(node->objectProperty->objectDimList->variableName->name); } ifDebug(qCDebug(DUCHAIN) << "property id:" << propertyId.toString();) QList decs; foreach ( Declaration* dec, context->findDeclarations(propertyId) ) { if ( node->isFunctionCall != -1 ) { if ( dec->isFunctionDeclaration() ) { decs << dec; ifDebug(qCDebug(DUCHAIN) << "found:" << dec->toString();) } } else { if ( !dec->isFunctionDeclaration() ) { ClassMemberDeclaration *classDec = dynamic_cast(dec); if (classDec && classDec->accessPolicy() == Declaration::Private) { if (declaration == dec->context()->owner()) { decs << dec; ifDebug(qCDebug(DUCHAIN) << "found private:" << dec->toString();) } } else { decs << dec; ifDebug(qCDebug(DUCHAIN) << "found:" << dec->toString();) } } } } m_result.setDeclarations(decs); lock.unlock(); if (!m_result.allDeclarations().isEmpty()) { if ( !m_isAssignmentExpressionEqual ) { usingDeclaration(node->objectProperty->objectDimList->variableName, m_result.allDeclarations().last()); } if (node->isFunctionCall != -1) { FunctionType::Ptr function = m_result.allDeclarations().last()->type(); if (function) { m_result.setType(function->returnType()); } else { m_result.setType(AbstractType::Ptr()); } } } else { if ( !m_isAssignmentExpressionEqual ) { usingDeclaration(node->objectProperty->objectDimList->variableName, DeclarationPointer()); } m_result.setType(AbstractType::Ptr()); } } else { m_result.setType(AbstractType::Ptr()); } } else { m_result.setType(AbstractType::Ptr()); } } } DefaultVisitor::visitVariableProperty(node); } void ExpressionVisitor::visitStaticMember(StaticMemberAst* node) { //don't call DefaultVisitor::visitStaticMember(node); //because we would end up in visitCompoundVariableWithSimpleIndirectReference - if (node->variable->variable->variable) { + if (node->staticProperty->staticProperty->variable->variable) { DUContext* context = findClassContext(node->className); if (context) { - DUChainReadLocker lock(DUChain::lock()); - m_result.setDeclarations(context->findDeclarations(identifierForNode(node->variable->variable->variable))); - lock.unlock(); - if (!m_result.allDeclarations().isEmpty()) { - usingDeclaration(node->variable->variable->variable, m_result.allDeclarations().last()); - } else { - usingDeclaration(node->variable->variable->variable, DeclarationPointer()); - } + useDeclaration(node->staticProperty->staticProperty->variable, context); } else { usingDeclaration(node->className, DeclarationPointer()); m_result.setType(AbstractType::Ptr()); } - if (node->variable->offsetItemsSequence) { - const KDevPG::ListNode< DimListItemAst* >* it = node->variable->offsetItemsSequence->front(); + if (node->staticProperty->offsetItemsSequence) { + const KDevPG::ListNode< DimListItemAst* >* it = node->staticProperty->offsetItemsSequence->front(); do { visitDimListItem(it->element); } while(it->hasNext() && (it = it->next)); } } } +void ExpressionVisitor::visitClassNameReference(ClassNameReferenceAst* node) +{ + if (node->staticProperty) { + DUContext* context = findClassContext(node->className->identifier); + + if (context) { + useDeclaration(node->staticProperty->staticProperty->variable, context); + } + + if (node->staticProperty->offsetItemsSequence) { + const KDevPG::ListNode< DimListItemAst* >* dim_it = node->staticProperty->offsetItemsSequence->front(); + do { + visitDimListItem(dim_it->element); + } while(dim_it->hasNext() && (dim_it = dim_it->next)); + } + } + + if (node->baseVariable) { + DefaultVisitor::visitVariableWithoutObjects(node->baseVariable); + } + + if (node->propertiesSequence) { + if (!m_result.allDeclarations().isEmpty()) { + DUContext* context = nullptr; + StructureType::Ptr type; + + Declaration *declaration = nullptr; + const KDevPG::ListNode< ClassPropertyAst* >* it = node->propertiesSequence->front(); + + do { + // first check for property names held in variables ($object->$property) + if (it->element->property && it->element->property->variableWithoutObjects + && it->element->property->variableWithoutObjects->variable->variable) { + VariableIdentifierAst *varnode = it->element->property->variableWithoutObjects->variable->variable; + useDeclaration(varnode, m_currentContext); + } else if (!m_result.allDeclarations().isEmpty()) { + // handle array indices after normal/static properties ($object->property[$index] // $object::$property[$index]) + if (it->element->property && it->element->property->objectDimList && it->element->property->objectDimList->offsetItemsSequence) { + const KDevPG::ListNode< DimListItemAst* >* dim_it = it->element->property->objectDimList->offsetItemsSequence->front(); + do { + visitDimListItem(dim_it->element); + } while(dim_it->hasNext() && (dim_it = dim_it->next)); + } else if (it->element->staticProperty && it->element->staticProperty->offsetItemsSequence) { + const KDevPG::ListNode< DimListItemAst* >* dim_it = it->element->staticProperty->offsetItemsSequence->front(); + do { + visitDimListItem(dim_it->element); + } while(dim_it->hasNext() && (dim_it = dim_it->next)); + } + + type = m_result.allDeclarations().last()->type(); + + if (!type) { + context = nullptr; + continue; + } + + DUChainReadLocker lock(DUChain::lock()); + declaration = type->declaration(m_currentContext->topContext()); + lock.unlock(); + + if (!declaration) { + context = nullptr; + continue; + } + + context = declaration->internalContext(); + + if (!context || context->type() != DUContext::Class) { + context = nullptr; + continue; + } + + if (it->element->staticProperty) { + // static properties ($object::$property) + VariableIdentifierAst *varnode = it->element->staticProperty->staticProperty->variable; + useDeclaration(varnode, context); + } else if (it->element->property && it->element->property->objectDimList + && it->element->property->objectDimList->variableName->name) { + // normal properties ($object->property) + IdentifierAst *varidnode = it->element->property->objectDimList->variableName->name; + useDeclaration(varidnode, context); + } else { + context = nullptr; + } + } + } while(it->hasNext() && (it = it->next)); + } + } + +} + void ExpressionVisitor::visitUnaryExpression(UnaryExpressionAst* node) { DefaultVisitor::visitUnaryExpression(node); if (node->castType) { uint type = 0; switch (node->castType) { case CastInt: type = IntegralType::TypeInt; break; case CastDouble: type = IntegralType::TypeFloat; break; case CastString: type = IntegralType::TypeString; break; case CastArray: type = IntegralType::TypeArray; break; case CastObject: { /// Qualified identifier for 'stdclass' static const QualifiedIdentifier stdclassQId(QStringLiteral("stdclass")); DUChainReadLocker lock(DUChain::lock()); m_result.setDeclarations(m_currentContext->findDeclarations(stdclassQId)); break; } case CastBool: type = IntegralType::TypeBoolean; break; case CastUnset: //TODO break; } if (type) { m_result.setType(AbstractType::Ptr(new IntegralType(type))); } } } void ExpressionVisitor::visitAdditiveExpressionRest(AdditiveExpressionRestAst* node) { DefaultVisitor::visitAdditiveExpressionRest(node); if (node->operation == OperationPlus || node->operation == OperationMinus) { m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeInt))); } else if (node->operation == OperationConcat) { m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeString))); } } void ExpressionVisitor::visitRelationalExpression(RelationalExpressionAst *node) { DefaultVisitor::visitRelationalExpression(node); - if (node->instanceofType && node->instanceofType->identifier) { - const QualifiedIdentifier id = identifierForNamespace(node->instanceofType->identifier, m_editor); + if (node->instanceofType && node->instanceofType->className && node->instanceofType->className->identifier) { + const QualifiedIdentifier id = identifierForNamespace(node->instanceofType->className->identifier, m_editor); DeclarationPointer dec = findDeclarationImport(ClassDeclarationType, id); - usingDeclaration(node->instanceofType->identifier->namespaceNameSequence->back()->element, dec); - buildNamespaceUses(node->instanceofType->identifier, id); + usingDeclaration(node->instanceofType->className->identifier->namespaceNameSequence->back()->element, dec); + buildNamespaceUses(node->instanceofType->className->identifier, id); m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeBoolean))); } } void ExpressionVisitor::visitRelationalExpressionRest(RelationalExpressionRestAst *node) { DefaultVisitor::visitRelationalExpressionRest(node); m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeBoolean))); } void ExpressionVisitor::visitEqualityExpressionRest(EqualityExpressionRestAst *node) { DefaultVisitor::visitEqualityExpressionRest(node); if (node->operation && node->operation == OperationSpaceship) { m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeInt))); } else { m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeBoolean))); } } void ExpressionVisitor::visitStatement(StatementAst *node) { DefaultVisitor::visitStatement(node); if (node->returnExpr) { FunctionType::Ptr closureType = currentClosureReturnType().cast(); if (closureType) { closureType->setReturnType(m_result.type()); } } } QString ExpressionVisitor::stringForNode(AstNode* id) { if (!id) return QString(); return m_editor->parseSession()->symbol(id); } QualifiedIdentifier ExpressionVisitor::identifierForNode(IdentifierAst* id) { if (!id) return QualifiedIdentifier(); return QualifiedIdentifier(stringForNode(id)); } QString ExpressionVisitor::stringForNode(VariableIdentifierAst* id) { if (!id) return QString(); QString ret(m_editor->parseSession()->symbol(id->variable)); ret = ret.mid(1); //cut off $ return ret; } QualifiedIdentifier ExpressionVisitor::identifierForNode(VariableIdentifierAst* id) { if (!id) return QualifiedIdentifier(); return QualifiedIdentifier(stringForNode(id)); } void ExpressionVisitor::setCreateProblems(bool v) { m_createProblems = v; } void ExpressionVisitor::setOffset(const CursorInRevision& offset) { m_offset = offset; } void ExpressionVisitor::buildNamespaceUses(NamespacedIdentifierAst* namespaces, const QualifiedIdentifier& identifier) { QualifiedIdentifier curId; curId.setExplicitlyGlobal(identifier.explicitlyGlobal()); Q_ASSERT(identifier.count() == namespaces->namespaceNameSequence->count()); for ( int i = 0; i < identifier.count() - 1; ++i ) { curId.push(identifier.at(i)); AstNode* node = namespaces->namespaceNameSequence->at(i)->element; DeclarationPointer dec = findDeclarationImport(NamespaceDeclarationType, curId); usingDeclaration(node, dec); } } +void ExpressionVisitor::useDeclaration(VariableIdentifierAst* node, DUContext* context) +{ + DUChainReadLocker lock(DUChain::lock()); + m_result.setDeclarations(context->findDeclarations(identifierForNode(node))); + lock.unlock(); + if (!m_result.allDeclarations().isEmpty()) { + usingDeclaration(node, m_result.allDeclarations().last()); + } else { + usingDeclaration(node, DeclarationPointer()); + } +} + +void ExpressionVisitor::useDeclaration(IdentifierAst* node, DUContext* context) +{ + DUChainReadLocker lock(DUChain::lock()); + m_result.setDeclarations(context->findDeclarations(identifierForNode(node))); + lock.unlock(); + if (!m_result.allDeclarations().isEmpty()) { + usingDeclaration(node, m_result.allDeclarations().last()); + } else { + usingDeclaration(node, DeclarationPointer()); + } +} + DeclarationPointer ExpressionVisitor::findDeclarationImport(DeclarationType declarationType, IdentifierAst* node) { // methods and class names are case insensitive QualifiedIdentifier id; if ( declarationType == ClassDeclarationType || declarationType == FunctionDeclarationType ) { id = QualifiedIdentifier(stringForNode(node).toLower()); } else { id = identifierForNode(node); } return findDeclarationImport(declarationType, id); } DeclarationPointer ExpressionVisitor::findDeclarationImport(DeclarationType declarationType, VariableIdentifierAst* node) { return findDeclarationImport(declarationType, identifierForNode(node)); } DeclarationPointer ExpressionVisitor::findDeclarationImport( DeclarationType declarationType, const QualifiedIdentifier& identifier) { return findDeclarationImportHelper(m_currentContext, identifier, declarationType); } Declaration* ExpressionVisitor::findVariableDeclaration(DUContext* context, Identifier identifier, CursorInRevision position, DUContext::SearchFlag flag) { QList decls = context->findDeclarations(identifier, position, nullptr, flag); for (int i = decls.count() - 1; i >= 0; i--) { Declaration *dec = decls.at(i); if (dec->kind() == Declaration::Instance && dynamic_cast(dec)) { return dec; } } return nullptr; } } diff --git a/duchain/expressionvisitor.h b/duchain/expressionvisitor.h index 4a463d7..ca321e6 100644 --- a/duchain/expressionvisitor.h +++ b/duchain/expressionvisitor.h @@ -1,150 +1,153 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2008 Niko Sams * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library 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 Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef EXPRESSIONVISITOR_H #define EXPRESSIONVISITOR_H #include "phpdefaultvisitor.h" #include "phpduchainexport.h" #include "expressionevaluationresult.h" #include "helper.h" #include #include namespace KDevelop { class TopDUContext; class Declaration; } namespace Php { class EditorIntegrator; class KDEVPHPDUCHAIN_EXPORT ExpressionVisitor : public DefaultVisitor { public: ExpressionVisitor(EditorIntegrator* editor); ExpressionEvaluationResult result() { return m_result; } void setCreateProblems(bool v); void setOffset(const KDevelop::CursorInRevision& offset); void visitNode(AstNode *node) override; protected: KDevelop::DeclarationPointer processVariable( VariableIdentifierAst* variable); void visitAssignmentExpression(AssignmentExpressionAst *node) override; void visitArrayIndexSpecifier(ArrayIndexSpecifierAst* node) override; void visitCompoundVariableWithSimpleIndirectReference(CompoundVariableWithSimpleIndirectReferenceAst *node) override; void visitVarExpression(VarExpressionAst *node) override; void visitVarExpressionNewObject(VarExpressionNewObjectAst *node) override; void visitVarExpressionArray(VarExpressionArrayAst *node) override; void visitClosure(ClosureAst* node) override; void visitFunctionCall(FunctionCallAst* node) override; void visitConstantOrClassConst(ConstantOrClassConstAst *node) override; void visitScalar(ScalarAst *node) override; void visitStaticScalar(StaticScalarAst *node) override; void visitEncapsVar(EncapsVarAst *node) override; void visitVariableProperty(VariablePropertyAst *node) override; void visitStaticMember(StaticMemberAst* node) override; + void visitClassNameReference(ClassNameReferenceAst* node) override; void visitUnaryExpression(UnaryExpressionAst* node) override; void visitAdditiveExpressionRest(AdditiveExpressionRestAst* node) override; void visitVariable(VariableAst* node) override; void visitFunctionCallParameterList( FunctionCallParameterListAst* node ) override; void visitFunctionCallParameterListElement(FunctionCallParameterListElementAst* node) override; void visitRelationalExpression(RelationalExpressionAst* node) override; void visitRelationalExpressionRest(RelationalExpressionRestAst* node) override; void visitEqualityExpressionRest(EqualityExpressionRestAst* node) override; void visitStatement(StatementAst* node) override; QString stringForNode(AstNode* id); KDevelop::QualifiedIdentifier identifierForNode(IdentifierAst* id); QString stringForNode(VariableIdentifierAst* id); KDevelop::QualifiedIdentifier identifierForNode(VariableIdentifierAst* id); virtual void usingDeclaration(AstNode* node, const KDevelop::DeclarationPointer& decl) { Q_UNUSED(node) Q_UNUSED(decl) } KDevelop::DeclarationPointer findDeclarationImport(DeclarationType declarationType, IdentifierAst* node); KDevelop::DeclarationPointer findDeclarationImport(DeclarationType declarationType, VariableIdentifierAst* node); KDevelop::DeclarationPointer findDeclarationImport(DeclarationType declarationType, const KDevelop::QualifiedIdentifier& identifier); KDevelop::Declaration* findVariableDeclaration(KDevelop::DUContext* context, KDevelop::Identifier identifier, KDevelop::CursorInRevision position, KDevelop::DUContext::SearchFlag flag); protected: EditorIntegrator* m_editor; /** * Opens the given closure return type, and sets it to be the current closure return type. */ void openClosureReturnType(const KDevelop::AbstractType::Ptr& type) { m_closureReturnTypes.append(type); } /** * Close the current closure return type. */ void closeClosureReturnType() { // And the reference will be lost... m_closureReturnTypes.pop(); } /** * Retrieve the return type of the current closure. * * \returns the abstract type of the current context. */ inline KDevelop::AbstractType::Ptr currentClosureReturnType() { if (m_closureReturnTypes.isEmpty()) { return KDevelop::AbstractType::Ptr(); } else { return m_closureReturnTypes.top(); } } /// Determine if the expression visitor has a return type for the current closure. \returns true if there is a current closure return type, else returns false. inline bool hasCurrentClosureReturnType() { return !m_closureReturnTypes.isEmpty(); } private: KDevelop::DUContext* findClassContext(NamespacedIdentifierAst* className); KDevelop::DUContext* findClassContext(IdentifierAst* className); void buildNamespaceUses(NamespacedIdentifierAst* namespaces, const KDevelop::QualifiedIdentifier& identifier); + void useDeclaration(VariableIdentifierAst* node, KDevelop::DUContext* context); + void useDeclaration(IdentifierAst* node, KDevelop::DUContext* context); bool m_createProblems; KDevelop::CursorInRevision m_offset; KDevelop::DUContext* m_currentContext; ExpressionEvaluationResult m_result; KDevelop::Stack m_closureReturnTypes; bool m_isAssignmentExpressionEqual; bool m_inDefine; }; } #endif diff --git a/duchain/tests/duchain.cpp b/duchain/tests/duchain.cpp index 85e522f..d338a4e 100644 --- a/duchain/tests/duchain.cpp +++ b/duchain/tests/duchain.cpp @@ -1,4066 +1,4070 @@ /* This file is part of KDevelop Copyright 2008 Niko Sams 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.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "helper.h" #include "../declarations/classdeclaration.h" #include "../declarations/classmethoddeclaration.h" #include "../declarations/functiondeclaration.h" #include "../declarations/variabledeclaration.h" #include "../types/structuretype.h" #include "../types/integraltypeextended.h" #include "../types/indexedcontainer.h" #include using namespace KDevelop; using namespace Php; QTEST_MAIN(Php::TestDUChain) TestDUChain::TestDUChain() { } void TestDUChain::declareFunction() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("childContexts().count(), 2); QCOMPARE(top->localDeclarations().count(), 1); Declaration* dec = top->localDeclarations().at(0); QVERIFY(dec); QCOMPARE(dec->context(), top); QCOMPARE(dec->internalContext(), top->childContexts().at(1)); // no return means void as return type FunctionType::Ptr ftype = FunctionType::Ptr::dynamicCast(dec->abstractType()); QVERIFY(ftype); IntegralType::Ptr itype = IntegralType::Ptr::dynamicCast(ftype->returnType()); QVERIFY(itype->dataType() == IntegralType::TypeVoid); QCOMPARE(top->childContexts().at(0)->type(), DUContext::Function); QCOMPARE(top->childContexts().at(1)->type(), DUContext::Other); } void TestDUChain::declareBaseTypeFunction() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("childContexts().count(), 2); QCOMPARE(top->localDeclarations().count(), 1); Declaration* dec = top->localDeclarations().at(0); QVERIFY(dec); QCOMPARE(dec->context(), top); QCOMPARE(dec->internalContext(), top->childContexts().at(1)); // no return means void as return type FunctionType::Ptr ftype = FunctionType::Ptr::dynamicCast(dec->abstractType()); QVERIFY(ftype); IntegralType::Ptr itype = IntegralType::Ptr::dynamicCast(ftype->returnType()); QVERIFY(itype->dataType() == IntegralType::TypeVoid); QCOMPARE(top->childContexts().at(0)->type(), DUContext::Function); QCOMPARE(top->childContexts().at(1)->type(), DUContext::Other); } void TestDUChain::declareSemiReservedFunction() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("parentContext()); QCOMPARE(top->childContexts().count(), 2); QCOMPARE(top->localDeclarations().count(), 5); //class A Declaration* dec = top->localDeclarations().at(0); QCOMPARE(dec->uses().count(), 1); QCOMPARE(dec->uses().begin()->count(), 1); //$i Declaration* decVar = top->localDeclarations().at(2); QCOMPARE(decVar->identifier(), Identifier("i")); qDebug() << decVar->abstractType()->toString(); UnsureType::Ptr unsureType = decVar->type(); QVERIFY(unsureType); QCOMPARE(unsureType->typesSize(), 3u); // = new A(); QCOMPARE(unsureType->types()[0].abstractType().cast()->qualifiedIdentifier(), QualifiedIdentifier("a")); QVERIFY(unsureType->types()[0].abstractType()->equals(dec->abstractType().data())); // = new B(); //class B dec = top->localDeclarations().at(1); QCOMPARE(dec->uses().count(), 1); QCOMPARE(dec->uses().begin()->count(), 2); QCOMPARE(unsureType->types()[1].abstractType().cast()->qualifiedIdentifier(), QualifiedIdentifier("b")); QVERIFY(unsureType->types()[1].abstractType()->equals(dec->abstractType().data())); // = 'foo'; QVERIFY(unsureType->types()[2].abstractType().cast()); QVERIFY(unsureType->types()[2].abstractType().cast()->dataType() == IntegralType::TypeString); //$j decVar = top->localDeclarations().at(3); QCOMPARE(decVar->identifier(), Identifier("j")); StructureType::Ptr classType = decVar->type(); QVERIFY(classType); QCOMPARE(classType->qualifiedIdentifier(), QualifiedIdentifier("b")); QVERIFY(classType->equals(dec->abstractType().data())); // $a decVar = top->localDeclarations().at(4); QCOMPARE(decVar->identifier(), Identifier("a")); QVERIFY(decVar->type()); } void TestDUChain::varTypehint() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().at(0); //$i Declaration* decVar = top->localDeclarations().at(1); QCOMPARE(decVar->identifier(), Identifier("i")); StructureType::Ptr classType = decVar->type(); QVERIFY(classType); QCOMPARE(classType->qualifiedIdentifier(), QualifiedIdentifier("a")); QVERIFY(classType->equals(dec->abstractType().data())); } void TestDUChain::declareClass() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("parentContext()); QCOMPARE(top->childContexts().count(), 1); DUContext* contextClassA = top->childContexts().first(); QCOMPARE(top->localDeclarations().count(), 1); Declaration* dec = top->localDeclarations().first(); QCOMPARE(dec->kind(), Declaration::Type); QCOMPARE(dec->toString(), QString("class A")); QCOMPARE(dec->qualifiedIdentifier(), QualifiedIdentifier("a")); QCOMPARE(dec->isDefinition(), true); QCOMPARE(dec->logicalInternalContext(top), contextClassA); qDebug() << contextClassA->localScopeIdentifier().toString(); QCOMPARE(contextClassA->localScopeIdentifier(), QualifiedIdentifier("a")); QCOMPARE(contextClassA->childContexts().count(), 8); QCOMPARE(contextClassA->childContexts().first()->localScopeIdentifier(), QualifiedIdentifier("foo")); DUContext* contextMethodBodyFoo = contextClassA->childContexts().at(1); QCOMPARE(contextMethodBodyFoo->localScopeIdentifier(), QualifiedIdentifier("foo")); QCOMPARE(contextMethodBodyFoo->importedParentContexts().count(), 1); QCOMPARE(contextMethodBodyFoo->childContexts().count(), 0); QVERIFY(contextMethodBodyFoo->importedParentContexts().first().context(top) == contextClassA->childContexts().first()); //foo() dec = contextClassA->localDeclarations().at(0); ClassFunctionDeclaration* funDec = dynamic_cast(dec); QVERIFY(funDec); QCOMPARE(funDec->kind(), Declaration::Type); QCOMPARE(funDec->identifier(), Identifier("foo")); QCOMPARE(funDec->accessPolicy(), Declaration::Public); QCOMPARE(funDec->isStatic(), false); { // no return means void as return type FunctionType::Ptr ftype = FunctionType::Ptr::dynamicCast(dec->abstractType()); QVERIFY(ftype); IntegralType::Ptr itype = IntegralType::Ptr::dynamicCast(ftype->returnType()); QVERIFY(itype->dataType() == IntegralType::TypeVoid); } //bar() dec = contextClassA->localDeclarations().at(1); funDec = dynamic_cast(dec); QVERIFY(funDec); QCOMPARE(funDec->identifier(), Identifier("bar")); QCOMPARE(funDec->accessPolicy(), Declaration::Protected); QCOMPARE(funDec->isStatic(), true); //baz() dec = contextClassA->localDeclarations().at(2); funDec = dynamic_cast(dec); QVERIFY(funDec); QCOMPARE(funDec->identifier(), Identifier("baz")); QCOMPARE(funDec->accessPolicy(), Declaration::Private); QCOMPARE(funDec->isStatic(), false); //boo() dec = contextClassA->localDeclarations().at(3); funDec = dynamic_cast(dec); QVERIFY(funDec); QCOMPARE(funDec->identifier(), Identifier("boo")); QCOMPARE(funDec->accessPolicy(), Declaration::Public); } void TestDUChain::declareBaseTypeClass() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("problems().count(), 1); } void TestDUChain::declareClassWithSemiReservedMethod() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("parentContext()); QCOMPARE(top->childContexts().count(), 1); DUContext* contextClassA = top->childContexts().first(); QCOMPARE(top->localDeclarations().count(), 1); Declaration* dec = top->localDeclarations().first(); QCOMPARE(dec->kind(), Declaration::Type); QCOMPARE(dec->toString(), QString("class A")); QCOMPARE(dec->qualifiedIdentifier(), QualifiedIdentifier("a")); QCOMPARE(dec->isDefinition(), true); QCOMPARE(dec->logicalInternalContext(top), contextClassA); qDebug() << contextClassA->localScopeIdentifier().toString(); QCOMPARE(contextClassA->localScopeIdentifier(), QualifiedIdentifier("a")); QCOMPARE(contextClassA->childContexts().count(), 4); QCOMPARE(contextClassA->childContexts().first()->localScopeIdentifier(), QualifiedIdentifier("switch")); DUContext* contextMethodBodyFoo = contextClassA->childContexts().at(1); QCOMPARE(contextMethodBodyFoo->localScopeIdentifier(), QualifiedIdentifier("switch")); QCOMPARE(contextMethodBodyFoo->importedParentContexts().count(), 1); QCOMPARE(contextMethodBodyFoo->childContexts().count(), 0); QVERIFY(contextMethodBodyFoo->importedParentContexts().first().context(top) == contextClassA->childContexts().first()); //switch() dec = contextClassA->localDeclarations().at(0); ClassFunctionDeclaration* funDec = dynamic_cast(dec); QVERIFY(funDec); QCOMPARE(funDec->kind(), Declaration::Type); QCOMPARE(funDec->identifier(), Identifier("switch")); QCOMPARE(funDec->accessPolicy(), Declaration::Public); QCOMPARE(funDec->isStatic(), false); { // no return means void as return type FunctionType::Ptr ftype = FunctionType::Ptr::dynamicCast(dec->abstractType()); QVERIFY(ftype); IntegralType::Ptr itype = IntegralType::Ptr::dynamicCast(ftype->returnType()); QVERIFY(itype->dataType() == IntegralType::TypeVoid); } //public() dec = contextClassA->localDeclarations().at(1); funDec = dynamic_cast(dec); QVERIFY(funDec); QCOMPARE(funDec->identifier(), Identifier("public")); QCOMPARE(funDec->accessPolicy(), Declaration::Protected); QCOMPARE(funDec->isStatic(), true); } void TestDUChain::declareClassWithBaseTypeMethod() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("parentContext()); QCOMPARE(top->childContexts().count(), 1); DUContext* contextClassA = top->childContexts().first(); QCOMPARE(top->localDeclarations().count(), 1); Declaration* dec = top->localDeclarations().first(); QCOMPARE(dec->kind(), Declaration::Type); QCOMPARE(dec->toString(), QString("class A")); QCOMPARE(dec->qualifiedIdentifier(), QualifiedIdentifier("a")); QCOMPARE(dec->isDefinition(), true); QCOMPARE(dec->logicalInternalContext(top), contextClassA); qDebug() << contextClassA->localScopeIdentifier().toString(); QCOMPARE(contextClassA->localScopeIdentifier(), QualifiedIdentifier("a")); QCOMPARE(contextClassA->childContexts().count(), 4); QCOMPARE(contextClassA->childContexts().first()->localScopeIdentifier(), QualifiedIdentifier("string")); DUContext* contextMethodBodyFoo = contextClassA->childContexts().at(1); QCOMPARE(contextMethodBodyFoo->localScopeIdentifier(), QualifiedIdentifier("string")); QCOMPARE(contextMethodBodyFoo->importedParentContexts().count(), 1); QCOMPARE(contextMethodBodyFoo->childContexts().count(), 0); QVERIFY(contextMethodBodyFoo->importedParentContexts().first().context(top) == contextClassA->childContexts().first()); //string() dec = contextClassA->localDeclarations().at(0); ClassFunctionDeclaration* funDec = dynamic_cast(dec); QVERIFY(funDec); QCOMPARE(funDec->kind(), Declaration::Type); QCOMPARE(funDec->identifier(), Identifier("string")); QCOMPARE(funDec->accessPolicy(), Declaration::Public); QCOMPARE(funDec->isStatic(), false); { // no return means void as return type FunctionType::Ptr ftype = FunctionType::Ptr::dynamicCast(dec->abstractType()); QVERIFY(ftype); IntegralType::Ptr itype = IntegralType::Ptr::dynamicCast(ftype->returnType()); QVERIFY(itype->dataType() == IntegralType::TypeVoid); } //iterable() dec = contextClassA->localDeclarations().at(1); funDec = dynamic_cast(dec); QVERIFY(funDec); QCOMPARE(funDec->identifier(), Identifier("iterable")); QCOMPARE(funDec->accessPolicy(), Declaration::Protected); QCOMPARE(funDec->isStatic(), true); } void TestDUChain::classMemberVar() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("parentContext()); QCOMPARE(top->childContexts().count(), 1); DUContext* contextClassA = top->childContexts().first(); QCOMPARE(top->localDeclarations().count(), 1); Declaration* dec = top->localDeclarations().first(); QCOMPARE(dec->qualifiedIdentifier(), QualifiedIdentifier("a")); QCOMPARE(dec->isDefinition(), true); QCOMPARE(dec->logicalInternalContext(top), contextClassA); QCOMPARE(contextClassA->localScopeIdentifier(), QualifiedIdentifier("a")); QCOMPARE(contextClassA->childContexts().count(), 0); QCOMPARE(contextClassA->localDeclarations().count(), 4); //$foo ClassMemberDeclaration* var = dynamic_cast(contextClassA->localDeclarations().first()); QVERIFY(var); QCOMPARE(var->identifier(), Identifier("foo")); QCOMPARE(var->accessPolicy(), Declaration::Public); QCOMPARE(var->isStatic(), false); QVERIFY(var->type()); QVERIFY(var->type()->dataType() == IntegralType::TypeNull); //$bar var = dynamic_cast(contextClassA->localDeclarations().at(1)); QVERIFY(var); QCOMPARE(var->identifier(), Identifier("bar")); QCOMPARE(var->accessPolicy(), Declaration::Protected); QCOMPARE(var->isStatic(), false); StructureType::Ptr type = var->type(); QVERIFY(type); QCOMPARE(type->qualifiedIdentifier(), QualifiedIdentifier("a")); //$baz var = dynamic_cast(contextClassA->localDeclarations().at(2)); QVERIFY(var); QCOMPARE(var->identifier(), Identifier("baz")); QCOMPARE(var->accessPolicy(), Declaration::Private); QCOMPARE(var->isStatic(), true); QVERIFY(var->type()); QVERIFY(var->type()->dataType() == IntegralType::TypeString); //$boo var = dynamic_cast(contextClassA->localDeclarations().at(3)); QVERIFY(var); QCOMPARE(var->identifier(), Identifier("boo")); QCOMPARE(var->accessPolicy(), Declaration::Public); QCOMPARE(var->isStatic(), false); QVERIFY(var->type()); QVERIFY(var->type()->dataType() == IntegralType::TypeInt); } void TestDUChain::classMemberVarAfterUse() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("a = 1; } public $a = 1; }"); TopDUContext* top = parse(method, DumpNone); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); QVERIFY(!top->parentContext()); QCOMPARE(top->childContexts().count(), 1); QVERIFY(top->problems().isEmpty()); DUContext* contextClassB = top->childContexts().first(); QCOMPARE(top->localDeclarations().count(), 1); Declaration* dec = top->localDeclarations().first(); QCOMPARE(dec->qualifiedIdentifier(), QualifiedIdentifier("b")); QCOMPARE(dec->isDefinition(), true); QCOMPARE(dec->logicalInternalContext(top), contextClassB); QCOMPARE(contextClassB->localScopeIdentifier(), QualifiedIdentifier("b")); QCOMPARE(contextClassB->childContexts().count(), 2); QCOMPARE(contextClassB->localDeclarations().count(), 2); //$foo ClassMemberDeclaration* var = dynamic_cast(contextClassB->localDeclarations().at(1)); QVERIFY(var); QCOMPARE(var->identifier(), Identifier("a")); QCOMPARE(var->accessPolicy(), Declaration::Public); QCOMPARE(var->isStatic(), false); QVERIFY(var->type()); QVERIFY(var->type()->dataType() == IntegralType::TypeInt); QVERIFY(var->range() == RangeInRevision(0, 54, 0, 56)); } void TestDUChain::classMemberVarDocBlockType() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("parentContext()); QCOMPARE(top->childContexts().count(), 2); DUContext* contextClassA = top->childContexts().at(0)->childContexts().first(); DUContext* contextClassB = top->childContexts().at(1)->childContexts().first(); QCOMPARE(top->localDeclarations().count(), 2); Declaration* dec = top->childContexts().first()->localDeclarations().first(); QCOMPARE(dec->qualifiedIdentifier(), QualifiedIdentifier("test::a")); QCOMPARE(dec->isDefinition(), true); QCOMPARE(contextClassA->localScopeIdentifier(), QualifiedIdentifier("a")); QCOMPARE(contextClassA->childContexts().count(), 0); QCOMPARE(contextClassA->localDeclarations().count(), 0); QCOMPARE(contextClassB->localScopeIdentifier(), QualifiedIdentifier("b")); QCOMPARE(contextClassB->childContexts().count(), 0); QCOMPARE(contextClassB->localDeclarations().count(), 1); //$foo ClassMemberDeclaration* var = dynamic_cast(contextClassB->localDeclarations().first()); QVERIFY(var); QCOMPARE(var->identifier(), Identifier("foo")); QCOMPARE(var->accessPolicy(), Declaration::Public); QCOMPARE(var->isStatic(), false); StructureType::Ptr type = var->type(); QVERIFY(type); QCOMPARE(type->qualifiedIdentifier(), QualifiedIdentifier("test::a")); } void TestDUChain::returnTypeGenerator_data() { QTest::addColumn("code"); //Note: in practice, Generator is defined by php, but this class is not loaded in this test, so define it ourselves QTest::newRow("simple yield expression") << QStringLiteral(" 1; }\n"); QTest::newRow("yield equality expression") << QStringLiteral("> 1; }\n"); QTest::newRow("yield bit expression") << QStringLiteral(" 'value'; }\n"); } void TestDUChain::returnTypeGenerator() { QFETCH(QString, code); TopDUContext* top = parse(code.toUtf8(), DumpNone); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); QVERIFY(!top->parentContext()); QCOMPARE(top->childContexts().count(), 3); QCOMPARE(top->localDeclarations().count(), 2); Declaration* dec = top->localDeclarations().at(1); QCOMPARE(dec->qualifiedIdentifier(), QualifiedIdentifier("foo")); FunctionType::Ptr functionType = dec->type(); QVERIFY(functionType); StructureType::Ptr retType = StructureType::Ptr::dynamicCast(functionType->returnType()); QVERIFY(retType); QCOMPARE(retType->qualifiedIdentifier(), QualifiedIdentifier("generator")); } void TestDUChain::returnTypeGeneratorDelegation() { //Note: in practice, Generator is defined by php, but this class is not loaded in this test, so define it ourselves // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("parentContext()); QCOMPARE(top->childContexts().count(), 5); QCOMPARE(top->localDeclarations().count(), 3); Declaration* dec = top->localDeclarations().at(1); QCOMPARE(dec->qualifiedIdentifier(), QualifiedIdentifier("foo")); FunctionType::Ptr functionType = dec->type(); QVERIFY(functionType); StructureType::Ptr retType = StructureType::Ptr::dynamicCast(functionType->returnType()); QVERIFY(retType); QCOMPARE(retType->qualifiedIdentifier(), QualifiedIdentifier("generator")); dec = top->localDeclarations().at(2); QCOMPARE(dec->qualifiedIdentifier(), QualifiedIdentifier("bar")); functionType = dec->type(); QVERIFY(functionType); retType = StructureType::Ptr::dynamicCast(functionType->returnType()); QVERIFY(retType); QCOMPARE(retType->qualifiedIdentifier(), QualifiedIdentifier("generator")); } void TestDUChain::returnTypeClass() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("parentContext()); QCOMPARE(top->childContexts().count(), 5); QCOMPARE(top->localDeclarations().count(), 3); Declaration* dec = top->localDeclarations().at(1); QCOMPARE(dec->qualifiedIdentifier(), QualifiedIdentifier("foo")); FunctionType::Ptr functionType = dec->type(); QVERIFY(functionType); StructureType::Ptr retType = StructureType::Ptr::dynamicCast(functionType->returnType()); QVERIFY(retType); QCOMPARE(retType->qualifiedIdentifier(), QualifiedIdentifier("a")); dec = top->localDeclarations().at(2); QCOMPARE(dec->qualifiedIdentifier(), QualifiedIdentifier("bar")); functionType = dec->type(); QVERIFY(functionType); retType = StructureType::Ptr::dynamicCast(functionType->returnType()); QVERIFY(retType); QCOMPARE(retType->qualifiedIdentifier(), QualifiedIdentifier("a")); } void TestDUChain::declarationReturnType() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("parentContext()); QCOMPARE(top->childContexts().count(), 3); QCOMPARE(top->localDeclarations().count(), 3); Declaration* dec = top->localDeclarations().at(1); FunctionType::Ptr fType = dec->type(); QVERIFY(fType); QVERIFY(StructureType::Ptr::dynamicCast(fType->returnType())); QCOMPARE(StructureType::Ptr::dynamicCast(fType->returnType())->qualifiedIdentifier(), QualifiedIdentifier("a")); dec = top->localDeclarations().at(2); QCOMPARE(dec->identifier(), Identifier("i")); StructureType::Ptr type = dec->type(); QVERIFY(type); QCOMPARE(type->qualifiedIdentifier(), QualifiedIdentifier("a")); } void TestDUChain::declarationReturnTypeInRecursingFunction() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method(" decs = top->childContexts().last()->findDeclarations(Identifier(QStringLiteral("i"))); QCOMPARE(decs.size(), 1); Declaration* dec = decs.first(); StructureType::Ptr type = dec->type(); QVERIFY(type); QCOMPARE(type->qualifiedIdentifier(), QualifiedIdentifier("a")); } void TestDUChain::declarationMultipleReturnTypes() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().at(1)->type(); QVERIFY(fType); qDebug() << fType->toString(); TypePtr ut = UnsureType::Ptr::dynamicCast(fType->returnType()); QVERIFY(ut); QCOMPARE(2u, ut->typesSize()); ///TODO: why are the types not in the correct order, i.e. null, A QVERIFY(ut->types()[0].type()); QVERIFY(ut->types()[0].type()->declaration(top)); QCOMPARE(ut->types()[0].type()->declaration(top)->qualifiedIdentifier(), QualifiedIdentifier("a")); QVERIFY(ut->types()[1].type()); QVERIFY(ut->types()[1].type()->dataType() == IntegralType::TypeNull); fType = top->localDeclarations().at(2)->type(); QVERIFY(fType); qDebug() << fType->toString(); QVERIFY(IntegralType::Ptr::dynamicCast(fType->returnType())); QVERIFY(IntegralType::Ptr::dynamicCast(fType->returnType())->dataType() == IntegralType::TypeInt); } void TestDUChain::returnTypeViaMember() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("fa($param); }\n" " function fb2($param) { $i = $this->anormal->fa($param); } }"); TopDUContext* top = parse(method, DumpNone); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); QVector decs = top->localDeclarations(); QCOMPARE(decs.size(), 2); ClassDeclaration* aDec = dynamic_cast(decs.first()); QVERIFY(aDec); ClassDeclaration* bDec = dynamic_cast(decs.last()); QVERIFY(bDec); QCOMPARE(bDec->logicalInternalContext(top)->localDeclarations().size(), 4); typedef QPair idPair; foreach ( const idPair & pair, QList< idPair >() << qMakePair(QString("fb1"), QString("astatic")) << qMakePair(QString("fb2"), QString("anormal")) ) { qDebug() << pair.first << pair.second; ClassMethodDeclaration* fDec = dynamic_cast( bDec->logicalInternalContext(top)->findDeclarations(Identifier(pair.first)).first() ); QVERIFY(fDec); ClassMemberDeclaration* mDec = dynamic_cast( bDec->logicalInternalContext(top)->findDeclarations(Identifier(pair.second)).first() ); QVERIFY(mDec); QVERIFY(mDec->type()); QCOMPARE(mDec->type()->declaration(top), aDec); QCOMPARE(fDec->logicalInternalContext(top)->localDeclarations().size(), 1); Declaration* iDec = fDec->logicalInternalContext(top)->localDeclarations().first(); QCOMPARE(iDec->identifier().toString(), QString("i")); QVERIFY(iDec->type()); QCOMPARE(iDec->type()->declaration(top), aDec); } } void TestDUChain::declarationReturnTypeDocBlock() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("childContexts().at(0)->localDeclarations().at(0); FunctionType::Ptr fType = dec->type(); QVERIFY(fType); QVERIFY(StructureType::Ptr::dynamicCast(fType->returnType())); QCOMPARE(StructureType::Ptr::dynamicCast(fType->returnType())->qualifiedIdentifier(), QualifiedIdentifier("a")); //function foo dec = top->localDeclarations().at(2); fType = dec->type(); QVERIFY(fType); QVERIFY(StructureType::Ptr::dynamicCast(fType->returnType())); QCOMPARE(StructureType::Ptr::dynamicCast(fType->returnType())->qualifiedIdentifier(), QualifiedIdentifier("a")); //function bar dec = top->localDeclarations().at(3); fType = dec->type(); QVERIFY(fType); QVERIFY(IntegralTypeExtended::Ptr::dynamicCast(fType->returnType())); QVERIFY(IntegralTypeExtended::Ptr::dynamicCast(fType->returnType())->dataType() == IntegralTypeExtended::TypeObject); //test hint in internal functions file of a type that is added later on // function QList decs = top->findDeclarations(Identifier(QStringLiteral("should_return_exception"))); QCOMPARE(decs.size(), 1); dec = decs.first(); fType = dec->type(); QVERIFY(fType); QVERIFY(StructureType::Ptr::dynamicCast(fType->returnType())); QCOMPARE(StructureType::Ptr::dynamicCast(fType->returnType())->qualifiedIdentifier(), QualifiedIdentifier("exception")); // method decs = top->findDeclarations(Identifier(QStringLiteral("internal_test_class"))); QCOMPARE(decs.size(), 1); ClassDeclaration* cdec = dynamic_cast(decs.first()); QVERIFY(cdec); decs = cdec->logicalInternalContext(top)->findDeclarations(Identifier(QStringLiteral("should_return_exception"))); QCOMPARE(decs.size(), 1); dec = decs.first(); fType = dec->type(); QVERIFY(fType); QVERIFY(StructureType::Ptr::dynamicCast(fType->returnType())); QCOMPARE(StructureType::Ptr::dynamicCast(fType->returnType())->qualifiedIdentifier(), QualifiedIdentifier("exception")); } void TestDUChain::declarationReturnTypeDocBlockIntegral() { QByteArray method("localDeclarations().at(0)->type(); QVERIFY(fType); QVERIFY(IntegralType::Ptr::dynamicCast(fType->returnType())); QVERIFY(IntegralType::Ptr::dynamicCast(fType->returnType())->dataType() == IntegralType::TypeString); //function bar fType = top->localDeclarations().at(1)->type(); QVERIFY(fType); QVERIFY(IntegralType::Ptr::dynamicCast(fType->returnType())); QVERIFY(IntegralType::Ptr::dynamicCast(fType->returnType())->dataType() == IntegralType::TypeMixed); //function aaa fType = top->childContexts().at(4)->localDeclarations().first()->type(); QVERIFY(fType); QVERIFY(IntegralType::Ptr::dynamicCast(fType->returnType())); QVERIFY(IntegralType::Ptr::dynamicCast(fType->returnType())->dataType() == IntegralType::TypeInt); } void TestDUChain::declarationReturnTypeClassChain() { QByteArray method("childContexts().first(); QCOMPARE(ctx->type(), DUContext::Class); QVERIFY(ctx->owner()); QVERIFY(StructureType::Ptr::dynamicCast(ctx->owner()->abstractType())); //function a // FIXME QEXPECT_FAIL("", "This test fails after porting the plugin to KF5.", Abort); QVERIFY(/* func a (this) */ ctx->localDeclarations().at(0)->type().data() == ctx->owner()->abstractType().data()); QVERIFY(/* func b (self) */ ctx->localDeclarations().at(1)->type().data() == ctx->owner()->abstractType().data()); } void TestDUChain::declarationReturnTypeTypehint() { //Typehint preferred over phpdoc preferred over inferred type QByteArray method("parentContext()); QCOMPARE(top->childContexts().count(), 2); QCOMPARE(top->localDeclarations().count(), 1); FunctionType::Ptr fun = top->localDeclarations().first()->type(); QVERIFY(fun); IntegralType::Ptr returnType = IntegralType::Ptr::dynamicCast(fun->returnType()); QVERIFY(returnType); QVERIFY(returnType->dataType() == IntegralType::TypeBoolean); } void TestDUChain::declarationReturnTypeTypehintVoid() { //Typehint preferred over phpdoc preferred over inferred type QByteArray method("parentContext()); QCOMPARE(top->childContexts().count(), 2); QCOMPARE(top->localDeclarations().count(), 1); FunctionType::Ptr fun = top->localDeclarations().first()->type(); QVERIFY(fun); IntegralType::Ptr returnType = IntegralType::Ptr::dynamicCast(fun->returnType()); QVERIFY(returnType); QVERIFY(returnType->dataType() == IntegralType::TypeVoid); } void TestDUChain::declarationReturnTypeTypehintObject() { //Typehint preferred over phpdoc preferred over inferred type QByteArray method("parentContext()); QCOMPARE(top->childContexts().count(), 2); QCOMPARE(top->localDeclarations().count(), 1); FunctionType::Ptr fun = top->localDeclarations().first()->type(); QVERIFY(fun); IntegralTypeExtended::Ptr returnType = IntegralTypeExtended::Ptr::dynamicCast(fun->returnType()); QVERIFY(returnType); QVERIFY(returnType->dataType() == IntegralTypeExtended::TypeObject); } void TestDUChain::declareTypehintFunction() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("parentContext()); QCOMPARE(top->childContexts().count(), 3); QCOMPARE(top->localDeclarations().count(), 2); Declaration* dec = top->localDeclarations().at(0); QCOMPARE(dec->internalContext(), top->childContexts().at(0)); QCOMPARE(dec->uses().count(), 1); QCOMPARE(dec->uses().begin()->count(), 1); QCOMPARE(top->childContexts().at(0)->localScopeIdentifier(), QualifiedIdentifier("a")); QCOMPARE(top->childContexts().at(0)->childContexts().count(), 0); DUContext* contextFunctionFoo = top->childContexts().at(1); QCOMPARE(contextFunctionFoo->localScopeIdentifier(), QualifiedIdentifier("foo")); DUContext* contextFunctionBodyFoo = top->childContexts().at(2); QCOMPARE(contextFunctionBodyFoo->localScopeIdentifier(), QualifiedIdentifier("foo")); QCOMPARE(contextFunctionBodyFoo->importedParentContexts().count(), 1); QCOMPARE(contextFunctionBodyFoo->childContexts().count(), 0); QVERIFY(contextFunctionBodyFoo->importedParentContexts().first().context(top) == contextFunctionFoo); QVERIFY(top->childContexts().at(1)->localDeclarations().first()->type()); QCOMPARE(top->childContexts().at(1)->localDeclarations().first()->type()->qualifiedIdentifier(), QualifiedIdentifier("a")); FunctionType::Ptr fType = top->localDeclarations().at(1)->type(); QVERIFY(fType); QVERIFY(StructureType::Ptr::dynamicCast(fType->returnType())); QCOMPARE(StructureType::Ptr::dynamicCast(fType->returnType())->qualifiedIdentifier(), QualifiedIdentifier("a")); } void TestDUChain::declareVariadicFunction() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().first()->type(); QVERIFY(fun); QCOMPARE(fun->arguments().count(), 1); AbstractType::Ptr arg = fun->arguments().first(); QVERIFY(arg); QVERIFY(arg.cast()); QCOMPARE(arg.cast()->typesCount(), 1); QCOMPARE(arg.cast()->prettyName().str(), QStringLiteral("array")); AbstractType::Ptr typehint = arg.cast()->typeAt(0).abstractType(); QVERIFY(typehint); QVERIFY(IntegralType::Ptr::dynamicCast(typehint)); QVERIFY(IntegralType::Ptr::dynamicCast(typehint)->dataType() == IntegralType::TypeMixed); } void TestDUChain::declareTypehintVariadicFunction() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().at(1)->type(); QVERIFY(fun); QCOMPARE(fun->arguments().count(), 1); AbstractType::Ptr arg = fun->arguments().first(); QVERIFY(arg); QVERIFY(arg.cast()); QCOMPARE(arg.cast()->typesCount(), 1); QCOMPARE(arg.cast()->prettyName().str(), QStringLiteral("array")); AbstractType::Ptr typehint = arg.cast()->typeAt(0).abstractType(); QVERIFY(typehint); QCOMPARE(typehint->toString(), QStringLiteral("A")); } void TestDUChain::declareTypehintObjectFunction() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().first()->type(); QVERIFY(fun); QCOMPARE(fun->arguments().count(), 1); QVERIFY(IntegralType::Ptr::dynamicCast(fun->arguments().first())); QVERIFY(IntegralType::Ptr::dynamicCast(fun->arguments().first())->dataType() == IntegralTypeExtended::TypeObject); IntegralTypeExtended::Ptr type = top->childContexts().first()->localDeclarations().first()->type(); QVERIFY(type); QVERIFY(type->dataType() == IntegralTypeExtended::TypeObject); } void TestDUChain::declareTypehintArrayFunction() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().first()->type(); QVERIFY(fun); QCOMPARE(fun->arguments().count(), 1); QVERIFY(IntegralType::Ptr::dynamicCast(fun->arguments().first())); QVERIFY(IntegralType::Ptr::dynamicCast(fun->arguments().first())->dataType() == IntegralType::TypeArray); IntegralType::Ptr type = top->childContexts().first()->localDeclarations().first()->type(); QVERIFY(type); QVERIFY(type->dataType() == IntegralType::TypeArray); } void TestDUChain::declareTypehintCallableFunction() { // 0 1 2 3 // 0123456789012345678901234567890123 QByteArray method("localDeclarations().first()->type(); QVERIFY(fun); QCOMPARE(fun->arguments().count(), 1); QVERIFY(IntegralTypeExtended::Ptr::dynamicCast(fun->arguments().first())); QVERIFY(IntegralTypeExtended::Ptr::dynamicCast(fun->arguments().first())->dataType() == IntegralTypeExtended::TypeCallable); IntegralTypeExtended::Ptr type = top->childContexts().first()->localDeclarations().first()->type(); QVERIFY(type); QVERIFY(type->dataType() == IntegralTypeExtended::TypeCallable); } void Php::TestDUChain::functionWithCallableAndFunctionReturn() { QByteArray method("localDeclarations().first()->type(); QVERIFY(fun); QCOMPARE(fun->arguments().count(), 1); QVERIFY(IntegralTypeExtended::Ptr::dynamicCast(fun->arguments().first())); QVERIFY(IntegralTypeExtended::Ptr::dynamicCast(fun->arguments().first())->dataType() == IntegralTypeExtended::TypeCallable); IntegralTypeExtended::Ptr retType = IntegralTypeExtended::Ptr::dynamicCast(fun->returnType()); QVERIFY(retType); QVERIFY(retType->dataType() == IntegralTypeExtended::TypeCallable); } void TestDUChain::declareTypehintIterableFunction() { //Note: in practice, Traversable is defined by php, but this interface is not loaded in this test, so define it ourselves // 0 1 2 3 // 0123456789012345678901234567890123 QByteArray method("localDeclarations().count(), 2); FunctionType::Ptr fun = top->localDeclarations().at(1)->type(); QVERIFY(fun); QCOMPARE(fun->arguments().count(), 1); UnsureType::Ptr argType = UnsureType::Ptr::dynamicCast(fun->arguments().first()); QVERIFY(argType); QCOMPARE(argType->typesSize(), 2u); QVERIFY(argType->types()[0].abstractType().cast()); QVERIFY(argType->types()[0].abstractType().cast()->dataType() == IntegralType::TypeArray); QVERIFY(argType->types()[1].abstractType().cast()); QCOMPARE(argType->types()[1].abstractType().cast()->qualifiedIdentifier(), QualifiedIdentifier("traversable")); } void TestDUChain::declareTypehintBoolFunction() { // 0 1 2 3 // 0123456789012345678901234567890123 QByteArray method("localDeclarations().first()->type(); QVERIFY(fun); QCOMPARE(fun->arguments().count(), 1); QVERIFY(IntegralType::Ptr::dynamicCast(fun->arguments().first())); QVERIFY(IntegralType::Ptr::dynamicCast(fun->arguments().first())->dataType() == IntegralType::TypeBoolean); IntegralType::Ptr type = top->childContexts().first()->localDeclarations().first()->type(); QVERIFY(type); QVERIFY(type->dataType() == IntegralType::TypeBoolean); } void TestDUChain::declareTypehintFloatFunction() { // 0 1 2 3 // 0123456789012345678901234567890123 QByteArray method("localDeclarations().first()->type(); QVERIFY(fun); QCOMPARE(fun->arguments().count(), 1); QVERIFY(IntegralType::Ptr::dynamicCast(fun->arguments().first())); QVERIFY(IntegralType::Ptr::dynamicCast(fun->arguments().first())->dataType() == IntegralType::TypeFloat); IntegralType::Ptr type = top->childContexts().first()->localDeclarations().first()->type(); QVERIFY(type); QVERIFY(type->dataType() == IntegralType::TypeFloat); } void TestDUChain::declareTypehintIntFunction() { // 0 1 2 3 // 0123456789012345678901234567890123 QByteArray method("localDeclarations().first()->type(); QVERIFY(fun); QCOMPARE(fun->arguments().count(), 1); QVERIFY(IntegralType::Ptr::dynamicCast(fun->arguments().first())); QVERIFY(IntegralType::Ptr::dynamicCast(fun->arguments().first())->dataType() == IntegralType::TypeInt); IntegralType::Ptr type = top->childContexts().first()->localDeclarations().first()->type(); QVERIFY(type); QVERIFY(type->dataType() == IntegralType::TypeInt); } void TestDUChain::declareTypehintStringFunction() { // 0 1 2 3 // 0123456789012345678901234567890123 QByteArray method("localDeclarations().first()->type(); QVERIFY(fun); QCOMPARE(fun->arguments().count(), 1); QVERIFY(IntegralType::Ptr::dynamicCast(fun->arguments().first())); QVERIFY(IntegralType::Ptr::dynamicCast(fun->arguments().first())->dataType() == IntegralType::TypeString); IntegralType::Ptr type = top->childContexts().first()->localDeclarations().first()->type(); QVERIFY(type); QVERIFY(type->dataType() == IntegralType::TypeString); } void TestDUChain::declareNullableTypehintArrayFunction() { // 0 1 2 3 // 0123456789012345678901234567890123 QByteArray method("localDeclarations().first()->type(); QVERIFY(fun); QCOMPARE(fun->arguments().count(), 1); UnsureType::Ptr argType = UnsureType::Ptr::dynamicCast(fun->arguments().first()); QVERIFY(argType); QCOMPARE(argType->typesSize(), 2u); QVERIFY(argType->types()[0].abstractType().cast()); QVERIFY(argType->types()[0].abstractType().cast()->dataType() == IntegralType::TypeArray); QVERIFY(argType->types()[1].abstractType().cast()); QVERIFY(argType->types()[1].abstractType().cast()->dataType() == IntegralType::TypeNull); UnsureType::Ptr type = top->childContexts().first()->localDeclarations().first()->type(); QVERIFY(type); QCOMPARE(type->typesSize(), 2u); QVERIFY(type->types()[0].abstractType().cast()); QVERIFY(type->types()[0].abstractType().cast()->dataType() == IntegralType::TypeArray); QVERIFY(type->types()[1].abstractType().cast()); QVERIFY(type->types()[1].abstractType().cast()->dataType() == IntegralType::TypeNull); } void TestDUChain::declareTypehintWithPhpdocFunction() { // 0 1 2 3 // 0123456789012345678901234567890123 QByteArray method("localDeclarations().first()->type(); QVERIFY(fun); QCOMPARE(fun->arguments().count(), 1); QVERIFY(IntegralType::Ptr::dynamicCast(fun->arguments().first())); QVERIFY(IntegralType::Ptr::dynamicCast(fun->arguments().first())->dataType() == IntegralType::TypeInt); IntegralType::Ptr type = top->childContexts().first()->localDeclarations().first()->type(); QVERIFY(type); QVERIFY(type->dataType() == IntegralType::TypeInt); } void TestDUChain::declareNullableTypehintMixedFunction() { // 0 1 2 3 // 0123456789012345678901234567890123 QByteArray method("localDeclarations().first()->type(); QVERIFY(fun); QCOMPARE(fun->arguments().count(), 1); QVERIFY(IntegralType::Ptr::dynamicCast(fun->arguments().first())); QVERIFY(IntegralType::Ptr::dynamicCast(fun->arguments().first())->dataType() == IntegralType::TypeMixed); IntegralType::Ptr type = top->childContexts().first()->localDeclarations().first()->type(); QVERIFY(type); QVERIFY(type->dataType() == IntegralType::TypeMixed); } void TestDUChain::declareTypehintNullableIterableFunction() { //Note: in practice, Traversable is defined by php, but this interface is not loaded in this test, so define it ourselves // 0 1 2 3 // 0123456789012345678901234567890123 QByteArray method("localDeclarations().count(), 2); FunctionType::Ptr fun = top->localDeclarations().at(1)->type(); QVERIFY(fun); QCOMPARE(fun->arguments().count(), 1); UnsureType::Ptr argType = UnsureType::Ptr::dynamicCast(fun->arguments().first()); QVERIFY(argType); QCOMPARE(argType->typesSize(), 3u); QVERIFY(argType->types()[0].abstractType().cast()); QVERIFY(argType->types()[0].abstractType().cast()->dataType() == IntegralType::TypeArray); QVERIFY(argType->types()[1].abstractType().cast()); QCOMPARE(argType->types()[1].abstractType().cast()->qualifiedIdentifier(), QualifiedIdentifier("traversable")); QVERIFY(argType->types()[2].abstractType().cast()); QVERIFY(argType->types()[2].abstractType().cast()->dataType() == IntegralType::TypeNull); } void TestDUChain::classImplementsInterface() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("childContexts().count(), 2); QCOMPARE(top->localDeclarations().count(), 2); //interface I Declaration* dec = top->localDeclarations().at(0); QVERIFY(dec->isDefinition()); QCOMPARE(dec->identifier(), Identifier("i")); QCOMPARE(dec->toString(), QString("interface I")); StructureType::Ptr typeI = dec->type(); QCOMPARE(typeI->qualifiedIdentifier(), QualifiedIdentifier("i")); QVERIFY(typeI->declaration(top) == dec); ClassDeclaration* classDec = dynamic_cast(dec); QVERIFY(classDec); QCOMPARE(classDec->classType(), ClassDeclarationData::Interface); QCOMPARE(dec->internalContext(), top->childContexts().at(0)); QCOMPARE(dec->internalContext()->childContexts().count(), 0); QCOMPARE(dec->internalContext()->importedParentContexts().count(), 0); QCOMPARE(dec->internalContext()->localScopeIdentifier(), QualifiedIdentifier("i")); QCOMPARE(dec->uses().count(), 1); QCOMPARE(dec->uses().begin()->count(), 1); IndexedType indexedTypeI = classDec->indexedType(); //class A dec = top->localDeclarations().at(1); QVERIFY(dec->isDefinition()); QCOMPARE(dec->identifier(), Identifier("a")); StructureType::Ptr typeA = dec->type(); QCOMPARE(typeA->qualifiedIdentifier(), QualifiedIdentifier("a")); QVERIFY(typeA->declaration(top) == dec); classDec = dynamic_cast(dec); QVERIFY(classDec); QCOMPARE(classDec->classType(), ClassDeclarationData::Class); QCOMPARE(dec->internalContext(), top->childContexts().at(1)); QCOMPARE(dec->internalContext()->childContexts().count(), 0); QCOMPARE(dec->internalContext()->localScopeIdentifier(), QualifiedIdentifier("a")); //class A imports interface I context QCOMPARE(dec->internalContext()->importedParentContexts().count(), 1); QVERIFY(dec->internalContext()->importedParentContexts().at(0).context(top) == top->childContexts().at(0)); QCOMPARE(classDec->baseClassesSize(), 1u); QCOMPARE(classDec->baseClasses()[0].baseClass, indexedTypeI); QCOMPARE(dec->uses().count(), 0); } void TestDUChain::classExtends() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("childContexts().count(), 2); QCOMPARE(top->localDeclarations().count(), 2); //class A Declaration* dec = top->localDeclarations().at(0); QVERIFY(dec->isDefinition()); QCOMPARE(dec->identifier(), Identifier("a")); StructureType::Ptr typeA = dec->type(); QCOMPARE(typeA->qualifiedIdentifier(), QualifiedIdentifier("a")); QVERIFY(typeA->declaration(top) == dec); ClassDeclaration* classDec = dynamic_cast(dec); QVERIFY(classDec); QCOMPARE(classDec->classType(), ClassDeclarationData::Class); QCOMPARE(dec->internalContext(), top->childContexts().at(0)); QCOMPARE(dec->internalContext()->childContexts().count(), 0); QCOMPARE(dec->internalContext()->importedParentContexts().count(), 0); QCOMPARE(dec->internalContext()->localScopeIdentifier(), QualifiedIdentifier("a")); QCOMPARE(dec->uses().count(), 1); QCOMPARE(dec->uses().begin()->count(), 1); IndexedType indexedTypeA = classDec->indexedType(); //class B dec = top->localDeclarations().at(1); QVERIFY(dec->isDefinition()); QCOMPARE(dec->identifier(), Identifier("b")); StructureType::Ptr typeB = dec->type(); QCOMPARE(typeB->qualifiedIdentifier(), QualifiedIdentifier("b")); QVERIFY(typeB->declaration(top) == dec); classDec = dynamic_cast(dec); QVERIFY(classDec); QCOMPARE(classDec->classType(), ClassDeclarationData::Class); QCOMPARE(dec->internalContext(), top->childContexts().at(1)); QCOMPARE(dec->internalContext()->childContexts().count(), 0); QCOMPARE(dec->internalContext()->localScopeIdentifier(), QualifiedIdentifier("b")); //class B imports class A context QCOMPARE(dec->internalContext()->importedParentContexts().count(), 1); QVERIFY(dec->internalContext()->importedParentContexts().at(0).context(top) == top->childContexts().at(0)); QCOMPARE(classDec->baseClassesSize(), 1u); QCOMPARE(classDec->baseClasses()[0].baseClass, indexedTypeA); QCOMPARE(dec->uses().count(), 0); } void TestDUChain::staticMethod() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().at(2)->type(); QVERIFY(type); QCOMPARE(type->qualifiedIdentifier(), QualifiedIdentifier("b")); } void TestDUChain::ownStaticMethod() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("childContexts().at(1)); QVERIFY(top->childContexts().at(1)->localDeclarations().at(0)); QVERIFY(top->childContexts().at(1)->localDeclarations().at(0)->type()); AbstractType::Ptr ret = top->childContexts().at(1)->localDeclarations().at(0) ->type()->returnType(); QVERIFY(StructureType::Ptr::dynamicCast(ret)); QCOMPARE(StructureType::Ptr::dynamicCast(ret)->declaration(top), top->localDeclarations().at(0)); QVERIFY(top->childContexts().at(1)->childContexts().at(1 + 2)); QVERIFY(top->childContexts().at(1)->childContexts().at(1 + 2)->localDeclarations().at(0)); QVERIFY(top->childContexts().at(1)->childContexts().at(1 + 2)->localDeclarations().at(0)->type()); QCOMPARE(top->childContexts().at(1)->childContexts().at(1 + 2)->localDeclarations().at(0) ->type()->qualifiedIdentifier(), QualifiedIdentifier("b")); QCOMPARE(top->childContexts().at(1)->childContexts().at(1 + 2)->localDeclarations().at(1) ->type()->qualifiedIdentifier(), QualifiedIdentifier("b")); } void TestDUChain::thisVar() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("x(); } } "); TopDUContext* top = parse(method, DumpNone); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); FunctionType::Ptr fn = top->childContexts().at(0)->localDeclarations().at(0)->type(); QVERIFY(fn); StructureType::Ptr cls = StructureType::Ptr::dynamicCast(fn->returnType()); QVERIFY(cls); QCOMPARE(cls->qualifiedIdentifier(), QualifiedIdentifier("a")); fn = top->childContexts().at(0)->localDeclarations().at(1)->type(); QVERIFY(fn); cls = StructureType::Ptr::dynamicCast(fn->returnType()); QVERIFY(cls); QCOMPARE(cls->qualifiedIdentifier(), QualifiedIdentifier("a")); } void TestDUChain::objectFunctionCall() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("x(); } } "); TopDUContext* top = parse(method, DumpNone); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); FunctionType::Ptr fn = top->childContexts().at(1)->localDeclarations().at(0)->type(); QVERIFY(fn); StructureType::Ptr cls = StructureType::Ptr::dynamicCast(fn->returnType()); QVERIFY(cls); QCOMPARE(cls->qualifiedIdentifier(), QualifiedIdentifier("b")); fn = top->childContexts().at(1)->localDeclarations().at(1)->type(); QVERIFY(fn); cls = StructureType::Ptr::dynamicCast(fn->returnType()); QVERIFY(cls); QCOMPARE(cls->qualifiedIdentifier(), QualifiedIdentifier("b")); } void TestDUChain::objectFunctionCall2() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("x()->c(); } } "); TopDUContext* top = parse(method, DumpNone); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); FunctionType::Ptr fn = top->childContexts().at(2)->localDeclarations().at(1)->type(); QVERIFY(fn); StructureType::Ptr cls = StructureType::Ptr::dynamicCast(fn->returnType()); QVERIFY(cls); QCOMPARE(cls->qualifiedIdentifier(), QualifiedIdentifier("c")); } void TestDUChain::objectFunctionCall3() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("b();"); TopDUContext* top = parse(method, DumpNone); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(top->localDeclarations().at(2)->qualifiedIdentifier(), QualifiedIdentifier("i")); QCOMPARE(top->localDeclarations().at(2)->type()->qualifiedIdentifier(), QualifiedIdentifier("a"));; QCOMPARE(top->localDeclarations().at(3)->qualifiedIdentifier(), QualifiedIdentifier("j")); QCOMPARE(top->localDeclarations().at(3)->type()->qualifiedIdentifier(), QualifiedIdentifier("b"));; } void TestDUChain::objectVariable() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("foo;"); TopDUContext* top = parse(method, DumpNone); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(top->localDeclarations().at(3)->qualifiedIdentifier(), QualifiedIdentifier("i")); QCOMPARE(top->localDeclarations().at(3)->type()->qualifiedIdentifier(), QualifiedIdentifier("b"));; } void TestDUChain::staticMemberVariable() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().at(2)->qualifiedIdentifier(), QualifiedIdentifier("i")); QCOMPARE(top->localDeclarations().at(2)->type()->qualifiedIdentifier(), QualifiedIdentifier("b"));; } void TestDUChain::ownStaticMemberVariable() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("childContexts().at(1)->childContexts().at(1); QCOMPARE(barContext->localDeclarations().at(0)->type()->qualifiedIdentifier(), QualifiedIdentifier("b")); QCOMPARE(barContext->localDeclarations().at(1)->type()->qualifiedIdentifier(), QualifiedIdentifier("b")); } void TestDUChain::classConst_data() { QTest::addColumn("classBody"); QTest::addColumn("problems"); QTest::newRow("int") << "const C = 1;" << 0; QTest::newRow("string") << "const C = 'asdf';" << 0; QTest::newRow("float") << "const C = 0.5;" << 0; QTest::newRow("bool") << "const C = true;" << 0; QTest::newRow("selfConst") << "const C2 = 1; const C = self::C2;" << 0; QTest::newRow("parentConst") << "const C = parent::P;" << 0; QTest::newRow("null") << "const C = null;" << 0; QTest::newRow("array") << "const C = array();" << 0; QTest::newRow("expression") << "const C = 'foo' . 'foo';" << 0; } void TestDUChain::classConst() { QFETCH(QString, classBody); QFETCH(int, problems); QString fullClass("childContexts().count(), 2); QCOMPARE(top->problems().count(), problems); QCOMPARE(top->findDeclarations(QualifiedIdentifier("a::C")).count(), 1); QCOMPARE(top->findDeclarations(QualifiedIdentifier("a::C")).first()->context(), top->childContexts().last()); } void TestDUChain::semiReservedClassConst() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("childContexts().count(), 1); QCOMPARE(top->problems().count(), 0); QCOMPARE(top->findDeclarations(QualifiedIdentifier("a::SWITCH")).count(), 1); QCOMPARE(top->findDeclarations(QualifiedIdentifier("a::SWITCH")).first()->context(), top->childContexts().last()); QCOMPARE(top->findDeclarations(QualifiedIdentifier("a::PUBLIC")).count(), 1); QCOMPARE(top->findDeclarations(QualifiedIdentifier("a::PUBLIC")).first()->context(), top->childContexts().last()); QCOMPARE(top->findDeclarations(QualifiedIdentifier("a::STRING")).count(), 1); QCOMPARE(top->findDeclarations(QualifiedIdentifier("a::STRING")).first()->context(), top->childContexts().last()); } void TestDUChain::fileConst_data() { QTest::addColumn("code"); QTest::addColumn("problems"); QTest::addColumn("dataType"); QTest::newRow("int") << "const C = 1;" << 0 << (uint) IntegralType::TypeInt; QTest::newRow("string") << "const C = 'asdf';" << 0 << (uint) IntegralType::TypeString; QTest::newRow("float") << "const C = 0.5;" << 0 << (uint) IntegralType::TypeFloat; QTest::newRow("bool") << "const C = true;" << 0 << (uint) IntegralType::TypeBoolean; QTest::newRow("array") << "const C = array();" << 0 << (uint) IntegralType::TypeArray; QTest::newRow("expression") << "const C = 'foo' . 'foo';" << 0 << (uint) IntegralType::TypeString; } void TestDUChain::fileConst() { QFETCH(QString, code); QFETCH(int, problems); QFETCH(uint, dataType); code.prepend("problems().count(), problems); QList< Declaration* > decs = top->findDeclarations(QualifiedIdentifier(QStringLiteral("C"))); QCOMPARE(decs.count(), 1); IntegralType::Ptr type = decs.first()->abstractType().cast(); QVERIFY(type); QCOMPARE(type->dataType(), dataType); QVERIFY(type->modifiers() & AbstractType::ConstModifier); } void TestDUChain::semiReservedFileConst() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("findDeclarations(QualifiedIdentifier("FOO")).count(), 1); QCOMPARE(top->findDeclarations(QualifiedIdentifier("BAR")).count(), 1); QCOMPARE(top->findDeclarations(QualifiedIdentifier("FOO")).first()->context(), top); QCOMPARE(top->findDeclarations(QualifiedIdentifier("BAR")).first()->context(), top); QVERIFY(top->findDeclarations(QualifiedIdentifier("FOO")).first()->abstractType()->modifiers() & AbstractType::ConstModifier); QVERIFY(top->findDeclarations(QualifiedIdentifier("BAR")).first()->abstractType()->modifiers() & AbstractType::ConstModifier); } void TestDUChain::defaultFunctionParam() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("(top->localDeclarations().first()); QVERIFY(fun); QCOMPARE(fun->defaultParametersSize(), 3u); QVERIFY(fun->defaultParameters()[0].isEmpty()); QCOMPARE(fun->defaultParameters()[1].str(), QString("false")); QCOMPARE(fun->defaultParameters()[2].str(), QString("null")); } void TestDUChain::defaultFunctionParamWithTypehint() { QByteArray method("localDeclarations().first()->type(); QVERIFY(fun); QCOMPARE(fun->arguments().count(), 1); IntegralType::Ptr argType = IntegralType::Ptr::dynamicCast(fun->arguments().first()); QVERIFY(argType); QVERIFY(argType->dataType() == IntegralType::TypeArray); } void TestDUChain::nullDefaultFunctionParamWithTypehint() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().first()->type(); QVERIFY(fun); QCOMPARE(fun->arguments().count(), 1); UnsureType::Ptr argType = UnsureType::Ptr::dynamicCast(fun->arguments().first()); QVERIFY(argType); QCOMPARE(argType->typesSize(), 2u); QVERIFY(argType->types()[0].abstractType().cast()); QVERIFY(argType->types()[0].abstractType().cast()->dataType() == IntegralType::TypeArray); QVERIFY(argType->types()[1].abstractType().cast()); QVERIFY(argType->types()[1].abstractType().cast()->dataType() == IntegralType::TypeNull); } void TestDUChain::globalFunction() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("importedParentContexts().count(), 1); QVERIFY(DUChain::self()->chainForDocument(internalFunctionFile())); QCOMPARE(DUChain::self()->chainForDocument(internalFunctionFile()), top->importedParentContexts().first().context(top)); QCOMPARE(top->findDeclarations(QualifiedIdentifier("substr")).count(), 1); } void TestDUChain::globalVariableFromInternalFunctions() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("importedParentContexts().count(), 1); QVERIFY(DUChain::self()->chainForDocument(internalFunctionFile())); QCOMPARE(DUChain::self()->chainForDocument(internalFunctionFile()), top->importedParentContexts().first().context(top)); QCOMPARE(top->findDeclarations(QualifiedIdentifier("_GET")).count(), 1); } void TestDUChain::newObjectFromOtherFile() { TopDUContext* addTop = parseAdditionalFile(IndexedString("/duchaintest/foo.php"), "localDeclarations().first()->type()->declaration(top), addTop->localDeclarations().first()); } void TestDUChain::unknownReturnType() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().at(0); FunctionType::Ptr fType = dec->type(); QVERIFY(fType); QVERIFY(IntegralType::Ptr::dynamicCast(fType->returnType())); QVERIFY(IntegralType::Ptr::staticCast(fType->returnType())->dataType() == IntegralType::TypeVoid); } void TestDUChain::staticFunctionCallFromOtherFile() { TopDUContext* addTop = parseAdditionalFile(IndexedString("/duchaintest/foo2.php"), "childContexts().first()->localDeclarations().first()->type(); QVERIFY(fun); StructureType::Ptr ret = StructureType::Ptr::dynamicCast(fun->returnType()); qDebug() << fun->returnType()->toString(); QVERIFY(ret); QCOMPARE(ret->declaration(top), top->localDeclarations().first()); } void TestDUChain::internalFunctions() { return; //disabled because it is too slow QString fileName = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kdevphpsupport/phpfunctions.php")); QFile file(fileName); file.open(QIODevice::ReadOnly | QIODevice::Text); TopDUContext* top = parse(file.readAll(), DumpNone); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); } void TestDUChain::trueFalse() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().at(0)->type()->dataType() == IntegralType::TypeBoolean); QVERIFY(top->localDeclarations().at(1)->type()->dataType() == IntegralType::TypeBoolean); } void TestDUChain::null() { QByteArray method("localDeclarations().at(0)->type()->dataType() == IntegralType::TypeNull); } void TestDUChain::array() { QByteArray method("localDeclarations().at(0)->type()->dataType() == IntegralType::TypeArray); QVERIFY(top->localDeclarations().at(1)->type()->dataType() == IntegralType::TypeArray); // $b[] = 'test'; is not a redeclaration of b! Esp. it's type should not change. QCOMPARE(top->findDeclarations(Identifier("b")).count(), 1); } void TestDUChain::functionDocBlock() { { TopDUContext* top = parse("localDeclarations().first()->comment(), QByteArray("Foo")); } { TopDUContext* top = parse("localDeclarations().first()->comment(), QByteArray("Bar")); QCOMPARE(top->childContexts().first()->localDeclarations().first()->comment(), QByteArray("Foo")); } { TopDUContext* top = parse("localDeclarations().first()->comment(), QByteArray("Foo")); } { TopDUContext* top = parse("childContexts().first()->localDeclarations().first()->comment(), QByteArray("Foo")); } { TopDUContext* top = parse("childContexts().first()->localDeclarations().first()->comment(), QByteArray("Foo")); } { TopDUContext* top = parse("localDeclarations().first()->comment(), QByteArray("Foo\n Bar")); } { // same as above but with indendation TopDUContext* top = parse("localDeclarations().first()->comment(), QByteArray("Foo\n Bar")); } } void TestDUChain::variableDocBlock() { { TopDUContext* top = parse("localDeclarations().first()->comment(), QByteArray("Foo")); QCOMPARE(top->localDeclarations().at(1)->comment(), QByteArray("Foo")); } { TopDUContext* top = parse("localDeclarations().first()->comment(), QByteArray("Foo")); QCOMPARE(top->localDeclarations().at(1)->comment(), QByteArray("Foo")); } } void TestDUChain::functionDocBlockParams() { TopDUContext* top = parse("localDeclarations().at(1)->type()->arguments().count(), 4); AbstractType::Ptr arg = top->localDeclarations().at(1)->type()->arguments().at(0); QVERIFY(IntegralType::Ptr::dynamicCast(arg)); QVERIFY(IntegralType::Ptr::dynamicCast(arg)->dataType() == IntegralType::TypeInt); QVERIFY(top->childContexts().at(1)->localDeclarations().at(0)->type()); QVERIFY(top->childContexts().at(1)->localDeclarations().at(0)->type()->dataType() == IntegralType::TypeInt); arg = top->localDeclarations().at(1)->type()->arguments().at(1); QVERIFY(StructureType::Ptr::dynamicCast(arg)); QCOMPARE(StructureType::Ptr::dynamicCast(arg)->declaration(top), top->localDeclarations().at(0)); QCOMPARE(top->childContexts().at(1)->localDeclarations().at(1)->type()->declaration(top), top->localDeclarations().at(0)); arg = top->localDeclarations().at(1)->type()->arguments().at(2); QVERIFY(IntegralType::Ptr::dynamicCast(arg)); QVERIFY(IntegralType::Ptr::dynamicCast(arg)->dataType() == IntegralType::TypeMixed); arg = top->localDeclarations().at(1)->type()->arguments().at(3); QVERIFY(IntegralType::Ptr::dynamicCast(arg)); QVERIFY(IntegralType::Ptr::dynamicCast(arg)->dataType() == IntegralType::TypeMixed); } } void TestDUChain::memberFunctionDocBlockParams() { TopDUContext* top = parse("childContexts().first()->localDeclarations().first()->type()->arguments().count(), 3); AbstractType::Ptr arg = top->childContexts().first()->localDeclarations().first()->type()->arguments().at(0); QVERIFY(IntegralType::Ptr::dynamicCast(arg)); QVERIFY(IntegralType::Ptr::dynamicCast(arg)->dataType() == IntegralType::TypeBoolean); arg = top->childContexts().first()->localDeclarations().first()->type()->arguments().at(1); QVERIFY(StructureType::Ptr::dynamicCast(arg)); QCOMPARE(StructureType::Ptr::dynamicCast(arg)->declaration(top), top->localDeclarations().at(0)); arg = top->childContexts().first()->localDeclarations().first()->type()->arguments().at(2); QVERIFY(IntegralType::Ptr::dynamicCast(arg)); QVERIFY(IntegralType::Ptr::dynamicCast(arg)->dataType() == IntegralType::TypeArray); } } void TestDUChain::foreachLoop() { { TopDUContext* top = parse("$i) { $i; }", DumpAll); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(top->localDeclarations().count(), 3); QCOMPARE(top->localDeclarations().at(1)->qualifiedIdentifier(), QualifiedIdentifier("k")); QVERIFY(top->localDeclarations().at(1)->abstractType().cast()); QCOMPARE(top->localDeclarations().at(1)->abstractType().cast()->dataType(), static_cast(IntegralType::TypeMixed)); QCOMPARE(top->localDeclarations().at(2)->qualifiedIdentifier(), QualifiedIdentifier("i")); QVERIFY(top->localDeclarations().at(2)->abstractType().cast()); QCOMPARE(top->localDeclarations().at(2)->abstractType().cast()->dataType(), static_cast(IntegralType::TypeMixed)); } { // bug: https://bugs.kde.org/show_bug.cgi?id=237110 TopDUContext* top = parse("localDeclarations().count(), 3); QCOMPARE(top->localDeclarations().at(1)->qualifiedIdentifier(), QualifiedIdentifier("b")); qDebug() << top->localDeclarations().at(1)->toString(); QVERIFY(top->localDeclarations().at(1)->abstractType().cast()); QCOMPARE(top->localDeclarations().at(1)->abstractType().cast()->dataType(), static_cast(IntegralType::TypeMixed)); QCOMPARE(top->localDeclarations().at(2)->qualifiedIdentifier(), QualifiedIdentifier("c")); QVERIFY(top->localDeclarations().at(2)->abstractType().cast()); QCOMPARE(top->localDeclarations().at(2)->abstractType().cast()->qualifiedIdentifier().toString(), QString("stdclass")); } } void TestDUChain::php4StyleConstructor() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("bb(); } } "); TopDUContext* top = parse(method, DumpAll); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); Declaration* dec = top->childContexts().first()->localDeclarations().at(0); QVERIFY(dec); QCOMPARE(dec->qualifiedIdentifier(), QualifiedIdentifier("aa::aa")); ClassFunctionDeclaration* classFuncDec = dynamic_cast(dec); QVERIFY(classFuncDec); QVERIFY(classFuncDec->isConstructor()); } void TestDUChain::constructor() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 { QByteArray method("childContexts().first()->localDeclarations().at(0); QVERIFY(dec); ClassFunctionDeclaration* classFuncDec = dynamic_cast(dec); QVERIFY(classFuncDec); QVERIFY(!classFuncDec->isDestructor()); QVERIFY(classFuncDec->isConstructor()); } { QByteArray method("childContexts().first()->localDeclarations().at(0); QVERIFY(dec); ClassFunctionDeclaration* classFuncDec = dynamic_cast(dec); QVERIFY(classFuncDec); QVERIFY(!classFuncDec->isDestructor()); QVERIFY(classFuncDec->isConstructor()); } } void TestDUChain::destructor() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("childContexts().first()->localDeclarations().at(0); QVERIFY(dec); ClassFunctionDeclaration* classFuncDec = dynamic_cast(dec); QVERIFY(classFuncDec); QVERIFY(classFuncDec->isDestructor()); QVERIFY(!classFuncDec->isConstructor()); } void TestDUChain::functionInFunction() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().at(0)->qualifiedIdentifier(), QualifiedIdentifier("aaa")); } void TestDUChain::objectWithClassName() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("foo();"); TopDUContext* top = parse(method, DumpNone, QUrl(QStringLiteral("file:///internal/testObjectWithClassName.php"))); DUChainReleaser releaseTop(top); // update top (the pointer will be the same) QByteArray method2("foo();"); TopDUContext* top2 = parse(method2, DumpNone, QUrl(QStringLiteral("file:///internal/testObjectWithClassName.php"))); QVERIFY(top2 == top); } void TestDUChain::largeNumberOfDeclarations() { TopDUContext* top = new TopDUContext(IndexedString(QUrl(QStringLiteral("file:///internal/testurl"))), RangeInRevision(0, 0, 6000, 0), nullptr); DUChain::self()->addDocumentChain(top); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); for (int i = 0; i < 6000; ++i) { RangeInRevision newRange(i, 0, i, 1); auto* dec = new Declaration(newRange, top); dec->setIdentifier(Identifier(QStringLiteral("dec%0").arg(i))); dec->setAbstractType(AbstractType::Ptr(nullptr)); } } void TestDUChain::staticVariable() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("childContexts().at(1)->localDeclarations().count(), 6); QCOMPARE(top->childContexts().at(1)->localDeclarations().first()->qualifiedIdentifier(), QualifiedIdentifier("aaa::foo")); QVERIFY(top->childContexts().at(1)->localDeclarations().first()->type()); QCOMPARE(top->childContexts().at(1)->localDeclarations().first()->type()->dataType(), (uint)IntegralType::TypeMixed); QCOMPARE(top->childContexts().at(1)->localDeclarations().at(1)->qualifiedIdentifier(), QualifiedIdentifier("aaa::bar")); QVERIFY(top->childContexts().at(1)->localDeclarations().at(1)->type()); QCOMPARE(top->childContexts().at(1)->localDeclarations().at(1)->type()->dataType(), (uint)IntegralType::TypeInt); QCOMPARE(top->childContexts().at(1)->localDeclarations().at(2)->qualifiedIdentifier(), QualifiedIdentifier("aaa::baz")); QVERIFY(top->childContexts().at(1)->localDeclarations().at(2)->type()); QCOMPARE(top->childContexts().at(1)->localDeclarations().at(2)->type()->dataType(), (uint)IntegralType::TypeString); QVERIFY(top->childContexts().at(1)->localDeclarations().at(3)->type()); QCOMPARE(top->childContexts().at(1)->localDeclarations().at(3)->type()->dataType(), (uint)IntegralType::TypeArray); QVERIFY(top->childContexts().at(1)->localDeclarations().at(4)->type()); QCOMPARE(top->childContexts().at(1)->localDeclarations().at(4)->type()->dataType(), (uint)IntegralType::TypeInt); QVERIFY(top->childContexts().at(1)->localDeclarations().at(5)->type()); QCOMPARE(top->childContexts().at(1)->localDeclarations().at(5)->type()->dataType(), (uint)IntegralType::TypeInt); } void TestDUChain::returnTypeTwoDeclarations() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().at(0); FunctionType::Ptr functionType = dec->type(); QVERIFY(functionType); UnsureType::Ptr retType = UnsureType::Ptr::dynamicCast(functionType->returnType()); QVERIFY(retType); QCOMPARE(retType->typesSize(), 2u); QVERIFY(retType->types()[0].abstractType().cast()); QCOMPARE(retType->types()[0].abstractType().cast()->dataType(), (uint)IntegralType::TypeString); QVERIFY(retType->types()[1].abstractType().cast()); QCOMPARE(retType->types()[1].abstractType().cast()->dataType(), (uint)IntegralType::TypeInt); } void TestDUChain::globalVariableNotVisibleInFunction() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("findDeclarations(QualifiedIdentifier("a")).first()->uses().count(), 0); } void TestDUChain::globalVariableInFunction() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("findDeclarations(QualifiedIdentifier("a")).first()->uses().count(), 1); } void TestDUChain::nonGlobalVariableInFunction() { // bug: https://bugs.kde.org/show_bug.cgi?id=240920 // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("findLocalDeclarations(Identifier("a")).count(), 1); QCOMPARE(top->findLocalDeclarations(Identifier("a")).first()->uses().count(), 0); QCOMPARE(top->childContexts().count(), 2); QCOMPARE(top->childContexts().last()->findLocalDeclarations(Identifier("a")).count(), 1); QCOMPARE(top->childContexts().last()->findLocalDeclarations(Identifier("a")).first()->uses().count(), 0); } void TestDUChain::superglobalInFunction() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("findDeclarations(QualifiedIdentifier("_GET")).count(), 1); Declaration* dec = top->findDeclarations(QualifiedIdentifier(QStringLiteral("_GET"))).first(); QVERIFY(dynamic_cast(dec)); QVERIFY(static_cast(dec)->isSuperglobal()); QCOMPARE(dec->uses().keys().count(), 1); QCOMPARE(dec->uses().values().count(), 1); QCOMPARE(dec->uses().values().first().count(), 2); QCOMPARE(dec->uses().values().first().first(), RangeInRevision(0, 3, 0, 8)); QCOMPARE(dec->uses().values().first().at(1), RangeInRevision(0, 27, 0, 32)); } void TestDUChain::returnWithoutFunction() { //yes, this is possible in php, you then have $a as return value of an include call QByteArray method("localDeclarations().at(2)->internalContext()->importedParentContexts().empty()); QCOMPARE(top->localDeclarations().at(1)->internalContext()->importedParentContexts().count(), 1); QCOMPARE(top->localDeclarations().at(1)->internalContext()->importedParentContexts().first().context(top), top->localDeclarations().at(2)->internalContext()); QCOMPARE(top->localDeclarations().at(0)->internalContext()->importedParentContexts().count(), 1); QCOMPARE(top->localDeclarations().at(0)->internalContext()->importedParentContexts().first().context(top), top->localDeclarations().at(1)->internalContext()); } void TestDUChain::circularInterface() { QByteArray method("problems().count(), 0); QVERIFY(top->localDeclarations().at(0)->internalContext()->importedParentContexts().empty()); QCOMPARE(top->localDeclarations().at(1)->internalContext()->importedParentContexts().count(), 1); QCOMPARE(top->localDeclarations().at(1)->internalContext()->importedParentContexts().first().context(top), top->localDeclarations().at(0)->internalContext()); QCOMPARE(top->localDeclarations().at(2)->internalContext()->importedParentContexts().count(), 1); QCOMPARE(top->localDeclarations().at(2)->internalContext()->importedParentContexts().first().context(top), top->localDeclarations().at(1)->internalContext()); } void TestDUChain::findDeclarations() { DUChainWriteLocker lock(DUChain::lock()); TopDUContext* top1 = new TopDUContext(IndexedString(QUrl(QStringLiteral("file:///internal/testfile1"))), RangeInRevision(0, 0, 0, 10), nullptr); DUChainReleaser releaseTop1(top1); DUChain::self()->addDocumentChain(top1); TopDUContext* top2 = new TopDUContext(IndexedString(QUrl(QStringLiteral("file:///internal/testfile2"))), RangeInRevision(0, 0, 0, 10), nullptr); DUChainReleaser releaseTop2(top2); DUChain::self()->addDocumentChain(top2); Declaration* declaration = new Declaration(RangeInRevision(0, 0, 0, 3), top1); declaration->setIdentifier(Identifier(QStringLiteral("foo"))); QVERIFY(!top1->usingImportsCache()); QVERIFY(!top2->usingImportsCache()); QCOMPARE(1, top1->findDeclarations(Identifier("foo")).count()); QCOMPARE(0, top2->findDeclarations(Identifier("foo")).count()); top2->addImportedParentContext(top1); QVERIFY(!top1->usingImportsCache()); QVERIFY(!top2->usingImportsCache()); QCOMPARE(1, top2->findDeclarations(Identifier("foo")).count()); top2->clearImportedParentContexts(); QCOMPARE(top2->importedParentContexts().size(), 0); QVERIFY(!top1->usingImportsCache()); QVERIFY(!top2->usingImportsCache()); QCOMPARE(0, top2->findDeclarations(Identifier("foo")).count()); top2->addImportedParentContext(top1); QCOMPARE(1, top2->findDeclarations(Identifier("foo")).count()); } void TestDUChain::memberTypeAfterMethod() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("childContexts().first(); // function foo { ClassMemberDeclaration* var = dynamic_cast(contextClassA->localDeclarations().first()); QVERIFY(var); QCOMPARE(var->identifier(), Identifier("foo")); QCOMPARE(var->accessPolicy(), Declaration::Public); QCOMPARE(var->isStatic(), false); QVERIFY(var->type()); IntegralType::Ptr ret = var->type()->returnType().cast(); QVERIFY(ret); QVERIFY(ret->dataType() == IntegralType::TypeVoid); } // public $bar { ClassMemberDeclaration* var = dynamic_cast(contextClassA->localDeclarations().at(1)); QVERIFY(var); QCOMPARE(var->identifier(), Identifier("bar")); QCOMPARE(var->accessPolicy(), Declaration::Public); QCOMPARE(var->isStatic(), false); QVERIFY(var->type()); QVERIFY(var->type()->dataType() == IntegralType::TypeNull); } } void TestDUChain::catchDeclaration() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("(top->localDeclarations().first()); QVERIFY(ex); QCOMPARE(ex->identifier(), Identifier("e")); QVERIFY(ex->type()); QCOMPARE(QualifiedIdentifier("exception"), ex->type()->declaration(top)->qualifiedIdentifier()); } void TestDUChain::resourceType() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("(top->localDeclarations().first()); QVERIFY(fun); FunctionType::Ptr ftype = FunctionType::Ptr::dynamicCast(fun->abstractType()); QVERIFY(ftype); IntegralType::Ptr rtype = IntegralType::Ptr::dynamicCast(ftype->returnType()); QVERIFY(rtype); QCOMPARE(rtype->toString(), QString("resource")); QVERIFY(rtype->dataType() == IntegralTypeExtended::TypeResource); } void TestDUChain::foreachIterator() { QByteArray code; code.append("localDeclarations().at(3); QCOMPARE(iDec->qualifiedIdentifier(), QualifiedIdentifier("i")); QVERIFY(iDec->type()); QCOMPARE(iDec->type()->qualifiedIdentifier(), QualifiedIdentifier("b")); QVERIFY(top->localDeclarations().first() == iDec->type()->declaration(top)); } void TestDUChain::foreachIterator2() { QByteArray code; code.append("localDeclarations().size(), 3); Declaration* iDec = top->localDeclarations().at(2); QCOMPARE(iDec->qualifiedIdentifier(), QualifiedIdentifier("i")); qDebug() << iDec->abstractType()->toString(); QVERIFY(iDec->type()); QCOMPARE(iDec->type()->qualifiedIdentifier(), QualifiedIdentifier("b")); QVERIFY(top->localDeclarations().first() == iDec->type()->declaration(top)); } void TestDUChain::foreachIterator3() { QByteArray code; code.append("localDeclarations().at(3); QCOMPARE(iDec->qualifiedIdentifier(), QualifiedIdentifier("i")); QVERIFY(iDec->type()); QCOMPARE(iDec->type()->qualifiedIdentifier(), QualifiedIdentifier("b")); QVERIFY(top->localDeclarations().first() == iDec->type()->declaration(top)); } void TestDUChain::foreachIterator4() { // see also: https://bugs.kde.org/show_bug.cgi?id=276603 QByteArray code = "i){}\n" " foreach(array(1,2) as $this->k => $this->v){}\n" " foreach(array(1,2) as A::$s){}\n" " }\n" "}\n"; TopDUContext* top = parse(code, DumpNone); DUChainReleaser releaseTop(top); DUChainWriteLocker lock; QVERIFY(top->problems().isEmpty()); Declaration* aDec = top->localDeclarations().first(); DUContext* fooCtx = top->childContexts().first()->childContexts().last(); QVERIFY(fooCtx->owner()); QCOMPARE(aDec->uses().size(), 1); QCOMPARE(aDec->uses().begin()->size(), 4); } void TestDUChain::returnThis() { QByteArray code("childContexts().first()->localDeclarations().first(); QVERIFY(dec->type()); AbstractType::Ptr t = dec->type()->returnType(); qDebug() << t->toString(); QVERIFY(StructureType::Ptr::dynamicCast(t)); QVERIFY(StructureType::Ptr::dynamicCast(t)->declaration(top) == top->localDeclarations().first()); } void TestDUChain::unsureReturnType() { QByteArray code("localDeclarations().first(); QVERIFY(dec->type()); TypePtr ut = dec->type()->returnType().cast(); QVERIFY(ut); QCOMPARE((uint)2, ut->typesSize()); QVERIFY(ut->types()[0].type()); QVERIFY(ut->types()[0].type()->dataType() == IntegralType::TypeBoolean); QVERIFY(ut->types()[1].type()); QVERIFY(ut->types()[1].type()->dataType() == IntegralType::TypeInt); } void TestDUChain::unsureReturnType2() { QByteArray code("localDeclarations().at(2); QVERIFY(dec->type()); TypePtr ut = dec->type()->returnType().cast(); QVERIFY(ut); QCOMPARE((uint)2, ut->typesSize()); QVERIFY(ut->types()[0].type()); QCOMPARE(ut->types()[0].type()->toString(), QString("A")); QVERIFY(ut->types()[1].type()); QCOMPARE(ut->types()[1].type()->toString(), QString("B")); } void TestDUChain::unsureReturnType3() { QByteArray code("localDeclarations().at(0); QVERIFY(dec->type()); qDebug() << dec->type()->returnType()->toString(); TypePtr ut = dec->type()->returnType().cast(); QVERIFY(ut); QCOMPARE((uint)3, ut->typesSize()); QVERIFY(ut->types()[0].type()); QVERIFY(ut->types()[0].type()->dataType() == IntegralType::TypeInt); QVERIFY(ut->types()[1].type()); QVERIFY(ut->types()[1].type()->dataType() == IntegralType::TypeBoolean); QVERIFY(ut->types()[2].type()); QVERIFY(ut->types()[2].type()->dataType() == IntegralType::TypeString); } void TestDUChain::unsureReturnType4() { QByteArray code("localDeclarations().first(); QVERIFY(dec->type()); TypePtr ut = dec->type()->returnType().cast(); QVERIFY(ut); QCOMPARE((uint)2, ut->typesSize()); QVERIFY(ut->types()[0].type()); QVERIFY(ut->types()[0].type()->dataType() == IntegralType::TypeBoolean); QVERIFY(ut->types()[1].type()); QVERIFY(ut->types()[1].type()->dataType() == IntegralType::TypeInt); } void TestDUChain::referencedArgument() { // php does not return references QByteArray code("localDeclarations().first(); QVERIFY(dec->type()); qDebug() << dec->abstractType()->toString(); IntegralType::Ptr aType = dec->type()->returnType().cast(); QVERIFY(aType); QCOMPARE(aType->dataType(), (uint)IntegralType::TypeInt); QCOMPARE(top->childContexts().first()->type(), DUContext::Function); ReferenceType::Ptr rType = top->childContexts().first()->localDeclarations().first()->abstractType().cast(); QVERIFY(rType); QVERIFY(rType->baseType()->equals(aType.data())); } void TestDUChain::unsureReferencedArgument() { // php does not return references QByteArray code("localDeclarations().first(); QVERIFY(dec->type()); qDebug() << dec->abstractType()->toString(); UnsureType::Ptr aType = dec->type()->returnType().cast(); QVERIFY(aType); QCOMPARE(aType->typesSize(), 2u); QCOMPARE(aType->types()[0].abstractType().cast()->dataType(), (uint)IntegralType::TypeInt); QCOMPARE(aType->types()[1].abstractType().cast()->dataType(), (uint)IntegralType::TypeString); QCOMPARE(top->childContexts().first()->type(), DUContext::Function); ReferenceType::Ptr rType = top->childContexts().first()->localDeclarations().first()->abstractType().cast(); QVERIFY(rType); QVERIFY(rType->baseType()->equals(aType.data())); } void TestDUChain::defaultArgument() { // php does not return references QByteArray code("childContexts().first()->localDeclarations().first(); QVERIFY(dec->type()); QCOMPARE(dec->type()->dataType(), (uint)IntegralType::TypeInt); } void TestDUChain::declareMemberOutOfClass() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray code("asdf = true; $bar->asdf = false;\n" // not allowed: "$bar->prot = 1;\n" // not allowed: "$bar->priv = 1;"); TopDUContext* top = parse(code, DumpAST); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); { // $bar is only declared once QList decs = top->findLocalDeclarations(Identifier(QStringLiteral("bar"))); QCOMPARE(decs.size(), 1); Declaration *dec = decs.first(); QVERIFY(dec->type()); QVERIFY(dec->type()->declaration(top)->identifier().nameEquals(Identifier("foo"))); // while we are at it, compare uses QCOMPARE(dec->uses().keys().count(), 1); QCOMPARE(dec->uses().values().count(), 1); QCOMPARE(dec->uses().values().first().count(), 4); qDebug() << dec->uses().values().first().at(0).castToSimpleRange(); QCOMPARE(dec->uses().values().first().at(0), RangeInRevision(1, 16, 1, 20)); qDebug() << dec->uses().values().first().at(1).castToSimpleRange(); QCOMPARE(dec->uses().values().first().at(1), RangeInRevision(1, 35, 1, 39)); qDebug() << dec->uses().values().first().at(2).castToSimpleRange(); QCOMPARE(dec->uses().values().first().at(2), RangeInRevision(2, 0, 2, 4)); qDebug() << dec->uses().values().first().at(3).castToSimpleRange(); QCOMPARE(dec->uses().values().first().at(3), RangeInRevision(3, 0, 3, 4)); } { // check if asdf got declared QList decs = top->childContexts().first()->findDeclarations(Identifier(QStringLiteral("asdf"))); // the type of both assignments to $bar->asdf are the same, hence it should only be declared once QCOMPARE(decs.size(), 1); ClassMemberDeclaration* cmdec = dynamic_cast(decs.first()); QVERIFY(cmdec); QVERIFY(cmdec->accessPolicy() == Declaration::Public); QVERIFY(!cmdec->isStatic()); QVERIFY(cmdec->type()); QVERIFY(cmdec->type()->dataType() == IntegralType::TypeBoolean); } // check that prot and priv don't get redeclared QCOMPARE(top->problems().count(), 2); QCOMPARE(top->problems().at(0)->finalLocation().start().line(), 2); QCOMPARE(top->problems().at(1)->finalLocation().start().line(), 3); } void TestDUChain::declareMemberOutOfClass2() { // see also: https://bugs.kde.org/show_bug.cgi?id=283356 // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray code("x = 1;\n" "class A { var $x = 1; }"); TopDUContext* top = parse(code, DumpAST); QVERIFY(top); // update top = parse(code, DumpNone, top->url().toUrl(), ReferencedTopDUContext(top)); DUChainReleaser releaseTop(top); DUChainWriteLocker lock; QVERIFY(top->problems().isEmpty()); QList decs = top->findLocalDeclarations(Identifier(QStringLiteral("a"))); QCOMPARE(decs.size(), 2); { Declaration *dec = decs.first(); QVERIFY(dynamic_cast(dec)); QVERIFY(dec->type()); QVERIFY(dec->type()->declaration(top)->identifier().nameEquals(Identifier("a"))); } { Declaration *dec = decs.last(); QVERIFY(dynamic_cast(dec)); QVERIFY(dec->type()); QVERIFY(dec->type()->declaration(top)->identifier().nameEquals(Identifier("a"))); } { // check if x got declared QList decs = top->childContexts().first()->findDeclarations(Identifier(QStringLiteral("x"))); // the type of both assignments to $a->x are the same, hence it should only be declared once QCOMPARE(decs.size(), 1); ClassMemberDeclaration* cmdec = dynamic_cast(decs.first()); QVERIFY(cmdec); QVERIFY(cmdec->accessPolicy() == Declaration::Public); QVERIFY(!cmdec->isStatic()); QVERIFY(cmdec->type()); QCOMPARE(cmdec->type()->dataType(), (uint) IntegralType::TypeInt); } } void TestDUChain::declareMemberInClassMethod() { QByteArray code("asdf = true; $this->asdf = false; }\n" // should only declare bar once as private " private $xyz = 0; function test2() { $this->xyz = 42; }\n" // should create a local declaration for the private attribute " function test3() { $this->prot = 42;\n$this->priv = 42; }\n" " }"); TopDUContext* top = parse(code, DumpAST); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); { // asdf QList decs = top->childContexts().last()->findLocalDeclarations(Identifier(QStringLiteral("asdf"))); QCOMPARE(decs.size(), 1); ClassMemberDeclaration *dec = dynamic_cast(decs.first()); QVERIFY(dec); QVERIFY(dec->accessPolicy() == Declaration::Public); QVERIFY(!dec->isStatic()); QVERIFY(dec->type()); QVERIFY(dec->type()->dataType() == IntegralType::TypeBoolean); } { // xyz QList decs = top->childContexts().last()->findLocalDeclarations(Identifier(QStringLiteral("xyz"))); QCOMPARE(decs.size(), 1); ClassMemberDeclaration *dec = dynamic_cast(decs.first()); QVERIFY(dec); QVERIFY(dec->accessPolicy() == Declaration::Private); QVERIFY(!dec->isStatic()); QVERIFY(dec->type()); QVERIFY(dec->type()->dataType() == IntegralType::TypeInt); } { // priv QList decs = top->childContexts().last()->findLocalDeclarations(Identifier(QStringLiteral("priv"))); QCOMPARE(decs.size(), 1); ClassMemberDeclaration *dec = dynamic_cast(decs.first()); QVERIFY(dec); QVERIFY(dec->accessPolicy() == Declaration::Public); QVERIFY(!dec->isStatic()); QVERIFY(dec->type()); QVERIFY(dec->type()->dataType() == IntegralType::TypeInt); } { // prot QVERIFY(top->childContexts().last()->findLocalDeclarations(Identifier("prot")).isEmpty()); } QCOMPARE(top->problems().count(), 0); } void TestDUChain::thisRedeclaration() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray code("test = true; $this = false;} }"); TopDUContext* top = parse(code, DumpAST); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); // only $this = false is a problem, $this->test = true is perfectly valid QCOMPARE(top->problems().count(), 1); qDebug() << top->problems().first()->finalLocation(); QVERIFY(top->problems().first()->finalLocation() == KDevelop::DocumentRange(top->url(), KTextEditor::Range(0, 50, 0, 55))); } void TestDUChain::implicitArrayDeclaration() { ///TODO: adapt to unsure type once it's supported { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray code(" decs = top->findDeclarations(Identifier(QStringLiteral("a"))); QCOMPARE(decs.size(), 1); VariableDeclaration* vdec = dynamic_cast(decs.first()); QVERIFY(vdec); QVERIFY(vdec->type()); QVERIFY(vdec->type()->dataType() == IntegralType::TypeArray); } { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray code(" decs = top->findDeclarations(Identifier(QStringLiteral("a"))); QCOMPARE(decs.size(), 1); VariableDeclaration* vdec = dynamic_cast(decs.first()); QVERIFY(vdec); QVERIFY(vdec->type()); QVERIFY(vdec->type()->dataType() == IntegralType::TypeArray); } { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray code("a[1] = true;"); TopDUContext* top = parse(code, DumpAST); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); QList decs = top->childContexts().first()->findDeclarations(Identifier(QStringLiteral("a"))); QCOMPARE(decs.size(), 1); ClassMemberDeclaration* cmdec = dynamic_cast(decs.first()); QVERIFY(cmdec); QVERIFY(cmdec->type()); QVERIFY(cmdec->type()->dataType() == IntegralType::TypeArray); } { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray code("a[$b] = true;"); TopDUContext* top = parse(code, DumpAST); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); QList decs = top->childContexts().first()->findDeclarations(Identifier(QStringLiteral("a"))); QCOMPARE(decs.size(), 1); ClassMemberDeclaration* cmdec = dynamic_cast(decs.first()); QVERIFY(cmdec); QVERIFY(cmdec->type()); QVERIFY(cmdec->type()->dataType() == IntegralType::TypeArray); } } void TestDUChain::implicitReferenceDeclaration() { { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray code(" decs = top->findDeclarations(Identifier(QStringLiteral("bar"))); QCOMPARE(decs.size(), 1); QVERIFY(dynamic_cast(decs.first())); QVERIFY(decs.first()->type()); qDebug() << decs.first()->type()->dataType() << decs.first()->toString(); QVERIFY(decs.first()->type()->dataType() == IntegralType::TypeNull); } { // a user reported a crash with the code example below // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray code("a);} }"); TopDUContext* top = parse(code, DumpAST); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); QVERIFY( top->childContexts().last()->localScopeIdentifier() == QualifiedIdentifier("foo")); // a is already declared QList decs = top->childContexts().last()->findDeclarations(Identifier(QStringLiteral("a"))); QCOMPARE(decs.size(), 1); ClassMemberDeclaration* cmdec = dynamic_cast(decs.first()); QVERIFY(cmdec); QVERIFY(cmdec->type()); qDebug() << cmdec->type()->dataType() << cmdec->toString(); QVERIFY(cmdec->type()->dataType() == IntegralType::TypeNull); } } void TestDUChain::classContextRange() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray code(" foobar = 1; $a->barFoo= 0;"); TopDUContext* top = parse(code, DumpAST); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(top->childContexts().first()->range(), KDevelop::RangeInRevision(0, 6, 0, 17)); QCOMPARE(top->childContexts().first()->localDeclarations().count(), 2); } void TestDUChain::lateClassMembers() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray code("val = 'b'; } private $val = 'a'; } "); TopDUContext* top = parse(code, DumpAST); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); ClassDeclaration* cdec = dynamic_cast(top->localDeclarations().first()); QVERIFY(cdec); QList decs = cdec->logicalInternalContext(top)->findDeclarations(Identifier(QStringLiteral("val"))); QCOMPARE(decs.count(), 1); ClassMemberDeclaration* cmdec = dynamic_cast(decs.first()); QVERIFY(cmdec); QCOMPARE(cmdec->accessPolicy(), Declaration::Private); } void TestDUChain::list() { foreach ( const QString& code, QStringList() << " decs = top->findDeclarations(Identifier(identifier)); QCOMPARE(decs.size(), 1); Declaration *dec = decs.first(); QVERIFY(dec->type()); QCOMPARE(dec->type()->dataType(), (uint) IntegralType::TypeMixed); ///TODO: support arrays better and compare to actual type } } } void TestDUChain::alternateDocCommentTypeHints() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("(top->localDeclarations().first()); QVERIFY(cdec); QVERIFY(cdec->type()); QVector decs = cdec->logicalInternalContext(top)->localDeclarations(); QCOMPARE(decs.size(), 1); Declaration *dec = decs.first(); QVERIFY(dec->type()); QCOMPARE(dec->type()->declaration(top), cdec); } void TestDUChain::findFunctionArgs() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("(top->localDeclarations().first()); QVERIFY(funcDec); QVERIFY(funcDec->internalContext()); QVERIFY(funcDec->internalFunctionContext()); QVERIFY(funcDec->internalContext()->imports(funcDec->internalFunctionContext())); QList decs; foreach ( Declaration* arg, funcDec->internalFunctionContext()->localDeclarations() ) { decs = funcDec->internalContext()->findDeclarations(arg->identifier()); QCOMPARE(decs.size(), 1); decs = funcDec->internalContext()->findDeclarations(arg->qualifiedIdentifier()); qDebug() << arg->qualifiedIdentifier().toString(); QEXPECT_FAIL("", "strangely the arg dec is only found with its identifier, not by its qualifiedidentifier...", Continue); QCOMPARE(decs.size(), 1); } } void TestDUChain::undeclaredPropertyInString() { // testcase for bug 209814 // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("baz\"; } }", DumpNone); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(top->childContexts().size(), 1); DUContext* classCtx = top->childContexts().first(); QVERIFY(classCtx->type() == DUContext::Class); QCOMPARE(classCtx->localDeclarations().size(), 2); QCOMPARE(classCtx->findDeclarations(Identifier("foo")).size(), 1); QCOMPARE(classCtx->findDeclarations(Identifier("bar")).size(), 1); } void TestDUChain::undeclaredVarPropertyInString() { // testcase for bug 210043 // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("baz\";", DumpNone); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); // just don't crash } void TestDUChain::upcommingClassInString() { // testcase for bug 232687 // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("blah\";\n" " }\n" "}\n" "class B {\n" " var $blah;\n" "}\n", DumpNone); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); // just don't crash } void TestDUChain::namespaces() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("problems().count(), 0); QCOMPARE(top->childContexts().size(), 4); QCOMPARE(top->childContexts().at(0)->localScopeIdentifier().toString(), QString("asdf")); QCOMPARE(top->childContexts().at(1)->localScopeIdentifier().toString(), QString("ns1")); QCOMPARE(top->childContexts().at(2)->type(), DUContext::Function); QCOMPARE(top->childContexts().at(3)->localScopeIdentifier().toString(), QString("a")); QCOMPARE(top->localDeclarations().size(), 3); QCOMPARE(top->localDeclarations().at(0)->kind(), Declaration::Namespace); QCOMPARE(top->localDeclarations().at(1)->kind(), Declaration::Namespace); QCOMPARE(top->localDeclarations().at(2)->kind(), Declaration::Type); QCOMPARE(top->findDeclarations(QualifiedIdentifier("asdf")).size(), 1); QCOMPARE(top->childContexts().at(0)->localDeclarations().size(), 3); QCOMPARE(top->findDeclarations(QualifiedIdentifier("asdf::a")).size(), 1); QCOMPARE(top->findDeclarations(QualifiedIdentifier("asdf::b")).size(), 1); QCOMPARE(top->findDeclarations(QualifiedIdentifier("asdf::c")).size(), 1); QCOMPARE(top->findDeclarations(QualifiedIdentifier("ns1")).size(), 1); QCOMPARE(top->childContexts().at(1)->localDeclarations().size(), 1); QCOMPARE(top->childContexts().at(1)->localDeclarations().first()->kind(), Declaration::Namespace); ///TODO: support \ as separator QCOMPARE(top->childContexts().at(1)->localDeclarations().first()->qualifiedIdentifier().toString(), QString("ns1::ns2")); QCOMPARE(top->findDeclarations(QualifiedIdentifier("ns1::ns2")).size(), 1); QCOMPARE(top->findDeclarations(QualifiedIdentifier("ns1::ns2")).first()->logicalInternalContext(top)->localDeclarations().size(), 3); QCOMPARE(top->childContexts().at(1)->childContexts().size(), 1); QCOMPARE(top->childContexts().at(1)->childContexts().first()->localDeclarations().size(), 3); QCOMPARE(top->findDeclarations(QualifiedIdentifier("ns1::ns2")).first()->logicalInternalContext(top)->localDeclarations().first()->qualifiedIdentifier().toString(), QString("ns1::ns2::a")); QCOMPARE(top->findDeclarations(QualifiedIdentifier("ns1::ns2::a")).size(), 1); QCOMPARE(top->findDeclarations(QualifiedIdentifier("ns1::ns2::b")).size(), 1); QCOMPARE(top->findDeclarations(QualifiedIdentifier("ns1::ns2::c")).size(), 1); QCOMPARE(top->findDeclarations(QualifiedIdentifier("a")).size(), 1); QCOMPARE(top->findDeclarations(QualifiedIdentifier("b")).size(), 0); QCOMPARE(top->findDeclarations(QualifiedIdentifier("c")).size(), 0); ///TODO: prevent redeclarations of namespaces } void TestDUChain::namespacesNoCurly() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("problems().count(), 0); foreach(ProblemPointer p, top->problems()) { qDebug() << p->description() << p->explanation() << p->finalLocation(); } QCOMPARE(top->childContexts().size(), 2); QCOMPARE(top->childContexts().at(0)->localScopeIdentifier().toString(), QString("asdf")); QCOMPARE(top->childContexts().at(1)->localScopeIdentifier().toString(), QString("ns1")); QCOMPARE(top->localDeclarations().size(), 2); QCOMPARE(top->localDeclarations().at(0)->kind(), Declaration::Namespace); QCOMPARE(top->localDeclarations().at(1)->kind(), Declaration::Namespace); } void TestDUChain::namespacesBaseType() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("problems().count(), 0); foreach(ProblemPointer p, top->problems()) { qDebug() << p->description() << p->explanation() << p->finalLocation(); } QCOMPARE(top->childContexts().size(), 1); QCOMPARE(top->childContexts().at(0)->localScopeIdentifier().toString(), QString("string")); QCOMPARE(top->localDeclarations().size(), 1); QCOMPARE(top->localDeclarations().at(0)->kind(), Declaration::Namespace); } void TestDUChain::useNamespace() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("localDeclarations().count(), 5); Declaration* dec = top->localDeclarations().at(2); QCOMPARE(dec->qualifiedIdentifier().toString(), QString("ns2")); QVERIFY(dynamic_cast(dec)); dec = top->localDeclarations().at(3); QCOMPARE(dec->qualifiedIdentifier().toString(), QString("ns5")); QVERIFY(dynamic_cast(dec)); dec = top->localDeclarations().at(4); QCOMPARE(dec->qualifiedIdentifier().toString(), QString("ns6")); QVERIFY(dynamic_cast(dec)); ///TODO: find out why this is explictly required QVERIFY(!dynamic_cast(dec)->importIdentifier().explicitlyGlobal()); } void TestDUChain::useBaseTypeNamespace() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("localDeclarations().count(), 4); Declaration* dec = top->localDeclarations().at(2); QCOMPARE(dec->qualifiedIdentifier().toString(), QString("string")); QVERIFY(dynamic_cast(dec)); dec = top->localDeclarations().at(3); QCOMPARE(dec->qualifiedIdentifier().toString(), QString("iterable")); QVERIFY(dynamic_cast(dec)); } void TestDUChain::useNamespaceBaseTypeAlias() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("problems().count(), 2); } void TestDUChain::namespaceStaticVar() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("problems().isEmpty()); Declaration* fooDec = top->findDeclarations(QualifiedIdentifier(QStringLiteral("ns::c::foo"))).first(); QVERIFY(fooDec); QVERIFY(!fooDec->uses().isEmpty()); QVERIFY(!fooDec->uses().begin()->isEmpty()); QCOMPARE(fooDec->uses().begin()->begin()->start.line, 5); } void TestDUChain::namespacedCatch() { // see also: https://bugs.kde.org/show_bug.cgi?id=281451 // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("problems().isEmpty()); Declaration* eDec = top->findDeclarations(QualifiedIdentifier(QStringLiteral("ns::e"))).first(); QVERIFY(eDec); QVERIFY(!eDec->uses().isEmpty()); QVERIFY(!eDec->uses().begin()->isEmpty()); QCOMPARE(eDec->uses().begin()->begin()->start.line, 6); } struct TestUse { TestUse(const QString& _id, Declaration::Kind _kind, int _uses) : id(_id), kind(_kind), uses(_uses) {} TestUse() {} QualifiedIdentifier id; Declaration::Kind kind; int uses; }; Q_DECLARE_METATYPE ( TestUse ) Q_DECLARE_METATYPE ( QList ) void TestDUChain::errorRecovery_data() { QTest::addColumn("code"); QTest::addColumn< QList >("usesMap"); QTest::newRow("conditional") << QStringLiteral("() << TestUse(QStringLiteral("a"), Declaration::Instance, 1)); QTest::newRow("namespace") << QStringLiteral("() << TestUse(QStringLiteral("foo"), Declaration::Namespace, 1) << TestUse(QStringLiteral("y"), Declaration::Namespace, 0) << TestUse(QStringLiteral("foo::a"), Declaration::Instance, 1)); QTest::newRow("class") << QStringLiteral("() << TestUse(QStringLiteral("foo"), Declaration::Type, 0) << TestUse(QStringLiteral("foo::bar"), Declaration::Instance, 1) << TestUse(QStringLiteral("foo::func"), Declaration::Type, 1) ); } void TestDUChain::errorRecovery() { QFETCH(QString, code); QFETCH(QList, usesMap); TopDUContext* top = parse(code.toLocal8Bit(), DumpAll); QVERIFY(top); DUChainReleaser releaseTop(top); DUChainWriteLocker lock; foreach ( const TestUse& use, usesMap ) { QList< Declaration* > decs = top->findDeclarations(use.id); QCOMPARE(decs.count(), 1); Declaration* dec = decs.first(); QCOMPARE(dec->kind(), use.kind); if (use.uses) { QCOMPARE(dec->uses().count(), 1); QCOMPARE(dec->uses().begin()->count(), use.uses); } } } void TestDUChain::varStatic() { //bug: https://bugs.kde.org/244076 // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("problems().empty()); // we cannot support anything though :( } void TestDUChain::staticNowdoc() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("problems().empty()); QCOMPARE(top->childContexts().first()->localDeclarations().count(), 2); QCOMPARE(top->childContexts().first()->localDeclarations().first()->type()->dataType(), static_cast(IntegralType::TypeString)); QCOMPARE(top->childContexts().first()->localDeclarations().last()->type()->dataType(), static_cast(IntegralType::TypeString)); } void TestDUChain::curlyVarAfterObj() { // bug: https://bugs.kde.org/show_bug.cgi?id=241645 // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("{$a->bar}();\n" "$a->{$a->asdf};\n" , DumpNone); QVERIFY(top); DUChainReleaser releaseTop(top); DUChainWriteLocker lock; QVERIFY(top->problems().empty()); } void TestDUChain::embeddedHTML_data() { QTest::addColumn("code"); QTest::newRow("if") << QStringLiteral("\n"); QTest::newRow("elseif") << QStringLiteral("\n\n"); QTest::newRow("foreach") << QStringLiteral("\n\n"); QTest::newRow("switch") << QStringLiteral("\n\n"); QTest::newRow("for") << QStringLiteral("\n\n"); QTest::newRow("while") << QStringLiteral("\n\n"); QTest::newRow("else") << QStringLiteral(""); } void TestDUChain::embeddedHTML() { QFETCH(QString, code); TopDUContext* top = parse(code.toLocal8Bit(), DumpNone); QVERIFY(top); DUChainReleaser releaseTop(top); DUChainWriteLocker lock; QVERIFY(top->problems().empty()); } void TestDUChain::cases() { // testcase for bug https://bugs.kde.org/show_bug.cgi?id=245832 TopDUContext* top = parse("problems().empty()); } void TestDUChain::closureParser() { // testcase for the parser after closures where introduced, // to make sure nothing brakes and all parser conflicts are resolved TopDUContext* top = parse("problems().empty()); } void TestDUChain::closures() { TopDUContext* top = parse("problems().isEmpty()); QCOMPARE(top->localDeclarations().count(), 2); Declaration* l = top->localDeclarations().first(); QCOMPARE(l->identifier().toString(), QString("l")); Declaration* closure = top->localDeclarations().last(); QVERIFY(closure->identifier().isEmpty()); FunctionType::Ptr funcType = closure->type(); QVERIFY(funcType); QCOMPARE(funcType->arguments().count(), 2); QVERIFY(funcType->arguments().at(0).cast()); QCOMPARE(funcType->arguments().at(0).cast()->dataType(), static_cast(IntegralType::TypeMixed)); QVERIFY(funcType->arguments().at(1).cast()); QCOMPARE(funcType->arguments().at(1).cast()->qualifiedIdentifier().toString(), QString("stdclass")); QVERIFY(funcType->returnType().cast()); QCOMPARE(funcType->returnType().cast()->dataType(), static_cast(IntegralType::TypeInt)); QVERIFY(l->abstractType()->equals(closure->abstractType().constData())); } void TestDUChain::closureEmptyUse() { // test case for: https://bugs.kde.org/show_bug.cgi?id=267105 // don't crash but report parse error TopDUContext* top = parse(" 2; };\n", DumpNone); QVERIFY(top); DUChainReleaser releaseTop(top); DUChainWriteLocker lock; QCOMPARE(top->problems().size(), 1); } void TestDUChain::iifeParser() { // testcase for bug https://bugs.kde.org/show_bug.cgi?id=370515 TopDUContext* top = parse("problems().empty()); } void TestDUChain::iife() { TopDUContext* top = parse("problems().isEmpty()); QCOMPARE(top->localDeclarations().count(), 2); Declaration* l = top->localDeclarations().first(); QCOMPARE(l->identifier().toString(), QString("l")); Declaration* iife = top->localDeclarations().last(); QVERIFY(iife->identifier().isEmpty()); } void TestDUChain::gotoTest() { TopDUContext* top = parse("problems().isEmpty()); ///TODO: create declaration for destination label ///TODO: create use for goto label ///TODO: report error when trying to jump into loop or switch statement } void TestDUChain::ternary() { TopDUContext* top = parse("problems().isEmpty()); } void TestDUChain::bug296709() { // see also: https://bugs.kde.org/show_bug.cgi?id=296709 // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse( "problems().isEmpty()); QList< Declaration* > decs = top->findLocalDeclarations(Identifier(QStringLiteral("a"))); QCOMPARE(decs.size(), 1); QCOMPARE(decs.at(0)->range(), RangeInRevision(1, 19, 1, 21)); QCOMPARE(decs.at(0)->uses().count(), 1); QCOMPARE(decs.at(0)->uses().begin()->count(), 1); QCOMPARE(decs.at(0)->uses().begin()->first(), RangeInRevision(2, 2, 2, 4)); } void TestDUChain::declareFinalMethod() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("parentContext()); QCOMPARE(top->childContexts().count(), 1); DUContext* contextClassA = top->childContexts().first(); Declaration* dec = contextClassA->localDeclarations().at(0); ClassFunctionDeclaration* funDec = dynamic_cast(dec); QVERIFY(funDec); QCOMPARE(funDec->qualifiedIdentifier(), QualifiedIdentifier("a::foo")); QVERIFY(funDec->isFinal()); } void Php::TestDUChain::testTodoExtractor() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("languageController()->completionSettings()->todoMarkerWords().contains("TODO")); QVERIFY(KDevelop::ICore::self()->languageController()->completionSettings()->todoMarkerWords().contains("FIXME")); TopDUContext* top = parse(method, DumpAll); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); QVERIFY(top); QCOMPARE(top->problems().size(), 2); QCOMPARE(top->problems().at(0)->description(), QString("TODO: bla")); QCOMPARE(top->problems().at(0)->range(), RangeInRevision(1, 3, 1, 12)); QCOMPARE(top->problems().at(1)->description(), QString("FIXME blub")); QCOMPARE(top->problems().at(1)->range(), RangeInRevision(2, 4, 2, 14)); } void TestDUChain::useThisAsArray() { QByteArray method("values[$offset]; }\n" " function offsetSet($offset, $value) { $this->values[$offset] = $value; }\n" " function offsetExists($offset) { return array_key_exists($offset, $this->values); }\n" " function offsetUnset($offset) { unset($this->values[$offset]); }\n" " function setTest() { $this['test'] = 'test'; } \n" " }\n"); TopDUContext* top = parse(method); QVERIFY(top); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(top->importedParentContexts().count(), 1); QVERIFY(DUChain::self()->chainForDocument(internalFunctionFile())); QCOMPARE(DUChain::self()->chainForDocument(internalFunctionFile()), top->importedParentContexts().first().context(top)); QVERIFY(top->problems().isEmpty()); } void TestDUChain::wrongUseOfThisAsArray() { // missing functions from \ArrayAccess and not declared abstract QByteArray method("problems().size(),1); } void TestDUChain::staticFunctionClassPhp54() { QByteArray method("problems().isEmpty()); QCOMPARE(top->localDeclarations().count(),1); Declaration* dec = top->localDeclarations().at(0); ClassDeclaration* classDec = dynamic_cast(dec); QCOMPARE(classDec->uses().count(),1); } void TestDUChain::functionArgumentUnpacking_data() { QTest::addColumn("code"); QTest::newRow("unpackVariable") << QStringLiteral("problems().isEmpty()); } void TestDUChain::illegalExpression_data() { QTest::addColumn("code"); QTest::newRow("equality expression") << QStringLiteral(" 2 > 1;\n"); QTest::newRow("double print expression") << QStringLiteral("foo;\n"); + QTest::newRow("instanceof with class constant") << QStringLiteral("foo();\n"); + QTest::newRow("instanceof with static method") << QStringLiteral("("code"); QTest::newRow("simple print expression") << QStringLiteral("problems().isEmpty()); } void TestDUChain::generatorAssignment() { //Note: in practice, Generator is defined by php, but this class is not loaded in this test, so define it ourselves // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("parentContext()); QCOMPARE(top->childContexts().count(), 3); QCOMPARE(top->localDeclarations().count(), 2); Declaration* dec = top->localDeclarations().at(1); QCOMPARE(dec->qualifiedIdentifier(), QualifiedIdentifier("foo")); FunctionType::Ptr functionType = dec->type(); QVERIFY(functionType); StructureType::Ptr retType = StructureType::Ptr::dynamicCast(functionType->returnType()); QVERIFY(retType); QCOMPARE(retType->qualifiedIdentifier(), QualifiedIdentifier("generator")); dec = top->childContexts().at(2)->localDeclarations().at(0); QCOMPARE(dec->identifier(), Identifier("a")); IntegralType::Ptr type = dec->type(); QVERIFY(type); QVERIFY(type->dataType() == IntegralType::TypeMixed); } void TestDUChain::generatorClosure() { //Note: in practice, Generator is defined by php, but this class is not loaded in this test, so define it ourselves // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("parentContext()); QCOMPARE(top->childContexts().count(), 3); QCOMPARE(top->localDeclarations().count(), 2); Declaration* dec = top->localDeclarations().at(1); QCOMPARE(dec->qualifiedIdentifier(), QualifiedIdentifier("foo")); FunctionType::Ptr functionType = dec->type(); QVERIFY(functionType); IntegralType::Ptr retType = IntegralType::Ptr::dynamicCast(functionType->returnType()); QVERIFY(retType); QVERIFY(retType->dataType() == IntegralType::TypeVoid); dec = top->childContexts().at(2)->localDeclarations().at(0); QCOMPARE(dec->identifier(), Identifier("a")); Declaration* closure = top->childContexts().at(2)->localDeclarations().at(1); QVERIFY(closure->identifier().isEmpty()); FunctionType::Ptr funcType = closure->type(); QVERIFY(funcType); QVERIFY(funcType->returnType().cast()); QCOMPARE(funcType->returnType().cast()->dataType(), static_cast(IntegralType::TypeVoid)); QVERIFY(dec->abstractType()->equals(closure->abstractType().constData())); dec = top->childContexts().at(2)->childContexts().at(1)->localDeclarations().at(0); QCOMPARE(dec->identifier(), Identifier("b")); closure = top->childContexts().at(2)->childContexts().at(1)->localDeclarations().at(1); QVERIFY(closure->identifier().isEmpty()); funcType = closure->type(); QVERIFY(funcType); StructureType::Ptr generatorType = StructureType::Ptr::dynamicCast(funcType->returnType()); QVERIFY(generatorType); QCOMPARE(generatorType->qualifiedIdentifier(), QualifiedIdentifier("generator")); QVERIFY(dec->abstractType()->equals(closure->abstractType().constData())); } diff --git a/duchain/tests/uses.cpp b/duchain/tests/uses.cpp index fefc350..0e1695c 100644 --- a/duchain/tests/uses.cpp +++ b/duchain/tests/uses.cpp @@ -1,1357 +1,1589 @@ /* This file is part of KDevelop Copyright 2008 Niko Sams 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 "uses.h" #include #include #include +#include #include "../declarations/classdeclaration.h" #include "../declarations/variabledeclaration.h" #include "../declarations/traitmethodaliasdeclaration.h" #include "../declarations/traitmemberaliasdeclaration.h" +#include "../types/structuretype.h" + using namespace KDevelop; QTEST_MAIN(Php::TestUses) namespace Php { void compareUses(Declaration* dec, QList ranges) { qDebug() << "comparing uses for" << dec->toString(); QCOMPARE(dec->uses().keys().count(), 1); QCOMPARE(dec->uses().values().count(), 1); QCOMPARE(dec->uses().values().first().count(), ranges.count()); for (int i = 0; i < ranges.count(); ++i) { qDebug() << dec->uses().values().first().at(i) << ranges.at(i); QCOMPARE(dec->uses().values().first().at(i), ranges.at(i)); } } void compareUses(Declaration* dec, RangeInRevision range) { QList r; r << range; compareUses(dec, r); } TestUses::TestUses() { } void TestUses::newObject() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().first(), RangeInRevision(0, 25, 0, 28)); QCOMPARE(top->localDeclarations().first()->uses().keys().first(), IndexedString(QUrl("file:///internal/usestest/newObject.php"))); } void TestUses::functionCall() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().first(); compareUses(fun, RangeInRevision(0, 21, 0, 24)); QCOMPARE(fun->uses().keys().first(), IndexedString(QUrl("file:///internal/usestest/functionCall.php"))); } void TestUses::memberFunctionCall() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("foo(); "); TopDUContext* top = parse(method, DumpNone, QUrl(QStringLiteral("file:///internal/usestest/memberFunctionCall.php"))); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); Declaration* fun = top->childContexts().first()->localDeclarations().first(); compareUses(fun, RangeInRevision(0, 51, 0, 54)); QCOMPARE(fun->uses().keys().first(), IndexedString(QUrl("file:///internal/usestest/memberFunctionCall.php"))); } void TestUses::unsureMemberFunctionCall() { //First try with a single unsure structure type { // 0 1 2 3 4 5 6 7 8 // 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("foo();"); TopDUContext* top = parse(method, DumpNone); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); Declaration* fun = top->childContexts().first()->localDeclarations().first(); compareUses(fun, RangeInRevision(0, 85, 0, 88)); } //Now try with two unsure structure types { // 0 1 2 3 4 5 6 7 8 // 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("foo();"); TopDUContext* top = parse(method, DumpNone); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); Declaration* fun = top->childContexts().first()->localDeclarations().first(); QCOMPARE(fun->uses().keys().count(), 0); } } void TestUses::memberVariable() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("foo; "); TopDUContext* top = parse(method, DumpNone, QUrl(QStringLiteral("file:///internal/usestest/memberVariable.php"))); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); Declaration* var = top->childContexts().first()->localDeclarations().first(); compareUses(var, RangeInRevision(0, 46, 0, 49)); QCOMPARE(var->uses().keys().first(), IndexedString(QUrl("file:///internal/usestest/memberVariable.php"))); } void TestUses::implicitMemberVariable() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("y = 1; $x->y = 2; class A {}"); TopDUContext* top = parse(method, DumpNone); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); Declaration* var = top->childContexts().first()->localDeclarations().first(); QList ranges; ranges << RangeInRevision(0, 21, 0, 22) << RangeInRevision(0, 32, 0, 33); compareUses(var, ranges); QVERIFY(var->range() == RangeInRevision(0, 21, 0, 22)); } void TestUses::variable() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("foo; foo($a); "); TopDUContext* top = parse(method, DumpAll); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); QList ranges; ranges << RangeInRevision(1, 42 - 3, 1, 44 - 3) << RangeInRevision(1, 46 - 3, 1, 48 - 3) << RangeInRevision(1, 59 - 3, 1, 61 - 3); compareUses(top->localDeclarations().at(1), ranges); } void TestUses::varInString() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method(" ranges; ranges << RangeInRevision(0, 13, 0, 15) << RangeInRevision(0, 17, 0, 19); compareUses(top->localDeclarations().at(0), ranges); } void TestUses::variableInNamespace() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("foo; foo($a); };"); TopDUContext* top = parse(method, DumpAll); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); QList ranges; ranges << RangeInRevision(1, 55, 1, 57) << RangeInRevision(1, 59, 1, 61) << RangeInRevision(1, 72, 1, 74); compareUses(top->localDeclarations().at(2), ranges); } void TestUses::globalVariableInNamespace() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("foo; foo($a); };"); TopDUContext* top = parse(method, DumpAll); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); QList ranges; ranges << RangeInRevision(1, 55, 1, 57) << RangeInRevision(1, 59, 1, 61) << RangeInRevision(1, 72, 1, 74); compareUses(top->localDeclarations().at(1), ranges); } void TestUses::variableInOtherNamespace() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("foo; foo($a); };"); TopDUContext* top = parse(method, DumpAll); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); QList ranges; ranges << RangeInRevision(1, 73, 1, 75) << RangeInRevision(1, 77, 1, 79) << RangeInRevision(1, 90, 1, 92); compareUses(top->localDeclarations().at(2), ranges); } void TestUses::memberVarInString() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789 01234567890123 4567890123456789 QByteArray method("v; \"$a->v {$a->v}\"; "); TopDUContext* top = parse(method, DumpAll); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); QList ranges; ranges << RangeInRevision(0, 43, 0, 45) << RangeInRevision(0, 51, 0, 53) << RangeInRevision(0, 58, 0, 60); compareUses(top->localDeclarations().at(1), ranges); ranges.clear(); ranges << RangeInRevision(0, 47, 0, 48) << RangeInRevision(0, 55, 0, 56) << RangeInRevision(0, 62, 0, 63); compareUses(top->childContexts().first()->localDeclarations().first(), ranges); } void TestUses::memberFunctionInString() { // 0 1 2 3 4 5 6 7 // 012345678901234567890123456789012345678901234567 890123456789 01234567890123456789 QByteArray method("foo()}\"; "); TopDUContext* top = parse(method, DumpAll); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); //$a compareUses(top->localDeclarations().at(1), RangeInRevision(0, 50, 0, 52)); //foo compareUses(top->childContexts().first()->localDeclarations().first(), RangeInRevision(0, 54, 0, 57)); } void TestUses::variableTypeChange() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method(" ranges; ranges << RangeInRevision(0, 25, 0, 27); ranges << RangeInRevision(0, 29, 0, 31); ranges << RangeInRevision(0, 37, 0, 39); ranges << RangeInRevision(0, 41, 0, 43); ranges << RangeInRevision(0, 51, 0, 53); compareUses(top->localDeclarations().at(1), ranges); } void TestUses::variableTypeChangeInFunction() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method(" ranges; ranges << RangeInRevision(0, 28, 0, 30); ranges << RangeInRevision(0, 32, 0, 34); ranges << RangeInRevision(0, 38, 0, 40); ranges << RangeInRevision(0, 42, 0, 44); compareUses(top->childContexts().at(1)->localDeclarations().at(0), ranges); } void TestUses::classExtends() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().at(0), RangeInRevision(0, 31, 0, 32)); } void TestUses::classImplements() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().at(0), RangeInRevision(0, 38, 0, 39)); } void TestUses::classImplementsMultiple() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().at(0), RangeInRevision(0, 54, 0, 55)); compareUses(top->localDeclarations().at(1), RangeInRevision(0, 57, 0, 58)); } void TestUses::interfaceExtends() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().at(0), RangeInRevision(0, 39, 0, 40)); } void TestUses::interfaceExtendsMultiple() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().at(0), RangeInRevision(0, 55, 0, 56)); compareUses(top->localDeclarations().at(1), RangeInRevision(0, 58, 0, 59)); } void TestUses::staticMemberFunctionCall() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().first(), RangeInRevision(0, 47, 0, 48)); compareUses(top->childContexts().first()->localDeclarations().first(), RangeInRevision(0, 50, 0, 53)); } void TestUses::staticMemberVariable() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().first(), RangeInRevision(0, 43, 0, 44)); compareUses(top->childContexts().first()->localDeclarations().first(), RangeInRevision(0, 46, 0, 50)); compareUses(top->localDeclarations().at(1), RangeInRevision(0, 52, 0, 56)); } void TestUses::constant() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().first(), RangeInRevision(0, 28, 0, 29)); } void TestUses::classConstant() { { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().first(), RangeInRevision(0, 39, 0, 40)); compareUses(top->childContexts().first()->localDeclarations().first(), RangeInRevision(0, 42, 0, 45)); } { // bug: https://bugs.kde.org/show_bug.cgi?id=241597 // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("childContexts().first()->localDeclarations().first(); QVERIFY(dec->abstractType()->modifiers() & AbstractType::ConstModifier); QCOMPARE(dec->qualifiedIdentifier().toString(), QString("a::FOO")); compareUses(dec, QList() << RangeInRevision(1, 43, 1, 46) << RangeInRevision(2, 3, 2, 6) << RangeInRevision(3, 3, 3, 6)); } } void TestUses::classParent() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method(" range; range << RangeInRevision(0, 47, 0, 48); range << RangeInRevision(0, 66, 0, 72); compareUses(top->localDeclarations().first(), range); compareUses(top->childContexts().first()->localDeclarations().first(), RangeInRevision(0, 74, 0, 75)); } void TestUses::classSelf() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().first(), RangeInRevision(0, 28, 0, 32)); compareUses(top->childContexts().first()->localDeclarations().first(), RangeInRevision(0, 34, 0, 35)); } void TestUses::classThis() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("x(); } } "); TopDUContext* top = parse(method, DumpAll); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); compareUses(top->localDeclarations().first(), RangeInRevision(0, 28, 0, 33)); compareUses(top->childContexts().first()->localDeclarations().first(), RangeInRevision(0, 35, 0, 36)); } void TestUses::objectWithClassName() { // 0 1 2 3 4 5 6 7 8 // 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("k; Aa::j; Aa::$i;"); TopDUContext* top = parse(method, DumpAll); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); QList ranges; ranges << RangeInRevision(0, 66, 0, 66 + 2); ranges << RangeInRevision(0, 78, 0, 78 + 2); ranges << RangeInRevision(0, 85, 0, 85 + 2); compareUses(top->localDeclarations().first(), ranges); compareUses(top->localDeclarations().at(1), RangeInRevision(0, 70, 0, 70 + 3)); } void TestUses::classAndConstWithSameName() { // 0 1 2 3 4 5 6 7 8 // 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().first(), RangeInRevision(0, 38, 0, 39)); compareUses(top->localDeclarations().at(1), RangeInRevision(0, 31, 0, 32)); compareUses(top->localDeclarations().at(2), RangeInRevision(0, 76, 0, 77)); compareUses(top->localDeclarations().at(3), RangeInRevision(0, 73, 0, 74)); } void TestUses::classAndFunctionWithSameName() { // 0 1 2 3 4 5 6 7 8 // 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().first(), RangeInRevision(0, 35, 0, 36)); compareUses(top->localDeclarations().at(1), RangeInRevision(0, 38, 0, 39)); } void TestUses::constAndVariableWithSameName() { // 0 1 2 3 4 5 6 7 8 // 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().first(), RangeInRevision(0, 30, 0, 32)); compareUses(top->localDeclarations().at(1), RangeInRevision(0, 27, 0, 28)); } void TestUses::functionAndClassWithSameName() { // 0 1 2 3 4 5 6 7 8 // 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("childContexts().first()->localDeclarations().first(); QCOMPARE(fnAsdf->uses().keys().count(), 0); compareUses(top->localDeclarations().at(1), RangeInRevision(0, 70, 0, 74)); } void TestUses::constantInClassMember() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("findDeclarations(Identifier(QStringLiteral("TEST"))).first(); QList uses; uses << RangeInRevision(0, 41, 0, 45); uses << RangeInRevision(0, 63, 0, 67); uses << RangeInRevision(0, 73, 0, 77); compareUses(constant, uses); } void TestUses::useInAsignment() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().first(); compareUses(d, RangeInRevision(0, 16, 0, 18)); } void TestUses::foreachArray() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("$i) { var_dump($k, $i); } "); TopDUContext* top = parse(method, DumpAll); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); // $a, $k, $i QCOMPARE(top->localDeclarations().size(), 3); // $a Declaration *d = top->localDeclarations().at(0); compareUses(d, RangeInRevision(0, 26, 0, 28)); // $k d = top->localDeclarations().at(1); compareUses(d, RangeInRevision(0, 51, 0, 53)); // $i d = top->localDeclarations().at(2); compareUses(d, RangeInRevision(0, 55, 0, 57)); } void TestUses::assignmentToMemberArray() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("y[$a] = true; } }"); TopDUContext* top = parse(method, DumpAll); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); // class x Declaration *x = top->localDeclarations().first(); QVERIFY(x); // $this compareUses(x, RangeInRevision(0, 50, 0, 55)); // var $y Declaration *y = x->logicalInternalContext(top)->findDeclarations(Identifier(QStringLiteral("y"))).first(); QVERIFY(y); // $this->y compareUses(y, RangeInRevision(0, 57, 0, 58)); // function z Declaration *z = x->logicalInternalContext(top)->findDeclarations(Identifier(QStringLiteral("z"))).first(); QVERIFY(z); // $a Declaration *a = z->logicalInternalContext(top)->findDeclarations(Identifier(QStringLiteral("a"))).first(); QVERIFY(a); compareUses(a, QList() // $b = $a << RangeInRevision(0, 46, 0, 48) // $this->y[$a] << RangeInRevision(0, 59, 0, 61) ); } void TestUses::staticArrayIndex() { // bug: https://bugs.kde.org/show_bug.cgi?id=241160 // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("childContexts().first()->localDeclarations().first(); QCOMPARE(a->identifier().toString(), QString("a")); compareUses(a, RangeInRevision(0, 68, 0, 70)); Declaration* i = top->childContexts().first()->childContexts().first()->localDeclarations().first(); QCOMPARE(i->identifier().toString(), QString("i")); compareUses(i, RangeInRevision(0, 71, 0, 73)); } void TestUses::functionParamNewDeclaration() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("childContexts().first()->localDeclarations().first(); QList ranges; ranges << RangeInRevision(0, 22, 0, 24); ranges << RangeInRevision(0, 26, 0, 28); compareUses(d, ranges); } void TestUses::catchClass() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("findDeclarations(QualifiedIdentifier(QStringLiteral("exception"))).first(); compareUses(d, RangeInRevision(0, 18, 0, 27)); } void TestUses::variableRedeclaration() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method(" decs = top->findDeclarations(QualifiedIdentifier(QStringLiteral("s"))); QCOMPARE(decs.size(), 1); Declaration *d = decs.first(); compareUses(d, QList() << RangeInRevision(0, 13, 0, 15) << RangeInRevision(0, 18, 0, 20) << RangeInRevision(0, 23, 0, 25) ); } void TestUses::caseInsensitiveFunction() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method(" decs = top->findLocalDeclarations(Identifier(QStringLiteral("foobar"))); QCOMPARE(decs.size(), 1); Declaration *d = decs.first(); compareUses(d, QList() << RangeInRevision(1, 0, 1, 6) << RangeInRevision(2, 0, 2, 6) << RangeInRevision(3, 0, 3, 6) ); } void TestUses::caseInsensitiveMethod() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("fOoBar();\n$a->FOOBAR();\n$a->foobar();\n" "asdf::barfoo();\nasdf::bArFoo();\nasdf::BARFOO();\n"); TopDUContext* top = parse(method, DumpNone); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); { QList decs = top->childContexts().first()->findDeclarations(QualifiedIdentifier(QStringLiteral("foobar"))); QCOMPARE(decs.size(), 1); Declaration *d = decs.first(); compareUses(d, QList() << RangeInRevision(1, 4, 1, 10) << RangeInRevision(2, 4, 2, 10) << RangeInRevision(3, 4, 3, 10) ); } { QList decs = top->childContexts().first()->findDeclarations(QualifiedIdentifier(QStringLiteral("barfoo"))); QCOMPARE(decs.size(), 1); Declaration *d = decs.first(); compareUses(d, QList() << RangeInRevision(4, 6, 4, 12) << RangeInRevision(5, 6, 5, 12) << RangeInRevision(6, 6, 6, 12) ); } } void TestUses::caseInsensitiveClass() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method(" decs = top->findLocalDeclarations(Identifier(QStringLiteral("asdf"))); QCOMPARE(decs.size(), 1); Declaration *d = decs.first(); compareUses(d, QList() << RangeInRevision(1, 4, 1, 8) << RangeInRevision(2, 4, 2, 8) << RangeInRevision(3, 4, 3, 8) ); } void TestUses::functionUseBeforeDeclaration() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method(" decs = top->localDeclarations(); QCOMPARE(decs.size(), 1); QCOMPARE(decs.first()->range(), RangeInRevision(0, 20, 0, 24)); compareUses(decs.first(), RangeInRevision(0, 3, 0, 7)); } void TestUses::propertyAndMethodWithSameName() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("name1(); $a->name1;\n" "$a->name2; $a->name2();"); TopDUContext* top = parse(method, DumpNone); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); QVector decs = top->childContexts().first()->localDeclarations(); QCOMPARE(decs.size(), 4); // method name1 QVERIFY(decs[0]->identifier().nameEquals(Identifier("name1"))); QVERIFY(decs[0]->isFunctionDeclaration()); compareUses(decs[0], RangeInRevision(2, 4, 2, 9)); // property name1 QVERIFY(decs[1]->identifier().nameEquals(Identifier("name1"))); QVERIFY(!decs[1]->isFunctionDeclaration()); compareUses(decs[1], RangeInRevision(2, 17, 2, 22)); // property name2 QVERIFY(decs[2]->identifier().nameEquals(Identifier("name2"))); QVERIFY(!decs[2]->isFunctionDeclaration()); compareUses(decs[2], RangeInRevision(3, 4, 3, 9)); // method name2 QVERIFY(decs[3]->identifier().nameEquals(Identifier("name2"))); QVERIFY(decs[3]->isFunctionDeclaration()); compareUses(decs[3], RangeInRevision(3, 15, 3, 20)); } void TestUses::nestedMethodCalls() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("a($b->b());"); TopDUContext* top = parse(method, DumpNone); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); QVector topDecs = top->localDeclarations(); QCOMPARE(topDecs.size(), 4); // class a QVERIFY(topDecs[0]->identifier().nameEquals(Identifier("a"))); QVERIFY(dynamic_cast(topDecs[0])); compareUses(topDecs[0], RangeInRevision(3, 9, 3, 10)); // class b QVERIFY(topDecs[1]->identifier().nameEquals(Identifier("b"))); QVERIFY(dynamic_cast(topDecs[1])); compareUses(topDecs[1], RangeInRevision(4, 9, 4, 10)); // $a QVERIFY(topDecs[2]->identifier().nameEquals(Identifier("a"))); QVERIFY(dynamic_cast(topDecs[2])); compareUses(topDecs[2], RangeInRevision(5, 0, 5, 2)); // $b QVERIFY(topDecs[3]->identifier().nameEquals(Identifier("b"))); QVERIFY(dynamic_cast(topDecs[3])); compareUses(topDecs[3], RangeInRevision(5, 6, 5, 8)); // function a Declaration* methodADec = topDecs[0]->internalContext()->localDeclarations().first(); QVERIFY(methodADec->isFunctionDeclaration()); compareUses(methodADec, RangeInRevision(5, 4, 5, 5)); // function b Declaration* methodBDec = topDecs[1]->internalContext()->localDeclarations().first(); QVERIFY(methodBDec->isFunctionDeclaration()); compareUses(methodBDec, RangeInRevision(5, 10, 5, 11)); } void TestUses::unset() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method(" decs = top->localDeclarations(); QCOMPARE(decs.size(), 1); QCOMPARE(decs.first()->range(), RangeInRevision(0, 3, 0, 5)); compareUses(decs.first(), RangeInRevision(0, 17, 0, 19)); } void TestUses::functionArguments() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("childContexts().size(), 2); QCOMPARE(top->childContexts().first()->type(), DUContext::Function); // $a Declaration *d = top->childContexts().at(0)->localDeclarations().at(0); compareUses(d, RangeInRevision(0, 27, 0, 29)); // $b d = top->childContexts().at(0)->localDeclarations().at(1); compareUses(d, RangeInRevision(0, 35, 0, 37)); } void TestUses::namespaces() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("findDeclarations(QualifiedIdentifier(QStringLiteral("foo"))).last(); QCOMPARE(dec->kind(), Declaration::Namespace); compareUses(dec, QList() << RangeInRevision(9, 1, 9, 4) << RangeInRevision(10, 1, 10, 4) << RangeInRevision(11, 1, 11, 4) << RangeInRevision(12, 5, 12, 8) << RangeInRevision(13, 15, 13, 18) << RangeInRevision(14, 17, 14, 20) << RangeInRevision(14, 45, 14, 48)); dec = top->findDeclarations(QualifiedIdentifier(QStringLiteral("foo::bar"))).first(); QCOMPARE(dec->kind(), Declaration::Namespace); QVERIFY(dec->internalContext()); compareUses(dec, QList() << RangeInRevision(9, 5, 9, 8) << RangeInRevision(10, 5, 10, 8) << RangeInRevision(11, 5, 11, 8) << RangeInRevision(12, 9, 12, 12) << RangeInRevision(13, 19, 13, 22) << RangeInRevision(14, 21, 14, 24) << RangeInRevision(14, 49, 14, 52)); QCOMPARE(dec->internalContext()->localDeclarations().size(), 4); foreach(Declaration* d, dec->internalContext()->localDeclarations()) { qDebug() << d->toString() << d->qualifiedIdentifier(); } dec = top->findDeclarations(QualifiedIdentifier(QStringLiteral("foo::bar::MyConst"))).first(); compareUses(dec, QList() << RangeInRevision(3, 5, 3, 12) << RangeInRevision(9, 9, 9, 16)); dec = top->findDeclarations(QualifiedIdentifier(QStringLiteral("foo::bar::myclass"))).first(); QVERIFY(dynamic_cast(dec)); compareUses(dec, QList() << RangeInRevision(10, 9, 10, 16) << RangeInRevision(12, 13, 12, 20) << RangeInRevision(13, 23, 13, 30) << RangeInRevision(14, 25, 14, 32) ); dec = top->findDeclarations(QualifiedIdentifier(QStringLiteral("foo::bar::myinterface"))).first(); QVERIFY(dynamic_cast(dec)); compareUses(dec, RangeInRevision(14, 53, 14, 64) ); dec = top->findDeclarations(QualifiedIdentifier(QStringLiteral("foo::bar::myclass::ClassConst"))).first(); compareUses(dec, RangeInRevision(10, 18, 10, 28)); dec = top->findDeclarations(QualifiedIdentifier(QStringLiteral("foo::bar::myfunc"))).first(); compareUses(dec, RangeInRevision(11, 9, 11, 15)); } void TestUses::useNamespace() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("findDeclarations(QualifiedIdentifier(QStringLiteral("foo"))).first(); QCOMPARE(dec->kind(), Declaration::Namespace); compareUses(dec, RangeInRevision(5, 4, 5, 7)); dec = top->findDeclarations(QualifiedIdentifier(QStringLiteral("foo::bar"))).first(); QCOMPARE(dec->kind(), Declaration::Namespace); compareUses(dec, RangeInRevision(5, 8, 5, 11)); dec = top->findDeclarations(QualifiedIdentifier(QStringLiteral("verylong"))).first(); QCOMPARE(dec->kind(), Declaration::Namespace); compareUses(dec, RangeInRevision(5, 13, 5, 21)); dec = top->findDeclarations(QualifiedIdentifier(QStringLiteral("bar"))).first(); QCOMPARE(dec->kind(), Declaration::NamespaceAlias); compareUses(dec, QList() << RangeInRevision(7, 4, 7, 7) << RangeInRevision(7, 11, 7, 14) << RangeInRevision(7, 20, 7, 23) ); dec = top->findDeclarations(QualifiedIdentifier(QStringLiteral("short"))).first(); QCOMPARE(dec->kind(), Declaration::NamespaceAlias); compareUses(dec, QList() << RangeInRevision(8, 4, 8, 9) << RangeInRevision(8, 13, 8, 18) << RangeInRevision(8, 24, 8, 29) ); dec = top->findDeclarations(QualifiedIdentifier(QStringLiteral("baz::a"))).first(); compareUses(dec, QList() << RangeInRevision(6, 8, 6, 9) << RangeInRevision(9, 4, 9, 10)); dec = top->findDeclarations(QualifiedIdentifier(QStringLiteral("foo::bar::a"))).first(); compareUses(dec, RangeInRevision(7, 8, 7, 9)); dec = top->findDeclarations(QualifiedIdentifier(QStringLiteral("foo::bar::b"))).first(); compareUses(dec, RangeInRevision(7, 15, 7, 16)); dec = top->findDeclarations(QualifiedIdentifier(QStringLiteral("foo::bar::C"))).first(); compareUses(dec, RangeInRevision(7, 24, 7, 25)); dec = top->findDeclarations(QualifiedIdentifier(QStringLiteral("verylong::a"))).first(); compareUses(dec, RangeInRevision(8, 10, 8, 11)); dec = top->findDeclarations(QualifiedIdentifier(QStringLiteral("verylong::b"))).first(); compareUses(dec, RangeInRevision(8, 19, 8, 20)); dec = top->findDeclarations(QualifiedIdentifier(QStringLiteral("verylong::C"))).first(); compareUses(dec, RangeInRevision(8, 30, 8, 31)); } void TestUses::lateStatic() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("childContexts().first()->localDeclarations().first(), RangeInRevision(0, 39, 0, 40)); } void TestUses::closures() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("localDeclarations().count(), 4); Declaration* a = top->localDeclarations().at(0); QCOMPARE(a->identifier().toString(), QString("a")); compareUses(a, QList() << RangeInRevision(1, 23, 1, 25) << RangeInRevision(1, 36, 1, 38)); Declaration* b = top->localDeclarations().at(1); QCOMPARE(b->identifier().toString(), QString("b")); QVERIFY(b->uses().isEmpty()); } void TestUses::closureTypehints() { TopDUContext* top = parse("localDeclarations().count(), 3); Declaration* a = top->localDeclarations().at(0); QCOMPARE(a->qualifiedIdentifier(), QualifiedIdentifier("a")); compareUses(a, QList() << RangeInRevision(0, 32, 0, 33) << RangeInRevision(0, 39, 0, 40)); } void TestUses::instanceof() { // 0 1 2 3 4 5 // 012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("localDeclarations().count(), 3); Declaration* a = top->localDeclarations().at(0); QCOMPARE(a->identifier().toString(), QString("a")); compareUses(a, QList() << RangeInRevision(1, 9, 1, 10) << RangeInRevision(2, 19, 2, 20)); +} + +void TestUses::instanceofClassProperty() +{ + // 0 1 2 3 4 5 + // 012345678901234567890123456789012345678901234567890123456789 + TopDUContext* top = parse("foo->bar->foo;\n", DumpNone); + + QVERIFY(top); + DUChainReleaser releaseTop(top); + DUChainWriteLocker lock; + + QVERIFY(top->problems().isEmpty()); + + QVERIFY(!top->parentContext()); + QCOMPARE(top->childContexts().count(), 2); + QCOMPARE(top->localDeclarations().count(), 3); + + Declaration* dec = top->localDeclarations().at(0); + QCOMPARE(dec->identifier(), Identifier("a")); + compareUses(dec, QList() + << RangeInRevision(2, 9, 2, 10)); + + dec = top->localDeclarations().at(2); + QCOMPARE(dec->identifier(), Identifier("a")); + StructureType::Ptr classType = dec->type(); + QVERIFY(classType); + QCOMPARE(classType->qualifiedIdentifier(), QualifiedIdentifier("a")); + QVERIFY(classType->equals(dec->abstractType().data())); + compareUses(dec, QList() + << RangeInRevision(2, 14, 2, 16) + << RangeInRevision(2, 28, 2, 30)); + + dec = top->childContexts().at(0)->localDeclarations().at(0); + QVERIFY(dec); + QCOMPARE(dec->identifier(), Identifier("foo")); + compareUses(dec, QList() + << RangeInRevision(2, 32, 2, 35) + << RangeInRevision(2, 42, 2, 45)); + + dec = top->childContexts().at(1)->localDeclarations().at(0); + QVERIFY(dec); + QCOMPARE(dec->identifier(), Identifier("bar")); + compareUses(dec, QList() + << RangeInRevision(2, 37, 2, 40)); +} + +void TestUses::instanceofStaticProperty() +{ + // 0 1 2 3 4 5 + // 012345678901234567890123456789012345678901234567890123456789 + TopDUContext* top = parse("problems().isEmpty()); + + QVERIFY(!top->parentContext()); + QCOMPARE(top->childContexts().count(), 2); + QCOMPARE(top->localDeclarations().count(), 3); + + Declaration* dec = top->localDeclarations().at(0); + QCOMPARE(dec->identifier(), Identifier("a")); + compareUses(dec, QList() + << RangeInRevision(2, 9, 2, 10) + << RangeInRevision(2, 28, 2, 29)); + + dec = top->localDeclarations().at(2); + QCOMPARE(dec->identifier(), Identifier("a")); + StructureType::Ptr classType = dec->type(); + QVERIFY(classType); + QCOMPARE(classType->qualifiedIdentifier(), QualifiedIdentifier("a")); + QVERIFY(classType->equals(dec->abstractType().data())); + compareUses(dec, QList() + << RangeInRevision(2, 14, 2, 16)); + + dec = top->childContexts().at(0)->localDeclarations().at(0); + QVERIFY(dec); + QCOMPARE(dec->identifier(), Identifier("foo")); + compareUses(dec, QList() + << RangeInRevision(2, 31, 2, 35) + << RangeInRevision(2, 43, 2, 47)); + + dec = top->childContexts().at(1)->localDeclarations().at(0); + QVERIFY(dec); + QCOMPARE(dec->identifier(), Identifier("bar")); + compareUses(dec, QList() + << RangeInRevision(2, 37, 2, 41)); +} + +void TestUses::instanceofMixedProperty() +{ + // 0 1 2 3 4 5 + // 012345678901234567890123456789012345678901234567890123456789 + TopDUContext* top = parse("bar::$foo;\n", DumpNone); + + QVERIFY(top); + DUChainReleaser releaseTop(top); + DUChainWriteLocker lock; + + QVERIFY(top->problems().isEmpty()); + + QVERIFY(!top->parentContext()); + QCOMPARE(top->childContexts().count(), 2); + QCOMPARE(top->localDeclarations().count(), 3); + + Declaration* dec = top->localDeclarations().at(0); + QCOMPARE(dec->identifier(), Identifier("a")); + compareUses(dec, QList() + << RangeInRevision(2, 9, 2, 10) + << RangeInRevision(2, 28, 2, 29)); + + dec = top->localDeclarations().at(2); + QCOMPARE(dec->identifier(), Identifier("a")); + StructureType::Ptr classType = dec->type(); + QVERIFY(classType); + QCOMPARE(classType->qualifiedIdentifier(), QualifiedIdentifier("a")); + QVERIFY(classType->equals(dec->abstractType().data())); + compareUses(dec, QList() + << RangeInRevision(2, 14, 2, 16)); + + dec = top->childContexts().at(0)->localDeclarations().at(0); + QVERIFY(dec); + QCOMPARE(dec->identifier(), Identifier("foo")); + compareUses(dec, QList() + << RangeInRevision(2, 31, 2, 35) + << RangeInRevision(2, 42, 2, 46)); + + dec = top->childContexts().at(1)->localDeclarations().at(0); + QVERIFY(dec); + QCOMPARE(dec->identifier(), Identifier("bar")); + compareUses(dec, QList() + << RangeInRevision(2, 37, 2, 40)); +} + +void TestUses::instanceofVariableProperty() +{ + // 0 1 2 3 4 5 + // 012345678901234567890123456789012345678901234567890123456789 + TopDUContext* top = parse("$foo->$bar->$foo;\n", DumpNone); + + QVERIFY(top); + DUChainReleaser releaseTop(top); + DUChainWriteLocker lock; + + QVERIFY(top->problems().isEmpty()); + + QVERIFY(!top->parentContext()); + QCOMPARE(top->childContexts().count(), 2); + QCOMPARE(top->localDeclarations().count(), 5); + + Declaration* dec = top->localDeclarations().at(0); + QCOMPARE(dec->identifier(), Identifier("a")); + compareUses(dec, QList() + << RangeInRevision(3, 9, 3, 10)); + + dec = top->localDeclarations().at(4); + QCOMPARE(dec->identifier(), Identifier("a")); + StructureType::Ptr classType = dec->type(); + QVERIFY(classType); + QCOMPARE(classType->qualifiedIdentifier(), QualifiedIdentifier("a")); + QVERIFY(classType->equals(dec->abstractType().data())); + compareUses(dec, QList() + << RangeInRevision(3, 14, 3, 16) + << RangeInRevision(3, 28, 3, 30)); + + dec = top->localDeclarations().at(2); + QCOMPARE(dec->identifier(), Identifier("foo")); + compareUses(dec, QList() + << RangeInRevision(3, 32, 3, 36) + << RangeInRevision(3, 44, 3, 48)); + + dec = top->localDeclarations().at(3); + QCOMPARE(dec->identifier(), Identifier("bar")); + compareUses(dec, QList() + << RangeInRevision(3, 38, 3, 42)); +} + +void TestUses::instanceofPropertyArrayAccess() +{ + // 0 1 2 3 4 5 + // 012345678901234567890123456789012345678901234567890123456789 + TopDUContext* top = parse("foo[0]::$bar[0]->foo;\n", DumpNone); + + QVERIFY(top); + DUChainReleaser releaseTop(top); + DUChainWriteLocker lock; + + QVERIFY(top->problems().isEmpty()); + + QVERIFY(!top->parentContext()); + QCOMPARE(top->childContexts().count(), 2); + QCOMPARE(top->localDeclarations().count(), 3); + + Declaration* dec = top->localDeclarations().at(0); + QCOMPARE(dec->identifier(), Identifier("a")); + compareUses(dec, QList() + << RangeInRevision(2, 9, 2, 10)); + + dec = top->localDeclarations().at(2); + QCOMPARE(dec->identifier(), Identifier("a")); + StructureType::Ptr classType = dec->type(); + QVERIFY(classType); + QCOMPARE(classType->qualifiedIdentifier(), QualifiedIdentifier("a")); + QVERIFY(classType->equals(dec->abstractType().data())); + compareUses(dec, QList() + << RangeInRevision(2, 14, 2, 16) + << RangeInRevision(2, 28, 2, 30)); + + dec = top->childContexts().at(0)->localDeclarations().at(0); + QCOMPARE(dec->identifier(), Identifier("foo")); + compareUses(dec, QList() + << RangeInRevision(2, 32, 2, 35)); + dec = top->childContexts().at(1)->localDeclarations().at(0); + QCOMPARE(dec->identifier(), Identifier("bar")); + QVERIFY(dec->uses().isEmpty()); } void TestUses::classNameString() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().at(0); QCOMPARE(foo->identifier().toString(), QString("foo")); compareUses(foo, RangeInRevision(0, 22, 0, 27)); } void TestUses::useTrait() { // 0 1 2 3 4 5 6 7 8 // 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("one(); $a->two();\n" "$b = new Bar(); $b->one(); $b->two(); $b->four();\n" "$c = new Baz(); $c->one(); $c->two(); $c->baz; $c::six();\n" "$d = new Boo(); $d->one(); $d->switch(); $d->three(); $d->static();\n", DumpAll); QVERIFY(top); DUChainReleaser releaseTop(top); DUChainWriteLocker lock; Declaration* dec; QVector topDecs = top->localDeclarations(); TraitMethodAliasDeclaration* method; TraitMemberAliasDeclaration* member; QCOMPARE(topDecs.size(), 11); dec = top->findDeclarations(QualifiedIdentifier(QStringLiteral("a"))).first(); compareUses(dec, QList() << RangeInRevision(4, 16, 4, 17) << RangeInRevision(5, 16, 5, 17) << RangeInRevision(5, 22, 5, 23) << RangeInRevision(6, 16, 6, 17) << RangeInRevision(6, 41, 6, 42) << RangeInRevision(7, 16, 7, 17) << RangeInRevision(7, 24, 7, 25) << RangeInRevision(7, 46, 7, 47) ); dec = top->findDeclarations(QualifiedIdentifier(QStringLiteral("b"))).first(); compareUses(dec, QList() << RangeInRevision(5, 18, 5, 19) << RangeInRevision(5, 39, 5, 40) << RangeInRevision(5, 42, 5, 43) << RangeInRevision(6, 18, 6, 19) << RangeInRevision(6, 43, 6, 44) << RangeInRevision(7, 18, 7, 19) << RangeInRevision(7, 41, 7, 42) << RangeInRevision(7, 64, 7, 65) ); dec = top->findDeclarations(QualifiedIdentifier(QStringLiteral("c"))).first(); compareUses(dec, QList() << RangeInRevision(6, 20, 6, 21) << RangeInRevision(6, 24, 6, 25) << RangeInRevision(6, 46, 6, 47) << RangeInRevision(7, 20, 7, 21) << RangeInRevision(7, 43, 7, 44) << RangeInRevision(7, 84, 7, 85) ); dec = topDecs[3]->internalContext()->findLocalDeclarations(Identifier(QStringLiteral("one"))).first(); method = dynamic_cast(dec); QVERIFY(method); QCOMPARE(method->aliasedDeclaration().data()->context()->owner()->identifier(), Identifier("a")); compareUses(dec, QList() << RangeInRevision(8, 20, 8, 23) ); dec = topDecs[3]->internalContext()->findLocalDeclarations(Identifier(QStringLiteral("two"))).first(); method = dynamic_cast(dec); QVERIFY(method); QCOMPARE(method->aliasedDeclaration().data()->context()->owner()->identifier(), Identifier("a")); compareUses(dec, QList() << RangeInRevision(8, 31, 8, 34) ); dec = topDecs[4]->internalContext()->findLocalDeclarations(Identifier(QStringLiteral("one"))).first(); method = dynamic_cast(dec); QVERIFY(method); QCOMPARE(method->aliasedDeclaration().data()->context()->owner()->identifier(), Identifier("a")); compareUses(dec, QList() << RangeInRevision(9, 20, 9, 23) ); dec = topDecs[4]->internalContext()->findLocalDeclarations(Identifier(QStringLiteral("two"))).first(); method = dynamic_cast(dec); QVERIFY(method); QCOMPARE(method->aliasedDeclaration().data()->context()->owner()->identifier(), Identifier("a")); compareUses(dec, QList() << RangeInRevision(9, 31, 9, 34) ); dec = topDecs[4]->internalContext()->findLocalDeclarations(Identifier(QStringLiteral("four"))).first(); method = dynamic_cast(dec); QVERIFY(method); QCOMPARE(method->aliasedDeclaration().data()->context()->owner()->identifier(), Identifier("b")); QCOMPARE(method->aliasedDeclaration().data()->identifier(), Identifier("three")); QCOMPARE(method->accessPolicy(), Declaration::AccessPolicy::Public); compareUses(dec, QList() << RangeInRevision(9, 42, 9, 46) ); dec = topDecs[5]->internalContext()->findLocalDeclarations(Identifier(QStringLiteral("one"))).first(); method = dynamic_cast(dec); QVERIFY(method); QCOMPARE(method->aliasedDeclaration().data()->context()->owner()->identifier(), Identifier("c")); compareUses(dec, QList() << RangeInRevision(10, 20, 10, 23) ); dec = topDecs[5]->internalContext()->findLocalDeclarations(Identifier(QStringLiteral("two"))).first(); method = dynamic_cast(dec); QVERIFY(method); QCOMPARE(method->aliasedDeclaration().data()->context()->owner()->identifier(), Identifier("a")); compareUses(dec, QList() << RangeInRevision(10, 31, 10, 34) ); dec = topDecs[5]->internalContext()->findLocalDeclarations(Identifier(QStringLiteral("baz"))).first(); member = dynamic_cast(dec); QVERIFY(member); QCOMPARE(member->aliasedDeclaration().data()->context()->owner()->identifier(), Identifier("c")); compareUses(dec, QList() << RangeInRevision(10, 42, 10, 45) ); dec = topDecs[5]->internalContext()->findLocalDeclarations(Identifier(QStringLiteral("six"))).first(); method = dynamic_cast(dec); QVERIFY(method); QCOMPARE(method->aliasedDeclaration().data()->context()->owner()->identifier(), Identifier("c")); QCOMPARE(method->aliasedDeclaration().data()->identifier(), Identifier("five")); QVERIFY(method->isStatic()); compareUses(dec, QList() << RangeInRevision(10, 51, 10, 54) ); dec = topDecs[6]->internalContext()->findLocalDeclarations(Identifier(QStringLiteral("one"))).first(); method = dynamic_cast(dec); QVERIFY(method); QCOMPARE(method->aliasedDeclaration().data()->context()->owner()->identifier(), Identifier("a")); compareUses(dec, QList() << RangeInRevision(11, 20, 11, 23) ); dec = topDecs[6]->internalContext()->findLocalDeclarations(Identifier(QStringLiteral("switch"))).first(); method = dynamic_cast(dec); QVERIFY(method); QCOMPARE(method->aliasedDeclaration().data()->context()->owner()->identifier(), Identifier("a")); QCOMPARE(method->aliasedDeclaration().data()->identifier(), Identifier("two")); compareUses(dec, QList() << RangeInRevision(11, 31, 11, 37) ); dec = topDecs[6]->internalContext()->findLocalDeclarations(Identifier(QStringLiteral("three"))).first(); method = dynamic_cast(dec); QVERIFY(method); QCOMPARE(method->aliasedDeclaration().data()->context()->owner()->identifier(), Identifier("b")); QCOMPARE(method->aliasedDeclaration().data()->identifier(), Identifier("three")); QCOMPARE(method->accessPolicy(), Declaration::AccessPolicy::Public); compareUses(dec, QList() << RangeInRevision(11, 45, 11, 50) ); dec = topDecs[6]->internalContext()->findLocalDeclarations(Identifier(QStringLiteral("static"))).first(); method = dynamic_cast(dec); QVERIFY(method); QCOMPARE(method->aliasedDeclaration().data()->context()->owner()->identifier(), Identifier("c")); QCOMPARE(method->aliasedDeclaration().data()->identifier(), Identifier("five")); compareUses(dec, QList() << RangeInRevision(11, 58, 11, 64) ); } void TestUses::exceptionFinally() { // 0 1 2 3 4 // 01234567890123456789012345678901234567890123456 QByteArray method("localDeclarations().at(0); QCOMPARE(a->identifier().toString(), QString("a")); compareUses(a, QList() << RangeInRevision(0, 17, 0, 19) << RangeInRevision(0, 37, 0, 39)); } void TestUses::returnTypeClassFunction() { QByteArray method("localDeclarations().at(0); QCOMPARE(a->identifier().toString(), QString("a")); compareUses(a, QList() << RangeInRevision(0, 29, 0, 30) << RangeInRevision(0, 50, 0, 54)); } void TestUses::returnTypeFunction() { QByteArray method("localDeclarations().at(0); QCOMPARE(a->identifier().toString(), QString("a")); compareUses(a, QList() << RangeInRevision(0, 30, 0, 31)); } void TestUses::defaultValue() { QByteArray method("localDeclarations().at(0); QCOMPARE(a->identifier().toString(), QString("a")); compareUses(a, QList() << RangeInRevision(0, 44, 0, 45)); Declaration *c = top->childContexts().at(0)->localDeclarations().at(0); QCOMPARE(c->identifier().toString(), QString("C")); compareUses(c, QList() << RangeInRevision(0, 47, 0, 48)); } } diff --git a/duchain/tests/uses.h b/duchain/tests/uses.h index 7963001..f7d2718 100644 --- a/duchain/tests/uses.h +++ b/duchain/tests/uses.h @@ -1,98 +1,103 @@ /* This file is part of KDevelop Copyright 2008 Niko Sams 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. */ #ifndef TESTUSES_H #define TESTUSES_H #include "../../duchain/tests/duchaintestbase.h" namespace Php { class TestUses : public DUChainTestBase { Q_OBJECT public: TestUses(); private slots: void newObject(); void functionCall(); void memberFunctionCall(); void unsureMemberFunctionCall(); void memberVariable(); void implicitMemberVariable(); void variable(); void varInString(); void variableInNamespace(); void globalVariableInNamespace(); void variableInOtherNamespace(); void memberVarInString(); void memberFunctionInString(); void variableTypeChange(); void variableTypeChangeInFunction(); void classExtends(); void classImplements(); void classImplementsMultiple(); void interfaceExtends(); void interfaceExtendsMultiple(); void staticMemberFunctionCall(); void staticMemberVariable(); void constant(); void classConstant(); void classParent(); void classSelf(); void classThis(); void objectWithClassName(); void classAndConstWithSameName(); void classAndFunctionWithSameName(); void constAndVariableWithSameName(); void functionAndClassWithSameName(); void constantInClassMember(); void useInAsignment(); void foreachArray(); void assignmentToMemberArray(); void staticArrayIndex(); void functionParamNewDeclaration(); void catchClass(); void variableRedeclaration(); void caseInsensitiveFunction(); void caseInsensitiveMethod(); void caseInsensitiveClass(); void functionUseBeforeDeclaration(); void propertyAndMethodWithSameName(); void nestedMethodCalls(); void unset(); void functionArguments(); void namespaces(); void useNamespace(); void lateStatic(); void closures(); void closureTypehints(); void instanceof(); + void instanceofClassProperty(); + void instanceofStaticProperty(); + void instanceofMixedProperty(); + void instanceofVariableProperty(); + void instanceofPropertyArrayAccess(); void classNameString(); void useTrait(); void exceptionFinally(); void returnTypeClassFunction(); void returnTypeFunction(); void defaultValue(); }; } #endif diff --git a/kdevphpsupport.json b/kdevphpsupport.json index d180507..b374b05 100644 --- a/kdevphpsupport.json +++ b/kdevphpsupport.json @@ -1,68 +1,68 @@ { "KPlugin": { "Category": "Language Support", "Description": "PHP Language Support", "Description[ca@valencia]": "Implementació del llenguatge PHP", "Description[ca]": "Implementació del llenguatge PHP", "Description[cs]": "Podpora jazyka PHP", "Description[de]": "Sprachunterstützung für PHP", "Description[es]": "Implementación del lenguaje PHP", + "Description[et]": "PHP keele toetus", "Description[fi]": "PHP-kielituki", "Description[fr]": "Prise en charge du langage PHP", - "Description[gl]": "Compatibilidade coa linguaxe PHP", + "Description[gl]": "Compatibilidade coa linguaxe PHP.", "Description[it]": "Supporto al linguaggio PHP", - "Description[ko]": "PHP 언어 지원", "Description[nl]": "Ondersteuning van de PHP-taal", "Description[pl]": "Obsługa języka PHP", "Description[pt]": "Suporte da Linguagem PHP", "Description[pt_BR]": "Suporte à linguagem PHP", "Description[ru]": "Поддержка языка программирования PHP", "Description[sk]": "Jazyková podpora PHP", "Description[sl]": "Podpora jeziku PHP", "Description[sv]": "PHP-språkstöd", "Description[tr]": "PHP Dili Desteği", "Description[uk]": "Підтримка мови PHP", "Description[x-test]": "xxPHP Language Supportxx", "Description[zh_CN]": "PHP 语言支持", "Icon": "application-x-php", "Id": "KDevPhpSupport", "Name": "PHP Language Support", "Name[ca@valencia]": "Implementació del llenguatge PHP", "Name[ca]": "Implementació del llenguatge PHP", "Name[cs]": "Podpora jazyka PHP", "Name[de]": "Sprachunterstützung für PHP", "Name[es]": "Implementación del lenguaje PHP", + "Name[et]": "PHP keele toetus", "Name[fi]": "PHP-kielituki", "Name[fr]": "Prise en charge du langage PHP", "Name[gl]": "Compatibilidade coa linguaxe PHP", "Name[it]": "Supporto al linguaggio PHP", - "Name[ko]": "PHP 언어 지원", "Name[nl]": "Ondersteuning van de PHP-taal", "Name[pl]": "Obsługa języka PHP", "Name[pt]": "Suporte da Linguagem PHP", "Name[pt_BR]": "Suporte à linguagem PHP", "Name[ru]": "Поддержка PHP", "Name[sk]": "Jazyková podpora PHP", "Name[sl]": "Podpora jeziku PHP", "Name[sv]": "PHP-språkstöd", "Name[tr]": "PHP Dili Desteği", "Name[uk]": "Підтримка мови PHP", "Name[x-test]": "xxPHP Language Supportxx", - "Name[zh_CN]": "PHP 语言支持", + "Name[zh_CN]": "Php 语言支持", "ServiceTypes": [ "KDevelop/Plugin" ] }, "X-KDevelop-Interfaces": [ "ILanguageSupport", "org.kdevelop.PHPSupport" ], "X-KDevelop-Languages": [ "Php" ], "X-KDevelop-LoadMode": "AlwaysOn", "X-KDevelop-Mode": "NoGUI", "X-KDevelop-SupportedMimeTypes": [ "application/x-php" ] } diff --git a/org.kde.kdev-php.metainfo.xml b/org.kde.kdev-php.metainfo.xml index 6a5e775..d56ce3c 100644 --- a/org.kde.kdev-php.metainfo.xml +++ b/org.kde.kdev-php.metainfo.xml @@ -1,47 +1,49 @@ org.kde.kdev-php org.kde.kdevelop.desktop KDevelop PHP Support Implementació de PHP al KDevelop Implementació de PHP al KDevelop Podpora PHP v KDevelop KDevelop – PHP-Unterstützung KDevelop PHP Support Implementación de PHP para KDevelop Prise en charge de PHP pour KDevelop Compatibilidade con PHP para KDevelop Supporto PHP per KDevelop PHP ondersteuning in KDevelop - Obsługa PHP w KDevelop + KDevelop - obsługa PHP Suporte para PHP do KDevelop + Suporte a PHP do KDevelop PHP podpora pre KDevelop KDevelop PHP-stöd Підтримка PHP KDevelop xxKDevelop PHP Supportxx PHP language support for KDevelop Implementació del llenguatge PHP al KDevelop Implementació del llenguatge PHP al KDevelop Podpora jazyka PHP pro KDevelop PHP-Sprachunterstützung für KDevelop PHP language support for KDevelop Implementación del lenguaje PHP para KDevelop Prise en charge du langage PHP pour KDevelop Compatibilidade coa linguaxe PHP para KDevelop Supporto al linguaggio PHP per KDevelop Ondersteuning van PHP-taal voor KDevelop Obsługa języka PHP dla KDevelop Suporte à linguagem PHP do KDevelop + Suporte à linguagem PHP para o KDevelop Podpora jazyka PHP pre KDevelop Stöd för språket PHP i KDevelop Підтримка мови PHP у KDevelop xxPHP language support for KDevelopxx GPL-2.0+ CC0-1.0 https://kdevelop.org https://bugs.kde.org/enter_bug.cgi?format=guided&product=kdevelop&component=Language%20Support%3A%20PHP https://docs.kde.org/index.php?application=kdevelop https://www.kde.org/community/donations/?app=kdevelop KDE kdevelop-devel@kde.org diff --git a/parser/php.g b/parser/php.g index 6c48657..38ef2fa 100644 --- a/parser/php.g +++ b/parser/php.g @@ -1,1300 +1,1297 @@ ------------------------------------------------------------------------------- -- Copyright (c) 2008 Niko Sams -- -- This grammar is free software; you can redistribute it and/or -- modify it under the terms of the GNU Library General Public -- License as published by the Free Software Foundation; either -- version 2 of the License, or (at your option) any later version. -- -- This grammar 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 -- Lesser 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. ----------------------------------------------------------- ----------------------------------------------------------- -- Grammar for PHP 5.2 -- Modelled after the Zend Grammar shipped with PHP5.2 -- source, the PHP Language Reference documentation, -- and parts taken from KDevelop Java Grammar ----------------------------------------------------------- -- 4 first/first conflicts: -- - var_expression: variable vs. varExpressionNormal -- no problem because of ifs that allow always just one rule -- - classNameReference: STRING vs. staticMember (foo vs. foo::$bar) -- resolved by LA() -- - encapsVar: STRING_VARNAME LBRACKET vs. expr (expr allows STRING_VARNAME too - but not LBRACKET) -- resolved by LA() -- - constantOrClassConst: constant vs. class constant (FOO v.s Cls::FOO) -- resolved by LA() (could be avoided, but the Ast is much cleaner that way) -- 1 first/follow conflicts: -- - elseifList: dangling-else conflict - should be ok -- TODO: (post 1.0.0 release) -- 1) decrease memory consumption -- 1.1) use quint32 instead of qint64 for end/start tokens -- 1.2) investigate whether using a map/hash for the ducontext member of all -- ast nodes gives a significant memory decrease while not hampering performance -- 1.3) investigate how unions could be used for exclusive AST node members -- 1.4) see whether we can always use the expression lists instead of both -- single member pointer and list of members, esp. in expressions -- 2) better cope with invalid code, have at least a partial AST -- 3) investigate whether expanding the visitor lookup to a -- (albeit huge) switch() in KDev-PG-Qt gives a significant performance gain -- I have the gut feeling that the current lookup takes unnecessary much time -- ------------------------------------------------------------ -- Forward declaration in phpast.h ------------------------------------------------------------ [: #include namespace KDevelop { class DUContext; } :] ------------------------------------------------------------ -- Additional includes for the parser ------------------------------------------------------------ %parser_declaration_header "parser/tokenstream.h" %parser_declaration_header "QtCore/QString" %parser_declaration_header "language/duchain/problem.h" %parser_declaration_header "parser/phplexer.h" %parser_bits_header "parserdebug.h" ------------------------------------------------------------ -- Export macro to use the parser in a shared lib ------------------------------------------------------------ %export_macro "KDEVPHPPARSER_EXPORT" %export_macro_header "parserexport.h" ------------------------------------------------------------ -- Enumeration types for additional AST members, -- in the global "Php" namespace ------------------------------------------------------------ %namespace [: class Lexer; enum ModifierFlags { ModifierPrivate = 1, ModifierPublic = 1 << 1, ModifierProtected = 1 << 2, ModifierStatic = 1 << 3, ModifierFinal = 1 << 4, ModifierAbstract = 1 << 5 }; enum ClassModifier { NormalClass, AbstractClass, FinalClass }; enum ScalarTypes { ScalarTypeInt, ScalarTypeFloat, ScalarTypeString }; enum CastType { CastInt, CastDouble, CastString, CastArray, CastObject, CastBool, CastUnset }; enum OperationType { OperationPlus = 1, OperationMinus, OperationConcat, OperationMul, OperationDiv, OperationExp, OperationMod, OperationAnd, OperationOr, OperationXor, OperationSl, OperationSr, OperationSpaceship, }; :] ------------------------------------------------------------ -- Ast Node class members ------------------------------------------------------------ %ast_extra_members [: KDevelop::DUContext* ducontext; :] ------------------------------------------------------------ -- Parser class members ------------------------------------------------------------ %parserclass (public declaration) [: /** * Transform the raw input into tokens. * When this method returns, the parser's token stream has been filled * and any parse*() method can be called. */ void tokenize(const QString& contents, int initialState = Lexer::HtmlState); enum ProblemType { Error, Warning, Info, Todo }; KDevelop::ProblemPointer reportProblem( Parser::ProblemType type, const QString& message, int tokenOffset = -1 ); QList problems() { return m_problems; } QString tokenText(qint64 begin, qint64 end); void setDebug(bool debug); void setCurrentDocument(KDevelop::IndexedString url); void setTodoMarkers(const QStringList& markers); void extractTodosFromComment(const QString& comment, qint64 offset); enum InitialLexerState { HtmlState = 0, DefaultState = 1 }; :] %parserclass (private declaration) [: enum VarExpressionState { Normal, OnlyVariable, OnlyNewObject }; QString m_contents; bool m_debug; KDevelop::IndexedString m_currentDocument; QList m_problems; struct ParserState { VarExpressionState varExpressionState; bool varExpressionIsVariable; }; ParserState m_state; QRegularExpression m_todoMarkers; :] %parserclass (constructor) [: m_state.varExpressionState = Normal; m_state.varExpressionIsVariable = false; :] %token_stream TokenStream ;; ----------------------------------------------------------- -- List of defined tokens ----------------------------------------------------------- -- keywords: %token ABSTRACT ("abstract"), BREAK ("break"), CASE ("case"), CATCH ("catch"), CLASS ("class"), CONST ("const"), CONTINUE ("continue"), DEFAULT ("default"), DO ("do"), ELSE ("else"), EXTENDS ("extends"), FINAL ("final"), FOR ("for"), IF ("if"), IMPLEMENTS ("implements"), INSTANCEOF ("instanceof"), INTERFACE ("interface"), NEW ("new"), PRIVATE ("private"), PROTECTED ("protected"), PUBLIC ("public"), RETURN ("return"), STATIC ("static"), SWITCH ("switch"), THROW ("throw"), TRY ("try"), WHILE ("while"), ECHO ("echo"), PRINT ("print"), FINALLY ("finally"), CLONE ("clone"), EXIT ("exit"), ELSEIF ("elseif"), ENDIF ("endif"), ENDWHILE ("endwhile"), ENDFOR ("endfor"), FOREACH ("foreach"), ENDFOREACH ("endforeach"), DECLARE ("declare"), ENDDECLARE ("enddeclare"), AS ("as"), ENDSWITCH ("endswitch"), FUNCTION ("function"), USE ("use"), GLOBAL ("global"), VAR ("var "), UNSET ("unset"), ISSET ("isset"), EMPTY ("empty"), HALT_COMPILER ("halt compiler"), DOUBLE_ARROW ("=>"), LIST ("list"), ARRAY ("array"), CLASS_C ("__CLASS__"), METHOD_C ("__METHOD__"), FUNC_C ("__FUNCTION__"), LINE ("__LINE__"), FILE ("__FILE__"), COMMENT ("comment"), DOC_COMMENT ("doc comment"), PAAMAYIM_NEKUDOTAYIM ("::"), INCLUDE ("include"), INCLUDE_ONCE ("include_once"), EVAL ("eval"), REQUIRE ("require"), REQUIRE_ONCE ("require_once"), NAMESPACE ("namespace"), NAMESPACE_C("__NAMESPACE__"), USE("use"), GOTO ("goto"), TRAIT ("trait"), INSTEADOF ("insteadof"), CALLABLE ("callable"), VOID ("void"), DIR ("__DIR__"), TRAIT_C ("__TRAIT__"), YIELD ("yield"), YIELD_FROM("yield from") ;; -- casts: %token INT_CAST ("int cast"), DOUBLE_CAST ("double cast"), STRING_CAST ("string cast"), ARRAY_CAST ("array cast"), OBJECT_CAST ("object cast"), BOOL_CAST ("bool cast"), UNSET_CAST ("unset cast") ;; -- seperators: %token SEMICOLON (";"), DOUBLE_QUOTE ("\""), LBRACKET ("["), RBRACKET ("]"), LPAREN ("("), RPAREN (")"), LBRACE ("{"), RBRACE ("}"), COMMA (","), AT ("@"), CURLY_OPEN ("curly open"), -- { in "{$foo}"; not the same as LBRACE DOLLAR_OPEN_CURLY_BRACES ("${"), START_HEREDOC ("start heredoc"), END_HEREDOC ("end heredoc"), BACKTICK ("`"), BACKSLASH ("\\"), START_NOWDOC("start nowdoc"), END_NOWDOC("end nowdoc") ;; -- operators: %token IS_EQUAL ("=="), IS_NOT_EQUAL ("!="), IS_IDENTICAL ("==="), IS_NOT_IDENTICAL ("!=="), IS_SMALLER ("<"), IS_GREATER (">"), IS_SMALLER_OR_EQUAL ("<="), IS_GREATER_OR_EQUAL (">="), BOOLEAN_OR ("||"), BOOLEAN_AND ("&&"), ASSIGN ("="), EXP_ASSIGN("**="), PLUS_ASSIGN ("+="), MINUS_ASSIGN ("-="), MUL_ASSIGN ("*="), DIV_ASSIGN ("/="), CONCAT_ASSIGN (".="), MOD_ASSIGN ("%="), AND_ASSIGN ("&="), OR_ASSIGN ("|="), XOR_ASSIGN ("^="), SL_ASSIGN ("<<="), SR_ASSIGN (">>="), OBJECT_OPERATOR ("->"), PLUS ("+"), MINUS("-"), CONCAT("."), INC ("++"), DEC ("--"), BANG ("!"), QUESTION ("?"), COLON (":"), BIT_AND ("&"), BIT_OR("|"), BIT_XOR ("^"), SL ("<<"), SR (">>"), MUL("*"), DIV("/"), MOD ("%"), TILDE ("~"), DOLLAR ("$"), EXP ("**"), ELLIPSIS ("..."), NULL_COALESCE ("??"), SPACESHIP ("<=>"), LOGICAL_OR ("logical or"), LOGICAL_AND ("logical and"), LOGICAL_XOR ("logical xor") ;; -- literals and identifiers: %token INLINE_HTML ("inline html"), WHITESPACE ("whitespace"), CONSTANT_ENCAPSED_STRING ("constant encapsed string"), VARIABLE ("variable"), ENCAPSED_AND_WHITESPACE ("encapsed and whitespace"), DNUMBER ("double number"), LNUMBER ("long number"), NUM_STRING ("num string"), STRING ("string"), STRING_VARNAME ("string varname") ;; -- when in "${varname}" -- open/close tags %token OPEN_TAG (""), OPEN_TAG_WITH_ECHO (" start ;; namespaceDeclaration=namespaceDeclarationStatement | statement=topStatement -> outerTopStatement ;; -- first/first conflict for FUNCTION (?[: (LA(1).kind == Token_FUNCTION && ((LA(2).kind == Token_BIT_AND && LA(3).kind == Token_LPAREN) || LA(2).kind == Token_LPAREN)) || LA(1).kind != Token_FUNCTION :] statement=statement ) | functionDeclaration=functionDeclarationStatement | classDeclaration=classDeclarationStatement | traitDeclaration=traitDeclarationStatement | interfaceDeclaration=interfaceDeclarationStatement | HALT_COMPILER LPAREN RPAREN SEMICOLON -- Lexer stops allready -> topStatement ;; [: bool reported = false; while ( true ) { :] try/recover(#statements=topStatement)* [: if (yytoken != Token_RBRACE && yytoken != Token_EOF && yytoken != Token_CLOSE_TAG && yytoken != Token_ELSEIF && yytoken != Token_ELSE && yytoken != Token_ENDIF && yytoken != Token_ENDFOREACH && yytoken != Token_ENDFOR && yytoken != Token_ENDWHILE && yytoken != Token_ENDSWITCH && yytoken != Token_ENDDECLARE && yytoken != Token_CASE && yytoken != Token_DEFAULT) { if (!reported) { qint64 index = tokenStream->index() - 1; Token &token = tokenStream->at(index); QString tokenValue = token.kind != 0 ? tokenText(token.begin, token.end) : QStringLiteral("EOF"); reportProblem(Error, QStringLiteral("Unexpected token \"%1\".").arg(tokenValue)); reported = true; } yylex(); } else { break; } } :] -> innerStatementList ;; --Operator Precedence, from PHP Manual --left or --left xor --left and --right print --right = += -= *= /= .= %= &= |= ^= <<= >>= assignment --left ? : ternary --right ?? comparison --left || logical --left && logical --left | bitwise --left ^ bitwise --left & bitwise and references --non-associative == != === !== <=> comparison --non-associative < <= > >= comparison --left << >> bitwise --left + - . arithmetic and string --left * / % arithmetic --non-associative ! ~ - (int) (float) (string) (array) (object) @ types --non-associative ++ -- increment/decrement --left [ array() --non-associative new new expression=logicalOrExpression -> expr ;; #expression=logicalXorExpression @ LOGICAL_OR -> logicalOrExpression ;; #expression=logicalAndExpression @ LOGICAL_XOR -> logicalXorExpression ;; #expression=printExpression @ LOGICAL_AND -> logicalAndExpression ;; (print=PRINT | 0) expression=assignmentExpression -> printExpression ;; -- leftside must me a variable, we check afterwards if it was a variable and -- if not we report an error 0 --needed for line below [: m_state.varExpressionIsVariable = false; :] --reset flag expression=conditionalExpression ( assignmentExpressionEqual=assignmentExpressionEqual | ( ( PLUS_ASSIGN [: (*yynode)->operation = OperationPlus; :] | MINUS_ASSIGN [: (*yynode)->operation = OperationMinus; :] | MUL_ASSIGN [: (*yynode)->operation = OperationMul; :] | EXP_ASSIGN [: (*yynode)->operation = OperationExp; :] | DIV_ASSIGN [: (*yynode)->operation = OperationDiv; :] | CONCAT_ASSIGN [: (*yynode)->operation = OperationConcat; :] | MOD_ASSIGN [: (*yynode)->operation = OperationMod; :] | AND_ASSIGN [: (*yynode)->operation = OperationAnd; :] | OR_ASSIGN [: (*yynode)->operation = OperationOr; :] | XOR_ASSIGN [: (*yynode)->operation = OperationXor; :] | SL_ASSIGN [: (*yynode)->operation = OperationSl; :] | SR_ASSIGN [: (*yynode)->operation = OperationSr; :] ) assignmentExpressionCheckIfVariable assignmentExpression=assignmentExpression) | 0) -> assignmentExpression [ member variable operation: OperationType; ];; --=& is special: -- $foo =& $var; is allowed but not $foo =& 'static'; -- $foo =& new bar(); is allowed too but deprecated and reports a warning --we set a flag (varExpressionState) with that var_expression accepts only valid parts --this is done in such a strage way because we need the full expression to allow --things like $foo =& $bar || e(); ASSIGN assignmentExpressionCheckIfVariable --as in assignmentExpression (BIT_AND [: if (yytoken == Token_NEW) { reportProblem(Warning, QStringLiteral("=& new foo() is deprecated"), -2); m_state.varExpressionState = OnlyNewObject; } else { m_state.varExpressionState = OnlyVariable; }:] | 0) assignmentExpression=assignmentExpression [: m_state.varExpressionState = Normal; :] -> assignmentExpressionEqual ;; -- check if var_expression was a variable, if not report an error -- varExpressionIsVariable is set in var_expression 0 --to allow cpp-code [: if (!m_state.varExpressionIsVariable) { reportProblem(Error, QStringLiteral("Left side is not a variable")); return false; } :] -> assignmentExpressionCheckIfVariable ;; expression=nullCoalesceExpression ( QUESTION (ifExpression=expr|0) COLON elseExpression=conditionalExpression | 0 ) -> conditionalExpression ;; #expression=booleanOrExpression @ NULL_COALESCE -> nullCoalesceExpression ;; #expression=booleanAndExpression @ BOOLEAN_OR -> booleanOrExpression ;; #expression=bitOrExpression @ BOOLEAN_AND -> booleanAndExpression ;; #expression=bitXorExpression @ BIT_OR -> bitOrExpression ;; #expression=bitAndExpression @ BIT_XOR -> bitXorExpression ;; #expression=equalityExpression @ BIT_AND -> bitAndExpression ;; expression=relationalExpression (additionalExpression=equalityExpressionRest | 0) -> equalityExpression ;; ( IS_EQUAL | IS_NOT_EQUAL | IS_IDENTICAL | IS_NOT_IDENTICAL | SPACESHIP [: (*yynode)->operation = OperationSpaceship; :] ) expression=relationalExpression -> equalityExpressionRest [ member variable operation: OperationType; ];; expression=shiftExpression ( additionalExpression=relationalExpressionRest --instanceof as in java.g (correct??) | INSTANCEOF instanceofType=classNameReference | 0 ) -> relationalExpression ;; ( IS_SMALLER | IS_GREATER | IS_SMALLER_OR_EQUAL | IS_GREATER_OR_EQUAL ) expression=shiftExpression -> relationalExpressionRest ;; expression=additiveExpression (#additionalExpression=shiftExpressionRest)* -> shiftExpression ;; ( SL | SR ) expression=additiveExpression -> shiftExpressionRest ;; expression=multiplicativeExpression (#additionalExpression=additiveExpressionRest)* -> additiveExpression ;; ( PLUS [: (*yynode)->operation = OperationPlus; :] | MINUS [: (*yynode)->operation = OperationMinus; :] | CONCAT [: (*yynode)->operation = OperationConcat; :] ) expression=multiplicativeExpression -> additiveExpressionRest [ member variable operation: OperationType; ];; expression=unaryExpression (#additionalExpression=multiplicativeExpressionRest)* -> multiplicativeExpression ;; ( MUL [: (*yynode)->operation = OperationMul; :] | DIV [: (*yynode)->operation = OperationDiv; :] | EXP [: (*yynode)->operation = OperationExp; :] | MOD [: (*yynode)->operation = OperationMod; :] ) expression=unaryExpression -> multiplicativeExpressionRest [ member variable operation: OperationType; ];; ( MINUS unaryExpression=unaryExpression | PLUS unaryExpression=unaryExpression | BANG unaryExpression=unaryExpression | TILDE unaryExpression=unaryExpression | INT_CAST unaryExpression=unaryExpression [: (*yynode)->castType = CastInt; :] | DOUBLE_CAST unaryExpression=unaryExpression [: (*yynode)->castType = CastDouble; :] | STRING_CAST unaryExpression=unaryExpression [: (*yynode)->castType = CastString; :] | ARRAY_CAST unaryExpression=unaryExpression [: (*yynode)->castType = CastArray; :] | OBJECT_CAST unaryExpression=unaryExpression [: (*yynode)->castType = CastObject; :] | BOOL_CAST unaryExpression=unaryExpression [: (*yynode)->castType = CastBool; :] | UNSET_CAST unaryExpression=unaryExpression [: (*yynode)->castType = CastUnset; :] | AT unaryExpression=unaryExpression | LIST LPAREN assignmentList=assignmentList RPAREN ASSIGN unaryExpression=unaryExpression | EXIT (LPAREN (expression=expr | 0) RPAREN | 0) | EVAL LPAREN expression=expr RPAREN | INCLUDE includeExpression=unaryExpression | INCLUDE_ONCE includeExpression=unaryExpression | REQUIRE includeExpression=unaryExpression | REQUIRE_ONCE includeExpression=unaryExpression | unaryExpressionNotPlusminus=unaryExpressionNotPlusminus ) -> unaryExpression [ member variable castType: CastType; ];; (#prefixOperator=postprefixOperator)* varExpression=varExpression (#postfixOperator=postprefixOperator)* -> unaryExpressionNotPlusminus ;; op=INC | op=DEC -> postprefixOperator ;; -- 10 first follow conflicts because we go back up the chain (affects both print and yield) (print=PRINT+) printExpression=assignmentExpression | isGenerator=YIELD (generatorExpression=printExpression ( DOUBLE_ARROW generatorValueExpr=printExpression | 0 ) | 0) | isGenerator=YIELD_FROM generatorExpression=printExpression --first/first conflict - no problem because of ifs | ?[: m_state.varExpressionState == OnlyVariable :] 0 [: m_state.varExpressionState = Normal; :] variable=variable | ?[: m_state.varExpressionState == OnlyNewObject :] 0 [: m_state.varExpressionState = Normal; :] newObject=varExpressionNewObject | varExpressionNormal=varExpressionNormal | varExpressionArray=varExpressionArray arrayIndex=arrayIndexSpecifier* -> varExpression ;; (?[: LA(1).kind == Token_LPAREN && LA(2).kind == Token_FUNCTION && LA(3).kind == Token_LPAREN :] iife=iifeSyntax ) | LPAREN try/rollback (newObject=varExpressionNewObject RPAREN (#variableProperties=instantiationAccess*)) catch (expression=expr RPAREN) | BACKTICK encapsList=encapsList BACKTICK --try/rollback resolves conflict scalar vs. staticMember (foo::bar vs. foo::$bar) --varExpressionIsVariable flag is needed for assignmentExpression | try/rollback (variable=variable [: m_state.varExpressionIsVariable = true; :]) catch (scalar=scalar) | ISSET LPAREN (#issetVariable=variable @ COMMA) RPAREN | EMPTY LPAREN emptyExpression=expr RPAREN | newObject=varExpressionNewObject | CLONE cloneCar=varExpressionNormal | closure=closure -> varExpressionNormal ;; ARRAY LPAREN (#arrayValues=arrayPairValue -- break because array(1,) is allowed (solves FIRST/FOLLOW conflict) @ (COMMA [: if (yytoken == Token_RPAREN) { break; } :] ) | 0) RPAREN | LBRACKET (#arrayValues=arrayPairValue -- break because [1,] is allowed (solves FIRST/FOLLOW conflict) @ (COMMA [: if (yytoken == Token_RBRACKET) { break; } :] ) | 0) RBRACKET -> varExpressionArray ;; -- http://wiki.php.net/rfc/closures FUNCTION (isRef=BIT_AND|0) LPAREN parameters=parameterList RPAREN ( USE LPAREN lexicalVars=lexicalVarList RPAREN | 0) ( COLON returnType=returnType | 0) LBRACE try/recover(functionBody=innerStatementList) RBRACE -> closure ;; LPAREN try/rollback (closure=closure RPAREN LPAREN parameterList=functionCallParameterList RPAREN) catch (expression=expr RPAREN) -> iifeSyntax ;; (#lexicalVars=lexicalVar @ COMMA) | 0 [: reportProblem(Error, QStringLiteral("Use list of closure must not be empty.")); :] -> lexicalVarList ;; (isRef=BIT_AND | 0) variable=variableIdentifier -> lexicalVar ;; - NEW className=classNameReference ctor=ctorArguments + NEW classNameReference=classNameReference ctor=ctorArguments -> varExpressionNewObject ;; LPAREN parameterList=functionCallParameterList RPAREN | 0 -> ctorArguments ;; #parameters=functionCallParameterListElement @ COMMA | 0 -> functionCallParameterList ;; (BIT_AND variable=variable) | (isVariadic=ELLIPSIS | 0) expr=expr -> functionCallParameterListElement ;; #element=assignmentListElement @COMMA -> assignmentList ;; variable=variable | LIST LPAREN assignmentList=assignmentList RPAREN | 0 -> assignmentListElement ;; expr=expr (DOUBLE_ARROW (exprValue=expr | BIT_AND varValue=variable) | 0) | BIT_AND variable=variable -> arrayPairValue ;; var=baseVariableWithFunctionCalls (#variableProperties=variableObjectProperty*) -> variable ;; OBJECT_OPERATOR | PAAMAYIM_NEKUDOTAYIM -> objectOperator ;; ( ?[: LA(1).kind == Token_DOLLAR:] LBRACE variable=variable RBRACE | objectProperty=objectProperty ) (isFunctionCall=LPAREN parameterList=functionCallParameterList RPAREN arrayIndex=arrayIndexSpecifier* | 0) -> variableProperty ;; objectOperator variableProperty=variableProperty -> variableObjectProperty ;; OBJECT_OPERATOR variableProperty=variableProperty -> instantiationAccess ;; --Conflict -- foo::$bar[0] (=baseVariable-staticMember) --vs.foo::$bar[0](); (=static function call) try/rollback (functionCall=functionCall arrayIndex=arrayIndexSpecifier*) catch (baseVariable=baseVariable) -> baseVariableWithFunctionCalls ;; LBRACKET (expr=expr | 0) RBRACKET -> arrayIndexSpecifier ;; LBRACKET (expr=expr) RBRACKET -> stringIndexSpecifier ;; stringFunctionNameOrClass=namespacedIdentifier ( LPAREN stringParameterList=functionCallParameterList RPAREN | PAAMAYIM_NEKUDOTAYIM ( stringFunctionName=semiReservedIdentifier LPAREN stringParameterList=functionCallParameterList RPAREN | varFunctionName=variableWithoutObjects LPAREN stringParameterList=functionCallParameterList RPAREN | LBRACE (expr=expr) RBRACE LPAREN stringParameterList=functionCallParameterList RPAREN ) ) | varFunctionName=variableWithoutObjects LPAREN varParameterList=functionCallParameterList RPAREN -> functionCall ;; var=compoundVariableWithSimpleIndirectReference #offsetItems=dimListItem* | staticMember=staticMember -> baseVariable ;; variable=variableIdentifier | DOLLAR LBRACE expr=expr RBRACE -> compoundVariable ;; ( DOLLAR ( DOLLAR+ | 0 ) ( indirectVariable=variableIdentifier | LBRACE expr=expr RBRACE ) | variable=variableIdentifier ) -> compoundVariableWithSimpleIndirectReference ;; - className=namespacedIdentifier PAAMAYIM_NEKUDOTAYIM variable=variableWithoutObjects + className=namespacedIdentifier staticProperty=staticProperty -> staticMember ;; LBRACE try/recover(statements=innerStatementList) RBRACE | IF LPAREN ifExpr=expr RPAREN ( COLON statements=innerStatementList newElseifList newElseSingle ENDIF semicolonOrCloseTag | ifStatement=statement elseifList=elseifList elseSingle=elseSingle ) | WHILE LPAREN whileExpr=expr RPAREN whileStatement=whileStatement | FOR LPAREN forExpr1=forExpr SEMICOLON forExpr2=forExpr SEMICOLON forExpr3=forExpr RPAREN forStatement=forStatement | SWITCH LPAREN swtichExpr=expr RPAREN switchCaseList=switchCaseList | FOREACH LPAREN ( -- allow $var as &$i and not expr() as &$i try/rollback(foreachVar=variable AS foreachVarAsVar=foreachVariable) catch(foreachExpr=expr AS foreachExprAsVar=variable)) (DOUBLE_ARROW foreachVariable=foreachVariable | 0) RPAREN foreachStatement=foreachStatement | DECLARE LPAREN declareItem=declareItem @ COMMA RPAREN declareStatement | SEMICOLON -- empty statement | TRY LBRACE try/recover(statements=innerStatementList) RBRACE #catches=catchItem* (FINALLY LBRACE finallyBody=innerStatementList RBRACE | 0) | UNSET LPAREN #unsetVariables=variable @ COMMA RPAREN semicolonOrCloseTag -- fix first/follow with goto target | ( ?[: LA(1).kind != Token_STRING || LA(2).kind != Token_COLON :] expr=expr semicolonOrCloseTag ) | DO doStatement=statement WHILE LPAREN whileExpr=expr RPAREN semicolonOrCloseTag | BREAK (breakExpr=expr | 0) semicolonOrCloseTag | CONTINUE (continueExpr=expr | 0) semicolonOrCloseTag | RETURN (returnExpr=expr | 0) semicolonOrCloseTag | GLOBAL #globalVars=globalVar @ COMMA semicolonOrCloseTag | STATIC #staticVars=staticVar @ COMMA semicolonOrCloseTag | ECHO #echoExprs=expr @ COMMA semicolonOrCloseTag | THROW throwExpr=expr semicolonOrCloseTag -- throws error in zend parser, so ignored | USE use_filename semicolonOrCloseTag | CLOSE_TAG | OPEN_TAG | OPEN_TAG_WITH_ECHO expr=expr semicolonOrCloseTag | INLINE_HTML | CONST #consts=constantDeclaration @ COMMA SEMICOLON | USE #useNamespace=useNamespace @ COMMA SEMICOLON | GOTO gotoLabel=STRING SEMICOLON | gotoTarget=STRING COLON -> statement ;; identifier=namespacedIdentifier (AS aliasIdentifier=identifier | 0) -> useNamespace ;; identifier=identifier ASSIGN scalar=expr -> constantDeclaration ;; identifier=semiReservedIdentifier ASSIGN scalar=expr -> classConstantDeclaration ;; SEMICOLON | CLOSE_TAG -> semicolonOrCloseTag ;; LBRACE (SEMICOLON | 0) try/recover(caseList=caseList) RBRACE | COLON (SEMICOLON | 0) caseList=caseList ENDSWITCH semicolonOrCloseTag -> switchCaseList ;; #caseItems=case_item* -> caseList ;; CASE expr=expr (COLON | SEMICOLON) statements=innerStatementList | def=DEFAULT (COLON | SEMICOLON) statements=innerStatementList -> case_item ;; CATCH LPAREN catchClass=namespacedIdentifier var=variableIdentifier RPAREN LBRACE try/recover(statements=innerStatementList) RBRACE -> catchItem ;; statement=statement | COLON statements=innerStatementList ENDDECLARE semicolonOrCloseTag -> declareStatement ;; STRING ASSIGN scalar=staticScalar -> declareItem ;; (BIT_AND | 0) variable=variable -> foreachVariable ;; statement=statement | COLON statements=innerStatementList ENDFOREACH semicolonOrCloseTag -> foreachStatement ;; var=variableIdentifier (ASSIGN value=staticScalar | 0) -> staticVar ;; var=variableIdentifier | DOLLAR (dollarVar=variable | LBRACE expr=expr RBRACE) -> globalVar ;; #exprs=expr @ COMMA | 0 -> forExpr ;; statement=statement | COLON statements=innerStatementList ENDFOR semicolonOrCloseTag -> forStatement ;; statement=statement | COLON statements=innerStatementList ENDWHILE semicolonOrCloseTag -> whileStatement ;; --first/follow conflict; todo check if this is a problem #elseifListItem=elseifListItem* -> elseifList ;; ELSEIF LPAREN expr=expr RPAREN statement=statement -> elseifListItem ;; ELSE statement=statement | 0 -> elseSingle ;; #newElseifListItem=newelseifListItem* -> newElseifList ;; ELSEIF LPAREN expr=expr RPAREN COLON statements=innerStatementList -> newelseifListItem ;; ELSE COLON statements=innerStatementList | 0 -> newElseSingle ;; ---TODO --resolve STRING vs. staticMember conflict --- ?[: LA(2).kind != Token_PAAMAYIM_NEKUDOTAYIM :] - identifier=namespacedIdentifier - | staticIdentifier = STATIC - | dynamicClassNameReference=dynamicClassNameReference + className=className (staticProperty=staticProperty #properties=classProperty* | 0) + | baseVariable=variableWithoutObjects #properties=classProperty* -> classNameReference ;; - baseVariable=baseVariable (OBJECT_OPERATOR objectProperty=objectProperty - properties=dynamicClassNameVariableProperties | 0) --> dynamicClassNameReference ;; + identifier=namespacedIdentifier + | staticIdentifier = STATIC +-> className ;; - #properties=dynamicClassNameVariableProperty* --> dynamicClassNameVariableProperties ;; + PAAMAYIM_NEKUDOTAYIM staticProperty=compoundVariableWithSimpleIndirectReference #offsetItems=dimListItem* +-> staticProperty ;; - OBJECT_OPERATOR property=objectProperty --> dynamicClassNameVariableProperty ;; + (staticProperty=staticProperty | OBJECT_OPERATOR property=objectProperty) +-> classProperty ;; objectDimList=objectDimList | variableWithoutObjects=variableWithoutObjects -> objectProperty ;; variableName=variableName #offsetItems=dimListItem* -> objectDimList ;; variable=compoundVariableWithSimpleIndirectReference #offsetItems=dimListItem* -> variableWithoutObjects ;; arrayIndex=arrayIndexSpecifier | LBRACE expr=expr RBRACE -> dimListItem ;; name=identifier | LBRACE expr=expr RBRACE -> variableName ;; commonScalar=commonScalar | constantOrClassConst=constantOrClassConst | varname=STRING_VARNAME | DOUBLE_QUOTE encapsList=encapsList DOUBLE_QUOTE stringIndex=stringIndexSpecifier* | START_HEREDOC encapsList=encapsList END_HEREDOC -> scalar ;; constant=namespacedIdentifier ( PAAMAYIM_NEKUDOTAYIM classConstant=classConstant | 0 ) -> constantOrClassConst ;; semiReservedIdentifier -> classConstant ;; #encaps=encaps* -> encapsList ;; var=encapsVar | value=ENCAPSED_AND_WHITESPACE -> encaps ;; -- first/first conflict resolved by LA(2) --(expr allows STRING_VARNAME too - but without [expr]) DOLLAR_OPEN_CURLY_BRACES ( ?[: LA(2).kind == Token_LBRACKET:] STRING_VARNAME arrayIndex=arrayIndexSpecifier RBRACE | expr=expr RBRACE ) | variable=variableIdentifier (OBJECT_OPERATOR propertyIdentifier=identifier | LBRACKET offset=encapsVarOffset RBRACKET | 0) | CURLY_OPEN expr=expr RBRACE -> encapsVar ;; STRING | NUM_STRING | variableIdentifier -> encapsVarOffset ;; LNUMBER [: (*yynode)->scalarType = ScalarTypeInt; :] | DNUMBER [: (*yynode)->scalarType = ScalarTypeFloat; :] | string=CONSTANT_ENCAPSED_STRING [: (*yynode)->scalarType = ScalarTypeString; :] stringIndex=stringIndexSpecifier* | LINE [: (*yynode)->scalarType = ScalarTypeInt; :] | DIR [: (*yynode)->scalarType = ScalarTypeString; :] | FILE [: (*yynode)->scalarType = ScalarTypeString; :] | CLASS_C [: (*yynode)->scalarType = ScalarTypeString; :] | TRAIT_C [: (*yynode)->scalarType = ScalarTypeString; :] | METHOD_C [: (*yynode)->scalarType = ScalarTypeString; :] | FUNC_C [: (*yynode)->scalarType = ScalarTypeString; :] | NAMESPACE_C [: (*yynode)->scalarType = ScalarTypeString; :] | START_NOWDOC STRING END_NOWDOC [: (*yynode)->scalarType = ScalarTypeString; :] -> commonScalar [ member variable scalarType: ScalarTypes; ] ;; FUNCTION (BIT_AND | 0) functionName=identifier LPAREN parameters=parameterList RPAREN (COLON returnType=returnType | 0) LBRACE try/recover(functionBody=innerStatementList) RBRACE -> functionDeclarationStatement ;; (#parameters=parameter @ COMMA) | 0 -> parameterList ;; (parameterType=parameterType | 0) (isRef=BIT_AND | 0) (isVariadic=ELLIPSIS | 0) variable=variableIdentifier (ASSIGN defaultValue=expr | 0) -> parameter ;; genericType=namespacedIdentifier | arrayType=ARRAY | callableType=CALLABLE -> genericTypeHint ;; (isNullable=QUESTION | 0) typehint=genericTypeHint -> parameterType ;; (isNullable=QUESTION | 0) ( typehint=genericTypeHint | voidType=VOID ) -> returnType ;; value=commonScalar | constantOrClassConst=constantOrClassConst | PLUS plusValue=staticScalar | MINUS minusValue=staticScalar | array=ARRAY LPAREN (#arrayValues=staticArrayPairValue -- break because array(1,) is allowed @ (COMMA [: if (yytoken == Token_RPAREN) { break; } :] ) | 0) RPAREN | array=LBRACKET (#arrayValues=staticArrayPairValue -- break because [1,] is allowed @ (COMMA [: if (yytoken == Token_RBRACKET) { break; } :] ) | 0) RBRACKET -> staticScalar ;; #val1=staticScalar (DOUBLE_ARROW #val2=staticScalar | 0) -> staticArrayPairValue ;; (isGlobal=BACKSLASH | 0) #namespaceName=identifier+ @ BACKSLASH -> namespacedIdentifier ;; string=STRING -> identifier ;; INCLUDE | INCLUDE_ONCE | EVAL | REQUIRE | REQUIRE_ONCE | LOGICAL_OR | LOGICAL_XOR | LOGICAL_AND | INSTANCEOF | NEW | CLONE | EXIT | IF | ELSEIF | ELSE | ENDIF | ECHO | DO | WHILE | ENDWHILE | FOR | ENDFOR | FOREACH | ENDFOREACH | DECLARE | ENDDECLARE | AS | TRY | CATCH | FINALLY | THROW | USE | INSTEADOF | GLOBAL | VAR | UNSET | ISSET | EMPTY | CONTINUE | GOTO | FUNCTION | CONST | RETURN | PRINT | YIELD | LIST | SWITCH | ENDSWITCH | CASE | DEFAULT | BREAK | ARRAY | CALLABLE | EXTENDS | IMPLEMENTS | NAMESPACE | TRAIT | INTERFACE | CLASS | CLASS_C | TRAIT_C | FUNC_C | METHOD_C | LINE | FILE | DIR | NAMESPACE_C -> reservedNonModifiers ;; reservedNonModifiers | STATIC | ABSTRACT | FINAL | PRIVATE | PROTECTED | PUBLIC -> semiReserved ;; identifier [: qint64 index = tokenStream->index() - 2; (*yynode)->string = index; :] | semiReserved [: qint64 index = tokenStream->index() - 2; (*yynode)->string = index; :] -> semiReservedIdentifier [ member variable string: qint64; ] ;; identifier [: qint64 index = tokenStream->index() - 2; (*yynode)->string = index; :] | reservedNonModifiers [: qint64 index = tokenStream->index() - 2; (*yynode)->string = index; :] -> reservedNonModifierIdentifier [ member variable string: qint64; ] ;; variable=VARIABLE -> variableIdentifier ;; NAMESPACE #namespaceName=identifier* @ BACKSLASH ( -- the semicolon case needs at least one namespace identifier, the {...} case not... SEMICOLON [: if (!(*yynode)->namespaceNameSequence) { reportProblem(Error, QStringLiteral("Missing namespace identifier."), -2); } :] | LBRACE try/recover(body=innerStatementList) RBRACE ) -> namespaceDeclarationStatement ;; INTERFACE interfaceName=identifier (EXTENDS extends=classImplements | 0) LBRACE try/recover(body=classBody) RBRACE -> interfaceDeclarationStatement ;; TRAIT traitName=identifier LBRACE body=classBody RBRACE -> traitDeclarationStatement ;; modifier=optionalClassModifier CLASS className=identifier (EXTENDS extends=classExtends | 0) (IMPLEMENTS implements=classImplements | 0) LBRACE body=classBody RBRACE -> classDeclarationStatement ;; identifier=namespacedIdentifier -> classExtends ;; #implements=namespacedIdentifier @ COMMA -> classImplements ;; -- error recovery, to understand it you probably have to look at the generated code ;-) [: bool reported = false; while ( true ) { :] try/recover(#classStatements=classStatement)* [: if (yytoken != Token_RBRACE && yytoken != Token_EOF && yytoken != Token_CLOSE_TAG) { if (!reported) { reportProblem(Error, QStringLiteral("Unexpected token in class context.")); reported = true; } yylex(); } else { break; } } :] RBRACE [: rewind(tokenStream->index() - 2); :] -> classBody ;; CONST #consts=classConstantDeclaration @ COMMA SEMICOLON | VAR variable=classVariableDeclaration SEMICOLON | modifiers=optionalModifiers ( variable=classVariableDeclaration SEMICOLON | FUNCTION (BIT_AND | 0) methodName=semiReservedIdentifier LPAREN parameters=parameterList RPAREN ( COLON returnType=returnType | 0) methodBody=methodBody ) | USE #traits=namespacedIdentifier @ COMMA (imports=traitAliasDeclaration|SEMICOLON) -> classStatement ;; LBRACE #statements=traitAliasStatement @ (SEMICOLON [: if (yytoken == Token_RBRACE) { break; } :]) RBRACE -> traitAliasDeclaration ;; importIdentifier=traitAliasIdentifier -- first/first conflict resolved by LA(2) -- We can either have a single token (modifier or identifier), or a combination ( AS (?[: LA(2).kind == Token_SEMICOLON :] (modifiers=traitVisibilityModifiers | aliasNonModifierIdentifier=reservedNonModifierIdentifier) | modifiers=traitVisibilityModifiers aliasIdentifier=semiReservedIdentifier ) | INSTEADOF #conflictIdentifier=namespacedIdentifier @ COMMA ) -> traitAliasStatement ;; identifier=namespacedIdentifier PAAMAYIM_NEKUDOTAYIM methodIdentifier=semiReservedIdentifier -> traitAliasIdentifier ;; SEMICOLON -- abstract method | LBRACE try/recover(statements=innerStatementList) RBRACE -> methodBody ;; #vars=classVariable @ COMMA -> classVariableDeclaration ;; variable=variableIdentifier (ASSIGN value=staticScalar | 0) -> classVariable ;; PUBLIC [: (*yynode)->modifiers |= ModifierPublic; :] | PROTECTED [: (*yynode)->modifiers |= ModifierProtected; :] | PRIVATE [: (*yynode)->modifiers |= ModifierPrivate; :] | STATIC [: (*yynode)->modifiers |= ModifierStatic; :] | ABSTRACT [: (*yynode)->modifiers |= ModifierAbstract; :] | FINAL [: (*yynode)->modifiers |= ModifierFinal; :] -> traitVisibilityModifiers[ member variable modifiers: unsigned int; ] ;; ( PUBLIC [: (*yynode)->modifiers |= ModifierPublic; :] | PROTECTED [: (*yynode)->modifiers |= ModifierProtected; :] | PRIVATE [: (*yynode)->modifiers |= ModifierPrivate; :] | STATIC [: (*yynode)->modifiers |= ModifierStatic; :] | ABSTRACT [: (*yynode)->modifiers |= ModifierAbstract; :] | FINAL [: (*yynode)->modifiers |= ModifierFinal; :] | 0 )* -> optionalModifiers[ member variable modifiers: unsigned int; ] ;; ( ABSTRACT [: (*yynode)->modifier = AbstractClass; :] | FINAL [: (*yynode)->modifier = FinalClass; :] | 0 ) -> optionalClassModifier[ member variable modifier: ClassModifier; ] ;; ----------------------------------------------------------------- -- Code segments copied to the implementation (.cpp) file. -- If existent, kdevelop-pg's current syntax requires this block -- to occur at the end of the file. ----------------------------------------------------------------- [: #include #include namespace Php { void Parser::tokenize(const QString& contents, int initialState) { m_contents = contents; Lexer lexer(tokenStream, contents, initialState); int kind = Parser::Token_EOF; int lastDocCommentBegin; int lastDocCommentEnd; do { lastDocCommentBegin = 0; lastDocCommentEnd = 0; kind = lexer.nextTokenKind(); while (kind == Parser::Token_WHITESPACE || kind == Parser::Token_COMMENT || kind == Parser::Token_DOC_COMMENT) { if (kind == Parser::Token_COMMENT || kind == Parser::Token_DOC_COMMENT) { extractTodosFromComment(tokenText(lexer.tokenBegin(), lexer.tokenEnd()), lexer.tokenBegin()); } if (kind == Parser::Token_DOC_COMMENT) { lastDocCommentBegin = lexer.tokenBegin(); lastDocCommentEnd = lexer.tokenEnd(); } kind = lexer.nextTokenKind(); } if ( !kind ) // when the lexer returns 0, the end of file is reached { kind = Parser::Token_EOF; } Parser::Token &t = tokenStream->push(); t.begin = lexer.tokenBegin(); t.end = lexer.tokenEnd(); t.kind = kind; t.docCommentBegin = lastDocCommentBegin; t.docCommentEnd = lastDocCommentEnd; //if ( m_debug ) qDebug() << kind << tokenText(t.begin,t.end) << t.begin << t.end; } while ( kind != Parser::Token_EOF ); yylex(); // produce the look ahead token } void Parser::extractTodosFromComment(const QString& comment, qint64 startPosition) { auto i = m_todoMarkers.globalMatch(comment); while (i.hasNext()) { auto match = i.next(); auto p = reportProblem(Todo, match.captured(1), 0); if (!p) { continue; } qint64 line = 0; qint64 column = 0; tokenStream->locationTable()->positionAt(startPosition, &line, &column); auto location = p->finalLocation(); location.setStart(KTextEditor::Cursor(line, column + match.capturedStart(1))); location.setEnd(KTextEditor::Cursor(line, column + match.capturedEnd(1))); p->setFinalLocation(location); }; } void Parser::setTodoMarkers(const QStringList& markers) { QString pattern = QStringLiteral("^(?:[/\\*\\s]*)(.*(?:"); bool first = true; foreach(const QString& marker, markers) { if (!first) { pattern += '|'; } pattern += QRegularExpression::escape(marker); first = false; } pattern += QStringLiteral(").*?)(?:[/\\*\\s]*)$"); m_todoMarkers.setPatternOptions(QRegularExpression::MultilineOption); m_todoMarkers.setPattern(pattern); } QString Parser::tokenText(qint64 begin, qint64 end) { return m_contents.mid(begin,end-begin+1); } KDevelop::ProblemPointer Parser::reportProblem( Parser::ProblemType type, const QString& message, int offset ) { qint64 sLine; qint64 sCol; qint64 index = tokenStream->index() + offset; if (index >= tokenStream->size()) { return {}; } tokenStream->startPosition(index, &sLine, &sCol); qint64 eLine; qint64 eCol; tokenStream->endPosition(index, &eLine, &eCol); auto p = KDevelop::ProblemPointer(new KDevelop::Problem()); p->setSource(KDevelop::IProblem::Parser); switch ( type ) { case Error: p->setSeverity(KDevelop::IProblem::Error); break; case Warning: p->setSeverity(KDevelop::IProblem::Warning); break; case Info: p->setSeverity(KDevelop::IProblem::Hint); break; case Todo: p->setSeverity(KDevelop::IProblem::Hint); p->setSource(KDevelop::IProblem::ToDo); break; } p->setDescription(message); KTextEditor::Range range(sLine, sCol, eLine, eCol + 1); p->setFinalLocation(KDevelop::DocumentRange(m_currentDocument, range)); m_problems << p; return p; } // custom error recovery void Parser::expectedToken(int /*expected*/, qint64 /*where*/, const QString& name) { reportProblem( Parser::Error, QStringLiteral("Expected token \"%1\"").arg(name)); } void Parser::expectedSymbol(int /*expectedSymbol*/, const QString& name) { qint64 line; qint64 col; qint64 index = tokenStream->index()-1; Token &token = tokenStream->at(index); qCDebug(PARSER) << "token starts at:" << token.begin; qCDebug(PARSER) << "index is:" << index; tokenStream->startPosition(index, &line, &col); QString tokenValue = tokenText(token.begin, token.end); qint64 eLine; qint64 eCol; tokenStream->endPosition(index, &eLine, &eCol); reportProblem( Parser::Error, QStringLiteral("Expected symbol \"%1\" (current token: \"%2\" [%3] at %4:%5 - %6:%7)") .arg(name, token.kind != 0 ? tokenValue : QStringLiteral("EOF")) .arg(token.kind) .arg(line) .arg(col) .arg(eLine) .arg(eCol)); } void Parser::setDebug( bool debug ) { m_debug = debug; } void Parser::setCurrentDocument(KDevelop::IndexedString url) { m_currentDocument = url; } Parser::ParserState *Parser::copyCurrentState() { ParserState *state = new ParserState(); state->varExpressionState = m_state.varExpressionState; state->varExpressionIsVariable = m_state.varExpressionIsVariable; return state; } void Parser::restoreState( Parser::ParserState* state) { m_state.varExpressionState = state->varExpressionState; m_state.varExpressionIsVariable = state->varExpressionIsVariable; } } // end of namespace Php :] -- kate: space-indent on; indent-width 4; tab-width 4; replace-tabs on; auto-insert-doxygen on; mode KDevelop-PG[-Qt] diff --git a/testprovider/kdevphpunitprovider.json b/testprovider/kdevphpunitprovider.json index 68a977d..c1f79d5 100644 --- a/testprovider/kdevphpunitprovider.json +++ b/testprovider/kdevphpunitprovider.json @@ -1,61 +1,55 @@ { "KPlugin": { "Authors": [ { "Name": "Miha Čančula", "Name[x-test]": "xxMiha Čančulaxx" } ], "Category": "Testing", "Description": "Finds and runs PHPUnit tests.", "Description[ca@valencia]": "Cerca i executa proves del PHPUnit.", "Description[ca]": "Cerca i executa proves del PHPUnit.", "Description[cs]": "Hledá a spouští testy PHPUnit.", "Description[de]": "Sucht und führt PHPUnit-Tests aus.", - "Description[es]": "Encuentra y ejecuta pruebas PHPUnit.", "Description[fr]": "Trouve et exécute des tests « PHPUnit ».", - "Description[gl]": "Atopa e executa probas de PHPUnit.", "Description[it]": "Trova ed esegue i test PHPUnit.", "Description[nl]": "Testen van PHPUnit vinden en uitvoeren.", "Description[pl]": "Znajduje i uruchamia testy PHPUnit.", "Description[pt]": "Procura e executa os testes do PHPUnit.", "Description[pt_BR]": "Procura e executa os testes do PHPUnit.", - "Description[sl]": "Najde in zažene preizkuse PHPUnit", "Description[sv]": "Söker efter och kör PHPUnit-tester.", "Description[uk]": "Знаходить і запускає перевірки PHPUnit.", "Description[x-test]": "xxFinds and runs PHPUnit tests.xx", "Description[zh_CN]": "查找并运行 PHPUnit 测试", "Icon": "application-x-php", "Id": "kdevphpunitprovider", "License": "GPL", "Name": "PHPUnit Integration", "Name[ca@valencia]": "Integració del PHPUnit", "Name[ca]": "Integració del PHPUnit", "Name[cs]": "Integrace PHPUnit", "Name[de]": "PHPUnit-Integration", - "Name[es]": "Integración de PHPUnit", "Name[fr]": "Intégration de « PHPUnit »", - "Name[gl]": "Integración con PHPUnit", "Name[it]": "Integrazione PHPUnit", "Name[nl]": "Integratie van PHPUnit", "Name[pl]": "Integracja PHPUnit", "Name[pt]": "Integração com o PHPUnit", "Name[pt_BR]": "Integração com o PHPUnit", - "Name[sl]": "Podpora PHPUnit", "Name[sv]": "Integrering av PHPUnit", "Name[uk]": "Інтеграція з PHPUnit", "Name[x-test]": "xxPHPUnit Integrationxx", "Name[zh_CN]": "PHPUnit 集成", "ServiceTypes": [ "KDevelop/Plugin" ] }, "X-KDevelop-Category": "Global", "X-KDevelop-IRequired": [ "org.kdevelop.PHPSupport" ], "X-KDevelop-Interfaces": [ "org.kdevelop.ITestProvider" ], "X-KDevelop-Mode": "GUI" }