diff --git a/duchain/builders/declarationbuilder.cpp b/duchain/builders/declarationbuilder.cpp index 1d7e054..8b3d843 100644 --- a/duchain/builders/declarationbuilder.cpp +++ b/duchain/builders/declarationbuilder.cpp @@ -1,1460 +1,1505 @@ /*************************************************************************** * 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 "declarationbuilder.h" #include #include #include #include #include #include #include #include #include #include "../declarations/variabledeclaration.h" #include "../declarations/classmethoddeclaration.h" #include "../declarations/classdeclaration.h" #include "../declarations/functiondeclaration.h" #include "../declarations/namespacedeclaration.h" #include "../declarations/namespacealiasdeclaration.h" #include "../declarations/traitmethodaliasdeclaration.h" #include "../declarations/traitmemberaliasdeclaration.h" #include "../parser/phpast.h" #include "../parser/parsesession.h" #include "../helper.h" #include "../duchaindebug.h" #include "../expressionvisitor.h" #include "predeclarationbuilder.h" #define ifDebug(x) using namespace KDevelop; namespace Php { DeclarationBuilder::FindVariableResults::FindVariableResults() : find(true) , isArray(false) , node(0) { } void DeclarationBuilder::getVariableIdentifier(VariableAst* node, QualifiedIdentifier &identifier, QualifiedIdentifier &parent, AstNode* &targetNode, bool &arrayAccess) { parent = QualifiedIdentifier(); if ( node->variablePropertiesSequence ) { // at least one "->" in the assigment target // => find he parent of the target // => find the target (last object property) if ( node->variablePropertiesSequence->count() == 1 ) { // $parent->target ///TODO: $parent[0]->target = ... (we don't know the type of [0] yet, need proper array handling first) if ( node->var && node->var->baseVariable && node->var->baseVariable->var && !node->var->baseVariable->offsetItemsSequence ) { parent = identifierForNode( node->var->baseVariable->var->variable ); } } else { // $var->...->parent->target ///TODO: $var->...->parent[0]->target = ... (we don't know the type of [0] yet, need proper array handling first) const KDevPG::ListNode< VariableObjectPropertyAst* >* parentNode = node->variablePropertiesSequence->at( node->variablePropertiesSequence->count() - 2 ); if ( parentNode->element && parentNode->element->variableProperty && parentNode->element->variableProperty->objectProperty && parentNode->element->variableProperty->objectProperty->objectDimList && parentNode->element->variableProperty->objectProperty->objectDimList->variableName && !parentNode->element->variableProperty->objectProperty->objectDimList->offsetItemsSequence ) { parent = identifierForNode( parentNode->element->variableProperty->objectProperty->objectDimList->variableName->name ); } } if ( !parent.isEmpty() ) { const KDevPG::ListNode< VariableObjectPropertyAst* >* tNode = node->variablePropertiesSequence->at( node->variablePropertiesSequence->count() - 1 ); if ( tNode->element && tNode->element->variableProperty && tNode->element->variableProperty->objectProperty && tNode->element->variableProperty->objectProperty->objectDimList && tNode->element->variableProperty->objectProperty->objectDimList->variableName ) { arrayAccess = (bool) tNode->element->variableProperty->objectProperty->objectDimList->offsetItemsSequence; identifier = identifierForNode( tNode->element->variableProperty->objectProperty->objectDimList->variableName->name ); targetNode = tNode->element->variableProperty->objectProperty->objectDimList->variableName->name; } } } else { // simple assignment to $var if ( node->var && node->var->baseVariable && node->var->baseVariable->var ) { arrayAccess = (bool) node->var->baseVariable->offsetItemsSequence; identifier = identifierForNode( node->var->baseVariable->var->variable ); targetNode = node->var->baseVariable->var->variable; } } } ReferencedTopDUContext DeclarationBuilder::build(const IndexedString& url, AstNode* node, ReferencedTopDUContext updateContext) { //Run DeclarationBuilder twice, to find uses of declarations that are //declared after the use. ($a = new Foo; class Foo {}) { PreDeclarationBuilder prebuilder(&m_types, &m_functions, &m_namespaces, &m_upcomingClassVariables, m_editor); updateContext = prebuilder.build(url, node, updateContext); m_actuallyRecompiling = prebuilder.didRecompile(); } // now skip through some things the DeclarationBuilderBase (ContextBuilder) would do, // most significantly don't clear imported parent contexts m_isInternalFunctions = url == internalFunctionFile(); if ( m_isInternalFunctions ) { m_reportErrors = false; } else if ( ICore::self() ) { m_reportErrors = ICore::self()->languageController()->completionSettings()->highlightSemanticProblems(); } return ContextBuilderBase::build(url, node, updateContext); } void DeclarationBuilder::startVisiting(AstNode* node) { setRecompiling(m_actuallyRecompiling); setCompilingContexts(false); DeclarationBuilderBase::startVisiting(node); } void DeclarationBuilder::closeDeclaration() { if (currentDeclaration() && lastType()) { DUChainWriteLocker lock(DUChain::lock()); currentDeclaration()->setType(lastType()); } eventuallyAssignInternalContext(); DeclarationBuilderBase::closeDeclaration(); } void DeclarationBuilder::classContextOpened(DUContext* context) { DUChainWriteLocker lock(DUChain::lock()); currentDeclaration()->setInternalContext(context); } void DeclarationBuilder::visitClassDeclarationStatement(ClassDeclarationStatementAst * node) { ClassDeclaration* classDec = openTypeDeclaration(node->className, ClassDeclarationData::Class); openType(classDec->abstractType()); DeclarationBuilderBase::visitClassDeclarationStatement(node); { DUChainWriteLocker lock; classDec->updateCompletionCodeModelItem(); } closeType(); closeDeclaration(); m_upcomingClassVariables.clear(); } void DeclarationBuilder::visitInterfaceDeclarationStatement(InterfaceDeclarationStatementAst *node) { ClassDeclaration* interfaceDec = openTypeDeclaration(node->interfaceName, ClassDeclarationData::Interface); openType(interfaceDec->abstractType()); DeclarationBuilderBase::visitInterfaceDeclarationStatement(node); closeType(); closeDeclaration(); } void DeclarationBuilder::visitTraitDeclarationStatement(TraitDeclarationStatementAst * node) { ClassDeclaration* traitDec = openTypeDeclaration(node->traitName, ClassDeclarationData::Trait); openType(traitDec->abstractType()); DeclarationBuilderBase::visitTraitDeclarationStatement(node); closeType(); closeDeclaration(); m_upcomingClassVariables.clear(); } ClassDeclaration* DeclarationBuilder::openTypeDeclaration(IdentifierAst* name, ClassDeclarationData::ClassType type) { ClassDeclaration* classDec = m_types.value(name->string, 0); Q_ASSERT(classDec); isGlobalRedeclaration(identifierForNode(name), name, ClassDeclarationType); Q_ASSERT(classDec->classType() == type); Q_UNUSED(type); // seems like we have to do that manually, else the usebuilder crashes... setEncountered(classDec); openDeclarationInternal(classDec); return classDec; } bool DeclarationBuilder::isBaseMethodRedeclaration(const IdentifierPair &ids, ClassDeclaration *curClass, ClassStatementAst *node) { DUChainWriteLocker lock(DUChain::lock()); while (curClass->baseClassesSize() > 0) { StructureType::Ptr type; FOREACH_FUNCTION(const BaseClassInstance& base, curClass->baseClasses) { DUChainReadLocker lock(DUChain::lock()); type = base.baseClass.type(); if (!type) { continue; } ClassDeclaration *nextClass = dynamic_cast(type->declaration(currentContext()->topContext())); if (!nextClass || nextClass->classType() != ClassDeclarationData::Class) { type.reset(); continue; } curClass = nextClass; break; } if (!type) { break; } { if (!type->internalContext(currentContext()->topContext())) { continue; } foreach(Declaration * dec, type->internalContext(currentContext()->topContext())->findLocalDeclarations(ids.second.first(), startPos(node))) { if (dec->isFunctionDeclaration()) { ClassMethodDeclaration* func = dynamic_cast(dec); if (!func || !wasEncountered(func)) { continue; } // we cannot redeclare final classes ever if (func->isFinal()) { reportRedeclarationError(dec, node->methodName); return true; } // also we may not redeclare an already abstract method, we would have to implement it // TODO: original error message? // -> Can't inherit abstract function class::func() (previously declared in otherclass) else if (func->isAbstract() && node->modifiers->modifiers & ModifierAbstract) { reportRedeclarationError(dec, node->methodName); return true; } } } } } return false; } void DeclarationBuilder::visitClassStatement(ClassStatementAst *node) { setComment(formatComment(node, m_editor)); ClassDeclaration *parent = dynamic_cast(currentDeclaration()); Q_ASSERT(parent); if (node->methodName) { //method declaration IdentifierPair ids = identifierPairForNode(node->methodName); if (m_reportErrors) { // check for redeclarations Q_ASSERT(currentContext()->type() == DUContext::Class); bool localError = false; { DUChainWriteLocker lock(DUChain::lock()); foreach(Declaration * dec, currentContext()->findLocalDeclarations(ids.second.first(), startPos(node->methodName))) { if (wasEncountered(dec) && dec->isFunctionDeclaration() && !dynamic_cast(dec)) { reportRedeclarationError(dec, node->methodName); localError = true; break; } } } if (!localError) { // if we have no local error, check that we don't try to overwrite a final method of a baseclass isBaseMethodRedeclaration(ids, parent, node); } } { DUChainWriteLocker lock(DUChain::lock()); ClassMethodDeclaration* dec = openDefinition(ids.second, editorFindRange(node->methodName, node->methodName)); dec->setPrettyName(ids.first); dec->clearDefaultParameters(); dec->setKind(Declaration::Type); if (node->modifiers->modifiers & ModifierPublic) { dec->setAccessPolicy(Declaration::Public); } else if (node->modifiers->modifiers & ModifierProtected) { dec->setAccessPolicy(Declaration::Protected); } else if (node->modifiers->modifiers & ModifierPrivate) { dec->setAccessPolicy(Declaration::Private); } if (node->modifiers->modifiers & ModifierStatic) { dec->setStatic(true); } if (parent->classType() == ClassDeclarationData::Interface) { if (m_reportErrors) { if (node->modifiers->modifiers & ModifierFinal || node->modifiers->modifiers & ModifierAbstract) { reportError(i18n("Access type for interface method %1 must be omitted.", dec->toString()), node->modifiers); } if (!isEmptyMethodBody(node->methodBody)) { reportError(i18n("Interface function %1 cannot contain body.", dec->toString()), node->methodBody); } } // handle interface methods like abstract methods dec->setIsAbstract(true); } else { if (node->modifiers->modifiers & ModifierAbstract) { if (!m_reportErrors) { dec->setIsAbstract(true); } else { if (parent->classModifier() != ClassDeclarationData::Abstract && parent->classType() != ClassDeclarationData::Trait) { reportError(i18n("Class %1 contains abstract method %2 and must therefore be declared abstract " "or implement the method.", parent->identifier().toString(), dec->identifier().toString()), node->modifiers); } else if (!isEmptyMethodBody(node->methodBody)) { reportError(i18n("Abstract function %1 cannot contain body.", dec->toString()), node->methodBody); } else if (node->modifiers->modifiers & ModifierFinal) { reportError(i18n("Cannot use the final modifier on an abstract class member."), node->modifiers); } else { dec->setIsAbstract(true); } } } else if (node->modifiers->modifiers & ModifierFinal) { dec->setIsFinal(true); } if (m_reportErrors && !dec->isAbstract() && isEmptyMethodBody(node->methodBody)) { reportError(i18n("Non-abstract method %1 must contain body.", dec->toString()), node->methodBody); } } } DeclarationBuilderBase::visitClassStatement(node); closeDeclaration(); } else if (node->traitsSequence) { DeclarationBuilderBase::visitClassStatement(node); importTraitMethods(node); } else { if (node->modifiers) { m_currentModifers = node->modifiers->modifiers; if (m_reportErrors) { // have to report the errors here to get a good problem range if (m_currentModifers & ModifierFinal) { reportError(i18n("Properties cannot be declared final."), node->modifiers); } if (m_currentModifers & ModifierAbstract) { reportError(i18n("Properties cannot be declared abstract."), node->modifiers); } } } else { m_currentModifers = 0; } DeclarationBuilderBase::visitClassStatement(node); m_currentModifers = 0; } } void DeclarationBuilder::importTraitMethods(ClassStatementAst *node) { // Add trait members that don't need special handling const KDevPG::ListNode< NamespacedIdentifierAst* >* it = node->traitsSequence->front(); DUChainWriteLocker lock; forever { DeclarationPointer dec = findDeclarationImport(ClassDeclarationType, identifierForNamespace(it->element, m_editor)); if (!dec || !dec->internalContext()) { break; } QVector declarations = dec.data()->internalContext()->localDeclarations(0); QVector localDeclarations = currentContext()->localDeclarations(0); ifDebug(qCDebug(DUCHAIN) << "Importing from" << dec.data()->identifier().toString() << "to" << currentContext()->localScopeIdentifier().toString();) foreach (Declaration* import, declarations) { Declaration* found = nullptr; foreach (Declaration* local, localDeclarations) { ifDebug(qCDebug(DUCHAIN) << "Comparing" << import->identifier().toString() << "with" << local->identifier().toString();) if (auto trait = dynamic_cast(local)) { if (trait->aliasedDeclaration().data() == import) { ifDebug(qCDebug(DUCHAIN) << "Already imported";) found = local; break; } if (local->identifier() == import->identifier()) { ClassMethodDeclaration* importMethod = dynamic_cast(import); if (trait->isOverriding(import->context()->indexedLocalScopeIdentifier())) { ifDebug(qCDebug(DUCHAIN) << "Is overridden";) found = local; break; } else if (importMethod) { reportError( i18n("Trait method %1 has not been applied, because there are collisions with other trait methods on %2") .arg(importMethod->prettyName().str(), dynamic_cast(currentDeclaration())->prettyName().str()) , it->element, IProblem::Error ); found = local; break; } } } if (auto trait = dynamic_cast(local)) { if (trait->aliasedDeclaration().data() == import) { ifDebug(qCDebug(DUCHAIN) << "Already imported";) found = local; break; } } if (local->identifier() == import->identifier()) { if (dynamic_cast(local) && dynamic_cast(import)) { found = local; break; } } } if (found) { setEncountered(found); continue; } ifDebug(qCDebug(DUCHAIN) << "Importing new declaration";) CursorInRevision cursor = m_editor->findRange(it->element).start; if (auto olddec = dynamic_cast(import)) { TraitMethodAliasDeclaration* newdec = openDefinition(olddec->qualifiedIdentifier(), RangeInRevision(cursor, cursor)); openAbstractType(olddec->abstractType()); newdec->setPrettyName(olddec->prettyName()); newdec->setAccessPolicy(olddec->accessPolicy()); newdec->setKind(Declaration::Type); newdec->setAliasedDeclaration(IndexedDeclaration(olddec)); newdec->setStatic(olddec->isStatic()); closeType(); closeDeclaration(); } else if (auto olddec = dynamic_cast(import)) { TraitMemberAliasDeclaration* newdec = openDefinition(olddec->qualifiedIdentifier(), RangeInRevision(cursor, cursor)); openAbstractType(olddec->abstractType()); newdec->setAccessPolicy(olddec->accessPolicy()); newdec->setKind(Declaration::Instance); newdec->setAliasedDeclaration(IndexedDeclaration(olddec)); newdec->setStatic(olddec->isStatic()); closeType(); closeDeclaration(); } } if ( it->hasNext() ) { it = it->next; } else { break; } } } void DeclarationBuilder::visitClassExtends(ClassExtendsAst *node) { addBaseType(node->identifier); } void DeclarationBuilder::visitClassImplements(ClassImplementsAst *node) { const KDevPG::ListNode *__it = node->implementsSequence->front(), *__end = __it; do { addBaseType(__it->element); __it = __it->next; } while (__it != __end); DeclarationBuilderBase::visitClassImplements(node); } void DeclarationBuilder::visitClassVariable(ClassVariableAst *node) { QualifiedIdentifier name = identifierForNode(node->variable); if (m_reportErrors) { // check for redeclarations DUChainWriteLocker lock(DUChain::lock()); Q_ASSERT(currentContext()->type() == DUContext::Class); foreach(Declaration * dec, currentContext()->findLocalDeclarations(name.first(), startPos(node))) { if (wasEncountered(dec) && !dec->isFunctionDeclaration() && !(dec->abstractType()->modifiers() & AbstractType::ConstModifier)) { reportRedeclarationError(dec, node); break; } } } openClassMemberDeclaration(node->variable, name); DeclarationBuilderBase::visitClassVariable(node); closeDeclaration(); } void DeclarationBuilder::openClassMemberDeclaration(AstNode* node, const QualifiedIdentifier &name) { DUChainWriteLocker lock(DUChain::lock()); // dirty hack: declarations of class members outside the class context would // make the class context encompass the newRange. This is not what we want. RangeInRevision oldRange = currentContext()->range(); RangeInRevision newRange = editorFindRange(node, node); openDefinition(name, newRange); ClassMemberDeclaration* dec = dynamic_cast(currentDeclaration()); Q_ASSERT(dec); if (m_currentModifers & ModifierPublic) { dec->setAccessPolicy(Declaration::Public); } else if (m_currentModifers & ModifierProtected) { dec->setAccessPolicy(Declaration::Protected); } else if (m_currentModifers & ModifierPrivate) { dec->setAccessPolicy(Declaration::Private); } if (m_currentModifers & ModifierStatic) { dec->setStatic(true); } dec->setKind(Declaration::Instance); currentContext()->setRange(oldRange); } void DeclarationBuilder::declareClassMember(DUContext *parentCtx, AbstractType::Ptr type, const QualifiedIdentifier& identifier, AstNode* node ) { if ( m_upcomingClassVariables.contains(identifier) ) { if (m_actuallyRecompiling) { DUChainWriteLocker lock; if (Declaration* dec = currentContext()->findDeclarationAt(startPos(node))) { if (dynamic_cast(dec)) { // invalidate declaration, it got added // see also bug https://bugs.kde.org/show_bug.cgi?id=241750 delete dec; } } } return; } DUChainWriteLocker lock(DUChain::lock()); // check for redeclaration of private or protected stuff { // only interesting context might be the class context when we are inside a method DUContext *ctx = currentContext()->parentContext(); foreach ( Declaration* dec, parentCtx->findDeclarations(identifier) ) { if ( ClassMemberDeclaration* cdec = dynamic_cast(dec) ) { if ( cdec->accessPolicy() == Declaration::Private && cdec->context() != ctx ) { reportError(i18n("Cannot redeclare private property %1 from this context.", cdec->toString()), node); return; } else if ( cdec->accessPolicy() == Declaration::Protected && cdec->context() != ctx && ( !ctx || !ctx->imports(cdec->context()) ) ) { reportError(i18n("Cannot redeclare protected property %1 from this context.", cdec->toString()), node); return; } if ( cdec->abstractType()->indexed() == type->indexed() ) { encounter(dec); return; } } } } // this member should be public and non-static m_currentModifers = ModifierPublic; injectContext(parentCtx); openClassMemberDeclaration(node, identifier); m_currentModifers = 0; //own closeDeclaration() that doesn't use lastType() currentDeclaration()->setType(type); eventuallyAssignInternalContext(); DeclarationBuilderBase::closeDeclaration(); closeInjectedContext(); } void DeclarationBuilder::visitConstantDeclaration(ConstantDeclarationAst *node) { if (m_reportErrors) { // Check for constants in traits if (isMatch(currentDeclaration(), ClassDeclarationType)) { ClassDeclaration *parent = dynamic_cast(currentDeclaration()); Q_ASSERT(parent); if (parent->classType() == ClassDeclarationData::Trait) { reportError(i18n("Traits cannot have constants."), node); } } // check for redeclarations DUChainWriteLocker lock(DUChain::lock()); foreach(Declaration * dec, currentContext()->findLocalDeclarations(identifierForNode(node->identifier).first(), startPos(node->identifier))) { if (wasEncountered(dec) && !dec->isFunctionDeclaration() && dec->abstractType()->modifiers() & AbstractType::ConstModifier) { reportRedeclarationError(dec, node->identifier); break; } } } ClassMemberDeclaration* dec = openDefinition(node->identifier, node->identifier); { DUChainWriteLocker lock(DUChain::lock()); dec->setAccessPolicy(Declaration::Public); dec->setStatic(true); dec->setKind(Declaration::Instance); } DeclarationBuilderBase::visitConstantDeclaration(node); closeDeclaration(); if ( m_reportErrors ) { // const class members may only be ints, floats, bools or strings bool badType = true; if ( IntegralType* type = fastCast(lastType().data()) ) { switch( type->dataType() ) { case IntegralType::TypeBoolean: case IntegralType::TypeFloat: case IntegralType::TypeInt: case IntegralType::TypeString: case IntegralType::TypeNull: badType = false; break; default: // every other type is a badType (see above) break; } } if ( badType ) { reportError(i18n("Only booleans, ints, floats and strings are allowed for class constants."), node->scalar); } } } void DeclarationBuilder::visitTraitAliasStatement(TraitAliasStatementAst *node) { DUChainWriteLocker lock; DeclarationPointer dec = findDeclarationImport(ClassDeclarationType, identifierForNamespace(node->importIdentifier->identifier, m_editor)); if (dec && dec.data()->internalContext()) { createTraitAliasDeclarations(node, dec); } lock.unlock(); DeclarationBuilderBase::visitTraitAliasStatement(node); } void DeclarationBuilder::createTraitAliasDeclarations(TraitAliasStatementAst *node, DeclarationPointer dec) { QualifiedIdentifier original = identifierPairForNode(node->importIdentifier->methodIdentifier).second; QList list = dec.data()->internalContext()->findLocalDeclarations(original.last(), dec.data()->internalContext()->range().start); QualifiedIdentifier alias; if (node->aliasIdentifier) { alias = identifierPairForNode(node->aliasIdentifier).second; } else { alias = original; } if (!list.isEmpty()) { ClassMethodDeclaration* olddec = dynamic_cast(list.first()); TraitMethodAliasDeclaration* newdec; // no existing declaration found, create one if (node->aliasIdentifier) { newdec = openDefinition(alias, m_editor->findRange(node->aliasIdentifier)); newdec->setPrettyName(identifierPairForNode(node->aliasIdentifier).first); newdec->setAccessPolicy(olddec->accessPolicy()); openAbstractType(olddec->abstractType()); if (node->modifiers) { if (node->modifiers->modifiers & ModifierPublic) { newdec->setAccessPolicy(Declaration::Public); } else if (node->modifiers->modifiers & ModifierProtected) { newdec->setAccessPolicy(Declaration::Protected); } else if (node->modifiers->modifiers & ModifierPrivate) { newdec->setAccessPolicy(Declaration::Private); } if (node->modifiers->modifiers & ModifierFinal) { reportError(i18n("Cannot use 'final' as method modifier"), node->modifiers, IProblem::Error); } if (node->modifiers->modifiers & ModifierStatic) { reportError(i18n("Cannot use 'static' as method modifier"), node->modifiers, IProblem::Error); } } } else { CursorInRevision cursor = m_editor->findRange(node->importIdentifier).start; newdec = openDefinition(alias, RangeInRevision(cursor, cursor)); newdec->setPrettyName(identifierPairForNode(node->importIdentifier->methodIdentifier).first); newdec->setAccessPolicy(olddec->accessPolicy()); openAbstractType(olddec->abstractType()); } newdec->setKind(Declaration::Type); newdec->setAliasedDeclaration(IndexedDeclaration(olddec)); newdec->setStatic(olddec->isStatic()); QVector ids; if (node->conflictIdentifierSequence) { const KDevPG::ListNode< NamespacedIdentifierAst* >* it = node->conflictIdentifierSequence->front(); forever { DeclarationPointer dec = findDeclarationImport(ClassDeclarationType, identifierForNamespace(it->element, m_editor)); if (dec) { ids.append(IndexedQualifiedIdentifier(dec.data()->qualifiedIdentifier())); } if ( it->hasNext() ) { it = it->next; } else { break; } } newdec->setOverrides(ids); } closeType(); closeDeclaration(); } } void DeclarationBuilder::visitParameter(ParameterAst *node) { AbstractFunctionDeclaration* funDec = dynamic_cast(currentDeclaration()); Q_ASSERT(funDec); if (node->defaultValue) { QString symbol = m_editor->parseSession()->symbol(node->defaultValue); funDec->addDefaultParameter(IndexedString(symbol)); if ( node->parameterType && symbol.compare(QLatin1String("null"), Qt::CaseInsensitive) != 0 ) { reportError(i18n("Default value for parameters with a class type hint can only be NULL."), node->defaultValue); } } else if ( !node->defaultValue && funDec->defaultParametersSize() ) { reportError(i18n("Following parameters must have a default value assigned."), node); } { // create variable declaration for argument DUChainWriteLocker lock(DUChain::lock()); RangeInRevision newRange = editorFindRange(node->variable, node->variable); openDefinition(identifierForNode(node->variable), newRange); currentDeclaration()->setKind(Declaration::Instance); } DeclarationBuilderBase::visitParameter(node); closeDeclaration(); } void DeclarationBuilder::visitFunctionDeclarationStatement(FunctionDeclarationStatementAst* node) { isGlobalRedeclaration(identifierForNode(node->functionName), node->functionName, FunctionDeclarationType); FunctionDeclaration* dec = m_functions.value(node->functionName->string, 0); Q_ASSERT(dec); // seems like we have to set that, else the usebuilder crashes DeclarationBuilderBase::setEncountered(dec); openDeclarationInternal(dec); openType(dec->abstractType()); DeclarationBuilderBase::visitFunctionDeclarationStatement(node); closeType(); closeDeclaration(); } void DeclarationBuilder::visitClosure(ClosureAst* node) { setComment(formatComment(node, editor())); { DUChainWriteLocker lock; FunctionDeclaration *dec = openDefinition(QualifiedIdentifier(), editor()->findRange(node->startToken)); dec->setKind(Declaration::Type); dec->clearDefaultParameters(); } DeclarationBuilderBase::visitClosure(node); closeDeclaration(); } void DeclarationBuilder::visitLexicalVar(LexicalVarAst* node) { DeclarationBuilderBase::visitLexicalVar(node); QualifiedIdentifier id = identifierForNode(node->variable); DUChainWriteLocker lock; if ( recompiling() ) { // sadly we can't use findLocalDeclarations() here, since it un-aliases declarations foreach ( Declaration* dec, currentContext()->localDeclarations() ) { if ( dynamic_cast(dec) && dec->identifier() == id.first() ) { // don't redeclare but reuse the existing declaration encounter(dec); return; } } } // no existing declaration found, create one foreach(Declaration* aliasedDeclaration, currentContext()->findDeclarations(id)) { if (aliasedDeclaration->kind() == Declaration::Instance) { AliasDeclaration* dec = openDefinition(id, editor()->findRange(node->variable)); dec->setAliasedDeclaration(aliasedDeclaration); closeDeclaration(); break; } } } bool DeclarationBuilder::isGlobalRedeclaration(const QualifiedIdentifier &identifier, AstNode* node, DeclarationType type) { if (!m_reportErrors) { return false; } ///TODO: method redeclaration etc. if (type != ClassDeclarationType && type != FunctionDeclarationType && type != ConstantDeclarationType) { // the other types can be redeclared return false; } DUChainWriteLocker lock(DUChain::lock()); QList declarations = currentContext()->topContext()->findDeclarations( identifier, startPos(node) ); foreach(Declaration* dec, declarations) { if (wasEncountered(dec) && isMatch(dec, type)) { reportRedeclarationError(dec, node); return true; } } return false; } void DeclarationBuilder::reportRedeclarationError(Declaration* declaration, AstNode* node) { if (declaration->range().contains(startPos(node))) { // make sure this is not a wrongly reported redeclaration error return; } if (declaration->context()->topContext()->url() == internalFunctionFile()) { reportError(i18n("Cannot redeclare PHP internal %1.", declaration->toString()), node); } else if (auto trait = dynamic_cast(declaration)) { reportError( i18n("%1 and %2 define the same property (%3) in the composition of %1. This might be incompatible, to improve maintainability consider using accessor methods in traits instead.") .arg(dynamic_cast(currentDeclaration())->prettyName().str(), dynamic_cast(trait->aliasedDeclaration().data()->context()->owner())->prettyName().str(), dynamic_cast(trait)->identifier().toString()), node, IProblem::Warning ); } else { ///TODO: try to shorten the filename by removing the leading path to the current project reportError( i18n("Cannot redeclare %1, already declared in %2 on line %3.", declaration->toString(), declaration->context()->topContext()->url().str(), declaration->range().start.line + 1 ), node ); } } void DeclarationBuilder::visitOuterTopStatement(OuterTopStatementAst* node) { //docblock of an AssignmentExpression setComment(formatComment(node, m_editor)); m_lastTopStatementComment = m_editor->parseSession()->docComment(node->startToken); DeclarationBuilderBase::visitOuterTopStatement(node); } void DeclarationBuilder::visitAssignmentExpression(AssignmentExpressionAst* node) { if ( node->assignmentExpressionEqual ) { PushValue restore(m_findVariable); DeclarationBuilderBase::visitAssignmentExpression(node); } else { DeclarationBuilderBase::visitAssignmentExpression(node); } } void DeclarationBuilder::visitVariable(VariableAst* node) { if ( m_findVariable.find ) { getVariableIdentifier(node, m_findVariable.identifier, m_findVariable.parentIdentifier, m_findVariable.node, m_findVariable.isArray); m_findVariable.find = false; } DeclarationBuilderBase::visitVariable(node); } void DeclarationBuilder::declareVariable(DUContext* parentCtx, AbstractType::Ptr type, const QualifiedIdentifier& identifier, AstNode* node) { // we must not re-assign $this in a class context /// Qualified identifier for 'this' static const QualifiedIdentifier thisQId(QStringLiteral("this")); if ( identifier == thisQId && currentContext()->parentContext() && currentContext()->parentContext()->type() == DUContext::Class ) { + + // checks if imports \ArrayAccess + ClassDeclaration* currentClass = dynamic_cast(currentContext()->parentContext()->owner()); + ClassDeclaration* arrayAccess = nullptr; + + auto imports = currentContext()->parentContext()->importedParentContexts(); + for( const DUContext::Import& ctx : imports ) { + DUContext* import = ctx.context(topContext()); + if(import->type() == DUContext::Class) { + ClassDeclaration* importedClass = dynamic_cast(import->owner()); + if(importedClass) { + if(importedClass->prettyName().str() == "ArrayAccess" && importedClass->classType() == ClassDeclarationData::ClassType::Interface && !import->parentContext()->owner()) { + arrayAccess = importedClass; + } + } + } + } + + IntegralType* thisVar = static_cast(type.data()); + // check if this is used as array + if(arrayAccess && currentClass && thisVar && thisVar->dataType() == AbstractType::TypeArray) + { + uint noOfFunc = 0; + auto declarations = currentContext()->parentContext()->localDeclarations(); + // check if class implements all 4 functions + for(auto &dec : declarations) { + if(dec->isFunctionDeclaration()) { + QualifiedIdentifier func = dec->qualifiedIdentifier(); + QString funcname = func.last().identifier().str(); + if(funcname == "offsetexists" || funcname == "offsetget" || funcname == "offsetset" || funcname == "offsetunset") { + noOfFunc++; + } + } + } + + if(noOfFunc < 4) { + // check if class is not abstract + if(currentClass->classModifier() != ClassDeclarationData::ClassModifier::Abstract) { + reportError(i18n("Class %1 contains %2 abstract methods and must therefore be declared abstract or implement the remaining methods.",currentClass->prettyName().str(),4-noOfFunc), QList() << node); + } + } + + return; + } + reportError(i18n("Cannot re-assign $this."), QList() << node); return; } DUChainWriteLocker lock(DUChain::lock()); const RangeInRevision newRange = editorFindRange(node, node); // check if this variable is already declared { QList< Declaration* > decs = parentCtx->findDeclarations(identifier.first(), startPos(node), 0, DUContext::DontSearchInParent); if ( !decs.isEmpty() ) { QList< Declaration* >::const_iterator it = decs.constEnd() - 1; while ( true ) { // we expect that the list of declarations has the newest declaration at back if ( dynamic_cast( *it ) ) { if (!wasEncountered(*it)) { encounter(*it); // force new range https://bugs.kde.org/show_bug.cgi?id=262189, // might be wrong when we had syntax errors in there before (*it)->setRange(newRange); } if ( (*it)->abstractType() && !(*it)->abstractType()->equals(type.data()) ) { // if it's currently mixed and we now get something more definite, use that instead if ( ReferenceType::Ptr rType = ReferenceType::Ptr::dynamicCast((*it)->abstractType()) ) { if ( IntegralType::Ptr integral = IntegralType::Ptr::dynamicCast(rType->baseType()) ) { if ( integral->dataType() == IntegralType::TypeMixed ) { // referenced mixed to referenced @p type ReferenceType::Ptr newType(new ReferenceType()); newType->setBaseType(type); (*it)->setType(newType); return; } } } if ( IntegralType::Ptr integral = IntegralType::Ptr::dynamicCast((*it)->abstractType()) ) { if ( integral->dataType() == IntegralType::TypeMixed ) { // mixed to @p type (*it)->setType(type); return; } } // else make it unsure UnsureType::Ptr unsure = UnsureType::Ptr::dynamicCast((*it)->abstractType()); // maybe it's referenced? ReferenceType::Ptr rType = ReferenceType::Ptr::dynamicCast((*it)->abstractType()); if ( !unsure && rType ) { unsure = UnsureType::Ptr::dynamicCast(rType->baseType()); } if ( !unsure ) { unsure = UnsureType::Ptr(new UnsureType()); if ( rType ) { unsure->addType(rType->baseType()->indexed()); } else { unsure->addType((*it)->indexedType()); } } unsure->addType(type->indexed()); if ( rType ) { rType->setBaseType(AbstractType::Ptr(unsure.data())); (*it)->setType(rType); } else { (*it)->setType(unsure); } } return; } if ( it == decs.constBegin() ) { break; } --it; } } } VariableDeclaration *dec = openDefinition(identifier, newRange); dec->setKind(Declaration::Instance); if (!m_lastTopStatementComment.isEmpty()) { QRegExp rx("(\\*|///)\\s*@superglobal"); if (rx.indexIn(m_lastTopStatementComment) != -1) { dec->setSuperglobal(true); } } //own closeDeclaration() that doesn't use lastType() dec->setType(type); // variable declarations are not namespaced in PHP if (currentContext()->type() == DUContext::Namespace) { dec->setContext(currentContext()->topContext()); } eventuallyAssignInternalContext(); DeclarationBuilderBase::closeDeclaration(); } DUContext* getClassContext(const QualifiedIdentifier &identifier, DUContext* currentCtx) { /// Qualified identifier for 'this' static const QualifiedIdentifier thisQId(QStringLiteral("this")); if ( identifier == thisQId ) { if ( currentCtx->parentContext() && currentCtx->parentContext()->type() == DUContext::Class ) { return currentCtx->parentContext(); } } else { DUChainReadLocker lock(DUChain::lock()); foreach( Declaration* parent, currentCtx->topContext()->findDeclarations(identifier) ) { if ( StructureType::Ptr ctype = parent->type() ) { return ctype->internalContext(currentCtx->topContext()); } } ///TODO: if we can't find anything here we might have to use the findDeclarationImport helper } return 0; } ///TODO: we need to handle assignment to array-members properly /// currently we just make sure the array is declared, but don't know /// anything about its contents void DeclarationBuilder::visitAssignmentExpressionEqual(AssignmentExpressionEqualAst *node) { DeclarationBuilderBase::visitAssignmentExpressionEqual(node); if ( !m_findVariable.identifier.isEmpty() && currentAbstractType()) { //create new declaration assignments to not-yet declared variables and class members AbstractType::Ptr type; if ( m_findVariable.isArray ) { // implicit array declaration type = AbstractType::Ptr(new IntegralType(IntegralType::TypeArray)); } else { type = currentAbstractType(); } if ( !m_findVariable.parentIdentifier.isEmpty() ) { // assignment to class members if ( DUContext* ctx = getClassContext(m_findVariable.parentIdentifier, currentContext()) ) { declareClassMember(ctx, type, m_findVariable.identifier, m_findVariable.node); } } else { // assigment to other variables declareVariable(currentContext(), type, m_findVariable.identifier, m_findVariable.node ); } } } void DeclarationBuilder::visitFunctionCall(FunctionCallAst* node) { QualifiedIdentifier id; if (!m_isInternalFunctions) { FunctionType::Ptr oldFunction = m_currentFunctionType; DeclarationPointer dec; if ( node->stringFunctionName ) { dec = findDeclarationImport(FunctionDeclarationType, node->stringFunctionName); } else if ( node->stringFunctionNameOrClass ) { id = identifierForNamespace(node->stringFunctionNameOrClass, m_editor); dec = findDeclarationImport(FunctionDeclarationType, id); } else { ///TODO: node->varFunctionName } if ( dec ) { m_currentFunctionType = dec->type(); } else { m_currentFunctionType = 0; } DeclarationBuilderBase::visitFunctionCall(node); m_currentFunctionType = oldFunction; } else { // optimize for internal function file DeclarationBuilderBase::visitFunctionCall(node); } if (node->stringFunctionNameOrClass && !node->stringFunctionName && !node->varFunctionName) { if (id.toString(RemoveExplicitlyGlobalPrefix) == QLatin1String("define") && node->stringParameterList && node->stringParameterList->parametersSequence && node->stringParameterList->parametersSequence->count() > 0) { //constant, defined through define-function //find name of the constant (first argument of the function call) CommonScalarAst* scalar = findCommonScalar(node->stringParameterList->parametersSequence->at(0)->element); if (scalar && scalar->string != -1) { QString constant = m_editor->parseSession()->symbol(scalar->string); constant = constant.mid(1, constant.length() - 2); RangeInRevision newRange = editorFindRange(scalar, scalar); AbstractType::Ptr type; if (node->stringParameterList->parametersSequence->count() > 1) { type = getTypeForNode(node->stringParameterList->parametersSequence->at(1)->element); Q_ASSERT(type); type->setModifiers(type->modifiers() | AbstractType::ConstModifier); } // TODO: else report error? DUChainWriteLocker lock; // find fitting context to put define in, // pick first namespace or global context otherwise DUContext* ctx = currentContext(); while (ctx->type() != DUContext::Namespace && ctx->parentContext()) { ctx = ctx->parentContext(); } injectContext(ctx); //constants are always global QualifiedIdentifier identifier(constant); isGlobalRedeclaration(identifier, scalar, ConstantDeclarationType); Declaration* dec = openDefinition(identifier, newRange); dec->setKind(Declaration::Instance); if (type) { dec->setType(type); injectType(type); } closeDeclaration(); closeInjectedContext(); } } } } void DeclarationBuilder::visitFunctionCallParameterList(FunctionCallParameterListAst* node) { int oldPos = m_functionCallParameterPos; m_functionCallParameterPos = 0; DeclarationBuilderBase::visitFunctionCallParameterList(node); m_functionCallParameterPos = oldPos; } void DeclarationBuilder::visitFunctionCallParameterListElement(FunctionCallParameterListElementAst* node) { PushValue restore(m_findVariable); DeclarationBuilderBase::visitFunctionCallParameterListElement(node); if ( m_findVariable.node && m_currentFunctionType && m_currentFunctionType->arguments().count() > m_functionCallParameterPos) { ReferenceType::Ptr refType = m_currentFunctionType->arguments() .at(m_functionCallParameterPos).cast(); if ( refType ) { // this argument is referenced, so if the node contains undeclared variables we have // to declare them with a NULL type, see also: // http://de.php.net/manual/en/language.references.whatdo.php // declare with NULL type, just like PHP does declareFoundVariable(AbstractType::Ptr(new IntegralType(IntegralType::TypeNull))); } } ++m_functionCallParameterPos; } void DeclarationBuilder::visitAssignmentListElement(AssignmentListElementAst* node) { PushValue restore(m_findVariable); DeclarationBuilderBase::DefaultVisitor::visitAssignmentListElement(node); if ( m_findVariable.node ) { ///TODO: get a proper type here, if possible declareFoundVariable(AbstractType::Ptr(new IntegralType(IntegralType::TypeMixed))); } } void DeclarationBuilder::declareFoundVariable(AbstractType::Ptr type) { Q_ASSERT(m_findVariable.node); ///TODO: support something like: foo($var[0]) if ( !m_findVariable.isArray ) { DUContext *ctx = 0; if ( m_findVariable.parentIdentifier.isEmpty() ) { ctx = currentContext(); } else { ctx = getClassContext(m_findVariable.parentIdentifier, currentContext()); } if ( ctx ) { bool isDeclared = false; { DUChainWriteLocker lock(DUChain::lock()); RangeInRevision range = m_editor->findRange(m_findVariable.node); foreach ( Declaration* dec, ctx->findDeclarations(m_findVariable.identifier) ) { if ( dec->kind() == Declaration::Instance ) { if (!wasEncountered(dec) || (dec->context() == ctx && range < dec->range())) { // just like a "redeclaration", hence we must update the range // TODO: do the same for all other uses of "encounter"? dec->setRange(editorFindRange(m_findVariable.node)); encounter(dec); } isDeclared = true; break; } } } if ( !isDeclared && m_findVariable.parentIdentifier.isEmpty() ) { // check also for global vars isDeclared = findDeclarationImport(GlobalVariableDeclarationType, m_findVariable.identifier); } if ( !isDeclared ) { // couldn't find the dec, declare it if ( !m_findVariable.parentIdentifier.isEmpty() ) { declareClassMember(ctx, type, m_findVariable.identifier, m_findVariable.node); } else { declareVariable(ctx, type, m_findVariable.identifier, m_findVariable.node); } } } } } void DeclarationBuilder::visitStatement(StatementAst* node) { DeclarationBuilderBase::visitStatement(node); if (node->foreachVariable) { PushValue restore(m_findVariable); visitForeachVariable(node->foreachVariable); if (m_findVariable.node) { declareFoundVariable(lastType()); } } if (node->foreachVarAsVar) { PushValue restore(m_findVariable); visitForeachVariable(node->foreachVarAsVar); if (m_findVariable.node) { declareFoundVariable(lastType()); } } if (node->foreachExprAsVar) { PushValue restore(m_findVariable); visitVariable(node->foreachExprAsVar); if (m_findVariable.node) { declareFoundVariable(lastType()); } } } void DeclarationBuilder::visitStaticVar(StaticVarAst* node) { DeclarationBuilderBase::visitStaticVar(node); DUChainWriteLocker lock(DUChain::lock()); openDefinition(identifierForNode(node->var), editorFindRange(node->var, node->var)); currentDeclaration()->setKind(Declaration::Instance); closeDeclaration(); } void DeclarationBuilder::visitGlobalVar(GlobalVarAst* node) { DeclarationBuilderBase::visitGlobalVar(node); if (node->var) { QualifiedIdentifier id = identifierForNode(node->var); if ( recompiling() ) { DUChainWriteLocker lock(DUChain::lock()); // sadly we can't use findLocalDeclarations() here, since it un-aliases declarations foreach ( Declaration* dec, currentContext()->localDeclarations() ) { if ( dynamic_cast(dec) && dec->identifier() == id.first() ) { // don't redeclare but reuse the existing declaration encounter(dec); return; } } } // no existing declaration found, create one DeclarationPointer aliasedDeclaration = findDeclarationImport(GlobalVariableDeclarationType, node->var); if (aliasedDeclaration) { DUChainWriteLocker lock(DUChain::lock()); AliasDeclaration* dec = openDefinition(id, m_editor->findRange(node->var)); dec->setAliasedDeclaration(aliasedDeclaration.data()); closeDeclaration(); } } } void DeclarationBuilder::visitCatchItem(CatchItemAst *node) { DeclarationBuilderBase::visitCatchItem(node); DUChainWriteLocker lock(DUChain::lock()); openDefinition(identifierForNode(node->var), editorFindRange(node->var, node->var)); currentDeclaration()->setKind(Declaration::Instance); closeDeclaration(); } void DeclarationBuilder::visitUnaryExpression(UnaryExpressionAst* node) { DeclarationBuilderBase::visitUnaryExpression(node); IndexedString includeFile = getIncludeFileForNode(node, m_editor); if ( !includeFile.isEmpty() ) { DUChainWriteLocker lock; TopDUContext* includedCtx = DUChain::self()->chainForDocument(includeFile); if ( !includedCtx ) { // invalid include return; } QualifiedIdentifier identifier(includeFile.str()); foreach ( Declaration* dec, includedCtx->findDeclarations(identifier, CursorInRevision(0, 1)) ) { if ( dec->kind() == Declaration::Import ) { encounter(dec); return; } } injectContext(includedCtx); openDefinition(identifier, RangeInRevision(0, 0, 0, 0)); currentDeclaration()->setKind(Declaration::Import); eventuallyAssignInternalContext(); DeclarationBuilderBase::closeDeclaration(); closeInjectedContext(); } } void DeclarationBuilder::openNamespace(NamespaceDeclarationStatementAst* parent, IdentifierAst* node, const IdentifierPair& identifier, const RangeInRevision& range) { NamespaceDeclaration* dec = m_namespaces.value(node->string, 0); Q_ASSERT(dec); DeclarationBuilderBase::setEncountered(dec); openDeclarationInternal(dec); DeclarationBuilderBase::openNamespace(parent, node, identifier, range); } void DeclarationBuilder::closeNamespace(NamespaceDeclarationStatementAst* parent, IdentifierAst* node, const IdentifierPair& identifier) { DeclarationBuilderBase::closeNamespace(parent, node, identifier); closeDeclaration(); } void DeclarationBuilder::visitUseNamespace(UseNamespaceAst* node) { DUChainWriteLocker lock; if ( currentContext()->type() != DUContext::Namespace && !node->aliasIdentifier && node->identifier->namespaceNameSequence->count() == 1 ) { reportError(i18n("The use statement with non-compound name '%1' has no effect.", identifierForNode(node->identifier->namespaceNameSequence->front()->element).toString()), node->identifier, IProblem::Warning); return; } IdentifierAst* idNode = node->aliasIdentifier ? node->aliasIdentifier : node->identifier->namespaceNameSequence->back()->element; IdentifierPair id = identifierPairForNode(idNode); ///TODO: case insensitive! QualifiedIdentifier qid = identifierForNamespace(node->identifier, m_editor); ///TODO: find out why this must be done (see mail to kdevelop-devel on jan 18th 2011) qid.setExplicitlyGlobal( false ); DeclarationPointer dec = findDeclarationImport(ClassDeclarationType, qid); if (dec) { // Check for a name conflict DeclarationPointer dec2 = findDeclarationImport(ClassDeclarationType, id.second); if (dec2 && dec2->context()->scopeIdentifier() == currentContext()->scopeIdentifier() && dec2->context()->topContext() == currentContext()->topContext() && dec2->identifier().toString() == id.second.toString()) { reportError(i18n("Cannot use '%1' as '%2' because the name is already in use.", dec.data()->identifier().toString(), id.second.toString()), node->identifier, IProblem::Error); return; } AliasDeclaration* decl = openDefinition(id.second, m_editor->findRange(idNode)); decl->setAliasedDeclaration(dec.data()); } else { NamespaceAliasDeclaration* decl = openDefinition(id.second, m_editor->findRange(idNode)); decl->setImportIdentifier( qid ); decl->setPrettyName( id.first ); decl->setKind(Declaration::NamespaceAlias); } closeDeclaration(); } void DeclarationBuilder::updateCurrentType() { DUChainWriteLocker lock(DUChain::lock()); currentDeclaration()->setAbstractType(currentAbstractType()); } void DeclarationBuilder::supportBuild(AstNode* node, DUContext* context) { // generally we are the second pass through the doc (see PreDeclarationBuilder) // so notify our base about it setCompilingContexts(false); DeclarationBuilderBase::supportBuild(node, context); } void DeclarationBuilder::closeContext() { if (currentContext()->type() == DUContext::Function) { Q_ASSERT(currentDeclaration()); currentDeclaration()->setInternalFunctionContext(currentContext()); } // We don't want the first pass to clean up stuff, since // there is lots of stuff we visit/encounter here first. // So we clean things up here. setCompilingContexts(true); DeclarationBuilderBase::closeContext(); setCompilingContexts(false); } void DeclarationBuilder::encounter(Declaration* dec) { // when we are recompiling, it's important to mark decs as encountered // and update their comments if ( recompiling() && !wasEncountered(dec) ) { dec->setComment(comment()); setEncountered(dec); } } } diff --git a/duchain/tests/duchain.cpp b/duchain/tests/duchain.cpp index 68038ce..bd55f85 100644 --- a/duchain/tests/duchain.cpp +++ b/duchain/tests/duchain.cpp @@ -1,2957 +1,3001 @@ /* 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 "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 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::declareVar() { // 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::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::TypeMixed); //$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::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(StructureType::Ptr::dynamicCast(fType->returnType())); QCOMPARE(StructureType::Ptr::dynamicCast(fType->returnType())->qualifiedIdentifier(), QualifiedIdentifier("stdclass")); //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::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::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(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::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();" << 1; } 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::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();" << 1 << (uint) IntegralType::TypeArray; } 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::define() { // the last define tests that we don't crash under that circumstance // 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(), 2u); QCOMPARE(fun->defaultParameters()[0].str(), QString("false")); QCOMPARE(fun->defaultParameters()[1].str(), QString("null")); } 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), 0); DUChain::self()->addDocumentChain(top); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); for (int i = 0; i < 6000; ++i) { RangeInRevision newRange(i, 0, i, 1); Declaration* dec = new Declaration(newRange, top); dec->setIdentifier(Identifier(QStringLiteral("dec%0").arg(i))); dec->setAbstractType(AbstractType::Ptr(0)); } } 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::findDeclarations() { DUChainWriteLocker lock(DUChain::lock()); TopDUContext* top1 = new TopDUContext(IndexedString(QUrl(QStringLiteral("file:///internal/testfile1"))), RangeInRevision(0, 0, 0, 10), 0); DUChainReleaser releaseTop1(top1); DUChain::self()->addDocumentChain(top1); TopDUContext* top2 = new TopDUContext(IndexedString(QUrl(QStringLiteral("file:///internal/testfile2"))), RangeInRevision(0, 0, 0, 10), 0); 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::TypeMixed); } } 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 $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()); 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 not create any local declarations, and issue an error for trying to // assign something to a private member variable of a parent class " 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); } { // prot and priv QVERIFY(top->childContexts().last()->findLocalDeclarations(Identifier("prot")).isEmpty()); QVERIFY(top->childContexts().last()->findLocalDeclarations(Identifier("priv")).isEmpty()); } // only one problem: error trying to assign to a private member of a parent class QCOMPARE(top->problems().count(), 1); QCOMPARE(top->problems().first()->finalLocation().start().line(), 4); } 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::TypeMixed); } } 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::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::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); +} diff --git a/duchain/tests/duchain.h b/duchain/tests/duchain.h index c9fe0a5..194bada 100644 --- a/duchain/tests/duchain.h +++ b/duchain/tests/duchain.h @@ -1,156 +1,158 @@ /* 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 TESTDUCHAIN_H #define TESTDUCHAIN_H #include "tests/duchaintestbase.h" namespace Php { class TestDUChain : public DUChainTestBase { Q_OBJECT public: TestDUChain(); private slots: void declareFunction(); void declareVar(); void varTypehint(); void declareClass(); void classMemberVar(); void declareTypehintFunction(); void declareTypehintArrayFunction(); void declareTypehintCallableFunction(); void returnTypeClass(); void declarationReturnType(); void declarationReturnTypeInRecursingFunction(); void returnTypeViaMember(); void declarationMultipleReturnTypes(); void declarationReturnTypeDocBlock(); void declarationReturnTypeDocBlockIntegral(); void declarationReturnTypeClassChain(); void classImplementsInterface(); void classExtends(); void staticMethod(); void ownStaticMethod(); void thisVar(); void objectFunctionCall(); void objectFunctionCall2(); void objectFunctionCall3(); void objectVariable(); void staticMemberVariable(); void ownStaticMemberVariable(); void classConst(); void classConst_data(); void fileConst(); void fileConst_data(); void define(); void defaultFunctionParam(); void globalFunction(); void globalVariableFromInternalFunctions(); void newObjectFromOtherFile(); void unknownReturnType(); void staticFunctionCallFromOtherFile(); void classConstantFromOtherFile(); void globalFunctionCallFromOtherFile(); void constantFromOtherFile(); void singleton(); void internalFunctions(); void trueFalse(); void null(); void array(); void functionDocBlock(); void variableDocBlock(); void functionDocBlockParams(); void memberFunctionDocBlockParams(); void foreachLoop(); void php4StyleConstructor(); void constructor(); void destructor(); void functionInFunction(); void objectWithClassName(); void largeNumberOfDeclarations(); void staticVariable(); void returnTypeTwoDeclarations(); void globalVariableNotVisibleInFunction(); void globalVariableInFunction(); void nonGlobalVariableInFunction(); void superglobalInFunction(); void returnWithoutFunction(); void circularInheritance(); void findDeclarations(); void memberTypeAfterMethod(); void catchDeclaration(); void resourceType(); void foreachIterator(); void foreachIterator2(); void foreachIterator3(); void foreachIterator4(); void returnThis(); void unsureReturnType(); void unsureReturnType2(); void unsureReturnType3(); void unsureReturnType4(); void referencedArgument(); void unsureReferencedArgument(); void defaultArgument(); void declareMemberOutOfClass(); void declareMemberOutOfClass2(); void declareMemberInClassMethod(); void thisRedeclaration(); void implicitArrayDeclaration(); void implicitReferenceDeclaration(); void classContextRange(); void lateClassMembers(); void list(); void alternateDocCommentTypeHints(); void findFunctionArgs(); void undeclaredPropertyInString(); void undeclaredVarPropertyInString(); void upcommingClassInString(); void namespaces(); void namespacesNoCurly(); void useNamespace(); void namespaceStaticVar(); void namespacedCatch(); void errorRecovery_data(); void errorRecovery(); void varStatic(); void staticNowdoc(); void curlyVarAfterObj(); void embeddedHTML_data(); void embeddedHTML(); void cases(); void closureParser(); void closures(); void closureEmptyUse(); void iifeParser(); void iife(); void gotoTest(); void ternary(); void bug296709(); void declareFinalMethod(); void testTodoExtractor(); + void useThisAsArray(); + void wrongUseOfThisAsArray(); }; } #endif