diff --git a/CMakeLists.txt b/CMakeLists.txt index 845e6e8..98674c2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,122 +1,122 @@ cmake_minimum_required(VERSION 3.0) cmake_policy(SET CMP0048 NEW) set(KDEVPHP_VERSION_MAJOR 5) set(KDEVPHP_VERSION_MINOR 3) set(KDEVPHP_VERSION_PATCH 40) project(kdev-php VERSION "${KDEVPHP_VERSION_MAJOR}.${KDEVPHP_VERSION_MINOR}.${KDEVPHP_VERSION_PATCH}") find_package (ECM "5.14.0" REQUIRED NO_MODULE) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${ECM_MODULE_PATH}) include(KDECompilerSettings NO_POLICY_SCOPE) include(ECMAddTests) include(ECMQtDeclareLoggingCategory) include(KDEInstallDirs) include(KDECMakeSettings) include(GenerateExportHeader) include(CMakePackageConfigHelpers) include(ECMSetupVersion) include(FeatureSummary) set(QT_MIN_VERSION "5.5.0") find_package(Qt5 ${QT_MIN_VERSION} CONFIG REQUIRED Core Widgets) set(KF5_DEP_VERSION "5.15.0") find_package(KF5 ${KF5_DEP_VERSION} REQUIRED COMPONENTS ThreadWeaver TextEditor I18n KCMUtils ) if(BUILD_TESTING) find_package(Qt5Test ${QT_MIN_VERSION} CONFIG REQUIRED) endif() # KDevplatform dependency version set(KDEVPLATFORM_VERSION "${KDEVPHP_VERSION_MAJOR}.${KDEVPHP_VERSION_MINOR}") find_package(KDevPlatform ${KDEVPLATFORM_VERSION} CONFIG REQUIRED) ecm_setup_version( ${PROJECT_VERSION} VARIABLE_PREFIX KDEVPHP VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/kdevphpversion.h" PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/KDevPHPConfigVersion.cmake" ) set(KDEVPHP_INCLUDE_DIR ${KDE_INSTALL_INCLUDEDIR}/kdev-php) set(KDEVPHP_PRIVATE_INCLUDE_DIR ${KDEVPHP_INCLUDE_DIR}/private/${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}) find_package(KDevelop-PG-Qt REQUIRED) if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wdocumentation") endif() add_definitions( -DTRANSLATION_DOMAIN=\"kdevphp\" ) include_directories( ${KDEVPGQT_INCLUDE_DIR} ${CMAKE_SOURCE_DIR} ) add_subdirectory(app_templates) add_subdirectory(parser) add_subdirectory(duchain) add_subdirectory(completion) add_subdirectory(testprovider) add_subdirectory(docs) set(kdevphplanguagesupport_PART_SRCS phplanguagesupport.cpp phpparsejob.cpp phphighlighting.cpp codegen/refactoring.cpp ) ecm_qt_declare_logging_category(kdevphplanguagesupport_PART_SRCS HEADER phpdebug.h IDENTIFIER PHP - CATEGORY_NAME "kdevelop.languages.php" + CATEGORY_NAME "kdevelop.plugins.php" ) kdevplatform_add_plugin(kdevphplanguagesupport JSON kdevphpsupport.json SOURCES ${kdevphplanguagesupport_PART_SRCS}) target_link_libraries(kdevphplanguagesupport KDev::Interfaces KDev::Language kdevphpduchain kdevphpparser kdevphpcompletion KF5::I18n KF5::TextEditor ) # not writeable so that the refactoring actions get hidden install(FILES phpfunctions.php DESTINATION ${KDE_INSTALL_DATADIR}/kdevphpsupport PERMISSIONS OWNER_READ GROUP_READ WORLD_READ) install(FILES org.kde.kdev-php.metainfo.xml DESTINATION ${KDE_INSTALL_METAINFODIR}) # kdebugsettings file install(FILES kdevphpsupport.categories DESTINATION ${KDE_INSTALL_CONFDIR}) # create a Config.cmake and a ConfigVersion.cmake file and install them set(CMAKECONFIG_INSTALL_DIR "${KDE_INSTALL_CMAKEPACKAGEDIR}/KDevPHP") configure_package_config_file( "${CMAKE_CURRENT_SOURCE_DIR}/KDevPHPConfig.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/KDevPHPConfig.cmake" INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR} ) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/KDevPHPConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/KDevPHPConfigVersion.cmake" DESTINATION "${CMAKECONFIG_INSTALL_DIR}" COMPONENT Devel ) install(EXPORT KDevPHPTargets DESTINATION "${CMAKECONFIG_INSTALL_DIR}" FILE KDevPHPTargets.cmake) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/kdevphpversion.h" DESTINATION ${KDEVPHP_INCLUDE_DIR}) feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/completion/CMakeLists.txt b/completion/CMakeLists.txt index 3425f1f..7e110c4 100644 --- a/completion/CMakeLists.txt +++ b/completion/CMakeLists.txt @@ -1,34 +1,34 @@ if (BUILD_TESTING) add_subdirectory(tests) endif() set(completion_SRCS worker.cpp item.cpp implementationitem.cpp includefileitem.cpp keyworditem.cpp context.cpp model.cpp helpers.cpp codemodelitem.cpp ) ecm_qt_declare_logging_category(completion_SRCS HEADER completiondebug.h IDENTIFIER COMPLETION - CATEGORY_NAME "kdevelop.languages.php.completion" + CATEGORY_NAME "kdevelop.plugins.php.completion" ) add_library( kdevphpcompletion SHARED ${completion_SRCS} ) generate_export_header( kdevphpcompletion EXPORT_MACRO_NAME KDEVPHPCOMPLETION_EXPORT EXPORT_FILE_NAME phpcompletionexport.h) target_link_libraries(kdevphpcompletion LINK_PRIVATE KDev::Language KDev::Interfaces KDev::Project kdevphpduchain kdevphpparser ) install(TARGETS kdevphpcompletion EXPORT KDevPHPTargets DESTINATION ${INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/docs/CMakeLists.txt b/docs/CMakeLists.txt index c5a3366..0fa932c 100644 --- a/docs/CMakeLists.txt +++ b/docs/CMakeLists.txt @@ -1,51 +1,51 @@ ########## shared settings ########## set(kdevphpdocs_settings_SRCS ) kconfig_add_kcfg_files( kdevphpdocs_settings_SRCS phpdocssettings.kcfgc ) ########## Documentation Plugin ##### set(kdevphpdocs_SRCS phpdocumentationwidget.cpp phpdocumentation.cpp phpdocsplugin.cpp phpdocsmodel.cpp ${kdevphpdocs_settings_SRCS} ) ecm_qt_declare_logging_category(kdevphpdocs_SRCS HEADER phpdocsdebug.h IDENTIFIER DOCS - CATEGORY_NAME "kdevelop.languages.php.docs" + CATEGORY_NAME "kdevelop.plugins.php.docs" ) kdevplatform_add_plugin(kdevphpdocs JSON kdevphpdocs.json SOURCES ${kdevphpdocs_SRCS}) target_link_libraries(kdevphpdocs KDev::Interfaces KDev::Language KDev::Documentation KF5::KCMUtils ) ### Configuration module for PHP documentation plugin set(kcm_kdevphpdocs_SRCS phpdocsconfig.cpp ${kdevphpdocs_settings_SRCS} ) ki18n_wrap_ui(kcm_kdevphpdocs_SRCS phpdocsconfig.ui) add_library( kcm_kdevphpdocs MODULE ${kcm_kdevphpdocs_SRCS} ) target_link_libraries( kcm_kdevphpdocs KF5::ConfigWidgets KF5::KIOWidgets KF5::I18n KF5::KCMUtils ) configure_file(kcm_kdevphpdocs.desktop.cmake ${CMAKE_CURRENT_BINARY_DIR}/kcm_kdevphpdocs.desktop) kcoreaddons_desktop_to_json(kcm_kdevphpdocs ${CMAKE_CURRENT_BINARY_DIR}/kcm_kdevphpdocs.desktop) install(TARGETS kcm_kdevphpdocs DESTINATION ${KDE_INSTALL_PLUGINDIR}/kdevplatform/${KDEV_PLUGIN_VERSION}/kcm) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/kcm_kdevphpdocs.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}) diff --git a/duchain/CMakeLists.txt b/duchain/CMakeLists.txt index 20c4178..3404e3a 100644 --- a/duchain/CMakeLists.txt +++ b/duchain/CMakeLists.txt @@ -1,55 +1,55 @@ if (BUILD_TESTING) add_subdirectory(tests) endif() set(duchain_SRCS types/indexedcontainer.cpp types/integraltypeextended.cpp types/structuretype.cpp builders/predeclarationbuilder.cpp builders/declarationbuilder.cpp builders/contextbuilder.cpp builders/usebuilder.cpp builders/typebuilder.cpp editorintegrator.cpp dumptypes.cpp expressionvisitor.cpp expressionparser.cpp expressionevaluationresult.cpp helper.cpp phpducontext.cpp declarations/variabledeclaration.cpp declarations/classmethoddeclaration.cpp declarations/classdeclaration.cpp declarations/functiondeclaration.cpp declarations/namespacedeclaration.cpp declarations/namespacealiasdeclaration.cpp declarations/traitmethodaliasdeclaration.cpp declarations/traitmemberaliasdeclaration.cpp navigation/navigationwidget.cpp navigation/declarationnavigationcontext.cpp navigation/includenavigationcontext.cpp navigation/magicconstantnavigationcontext.cpp completioncodemodel.cpp ) ecm_qt_declare_logging_category(duchain_SRCS HEADER duchaindebug.h IDENTIFIER DUCHAIN - CATEGORY_NAME "kdevelop.languages.php.duchain" + CATEGORY_NAME "kdevelop.plugins.php.duchain" ) add_library( kdevphpduchain SHARED ${duchain_SRCS} ) generate_export_header(kdevphpduchain EXPORT_MACRO_NAME KDEVPHPDUCHAIN_EXPORT EXPORT_FILE_NAME phpduchainexport.h) target_link_libraries(kdevphpduchain LINK_PRIVATE KDev::Interfaces KDev::Language KDev::Shell KDev::Project kdevphpparser ) install(TARGETS kdevphpduchain EXPORT KDevPHPTargets DESTINATION ${INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/duchain/expressionvisitor.cpp b/duchain/expressionvisitor.cpp index 4f48000..c3a3557 100644 --- a/duchain/expressionvisitor.cpp +++ b/duchain/expressionvisitor.cpp @@ -1,1018 +1,1050 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2008 Niko Sams * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "expressionvisitor.h" #include "parsesession.h" #include "editorintegrator.h" #include "helper.h" #include "declarations/variabledeclaration.h" #include "declarations/classdeclaration.h" #include #include #include #include #include #include #include #include #include "duchaindebug.h" #define ifDebug(x) using namespace KDevelop; namespace Php { ExpressionVisitor::ExpressionVisitor(EditorIntegrator* editor) : m_editor(editor), m_createProblems(false), m_offset(CursorInRevision::invalid()), m_currentContext(nullptr), m_isAssignmentExpressionEqual(false), m_inDefine(false) { } DeclarationPointer ExpressionVisitor::processVariable(VariableIdentifierAst* variable) { Q_ASSERT(m_currentContext); CursorInRevision position = m_editor->findPosition(variable->variable, EditorIntegrator::BackEdge); if ( m_offset.isValid() ) { position.line += m_offset.line; position.column += m_offset.column; } DeclarationPointer ret; Identifier identifier = identifierForNode(variable).last(); ifDebug(qCDebug(DUCHAIN) << "processing variable" << identifier.toString() << position.castToSimpleCursor();) DUChainReadLocker lock; if (identifier.nameEquals(Identifier(QStringLiteral("this")))) { if (m_currentContext->parentContext() && m_currentContext->parentContext()->type() == DUContext::Class && m_currentContext->parentContext()->owner()) { ret = m_currentContext->parentContext()->owner(); } } else { //DontSearchInParent-flag because (1) in Php global variables aren't available in function //context and (2) a function body consists of a single context (so this is no problem) ret = findVariableDeclaration(m_currentContext, identifier, position, DUContext::DontSearchInParent); } if (!ret && m_currentContext->type() == DUContext::Namespace) { ret = findVariableDeclaration(m_currentContext, identifier, position, DUContext::NoSearchFlags); } if (!ret) { //look for a function argument ///TODO: why doesn't m_currentContext->findDeclarations() work? /// evaluate if the stuff below is fast enough (faster?) than findDeclarations() ///see r1028306 foreach(const DUContext::Import &import, m_currentContext->importedParentContexts() ) { if ( !import.isDirect() || import.position > position ) { continue; } DUContext* ctx = import.context(m_currentContext->topContext()); if ( ctx->type() == DUContext::Function ) { QList args = ctx->findLocalDeclarations(identifier); if ( !args.isEmpty() ) { ret = args.first(); break; } } } } if (!ret) { //look for a superglobal variable foreach(Declaration* dec, m_currentContext->topContext()->findDeclarations(identifier, position)) { VariableDeclaration* varDec = dynamic_cast(dec); if (varDec && varDec->isSuperglobal()) { ret = dec; break; } } } lock.unlock(); if ( !m_isAssignmentExpressionEqual || identifier.nameEquals( Identifier(QStringLiteral("this")) ) // might be something like $s = $s . $s; in which case we have to add a use for the first $s || (ret && ret->range().end < position) ) { // also don't report uses for the place of declaration if (!ret || ret->range().end != position) { usingDeclaration(variable, ret); } } ifDebug(qCDebug(DUCHAIN) << "found declaration:" << (ret ? ret->toString() : QString("no declaration found"));) return ret; } void ExpressionVisitor::visitNode(AstNode *node) { if (node && node->ducontext) { m_currentContext = node->ducontext; } Q_ASSERT(m_currentContext); DefaultVisitor::visitNode(node); } void ExpressionVisitor::visitAssignmentExpression(AssignmentExpressionAst *node) { if (node->assignmentExpressionEqual) { m_isAssignmentExpressionEqual = true; } visitNode(node->expression); m_isAssignmentExpressionEqual = false; visitNode(node->assignmentExpressionEqual); visitNode(node->assignmentExpression); if (node->operation == OperationPlus || node->operation == OperationMinus || node->operation == OperationMul || node->operation == OperationDiv || node->operation == OperationExp) { m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeInt))); } else if (node->operation == OperationConcat) { m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeString))); } } void ExpressionVisitor::visitArrayIndexSpecifier(ArrayIndexSpecifierAst* node) { DefaultVisitor::visitArrayIndexSpecifier(node); // it's an array item but we don't support it really, so just assign type mixed and be done m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeMixed))); } void ExpressionVisitor::visitCompoundVariableWithSimpleIndirectReference(CompoundVariableWithSimpleIndirectReferenceAst *node) { if (node->variable) { m_result.setDeclaration(processVariable(node->variable)); } DefaultVisitor::visitCompoundVariableWithSimpleIndirectReference(node); } void ExpressionVisitor::visitVariable(VariableAst* node) { if ( node->variablePropertiesSequence && node->variablePropertiesSequence->front() && node->variablePropertiesSequence->front()->element && node->variablePropertiesSequence->front()->element->variableProperty && node->variablePropertiesSequence->front()->element->variableProperty->objectProperty ) { // make sure we mark $foo as a use in $foo->... bool isAssignmentExpressionEqual = m_isAssignmentExpressionEqual; m_isAssignmentExpressionEqual = false; DefaultVisitor::visitVariable(node); m_isAssignmentExpressionEqual = isAssignmentExpressionEqual; } else { DefaultVisitor::visitVariable(node); } } void ExpressionVisitor::visitVarExpression(VarExpressionAst *node) { DefaultVisitor::visitVarExpression(node); if (node->isGenerator != -1) { DeclarationPointer generatorDecl = findDeclarationImport(ClassDeclarationType, QualifiedIdentifier("generator")); if (generatorDecl) { m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeMixed))); if (hasCurrentClosureReturnType()) { FunctionType::Ptr closureType = currentClosureReturnType().cast(); closureType->setReturnType(generatorDecl->abstractType()); } } } } void ExpressionVisitor::visitVarExpressionNewObject(VarExpressionNewObjectAst *node) { DefaultVisitor::visitVarExpressionNewObject(node); if (node->classNameReference->className && node->classNameReference->className->staticIdentifier != -1) { static const QualifiedIdentifier id(QStringLiteral("static")); DeclarationPointer dec = findDeclarationImport(ClassDeclarationType, id); usingDeclaration(node->classNameReference->className, dec); m_result.setDeclaration(dec); } else if (node->classNameReference->className && node->classNameReference->className->identifier) { const QualifiedIdentifier id = identifierForNamespace(node->classNameReference->className->identifier, m_editor); DeclarationPointer dec = findDeclarationImport(ClassDeclarationType, id); usingDeclaration(node->classNameReference->className->identifier->namespaceNameSequence->back()->element, dec); buildNamespaceUses(node->classNameReference->className->identifier, id); m_result.setDeclaration(dec); } } void ExpressionVisitor::visitVarExpressionArray(VarExpressionArrayAst *node) { DefaultVisitor::visitVarExpressionArray(node); m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeArray))); } void ExpressionVisitor::visitClosure(ClosureAst* node) { auto* closureType = new FunctionType; closureType->setReturnType(AbstractType::Ptr(new IntegralType(IntegralType::TypeVoid))); openClosureReturnType(AbstractType::Ptr(closureType)); if (node->functionBody) { visitInnerStatementList(node->functionBody); } if (node->returnType && node->returnType->typehint && isClassTypehint(node->returnType->typehint, m_editor)) { NamespacedIdentifierAst* objectType = node->returnType->typehint->genericType; QualifiedIdentifier id = identifierForNamespace(objectType, m_editor); DeclarationPointer dec = findDeclarationImport(ClassDeclarationType, id); usingDeclaration(objectType->namespaceNameSequence->back()->element, dec); buildNamespaceUses(objectType, id); } //Override found type with return typehint or phpdoc return typehint AbstractType::Ptr type = returnType(node->returnType, {}, m_editor, m_currentContext); if (type) { closureType->setReturnType(type); } if (node->parameters->parametersSequence) { const KDevPG::ListNode< ParameterAst* >* it = node->parameters->parametersSequence->front(); forever { AbstractType::Ptr type = parameterType(it->element, {}, m_editor, m_currentContext); closureType->addArgument(type); if (it->element->parameterType && it->element->parameterType->typehint && isClassTypehint(it->element->parameterType->typehint, m_editor)) { NamespacedIdentifierAst* objectType = it->element->parameterType->typehint->genericType; QualifiedIdentifier id = identifierForNamespace(objectType, m_editor); DeclarationPointer dec = findDeclarationImport(ClassDeclarationType, id); usingDeclaration(objectType->namespaceNameSequence->back()->element, dec); buildNamespaceUses(objectType, id); } if (it->element->defaultValue) { visitExpr(it->element->defaultValue); } if ( it->hasNext() ) { it = it->next; } else { break; } } } if (node->lexicalVars && node->lexicalVars->lexicalVarsSequence) { const KDevPG::ListNode< LexicalVarAst* >* it = node->lexicalVars->lexicalVarsSequence->front(); DUChainWriteLocker lock; forever { DeclarationPointer found; foreach(Declaration* dec, m_currentContext->findDeclarations(identifierForNode(it->element->variable))) { if (dec->kind() == Declaration::Instance) { found = dec; break; } } usingDeclaration(it->element->variable, found); if ( it->hasNext() ) { it = it->next; } else { break; } } } m_result.setType(AbstractType::Ptr(closureType)); closeClosureReturnType(); } void ExpressionVisitor::visitFunctionCallParameterList( FunctionCallParameterListAst* node ) { QList decs = m_result.allDeclarations(); AbstractType::Ptr type = m_result.type(); DefaultVisitor::visitFunctionCallParameterList( node ); m_result.setDeclarations(decs); m_result.setType(type); } void ExpressionVisitor::visitFunctionCallParameterListElement(FunctionCallParameterListElementAst* node) { DefaultVisitor::visitFunctionCallParameterListElement(node); if (m_inDefine) m_inDefine = false; //reset after first parameter passed, the second argument can be a class name } void ExpressionVisitor::visitFunctionCall(FunctionCallAst* node) { if (node->stringFunctionNameOrClass && !node->stringFunctionName && !node->varFunctionName) { QualifiedIdentifier id = identifierForNamespace(node->stringFunctionNameOrClass, m_editor); if (id.toString(RemoveExplicitlyGlobalPrefix) == QLatin1String("define") && node->stringParameterList && node->stringParameterList->parametersSequence && node->stringParameterList->parametersSequence->count() > 0) { //in a define() call the first argument is the constant name. we don't want to look for a class name to build uses m_inDefine = true; } } DefaultVisitor::visitFunctionCall(node); m_inDefine = false; if (node->stringFunctionNameOrClass) { if (node->stringFunctionName) { //static function call foo::bar() DUContext* context = findClassContext(node->stringFunctionNameOrClass); if (context) { DUChainReadLocker lock(DUChain::lock()); QualifiedIdentifier methodName(stringForNode(node->stringFunctionName).toLower()); m_result.setDeclarations(context->findDeclarations(methodName)); lock.unlock(); if (!m_result.allDeclarations().isEmpty()) { usingDeclaration(node->stringFunctionName, m_result.allDeclarations().last()); FunctionType::Ptr function = m_result.allDeclarations().last()->type(); if (function) { m_result.setType(function->returnType()); } else { m_result.setType(AbstractType::Ptr()); } } } else { m_result.setHadUnresolvedIdentifiers(true); usingDeclaration(node->stringFunctionName, DeclarationPointer()); m_result.setType(AbstractType::Ptr()); } } else if (node->varFunctionName) { //static function call foo::$bar() } else if (node->expr) { //static function call foo::{expr}() const QualifiedIdentifier id = identifierForNamespace(node->stringFunctionNameOrClass, m_editor); DeclarationPointer dec = findDeclarationImport(ClassDeclarationType, id); usingDeclaration(node->stringFunctionNameOrClass->namespaceNameSequence->back()->element, dec); buildNamespaceUses(node->stringFunctionNameOrClass, id); m_result.setDeclaration(dec); } else { //global function call foo(); QualifiedIdentifier id = identifierForNamespace(node->stringFunctionNameOrClass, m_editor); DeclarationPointer dec = findDeclarationImport(FunctionDeclarationType, id); if (!dec) { id.setExplicitlyGlobal(true); dec = findDeclarationImport(FunctionDeclarationType, id); } ifDebug(qCDebug(DUCHAIN) << "function call of" << (dec ? dec->toString() : QString("function not found"));) m_result.setDeclaration(dec); usingDeclaration(node->stringFunctionNameOrClass->namespaceNameSequence->back()->element, dec); buildNamespaceUses(node->stringFunctionNameOrClass, id); if (dec) { FunctionType::Ptr function = dec->type(); if (function) { m_result.setType(function->returnType()); } else { m_result.setType(AbstractType::Ptr()); } } else { m_result.setHadUnresolvedIdentifiers(true); } } } } ///TODO: DUContext pointer? DUContext* ExpressionVisitor::findClassContext(IdentifierAst* className) { DUContext* context = nullptr; DeclarationPointer declaration = findDeclarationImport(ClassDeclarationType, className); usingDeclaration(className, declaration); if (declaration) { DUChainReadLocker lock(DUChain::lock()); context = declaration->internalContext(); if (!context && m_currentContext->parentContext() && m_currentContext->parentContext()->localScopeIdentifier() == declaration->qualifiedIdentifier()) { //className is currentClass (internalContext is not yet set) context = m_currentContext->parentContext(); } } return context; } ///TODO: DUContext pointer? DUContext* ExpressionVisitor::findClassContext(NamespacedIdentifierAst* className) { DUContext* context = nullptr; const QualifiedIdentifier id = identifierForNamespace(className, m_editor); DeclarationPointer declaration = findDeclarationImport(ClassDeclarationType, id); usingDeclaration(className->namespaceNameSequence->back()->element, declaration); buildNamespaceUses(className, id); if (declaration) { DUChainReadLocker lock(DUChain::lock()); context = declaration->internalContext(); if (!context && m_currentContext->parentContext() && m_currentContext->parentContext()->localScopeIdentifier() == declaration->qualifiedIdentifier()) { //className is currentClass (internalContext is not yet set) context = m_currentContext->parentContext(); } } return context; } void ExpressionVisitor::visitConstantOrClassConst(ConstantOrClassConstAst *node) { DefaultVisitor::visitConstantOrClassConst(node); if (node->classConstant) { //class constant Foo::BAR DUContext* context = findClassContext(node->constant); if (context) { DUChainReadLocker lock(DUChain::lock()); m_result.setDeclarations(context->findDeclarations(Identifier(m_editor->parseSession()->symbol(node->classConstant)))); lock.unlock(); if (!m_result.allDeclarations().isEmpty()) { usingDeclaration(node->classConstant, m_result.allDeclarations().last()); } else { usingDeclaration(node->classConstant, DeclarationPointer()); } if (!stringForNode(node->classConstant).compare(QLatin1String("class"), Qt::CaseInsensitive)) { m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeString))); } } else { m_result.setType(AbstractType::Ptr()); } } else { QString str(stringForNode(node->constant).toLower()); if (str == QLatin1String("true") || str == QLatin1String("false")) { m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeBoolean))); } else if (str == QLatin1String("null")) { m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeNull))); } else { //constant (created with declare('foo', 'bar')) or const Foo = 1; QualifiedIdentifier id = identifierForNamespace(node->constant, m_editor, true); DeclarationPointer declaration = findDeclarationImport(ConstantDeclarationType, id); if (!declaration) { id.setExplicitlyGlobal(true); declaration = findDeclarationImport(ConstantDeclarationType, id); } if (!declaration) { ///TODO: is this really wanted? //it could also be a global function call, without () declaration = findDeclarationImport(FunctionDeclarationType, id); } m_result.setDeclaration(declaration); usingDeclaration(node->constant->namespaceNameSequence->back()->element, declaration); buildNamespaceUses(node->constant, id); } } } void ExpressionVisitor::visitScalar(ScalarAst *node) { DefaultVisitor::visitScalar(node); if (node->commonScalar) { uint type = IntegralType::TypeVoid; switch (node->commonScalar->scalarType) { case ScalarTypeInt: type = IntegralType::TypeInt; break; case ScalarTypeFloat: type = IntegralType::TypeFloat; break; case ScalarTypeString: type = IntegralType::TypeString; break; } m_result.setType(AbstractType::Ptr(new IntegralType(type))); } else if (node->varname != -1) { //STRING_VARNAME-Token, probably the type of varname should be used m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeString))); } else if (node->encapsList) { m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeString))); } if (!m_inDefine && node->commonScalar && node->commonScalar->scalarType == ScalarTypeString) { QString str = m_editor->parseSession()->symbol(node->commonScalar); QRegExp exp("^['\"]([A-Za-z0-9_]+)['\"]$"); if (exp.exactMatch(str)) { //that *could* be a class name QualifiedIdentifier id(exp.cap(1).toLower()); DeclarationPointer declaration = findDeclarationImport(ClassDeclarationType, id); if (declaration) { usingDeclaration(node->commonScalar, declaration); } else { m_result.setHadUnresolvedIdentifiers(true); } } } } void ExpressionVisitor::visitStaticScalar(StaticScalarAst *node) { if (node->ducontext) { m_currentContext = node->ducontext; } Q_ASSERT(m_currentContext); DefaultVisitor::visitStaticScalar(node); uint type = IntegralType::TypeVoid; if (node->value) { switch (node->value->scalarType) { case ScalarTypeInt: type = IntegralType::TypeInt; break; case ScalarTypeFloat: type = IntegralType::TypeFloat; break; case ScalarTypeString: type = IntegralType::TypeString; break; } } else if (node->plusValue || node->minusValue) { type = IntegralType::TypeInt; } else if (node->array != -1) { type = IntegralType::TypeArray; } if (type != IntegralType::TypeVoid) { m_result.setType(AbstractType::Ptr(new IntegralType(type))); } } void ExpressionVisitor::visitEncapsVar(EncapsVarAst *node) { DefaultVisitor::visitEncapsVar(node); if (node->variable) { // handle $foo DeclarationPointer dec = processVariable(node->variable); if (dec && node->propertyIdentifier) { // handle property in $foo->bar DeclarationPointer foundDec; DUChainReadLocker lock(DUChain::lock()); if ( StructureType::Ptr structType = dec->type() ) { if ( ClassDeclaration* cdec = dynamic_cast(structType->declaration(m_currentContext->topContext())) ) { ///TODO: share code with visitVariableProperty DUContext* ctx = cdec->internalContext(); if (!ctx && m_currentContext->parentContext()) { if (m_currentContext->parentContext()->localScopeIdentifier() == cdec->qualifiedIdentifier()) { //class is currentClass (internalContext is not yet set) ctx = m_currentContext->parentContext(); } } if (ctx) { foreach( Declaration* pdec, ctx->findDeclarations(identifierForNode(node->propertyIdentifier)) ) { if ( !pdec->isFunctionDeclaration() ) { foundDec = pdec; break; } } } } } lock.unlock(); usingDeclaration(node->propertyIdentifier, foundDec); } } } void ExpressionVisitor::visitVariableProperty(VariablePropertyAst *node) { ifDebug(qCDebug(DUCHAIN) << "node:" << m_editor->parseSession()->symbol(node) << (node->isFunctionCall != -1 ? QString("is function call") : QString("is no function call"));) if (node->objectProperty && node->objectProperty->objectDimList) { //handle $foo->bar() and $foo->baz, $foo is m_result.type() AbstractType::Ptr type = m_result.type(); //If the variable type is unsure, try to see if it contains a StructureType. If so, use that // (since the other types do not allow accessing properties) if (type && type.cast()) { UnsureType::Ptr unsureType = type.cast(); int numStructureType = 0; StructureType::Ptr structureType; for (unsigned int i = 0; itypesSize(); ++i) { StructureType::Ptr subType = unsureType->types()[i].type(); if (subType) { structureType = subType; ++numStructureType; } } //Only use the found structureType if there's exactly *one* such type if (numStructureType == 1) { Q_ASSERT(structureType); type = AbstractType::Ptr(structureType); } } if (type && StructureType::Ptr::dynamicCast(type)) { DUChainReadLocker lock(DUChain::lock()); Declaration* declaration = StructureType::Ptr::staticCast(type)->declaration(m_currentContext->topContext()); if (declaration) { ifDebug(qCDebug(DUCHAIN) << "parent:" << declaration->toString();) DUContext* context = declaration->internalContext(); if (!context && m_currentContext->parentContext()) { if (m_currentContext->parentContext()->localScopeIdentifier() == declaration->qualifiedIdentifier()) { //class is currentClass (internalContext is not yet set) context = m_currentContext->parentContext(); } } if (context) { QualifiedIdentifier propertyId; if ( node->isFunctionCall != -1 ) { propertyId = QualifiedIdentifier(stringForNode(node->objectProperty->objectDimList->variableName->name).toLower()); } else { propertyId = identifierForNode(node->objectProperty->objectDimList->variableName->name); } ifDebug(qCDebug(DUCHAIN) << "property id:" << propertyId.toString();) QList decs; foreach ( Declaration* dec, context->findDeclarations(propertyId) ) { if ( node->isFunctionCall != -1 ) { if ( dec->isFunctionDeclaration() ) { decs << dec; ifDebug(qCDebug(DUCHAIN) << "found:" << dec->toString();) } } else { if ( !dec->isFunctionDeclaration() ) { ClassMemberDeclaration *classDec = dynamic_cast(dec); if (classDec && classDec->accessPolicy() == Declaration::Private) { if (declaration == dec->context()->owner()) { decs << dec; ifDebug(qCDebug(DUCHAIN) << "found private:" << dec->toString();) } } else { decs << dec; ifDebug(qCDebug(DUCHAIN) << "found:" << dec->toString();) } } } } m_result.setDeclarations(decs); lock.unlock(); if (!m_result.allDeclarations().isEmpty()) { if ( !m_isAssignmentExpressionEqual ) { usingDeclaration(node->objectProperty->objectDimList->variableName, m_result.allDeclarations().last()); } if (node->isFunctionCall != -1) { FunctionType::Ptr function = m_result.allDeclarations().last()->type(); if (function) { m_result.setType(function->returnType()); } else { m_result.setType(AbstractType::Ptr()); } } } else { if ( !m_isAssignmentExpressionEqual ) { usingDeclaration(node->objectProperty->objectDimList->variableName, DeclarationPointer()); } m_result.setType(AbstractType::Ptr()); } } else { m_result.setType(AbstractType::Ptr()); } } else { m_result.setType(AbstractType::Ptr()); } } } DefaultVisitor::visitVariableProperty(node); } void ExpressionVisitor::visitStaticMember(StaticMemberAst* node) { //don't call DefaultVisitor::visitStaticMember(node); //because we would end up in visitCompoundVariableWithSimpleIndirectReference - if (node->staticProperty->staticProperty->variable->variable) { - DUContext* context = findClassContext(node->className); - if (context) { - useDeclaration(node->staticProperty->staticProperty->variable, context); - } else { - usingDeclaration(node->className, DeclarationPointer()); + if (node->staticProperty && node->staticProperty->staticProperty) { + if (node->staticProperty->staticProperty->variable) { + DUContext* context = findClassContext(node->className); + if (context) { + useDeclaration(node->staticProperty->staticProperty->variable, context); + } else { + usingDeclaration(node->className, DeclarationPointer()); + m_result.setType(AbstractType::Ptr()); + } + } else if (node->staticProperty->staticProperty->expr) { + const QualifiedIdentifier id = identifierForNamespace(node->className, m_editor); + DeclarationPointer declaration = findDeclarationImport(ClassDeclarationType, id); + usingDeclaration(node->className->namespaceNameSequence->back()->element, declaration); + buildNamespaceUses(node->className, id); + + visitExpr(node->staticProperty->staticProperty->expr); + m_result.setType(AbstractType::Ptr()); } - if (node->staticProperty->offsetItemsSequence) { - const KDevPG::ListNode< DimListItemAst* >* it = node->staticProperty->offsetItemsSequence->front(); - do { - visitDimListItem(it->element); - } while(it->hasNext() && (it = it->next)); - } + } + + if (node->staticProperty && node->staticProperty->offsetItemsSequence) { + const KDevPG::ListNode< DimListItemAst* >* it = node->staticProperty->offsetItemsSequence->front(); + do { + visitDimListItem(it->element); + } while(it->hasNext() && (it = it->next)); } } void ExpressionVisitor::visitClassNameReference(ClassNameReferenceAst* node) { if (node->staticProperty) { DUContext* context = findClassContext(node->className->identifier); - if (context) { - useDeclaration(node->staticProperty->staticProperty->variable, context); + if (context && node->staticProperty && node->staticProperty->staticProperty) { + if (node->staticProperty->staticProperty->variable) { + // static properties (object::$property) + useDeclaration(node->staticProperty->staticProperty->variable, context); + } else if (node->staticProperty->staticProperty->expr) { + // variable static properties (object::${$property}) + visitExpr(node->staticProperty->staticProperty->expr); + usingDeclaration(node->className, DeclarationPointer()); + } } - if (node->staticProperty->offsetItemsSequence) { + if (node->staticProperty && node->staticProperty->offsetItemsSequence) { const KDevPG::ListNode< DimListItemAst* >* dim_it = node->staticProperty->offsetItemsSequence->front(); do { visitDimListItem(dim_it->element); } while(dim_it->hasNext() && (dim_it = dim_it->next)); } } if (node->baseVariable) { DefaultVisitor::visitVariableWithoutObjects(node->baseVariable); } if (node->propertiesSequence) { if (!m_result.allDeclarations().isEmpty()) { DUContext* context = nullptr; StructureType::Ptr type; Declaration *declaration = nullptr; const KDevPG::ListNode< ClassPropertyAst* >* it = node->propertiesSequence->front(); do { // first check for property names held in variables ($object->$property) if (it->element->property && it->element->property->variableWithoutObjects && it->element->property->variableWithoutObjects->variable->variable) { VariableIdentifierAst *varnode = it->element->property->variableWithoutObjects->variable->variable; useDeclaration(varnode, m_currentContext); + } else if (it->element->property && it->element->property->variableWithoutObjects + && it->element->property->variableWithoutObjects->variable->expr) { + // variable dynamic properties ($object->${$property}) + visitExpr(it->element->property->variableWithoutObjects->variable->expr); } else if (!m_result.allDeclarations().isEmpty()) { // handle array indices after normal/static properties ($object->property[$index] // $object::$property[$index]) if (it->element->property && it->element->property->objectDimList && it->element->property->objectDimList->offsetItemsSequence) { const KDevPG::ListNode< DimListItemAst* >* dim_it = it->element->property->objectDimList->offsetItemsSequence->front(); do { visitDimListItem(dim_it->element); } while(dim_it->hasNext() && (dim_it = dim_it->next)); } else if (it->element->staticProperty && it->element->staticProperty->offsetItemsSequence) { const KDevPG::ListNode< DimListItemAst* >* dim_it = it->element->staticProperty->offsetItemsSequence->front(); do { visitDimListItem(dim_it->element); } while(dim_it->hasNext() && (dim_it = dim_it->next)); } + // Handle dynamic static properties first, as they don't need a class context + if (it->element->staticProperty && it->element->staticProperty->staticProperty + && it->element->staticProperty->staticProperty->expr) { + // variable static properties ($object::${$property}) + visitExpr(it->element->staticProperty->staticProperty->expr); + usingDeclaration(it->element->staticProperty, DeclarationPointer()); + } + type = m_result.allDeclarations().last()->type(); if (!type) { context = nullptr; continue; } DUChainReadLocker lock(DUChain::lock()); declaration = type->declaration(m_currentContext->topContext()); lock.unlock(); if (!declaration) { context = nullptr; continue; } context = declaration->internalContext(); if (!context || context->type() != DUContext::Class) { context = nullptr; continue; } - if (it->element->staticProperty) { + if (it->element->staticProperty && it->element->staticProperty->staticProperty + && it->element->staticProperty->staticProperty->variable) { // static properties ($object::$property) VariableIdentifierAst *varnode = it->element->staticProperty->staticProperty->variable; useDeclaration(varnode, context); } else if (it->element->property && it->element->property->objectDimList && it->element->property->objectDimList->variableName->name) { // normal properties ($object->property) IdentifierAst *varidnode = it->element->property->objectDimList->variableName->name; useDeclaration(varidnode, context); } else { context = nullptr; } } } while(it->hasNext() && (it = it->next)); } } } void ExpressionVisitor::visitUnaryExpression(UnaryExpressionAst* node) { DefaultVisitor::visitUnaryExpression(node); if (node->castType) { uint type = 0; switch (node->castType) { case CastInt: type = IntegralType::TypeInt; break; case CastDouble: type = IntegralType::TypeFloat; break; case CastString: type = IntegralType::TypeString; break; case CastArray: type = IntegralType::TypeArray; break; case CastObject: { /// Qualified identifier for 'stdclass' static const QualifiedIdentifier stdclassQId(QStringLiteral("stdclass")); DUChainReadLocker lock(DUChain::lock()); m_result.setDeclarations(m_currentContext->findDeclarations(stdclassQId)); break; } case CastBool: type = IntegralType::TypeBoolean; break; case CastUnset: //TODO break; } if (type) { m_result.setType(AbstractType::Ptr(new IntegralType(type))); } } } void ExpressionVisitor::visitAdditiveExpressionRest(AdditiveExpressionRestAst* node) { DefaultVisitor::visitAdditiveExpressionRest(node); if (node->operation == OperationPlus || node->operation == OperationMinus) { m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeInt))); } else if (node->operation == OperationConcat) { m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeString))); } } void ExpressionVisitor::visitRelationalExpression(RelationalExpressionAst *node) { DefaultVisitor::visitRelationalExpression(node); if (node->instanceofType && node->instanceofType->className && node->instanceofType->className->identifier) { const QualifiedIdentifier id = identifierForNamespace(node->instanceofType->className->identifier, m_editor); DeclarationPointer dec = findDeclarationImport(ClassDeclarationType, id); usingDeclaration(node->instanceofType->className->identifier->namespaceNameSequence->back()->element, dec); buildNamespaceUses(node->instanceofType->className->identifier, id); m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeBoolean))); } } void ExpressionVisitor::visitRelationalExpressionRest(RelationalExpressionRestAst *node) { DefaultVisitor::visitRelationalExpressionRest(node); m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeBoolean))); } void ExpressionVisitor::visitEqualityExpressionRest(EqualityExpressionRestAst *node) { DefaultVisitor::visitEqualityExpressionRest(node); if (node->operation && node->operation == OperationSpaceship) { m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeInt))); } else { m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeBoolean))); } } void ExpressionVisitor::visitStatement(StatementAst *node) { DefaultVisitor::visitStatement(node); if (node->returnExpr) { FunctionType::Ptr closureType = currentClosureReturnType().cast(); if (closureType) { closureType->setReturnType(m_result.type()); } } } QString ExpressionVisitor::stringForNode(AstNode* id) { if (!id) return QString(); return m_editor->parseSession()->symbol(id); } QualifiedIdentifier ExpressionVisitor::identifierForNode(IdentifierAst* id) { if (!id) return QualifiedIdentifier(); return QualifiedIdentifier(stringForNode(id)); } QString ExpressionVisitor::stringForNode(VariableIdentifierAst* id) { if (!id) return QString(); QString ret(m_editor->parseSession()->symbol(id->variable)); ret = ret.mid(1); //cut off $ return ret; } QualifiedIdentifier ExpressionVisitor::identifierForNode(VariableIdentifierAst* id) { if (!id) return QualifiedIdentifier(); return QualifiedIdentifier(stringForNode(id)); } void ExpressionVisitor::setCreateProblems(bool v) { m_createProblems = v; } void ExpressionVisitor::setOffset(const CursorInRevision& offset) { m_offset = offset; } void ExpressionVisitor::buildNamespaceUses(NamespacedIdentifierAst* namespaces, const QualifiedIdentifier& identifier) { QualifiedIdentifier curId; curId.setExplicitlyGlobal(identifier.explicitlyGlobal()); Q_ASSERT(identifier.count() == namespaces->namespaceNameSequence->count()); for ( int i = 0; i < identifier.count() - 1; ++i ) { curId.push(identifier.at(i)); AstNode* node = namespaces->namespaceNameSequence->at(i)->element; DeclarationPointer dec = findDeclarationImport(NamespaceDeclarationType, curId); usingDeclaration(node, dec); } } void ExpressionVisitor::useDeclaration(VariableIdentifierAst* node, DUContext* context) { DUChainReadLocker lock(DUChain::lock()); m_result.setDeclarations(context->findDeclarations(identifierForNode(node))); lock.unlock(); if (!m_result.allDeclarations().isEmpty()) { usingDeclaration(node, m_result.allDeclarations().last()); } else { usingDeclaration(node, DeclarationPointer()); } } void ExpressionVisitor::useDeclaration(IdentifierAst* node, DUContext* context) { DUChainReadLocker lock(DUChain::lock()); m_result.setDeclarations(context->findDeclarations(identifierForNode(node))); lock.unlock(); if (!m_result.allDeclarations().isEmpty()) { usingDeclaration(node, m_result.allDeclarations().last()); } else { usingDeclaration(node, DeclarationPointer()); } } DeclarationPointer ExpressionVisitor::findDeclarationImport(DeclarationType declarationType, IdentifierAst* node) { // methods and class names are case insensitive QualifiedIdentifier id; if ( declarationType == ClassDeclarationType || declarationType == FunctionDeclarationType ) { id = QualifiedIdentifier(stringForNode(node).toLower()); } else { id = identifierForNode(node); } return findDeclarationImport(declarationType, id); } DeclarationPointer ExpressionVisitor::findDeclarationImport(DeclarationType declarationType, VariableIdentifierAst* node) { return findDeclarationImport(declarationType, identifierForNode(node)); } DeclarationPointer ExpressionVisitor::findDeclarationImport( DeclarationType declarationType, const QualifiedIdentifier& identifier) { return findDeclarationImportHelper(m_currentContext, identifier, declarationType); } Declaration* ExpressionVisitor::findVariableDeclaration(DUContext* context, Identifier identifier, CursorInRevision position, DUContext::SearchFlag flag) { QList decls = context->findDeclarations(identifier, position, nullptr, flag); for (int i = decls.count() - 1; i >= 0; i--) { Declaration *dec = decls.at(i); if (dec->kind() == Declaration::Instance && dynamic_cast(dec)) { return dec; } } return nullptr; } } diff --git a/duchain/helper.cpp b/duchain/helper.cpp index 27b819d..e0bdca3 100644 --- a/duchain/helper.cpp +++ b/duchain/helper.cpp @@ -1,599 +1,598 @@ /*************************************************************************** * 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 "helper.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "editorintegrator.h" #include "../parser/parsesession.h" #include "phpast.h" #include "phpdefaultvisitor.h" #include "declarations/classdeclaration.h" #include "declarations/classmethoddeclaration.h" #include "declarations/functiondeclaration.h" #include "types/indexedcontainer.h" #include "types/integraltypeextended.h" #include "expressionparser.h" #include "expressionvisitor.h" #include "duchaindebug.h" #define ifDebug(x) using namespace KDevelop; namespace Php { bool isMatch(Declaration* declaration, DeclarationType declarationType) { if (declarationType == ClassDeclarationType && dynamic_cast(declaration) ) { return true; } else if (declarationType == FunctionDeclarationType && dynamic_cast(declaration) ) { return true; } else if (declarationType == ConstantDeclarationType && declaration->abstractType() && declaration->abstractType()->modifiers() & AbstractType::ConstModifier && (!declaration->context() || declaration->context()->type() != DUContext::Class) ) { return true; } else if (declarationType == GlobalVariableDeclarationType && declaration->kind() == Declaration::Instance && !(declaration->abstractType() && declaration->abstractType()->modifiers() & AbstractType::ConstModifier) ) { return true; } else if (declarationType == NamespaceDeclarationType && (declaration->kind() == Declaration::Namespace || declaration->kind() == Declaration::NamespaceAlias || dynamic_cast(declaration)) ) { return true; } return false; } bool isClassTypehint(GenericTypeHintAst* genericType, EditorIntegrator *editor) { Q_ASSERT(genericType); if (genericType->callableType != -1) { return false; } else if (genericType->arrayType != -1) { return false; } else if (genericType->genericType) { NamespacedIdentifierAst* node = genericType->genericType; const KDevPG::ListNode< IdentifierAst* >* it = node->namespaceNameSequence->front(); QString typehint = editor->parseSession()->symbol(it->element); if (typehint.compare(QLatin1String("bool"), Qt::CaseInsensitive) == 0) { return false; } else if (typehint.compare(QLatin1String("float"), Qt::CaseInsensitive) == 0) { return false; } else if (typehint.compare(QLatin1String("int"), Qt::CaseInsensitive) == 0) { return false; } else if (typehint.compare(QLatin1String("string"), Qt::CaseInsensitive) == 0) { return false; } else if (typehint.compare(QLatin1String("iterable"), Qt::CaseInsensitive) == 0) { return false; } else if (typehint.compare(QLatin1String("object"), Qt::CaseInsensitive) == 0) { return false; } else { return true; } } else { return false; } } DeclarationPointer findDeclarationImportHelper(DUContext* currentContext, const QualifiedIdentifier& id, DeclarationType declarationType) { /// Qualified identifier for 'self' static const QualifiedIdentifier selfQId(QStringLiteral("self")); /// Qualified identifier for 'parent' static const QualifiedIdentifier parentQId(QStringLiteral("parent")); /// Qualified identifier for 'static' static const QualifiedIdentifier staticQId(QStringLiteral("static")); QualifiedIdentifier lookup; if (id.explicitlyGlobal()) { ifDebug(qCDebug(DUCHAIN) << id.toString() << declarationType;) lookup = id; lookup.setExplicitlyGlobal(false); } else { lookup = identifierWithNamespace(id, currentContext); ifDebug(qCDebug(DUCHAIN) << lookup.toString() << declarationType;) } if (declarationType == ClassDeclarationType && id == selfQId) { DUChainReadLocker lock(DUChain::lock()); if (currentContext->type() == DUContext::Class) { return DeclarationPointer(currentContext->owner()); } else if (currentContext->parentContext() && currentContext->parentContext()->type() == DUContext::Class) { return DeclarationPointer(currentContext->parentContext()->owner()); } else { return DeclarationPointer(); } } else if (declarationType == ClassDeclarationType && id == staticQId) { DUChainReadLocker lock; if (currentContext->type() == DUContext::Class) { return DeclarationPointer(currentContext->owner()); } else if (currentContext->parentContext() && currentContext->parentContext()->type() == DUContext::Class) { return DeclarationPointer(currentContext->parentContext()->owner()); } else { return DeclarationPointer(); } } else if (declarationType == ClassDeclarationType && id == parentQId) { //there can be just one Class-Context imported DUChainReadLocker lock; DUContext* classCtx = nullptr; if (currentContext->type() == DUContext::Class) { classCtx = currentContext; } else if (currentContext->parentContext() && currentContext->parentContext()->type() == DUContext::Class) { classCtx = currentContext->parentContext(); } if (classCtx) { foreach(const DUContext::Import &i, classCtx->importedParentContexts()) { DUContext* ctx = i.context(classCtx->topContext()); if (ctx && ctx->type() == DUContext::Class) { return DeclarationPointer(ctx->owner()); } } } return DeclarationPointer(); } else { DUChainReadLocker lock; QList foundDeclarations = currentContext->topContext()->findDeclarations(lookup); foreach(Declaration *declaration, foundDeclarations) { if (isMatch(declaration, declarationType)) { return DeclarationPointer(declaration); } } if ( currentContext->url() == internalFunctionFile() ) { // when compiling php internal functions, we don't need to ask the persistent symbol table for anything return DeclarationPointer(); } lock.unlock(); if (declarationType != GlobalVariableDeclarationType) { ifDebug(qCDebug(DUCHAIN) << "No declarations found with findDeclarations, trying through PersistentSymbolTable";) DeclarationPointer decl; decl = findDeclarationInPST(currentContext, lookup, declarationType); if (decl) { ifDebug(qCDebug(DUCHAIN) << "PST declaration exists";) } else { ifDebug(qCDebug(DUCHAIN) << "PST declaration does not exist";) } return decl; } } ifDebug(qCDebug(DUCHAIN) << "returning 0";) return DeclarationPointer(); } DeclarationPointer findDeclarationInPST(DUContext* currentContext, QualifiedIdentifier id, DeclarationType declarationType) { ifDebug(qCDebug(DUCHAIN) << "PST: " << id.toString() << declarationType;) uint nr; const IndexedDeclaration* declarations = nullptr; DUChainWriteLocker wlock; PersistentSymbolTable::self().declarations(id, nr, declarations); ifDebug(qCDebug(DUCHAIN) << "found declarations:" << nr;) /// Indexed string for 'Php', identifies environment files from this language plugin static const IndexedString phpLangString("Php"); for (uint i = 0; i < nr; ++i) { ParsingEnvironmentFilePointer env = DUChain::self()->environmentFileForDocument(declarations[i].indexedTopContext()); if(!env) { ifDebug(qCDebug(DUCHAIN) << "skipping declaration, missing meta-data";) continue; } if(env->language() != phpLangString) { ifDebug(qCDebug(DUCHAIN) << "skipping declaration, invalid language" << env->language().str();) continue; } if (!declarations[i].declaration()) { ifDebug(qCDebug(DUCHAIN) << "skipping declaration, doesn't have declaration";) continue; } else if (!isMatch(declarations[i].declaration(), declarationType)) { ifDebug(qCDebug(DUCHAIN) << "skipping declaration, doesn't match with declarationType";) continue; } TopDUContext* top = declarations[i].declaration()->context()->topContext(); /* * NOTE: * To enable PHPUnit test classes, this check has been disabled. * Formerly it only loaded declarations from open projects, but PHPUnit declarations * belong to no project. * * If this behavior is unwanted, reinstate the check. * Miha Cancula */ /* if (ICore::self() && !ICore::self()->projectController()->projects().isEmpty()) { bool loadedProjectContainsUrl = false; foreach(IProject *project, ICore::self()->projectController()->projects()) { if (project->fileSet().contains(top->url())) { loadedProjectContainsUrl = true; break; } } if (!loadedProjectContainsUrl) { ifDebug(qCDebug(DUCHAIN) << "skipping declaration, not in loaded project";) continue; } } */ currentContext->topContext()->addImportedParentContext(top); currentContext->topContext()->parsingEnvironmentFile() ->addModificationRevisions(top->parsingEnvironmentFile()->allModificationRevisions()); currentContext->topContext()->updateImportsCache(); ifDebug(qCDebug(DUCHAIN) << "using" << declarations[i].declaration()->toString() << top->url();) wlock.unlock(); return DeclarationPointer(declarations[i].declaration()); } wlock.unlock(); ifDebug(qCDebug(DUCHAIN) << "returning 0";) return DeclarationPointer(); } QByteArray formatComment(AstNode* node, EditorIntegrator* editor) { return KDevelop::formatComment(editor->parseSession()->docComment(node->startToken).toUtf8()); } //Helper visitor to extract a commonScalar node //used to get the value of an function call argument class ScalarExpressionVisitor : public DefaultVisitor { public: ScalarExpressionVisitor() : m_node(nullptr) {} CommonScalarAst* node() const { return m_node; } private: void visitCommonScalar(CommonScalarAst* node) override { m_node = node; } CommonScalarAst* m_node; }; CommonScalarAst* findCommonScalar(AstNode* node) { ScalarExpressionVisitor visitor; visitor.visitNode(node); return visitor.node(); } static bool includeExists(const Path &include) { const QString path = include.pathOrUrl(); { DUChainReadLocker lock; if (DUChain::self()->chainForDocument(IndexedString(path))) { return true; } } if ( include.isLocalFile() ) { return QFile::exists(path); } else { return false; } } static IndexedString findIncludeFile(const QString &includePath, const IndexedString ¤tDocument) { if ( includePath.isEmpty() ) { return IndexedString(); } // check remote files if ( includePath.startsWith(QLatin1String("http://"), Qt::CaseInsensitive) || includePath.startsWith(QLatin1String("ftp://"), Qt::CaseInsensitive) ) { // always expect remote includes to exist return IndexedString(includePath); } const Path currentPath(currentDocument.str()); // look for file relative to current url Path include = Path(currentPath.parent(), includePath); if ( includeExists(include) ) { return IndexedString(include.pathOrUrl()); } // in the first round look for a project that is a parent of the current document // in the next round look for any project for (int i = 0; i < 2; ++i) { foreach(IProject* project, ICore::self()->projectController()->projects()) { if ( !i && !project->path().isParentOf(currentPath)) { continue; } include = Path(project->path(), includePath); if ( includeExists(include) ) { return IndexedString(include.pathOrUrl()); } } } //TODO configurable include paths return IndexedString(); } IndexedString getIncludeFileForNode(UnaryExpressionAst* node, EditorIntegrator* editor) { if ( node->includeExpression ) { //find name of the constant (first argument of the function call) CommonScalarAst* scalar = findCommonScalar(node->includeExpression); if (scalar && scalar->string != -1) { QString str = editor->parseSession()->symbol(scalar->string); str = str.mid(1, str.length() - 2); if ( str == QLatin1String(".") || str == QLatin1String("..") || str.endsWith('/') ) { return IndexedString(); } return findIncludeFile(str, editor->parseSession()->currentDocument()); } } return IndexedString(); } QString prettyName(Declaration* dec) { if (!dec) { return {}; } else if ( dec->context() && dec->context()->type() == DUContext::Class && dec->isFunctionDeclaration() ) { ClassMethodDeclaration* classMember = dynamic_cast(dec); Q_ASSERT(classMember); return classMember->prettyName().str(); } else if ( dec->isFunctionDeclaration() ) { FunctionDeclaration* func = dynamic_cast(dec); Q_ASSERT(func); return func->prettyName().str(); } else if ( dec->internalContext() && dec->internalContext()->type() == DUContext::Class ) { ClassDeclaration* classDec = dynamic_cast(dec); Q_ASSERT(classDec); return classDec->prettyName().str(); } else { return dec->identifier().toString(); } } const KDevelop::IndexedString& internalFunctionFile() { static const KDevelop::IndexedString internalFile(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kdevphpsupport/phpfunctions.php"))); return internalFile; } const KDevelop::IndexedString& phpLanguageString() { static const KDevelop::IndexedString phpLangString("Php"); return phpLangString; } const IndexedString& internalTestFile() { static const KDevelop::IndexedString phpUnitFile(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kdevphpsupport/phpunitdeclarations.php"))); return phpUnitFile; } QualifiedIdentifier identifierForNamespace(NamespacedIdentifierAst* node, EditorIntegrator* editor, bool lastIsConstIdentifier) { QualifiedIdentifier id; if (node->isGlobal != -1) { id.setExplicitlyGlobal(true); } const KDevPG::ListNode< IdentifierAst* >* it = node->namespaceNameSequence->front(); do { if (lastIsConstIdentifier && !it->hasNext()) { id.push(Identifier(editor->parseSession()->symbol(it->element))); } else { id.push(Identifier(editor->parseSession()->symbol(it->element).toLower())); } } while (it->hasNext() && (it = it->next)); return id; } QualifiedIdentifier identifierWithNamespace(const QualifiedIdentifier& base, DUContext* context) { DUChainReadLocker lock; auto scope = context; while (scope && scope->type() != DUContext::Namespace) { scope = scope->parentContext(); } if (scope) { return scope->scopeIdentifier() + base; } else { return base; } } template AbstractType::Ptr determineTypehint(const T* genericType, EditorIntegrator *editor, DUContext* currentContext) { Q_ASSERT(genericType); AbstractType::Ptr type; if (genericType->typehint) { if (genericType->typehint->callableType != -1) { type = AbstractType::Ptr(new IntegralTypeExtended(IntegralTypeExtended::TypeCallable)); } else if (genericType->typehint->arrayType != -1) { type = AbstractType::Ptr(new IntegralType(IntegralType::TypeArray)); } else if (genericType->typehint->genericType) { NamespacedIdentifierAst* node = genericType->typehint->genericType; const KDevPG::ListNode< IdentifierAst* >* it = node->namespaceNameSequence->front(); QString typehint = editor->parseSession()->symbol(it->element); if (typehint.compare(QLatin1String("bool"), Qt::CaseInsensitive) == 0) { type = AbstractType::Ptr(new IntegralType(IntegralType::TypeBoolean)); } else if (typehint.compare(QLatin1String("float"), Qt::CaseInsensitive) == 0) { type = AbstractType::Ptr(new IntegralType(IntegralType::TypeFloat)); } else if (typehint.compare(QLatin1String("int"), Qt::CaseInsensitive) == 0) { type = AbstractType::Ptr(new IntegralType(IntegralType::TypeInt)); } else if (typehint.compare(QLatin1String("string"), Qt::CaseInsensitive) == 0) { type = AbstractType::Ptr(new IntegralType(IntegralType::TypeString)); } else if (typehint.compare(QLatin1String("object"), Qt::CaseInsensitive) == 0) { type = AbstractType::Ptr(new IntegralTypeExtended(IntegralTypeExtended::TypeObject)); } else if (typehint.compare(QLatin1String("iterable"), Qt::CaseInsensitive) == 0) { DeclarationPointer traversableDecl = findDeclarationImportHelper(currentContext, QualifiedIdentifier("traversable"), ClassDeclarationType); if (traversableDecl) { UnsureType::Ptr unsure(new UnsureType()); AbstractType::Ptr arrayType = AbstractType::Ptr(new IntegralType(IntegralType::TypeArray)); unsure->addType(arrayType->indexed()); unsure->addType(traversableDecl->abstractType()->indexed()); type = AbstractType::Ptr(unsure); } } else { //don't use openTypeFromName as it uses cursor for findDeclarations DeclarationPointer decl = findDeclarationImportHelper(currentContext, identifierForNamespace(genericType->typehint->genericType, editor), ClassDeclarationType); if (decl) { type = decl->abstractType(); } } } } if (type && genericType->isNullable != -1) { AbstractType::Ptr nullType = AbstractType::Ptr(new IntegralType(IntegralType::TypeNull)); if (type.cast()) { UnsureType::Ptr unsure = type.cast(); unsure->addType(nullType->indexed()); } else { UnsureType::Ptr unsure(new UnsureType()); unsure->addType(type->indexed()); unsure->addType(nullType->indexed()); type = AbstractType::Ptr(unsure); } } return type; } AbstractType::Ptr parameterType(const ParameterAst* node, AbstractType::Ptr phpDocTypehint, EditorIntegrator* editor, DUContext* currentContext) { AbstractType::Ptr type; if (node->parameterType) { type = determineTypehint(node->parameterType, editor, currentContext); } if (node->defaultValue) { ExpressionVisitor v(editor); node->defaultValue->ducontext = currentContext; v.visitNode(node->defaultValue); AbstractType::Ptr defaultValueType = v.result().type(); /* * If a typehint is already set, default values can only be the same type or `null` in PHP * If it's the same type, the type is already correctly set, * so we can ignore this case. * If it's null (which cannot be a typehint), add it as UnsureType */ if (type && defaultValueType.cast() && defaultValueType.cast()->dataType() == IntegralType::TypeNull) { if (type.cast()) { UnsureType::Ptr unsure = type.cast(); AbstractType::Ptr nullType = AbstractType::Ptr(new IntegralType(IntegralType::TypeNull)); unsure->addType(defaultValueType->indexed()); } else { UnsureType::Ptr unsure(new UnsureType()); unsure->addType(type->indexed()); unsure->addType(defaultValueType->indexed()); type = AbstractType::Ptr(unsure); } } else { //Otherwise, let the default value dictate the parameter type type = defaultValueType; } } if (!type) { if (phpDocTypehint) { type = phpDocTypehint; } else { type = AbstractType::Ptr(new IntegralType(IntegralType::TypeMixed)); } } if ( node->isRef != -1 ) { ReferenceType::Ptr p( new ReferenceType() ); p->setBaseType( type ); type = p.cast(); } if (node->isVariadic != -1) { auto *container = new IndexedContainer(); - const IndexedString *containerType = new IndexedString("array"); container->addEntry(type); - container->setPrettyName(*containerType); + container->setPrettyName(IndexedString("array")); type = AbstractType::Ptr(container); } Q_ASSERT(type); return type; } AbstractType::Ptr returnType(const ReturnTypeAst* node, AbstractType::Ptr phpDocTypehint, EditorIntegrator* editor, DUContext* currentContext) { AbstractType::Ptr type; if (node) { if (node->voidType != -1) { type = AbstractType::Ptr(new IntegralType(IntegralType::TypeVoid)); } else { type = determineTypehint(node, editor, currentContext); } } if (!type) { type = phpDocTypehint; } return type; } } diff --git a/duchain/tests/uses.cpp b/duchain/tests/uses.cpp index 0e1695c..b63fd6d 100644 --- a/duchain/tests/uses.cpp +++ b/duchain/tests/uses.cpp @@ -1,1589 +1,1701 @@ /* This file is part of KDevelop Copyright 2008 Niko Sams This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "uses.h" #include #include #include #include #include "../declarations/classdeclaration.h" #include "../declarations/variabledeclaration.h" #include "../declarations/traitmethodaliasdeclaration.h" #include "../declarations/traitmemberaliasdeclaration.h" #include "../types/structuretype.h" using namespace KDevelop; QTEST_MAIN(Php::TestUses) namespace Php { void compareUses(Declaration* dec, QList ranges) { qDebug() << "comparing uses for" << dec->toString(); QCOMPARE(dec->uses().keys().count(), 1); QCOMPARE(dec->uses().values().count(), 1); QCOMPARE(dec->uses().values().first().count(), ranges.count()); for (int i = 0; i < ranges.count(); ++i) { qDebug() << dec->uses().values().first().at(i) << ranges.at(i); QCOMPARE(dec->uses().values().first().at(i), ranges.at(i)); } } void compareUses(Declaration* dec, RangeInRevision range) { QList r; r << range; compareUses(dec, r); } TestUses::TestUses() { } void TestUses::newObject() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().first(), RangeInRevision(0, 25, 0, 28)); QCOMPARE(top->localDeclarations().first()->uses().keys().first(), IndexedString(QUrl("file:///internal/usestest/newObject.php"))); } void TestUses::functionCall() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().first(); compareUses(fun, RangeInRevision(0, 21, 0, 24)); QCOMPARE(fun->uses().keys().first(), IndexedString(QUrl("file:///internal/usestest/functionCall.php"))); } void TestUses::memberFunctionCall() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("foo(); "); TopDUContext* top = parse(method, DumpNone, QUrl(QStringLiteral("file:///internal/usestest/memberFunctionCall.php"))); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); Declaration* fun = top->childContexts().first()->localDeclarations().first(); compareUses(fun, RangeInRevision(0, 51, 0, 54)); QCOMPARE(fun->uses().keys().first(), IndexedString(QUrl("file:///internal/usestest/memberFunctionCall.php"))); } void TestUses::unsureMemberFunctionCall() { //First try with a single unsure structure type { // 0 1 2 3 4 5 6 7 8 // 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("foo();"); TopDUContext* top = parse(method, DumpNone); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); Declaration* fun = top->childContexts().first()->localDeclarations().first(); compareUses(fun, RangeInRevision(0, 85, 0, 88)); } //Now try with two unsure structure types { // 0 1 2 3 4 5 6 7 8 // 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("foo();"); TopDUContext* top = parse(method, DumpNone); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); Declaration* fun = top->childContexts().first()->localDeclarations().first(); QCOMPARE(fun->uses().keys().count(), 0); } } void TestUses::memberVariable() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("foo; "); TopDUContext* top = parse(method, DumpNone, QUrl(QStringLiteral("file:///internal/usestest/memberVariable.php"))); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); Declaration* var = top->childContexts().first()->localDeclarations().first(); compareUses(var, RangeInRevision(0, 46, 0, 49)); QCOMPARE(var->uses().keys().first(), IndexedString(QUrl("file:///internal/usestest/memberVariable.php"))); } void TestUses::implicitMemberVariable() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("y = 1; $x->y = 2; class A {}"); TopDUContext* top = parse(method, DumpNone); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); Declaration* var = top->childContexts().first()->localDeclarations().first(); QList ranges; ranges << RangeInRevision(0, 21, 0, 22) << RangeInRevision(0, 32, 0, 33); compareUses(var, ranges); QVERIFY(var->range() == RangeInRevision(0, 21, 0, 22)); } void TestUses::variable() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("foo; foo($a); "); TopDUContext* top = parse(method, DumpAll); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); QList ranges; ranges << RangeInRevision(1, 42 - 3, 1, 44 - 3) << RangeInRevision(1, 46 - 3, 1, 48 - 3) << RangeInRevision(1, 59 - 3, 1, 61 - 3); compareUses(top->localDeclarations().at(1), ranges); } void TestUses::varInString() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method(" ranges; ranges << RangeInRevision(0, 13, 0, 15) << RangeInRevision(0, 17, 0, 19); compareUses(top->localDeclarations().at(0), ranges); } void TestUses::variableInNamespace() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("foo; foo($a); };"); TopDUContext* top = parse(method, DumpAll); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); QList ranges; ranges << RangeInRevision(1, 55, 1, 57) << RangeInRevision(1, 59, 1, 61) << RangeInRevision(1, 72, 1, 74); compareUses(top->localDeclarations().at(2), ranges); } void TestUses::globalVariableInNamespace() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("foo; foo($a); };"); TopDUContext* top = parse(method, DumpAll); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); QList ranges; ranges << RangeInRevision(1, 55, 1, 57) << RangeInRevision(1, 59, 1, 61) << RangeInRevision(1, 72, 1, 74); compareUses(top->localDeclarations().at(1), ranges); } void TestUses::variableInOtherNamespace() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("foo; foo($a); };"); TopDUContext* top = parse(method, DumpAll); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); QList ranges; ranges << RangeInRevision(1, 73, 1, 75) << RangeInRevision(1, 77, 1, 79) << RangeInRevision(1, 90, 1, 92); compareUses(top->localDeclarations().at(2), ranges); } void TestUses::memberVarInString() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789 01234567890123 4567890123456789 QByteArray method("v; \"$a->v {$a->v}\"; "); TopDUContext* top = parse(method, DumpAll); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); QList ranges; ranges << RangeInRevision(0, 43, 0, 45) << RangeInRevision(0, 51, 0, 53) << RangeInRevision(0, 58, 0, 60); compareUses(top->localDeclarations().at(1), ranges); ranges.clear(); ranges << RangeInRevision(0, 47, 0, 48) << RangeInRevision(0, 55, 0, 56) << RangeInRevision(0, 62, 0, 63); compareUses(top->childContexts().first()->localDeclarations().first(), ranges); } void TestUses::memberFunctionInString() { // 0 1 2 3 4 5 6 7 // 012345678901234567890123456789012345678901234567 890123456789 01234567890123456789 QByteArray method("foo()}\"; "); TopDUContext* top = parse(method, DumpAll); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); //$a compareUses(top->localDeclarations().at(1), RangeInRevision(0, 50, 0, 52)); //foo compareUses(top->childContexts().first()->localDeclarations().first(), RangeInRevision(0, 54, 0, 57)); } void TestUses::variableTypeChange() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method(" ranges; ranges << RangeInRevision(0, 25, 0, 27); ranges << RangeInRevision(0, 29, 0, 31); ranges << RangeInRevision(0, 37, 0, 39); ranges << RangeInRevision(0, 41, 0, 43); ranges << RangeInRevision(0, 51, 0, 53); compareUses(top->localDeclarations().at(1), ranges); } void TestUses::variableTypeChangeInFunction() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method(" ranges; ranges << RangeInRevision(0, 28, 0, 30); ranges << RangeInRevision(0, 32, 0, 34); ranges << RangeInRevision(0, 38, 0, 40); ranges << RangeInRevision(0, 42, 0, 44); compareUses(top->childContexts().at(1)->localDeclarations().at(0), ranges); } void TestUses::classExtends() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().at(0), RangeInRevision(0, 31, 0, 32)); } void TestUses::classImplements() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().at(0), RangeInRevision(0, 38, 0, 39)); } void TestUses::classImplementsMultiple() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().at(0), RangeInRevision(0, 54, 0, 55)); compareUses(top->localDeclarations().at(1), RangeInRevision(0, 57, 0, 58)); } void TestUses::interfaceExtends() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().at(0), RangeInRevision(0, 39, 0, 40)); } void TestUses::interfaceExtendsMultiple() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().at(0), RangeInRevision(0, 55, 0, 56)); compareUses(top->localDeclarations().at(1), RangeInRevision(0, 58, 0, 59)); } void TestUses::staticMemberFunctionCall() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().first(), RangeInRevision(0, 47, 0, 48)); compareUses(top->childContexts().first()->localDeclarations().first(), RangeInRevision(0, 50, 0, 53)); } void TestUses::staticMemberVariable() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().first(), RangeInRevision(0, 43, 0, 44)); compareUses(top->childContexts().first()->localDeclarations().first(), RangeInRevision(0, 46, 0, 50)); compareUses(top->localDeclarations().at(1), RangeInRevision(0, 52, 0, 56)); } +void TestUses::dynamicStaticMemberVariable() +{ + // 0 1 2 3 4 5 6 7 + // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 + QByteArray method("localDeclarations().at(0); + QCOMPARE(dec->identifier(), Identifier("a")); + compareUses(dec, QList() + << RangeInRevision(0, 47, 0, 48)); + + dec = top->localDeclarations().at(1); + QCOMPARE(dec->identifier(), Identifier("var")); + compareUses(dec, QList() + << RangeInRevision(0, 52, 0, 56)); +} + void TestUses::constant() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().first(), RangeInRevision(0, 28, 0, 29)); } void TestUses::classConstant() { { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().first(), RangeInRevision(0, 39, 0, 40)); compareUses(top->childContexts().first()->localDeclarations().first(), RangeInRevision(0, 42, 0, 45)); } { // bug: https://bugs.kde.org/show_bug.cgi?id=241597 // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("childContexts().first()->localDeclarations().first(); QVERIFY(dec->abstractType()->modifiers() & AbstractType::ConstModifier); QCOMPARE(dec->qualifiedIdentifier().toString(), QString("a::FOO")); compareUses(dec, QList() << RangeInRevision(1, 43, 1, 46) << RangeInRevision(2, 3, 2, 6) << RangeInRevision(3, 3, 3, 6)); } } void TestUses::classParent() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method(" range; range << RangeInRevision(0, 47, 0, 48); range << RangeInRevision(0, 66, 0, 72); compareUses(top->localDeclarations().first(), range); compareUses(top->childContexts().first()->localDeclarations().first(), RangeInRevision(0, 74, 0, 75)); } void TestUses::classSelf() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().first(), RangeInRevision(0, 28, 0, 32)); compareUses(top->childContexts().first()->localDeclarations().first(), RangeInRevision(0, 34, 0, 35)); } void TestUses::classThis() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("x(); } } "); TopDUContext* top = parse(method, DumpAll); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); compareUses(top->localDeclarations().first(), RangeInRevision(0, 28, 0, 33)); compareUses(top->childContexts().first()->localDeclarations().first(), RangeInRevision(0, 35, 0, 36)); } void TestUses::objectWithClassName() { // 0 1 2 3 4 5 6 7 8 // 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("k; Aa::j; Aa::$i;"); TopDUContext* top = parse(method, DumpAll); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); QList ranges; ranges << RangeInRevision(0, 66, 0, 66 + 2); ranges << RangeInRevision(0, 78, 0, 78 + 2); ranges << RangeInRevision(0, 85, 0, 85 + 2); compareUses(top->localDeclarations().first(), ranges); compareUses(top->localDeclarations().at(1), RangeInRevision(0, 70, 0, 70 + 3)); } void TestUses::classAndConstWithSameName() { // 0 1 2 3 4 5 6 7 8 // 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().first(), RangeInRevision(0, 38, 0, 39)); compareUses(top->localDeclarations().at(1), RangeInRevision(0, 31, 0, 32)); compareUses(top->localDeclarations().at(2), RangeInRevision(0, 76, 0, 77)); compareUses(top->localDeclarations().at(3), RangeInRevision(0, 73, 0, 74)); } void TestUses::classAndFunctionWithSameName() { // 0 1 2 3 4 5 6 7 8 // 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().first(), RangeInRevision(0, 35, 0, 36)); compareUses(top->localDeclarations().at(1), RangeInRevision(0, 38, 0, 39)); } void TestUses::constAndVariableWithSameName() { // 0 1 2 3 4 5 6 7 8 // 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().first(), RangeInRevision(0, 30, 0, 32)); compareUses(top->localDeclarations().at(1), RangeInRevision(0, 27, 0, 28)); } void TestUses::functionAndClassWithSameName() { // 0 1 2 3 4 5 6 7 8 // 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("childContexts().first()->localDeclarations().first(); QCOMPARE(fnAsdf->uses().keys().count(), 0); compareUses(top->localDeclarations().at(1), RangeInRevision(0, 70, 0, 74)); } void TestUses::constantInClassMember() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("findDeclarations(Identifier(QStringLiteral("TEST"))).first(); QList uses; uses << RangeInRevision(0, 41, 0, 45); uses << RangeInRevision(0, 63, 0, 67); uses << RangeInRevision(0, 73, 0, 77); compareUses(constant, uses); } void TestUses::useInAsignment() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().first(); compareUses(d, RangeInRevision(0, 16, 0, 18)); } void TestUses::foreachArray() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("$i) { var_dump($k, $i); } "); TopDUContext* top = parse(method, DumpAll); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); // $a, $k, $i QCOMPARE(top->localDeclarations().size(), 3); // $a Declaration *d = top->localDeclarations().at(0); compareUses(d, RangeInRevision(0, 26, 0, 28)); // $k d = top->localDeclarations().at(1); compareUses(d, RangeInRevision(0, 51, 0, 53)); // $i d = top->localDeclarations().at(2); compareUses(d, RangeInRevision(0, 55, 0, 57)); } void TestUses::assignmentToMemberArray() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("y[$a] = true; } }"); TopDUContext* top = parse(method, DumpAll); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); // class x Declaration *x = top->localDeclarations().first(); QVERIFY(x); // $this compareUses(x, RangeInRevision(0, 50, 0, 55)); // var $y Declaration *y = x->logicalInternalContext(top)->findDeclarations(Identifier(QStringLiteral("y"))).first(); QVERIFY(y); // $this->y compareUses(y, RangeInRevision(0, 57, 0, 58)); // function z Declaration *z = x->logicalInternalContext(top)->findDeclarations(Identifier(QStringLiteral("z"))).first(); QVERIFY(z); // $a Declaration *a = z->logicalInternalContext(top)->findDeclarations(Identifier(QStringLiteral("a"))).first(); QVERIFY(a); compareUses(a, QList() // $b = $a << RangeInRevision(0, 46, 0, 48) // $this->y[$a] << RangeInRevision(0, 59, 0, 61) ); } void TestUses::staticArrayIndex() { // bug: https://bugs.kde.org/show_bug.cgi?id=241160 // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("childContexts().first()->localDeclarations().first(); QCOMPARE(a->identifier().toString(), QString("a")); compareUses(a, RangeInRevision(0, 68, 0, 70)); Declaration* i = top->childContexts().first()->childContexts().first()->localDeclarations().first(); QCOMPARE(i->identifier().toString(), QString("i")); compareUses(i, RangeInRevision(0, 71, 0, 73)); } void TestUses::functionParamNewDeclaration() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("childContexts().first()->localDeclarations().first(); QList ranges; ranges << RangeInRevision(0, 22, 0, 24); ranges << RangeInRevision(0, 26, 0, 28); compareUses(d, ranges); } void TestUses::catchClass() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("findDeclarations(QualifiedIdentifier(QStringLiteral("exception"))).first(); compareUses(d, RangeInRevision(0, 18, 0, 27)); } void TestUses::variableRedeclaration() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method(" decs = top->findDeclarations(QualifiedIdentifier(QStringLiteral("s"))); QCOMPARE(decs.size(), 1); Declaration *d = decs.first(); compareUses(d, QList() << RangeInRevision(0, 13, 0, 15) << RangeInRevision(0, 18, 0, 20) << RangeInRevision(0, 23, 0, 25) ); } void TestUses::caseInsensitiveFunction() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method(" decs = top->findLocalDeclarations(Identifier(QStringLiteral("foobar"))); QCOMPARE(decs.size(), 1); Declaration *d = decs.first(); compareUses(d, QList() << RangeInRevision(1, 0, 1, 6) << RangeInRevision(2, 0, 2, 6) << RangeInRevision(3, 0, 3, 6) ); } void TestUses::caseInsensitiveMethod() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("fOoBar();\n$a->FOOBAR();\n$a->foobar();\n" "asdf::barfoo();\nasdf::bArFoo();\nasdf::BARFOO();\n"); TopDUContext* top = parse(method, DumpNone); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); { QList decs = top->childContexts().first()->findDeclarations(QualifiedIdentifier(QStringLiteral("foobar"))); QCOMPARE(decs.size(), 1); Declaration *d = decs.first(); compareUses(d, QList() << RangeInRevision(1, 4, 1, 10) << RangeInRevision(2, 4, 2, 10) << RangeInRevision(3, 4, 3, 10) ); } { QList decs = top->childContexts().first()->findDeclarations(QualifiedIdentifier(QStringLiteral("barfoo"))); QCOMPARE(decs.size(), 1); Declaration *d = decs.first(); compareUses(d, QList() << RangeInRevision(4, 6, 4, 12) << RangeInRevision(5, 6, 5, 12) << RangeInRevision(6, 6, 6, 12) ); } } void TestUses::caseInsensitiveClass() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method(" decs = top->findLocalDeclarations(Identifier(QStringLiteral("asdf"))); QCOMPARE(decs.size(), 1); Declaration *d = decs.first(); compareUses(d, QList() << RangeInRevision(1, 4, 1, 8) << RangeInRevision(2, 4, 2, 8) << RangeInRevision(3, 4, 3, 8) ); } void TestUses::functionUseBeforeDeclaration() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method(" decs = top->localDeclarations(); QCOMPARE(decs.size(), 1); QCOMPARE(decs.first()->range(), RangeInRevision(0, 20, 0, 24)); compareUses(decs.first(), RangeInRevision(0, 3, 0, 7)); } void TestUses::propertyAndMethodWithSameName() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("name1(); $a->name1;\n" "$a->name2; $a->name2();"); TopDUContext* top = parse(method, DumpNone); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); QVector decs = top->childContexts().first()->localDeclarations(); QCOMPARE(decs.size(), 4); // method name1 QVERIFY(decs[0]->identifier().nameEquals(Identifier("name1"))); QVERIFY(decs[0]->isFunctionDeclaration()); compareUses(decs[0], RangeInRevision(2, 4, 2, 9)); // property name1 QVERIFY(decs[1]->identifier().nameEquals(Identifier("name1"))); QVERIFY(!decs[1]->isFunctionDeclaration()); compareUses(decs[1], RangeInRevision(2, 17, 2, 22)); // property name2 QVERIFY(decs[2]->identifier().nameEquals(Identifier("name2"))); QVERIFY(!decs[2]->isFunctionDeclaration()); compareUses(decs[2], RangeInRevision(3, 4, 3, 9)); // method name2 QVERIFY(decs[3]->identifier().nameEquals(Identifier("name2"))); QVERIFY(decs[3]->isFunctionDeclaration()); compareUses(decs[3], RangeInRevision(3, 15, 3, 20)); } void TestUses::nestedMethodCalls() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("a($b->b());"); TopDUContext* top = parse(method, DumpNone); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); QVector topDecs = top->localDeclarations(); QCOMPARE(topDecs.size(), 4); // class a QVERIFY(topDecs[0]->identifier().nameEquals(Identifier("a"))); QVERIFY(dynamic_cast(topDecs[0])); compareUses(topDecs[0], RangeInRevision(3, 9, 3, 10)); // class b QVERIFY(topDecs[1]->identifier().nameEquals(Identifier("b"))); QVERIFY(dynamic_cast(topDecs[1])); compareUses(topDecs[1], RangeInRevision(4, 9, 4, 10)); // $a QVERIFY(topDecs[2]->identifier().nameEquals(Identifier("a"))); QVERIFY(dynamic_cast(topDecs[2])); compareUses(topDecs[2], RangeInRevision(5, 0, 5, 2)); // $b QVERIFY(topDecs[3]->identifier().nameEquals(Identifier("b"))); QVERIFY(dynamic_cast(topDecs[3])); compareUses(topDecs[3], RangeInRevision(5, 6, 5, 8)); // function a Declaration* methodADec = topDecs[0]->internalContext()->localDeclarations().first(); QVERIFY(methodADec->isFunctionDeclaration()); compareUses(methodADec, RangeInRevision(5, 4, 5, 5)); // function b Declaration* methodBDec = topDecs[1]->internalContext()->localDeclarations().first(); QVERIFY(methodBDec->isFunctionDeclaration()); compareUses(methodBDec, RangeInRevision(5, 10, 5, 11)); } void TestUses::unset() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method(" decs = top->localDeclarations(); QCOMPARE(decs.size(), 1); QCOMPARE(decs.first()->range(), RangeInRevision(0, 3, 0, 5)); compareUses(decs.first(), RangeInRevision(0, 17, 0, 19)); } void TestUses::functionArguments() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("childContexts().size(), 2); QCOMPARE(top->childContexts().first()->type(), DUContext::Function); // $a Declaration *d = top->childContexts().at(0)->localDeclarations().at(0); compareUses(d, RangeInRevision(0, 27, 0, 29)); // $b d = top->childContexts().at(0)->localDeclarations().at(1); compareUses(d, RangeInRevision(0, 35, 0, 37)); } void TestUses::namespaces() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("findDeclarations(QualifiedIdentifier(QStringLiteral("foo"))).last(); QCOMPARE(dec->kind(), Declaration::Namespace); compareUses(dec, QList() << RangeInRevision(9, 1, 9, 4) << RangeInRevision(10, 1, 10, 4) << RangeInRevision(11, 1, 11, 4) << RangeInRevision(12, 5, 12, 8) << RangeInRevision(13, 15, 13, 18) << RangeInRevision(14, 17, 14, 20) << RangeInRevision(14, 45, 14, 48)); dec = top->findDeclarations(QualifiedIdentifier(QStringLiteral("foo::bar"))).first(); QCOMPARE(dec->kind(), Declaration::Namespace); QVERIFY(dec->internalContext()); compareUses(dec, QList() << RangeInRevision(9, 5, 9, 8) << RangeInRevision(10, 5, 10, 8) << RangeInRevision(11, 5, 11, 8) << RangeInRevision(12, 9, 12, 12) << RangeInRevision(13, 19, 13, 22) << RangeInRevision(14, 21, 14, 24) << RangeInRevision(14, 49, 14, 52)); QCOMPARE(dec->internalContext()->localDeclarations().size(), 4); foreach(Declaration* d, dec->internalContext()->localDeclarations()) { qDebug() << d->toString() << d->qualifiedIdentifier(); } dec = top->findDeclarations(QualifiedIdentifier(QStringLiteral("foo::bar::MyConst"))).first(); compareUses(dec, QList() << RangeInRevision(3, 5, 3, 12) << RangeInRevision(9, 9, 9, 16)); dec = top->findDeclarations(QualifiedIdentifier(QStringLiteral("foo::bar::myclass"))).first(); QVERIFY(dynamic_cast(dec)); compareUses(dec, QList() << RangeInRevision(10, 9, 10, 16) << RangeInRevision(12, 13, 12, 20) << RangeInRevision(13, 23, 13, 30) << RangeInRevision(14, 25, 14, 32) ); dec = top->findDeclarations(QualifiedIdentifier(QStringLiteral("foo::bar::myinterface"))).first(); QVERIFY(dynamic_cast(dec)); compareUses(dec, RangeInRevision(14, 53, 14, 64) ); dec = top->findDeclarations(QualifiedIdentifier(QStringLiteral("foo::bar::myclass::ClassConst"))).first(); compareUses(dec, RangeInRevision(10, 18, 10, 28)); dec = top->findDeclarations(QualifiedIdentifier(QStringLiteral("foo::bar::myfunc"))).first(); compareUses(dec, RangeInRevision(11, 9, 11, 15)); } void TestUses::useNamespace() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("findDeclarations(QualifiedIdentifier(QStringLiteral("foo"))).first(); QCOMPARE(dec->kind(), Declaration::Namespace); compareUses(dec, RangeInRevision(5, 4, 5, 7)); dec = top->findDeclarations(QualifiedIdentifier(QStringLiteral("foo::bar"))).first(); QCOMPARE(dec->kind(), Declaration::Namespace); compareUses(dec, RangeInRevision(5, 8, 5, 11)); dec = top->findDeclarations(QualifiedIdentifier(QStringLiteral("verylong"))).first(); QCOMPARE(dec->kind(), Declaration::Namespace); compareUses(dec, RangeInRevision(5, 13, 5, 21)); dec = top->findDeclarations(QualifiedIdentifier(QStringLiteral("bar"))).first(); QCOMPARE(dec->kind(), Declaration::NamespaceAlias); compareUses(dec, QList() << RangeInRevision(7, 4, 7, 7) << RangeInRevision(7, 11, 7, 14) << RangeInRevision(7, 20, 7, 23) ); dec = top->findDeclarations(QualifiedIdentifier(QStringLiteral("short"))).first(); QCOMPARE(dec->kind(), Declaration::NamespaceAlias); compareUses(dec, QList() << RangeInRevision(8, 4, 8, 9) << RangeInRevision(8, 13, 8, 18) << RangeInRevision(8, 24, 8, 29) ); dec = top->findDeclarations(QualifiedIdentifier(QStringLiteral("baz::a"))).first(); compareUses(dec, QList() << RangeInRevision(6, 8, 6, 9) << RangeInRevision(9, 4, 9, 10)); dec = top->findDeclarations(QualifiedIdentifier(QStringLiteral("foo::bar::a"))).first(); compareUses(dec, RangeInRevision(7, 8, 7, 9)); dec = top->findDeclarations(QualifiedIdentifier(QStringLiteral("foo::bar::b"))).first(); compareUses(dec, RangeInRevision(7, 15, 7, 16)); dec = top->findDeclarations(QualifiedIdentifier(QStringLiteral("foo::bar::C"))).first(); compareUses(dec, RangeInRevision(7, 24, 7, 25)); dec = top->findDeclarations(QualifiedIdentifier(QStringLiteral("verylong::a"))).first(); compareUses(dec, RangeInRevision(8, 10, 8, 11)); dec = top->findDeclarations(QualifiedIdentifier(QStringLiteral("verylong::b"))).first(); compareUses(dec, RangeInRevision(8, 19, 8, 20)); dec = top->findDeclarations(QualifiedIdentifier(QStringLiteral("verylong::C"))).first(); compareUses(dec, RangeInRevision(8, 30, 8, 31)); } void TestUses::lateStatic() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("childContexts().first()->localDeclarations().first(), RangeInRevision(0, 39, 0, 40)); } void TestUses::closures() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("localDeclarations().count(), 4); Declaration* a = top->localDeclarations().at(0); QCOMPARE(a->identifier().toString(), QString("a")); compareUses(a, QList() << RangeInRevision(1, 23, 1, 25) << RangeInRevision(1, 36, 1, 38)); Declaration* b = top->localDeclarations().at(1); QCOMPARE(b->identifier().toString(), QString("b")); QVERIFY(b->uses().isEmpty()); } void TestUses::closureTypehints() { TopDUContext* top = parse("localDeclarations().count(), 3); Declaration* a = top->localDeclarations().at(0); QCOMPARE(a->qualifiedIdentifier(), QualifiedIdentifier("a")); compareUses(a, QList() << RangeInRevision(0, 32, 0, 33) << RangeInRevision(0, 39, 0, 40)); } void TestUses::instanceof() { // 0 1 2 3 4 5 // 012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("localDeclarations().count(), 3); Declaration* a = top->localDeclarations().at(0); QCOMPARE(a->identifier().toString(), QString("a")); compareUses(a, QList() << RangeInRevision(1, 9, 1, 10) << RangeInRevision(2, 19, 2, 20)); } void TestUses::instanceofClassProperty() { // 0 1 2 3 4 5 // 012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("foo->bar->foo;\n", DumpNone); QVERIFY(top); DUChainReleaser releaseTop(top); DUChainWriteLocker lock; QVERIFY(top->problems().isEmpty()); QVERIFY(!top->parentContext()); QCOMPARE(top->childContexts().count(), 2); QCOMPARE(top->localDeclarations().count(), 3); Declaration* dec = top->localDeclarations().at(0); QCOMPARE(dec->identifier(), Identifier("a")); compareUses(dec, QList() << RangeInRevision(2, 9, 2, 10)); dec = top->localDeclarations().at(2); QCOMPARE(dec->identifier(), Identifier("a")); StructureType::Ptr classType = dec->type(); QVERIFY(classType); QCOMPARE(classType->qualifiedIdentifier(), QualifiedIdentifier("a")); QVERIFY(classType->equals(dec->abstractType().data())); compareUses(dec, QList() << RangeInRevision(2, 14, 2, 16) << RangeInRevision(2, 28, 2, 30)); dec = top->childContexts().at(0)->localDeclarations().at(0); QVERIFY(dec); QCOMPARE(dec->identifier(), Identifier("foo")); compareUses(dec, QList() << RangeInRevision(2, 32, 2, 35) << RangeInRevision(2, 42, 2, 45)); dec = top->childContexts().at(1)->localDeclarations().at(0); QVERIFY(dec); QCOMPARE(dec->identifier(), Identifier("bar")); compareUses(dec, QList() << RangeInRevision(2, 37, 2, 40)); } void TestUses::instanceofStaticProperty() { // 0 1 2 3 4 5 // 012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("problems().isEmpty()); QVERIFY(!top->parentContext()); QCOMPARE(top->childContexts().count(), 2); QCOMPARE(top->localDeclarations().count(), 3); Declaration* dec = top->localDeclarations().at(0); QCOMPARE(dec->identifier(), Identifier("a")); compareUses(dec, QList() << RangeInRevision(2, 9, 2, 10) << RangeInRevision(2, 28, 2, 29)); dec = top->localDeclarations().at(2); QCOMPARE(dec->identifier(), Identifier("a")); StructureType::Ptr classType = dec->type(); QVERIFY(classType); QCOMPARE(classType->qualifiedIdentifier(), QualifiedIdentifier("a")); QVERIFY(classType->equals(dec->abstractType().data())); compareUses(dec, QList() << RangeInRevision(2, 14, 2, 16)); dec = top->childContexts().at(0)->localDeclarations().at(0); QVERIFY(dec); QCOMPARE(dec->identifier(), Identifier("foo")); compareUses(dec, QList() << RangeInRevision(2, 31, 2, 35) << RangeInRevision(2, 43, 2, 47)); dec = top->childContexts().at(1)->localDeclarations().at(0); QVERIFY(dec); QCOMPARE(dec->identifier(), Identifier("bar")); compareUses(dec, QList() << RangeInRevision(2, 37, 2, 41)); } void TestUses::instanceofMixedProperty() { // 0 1 2 3 4 5 // 012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("bar::$foo;\n", DumpNone); QVERIFY(top); DUChainReleaser releaseTop(top); DUChainWriteLocker lock; QVERIFY(top->problems().isEmpty()); QVERIFY(!top->parentContext()); QCOMPARE(top->childContexts().count(), 2); QCOMPARE(top->localDeclarations().count(), 3); Declaration* dec = top->localDeclarations().at(0); QCOMPARE(dec->identifier(), Identifier("a")); compareUses(dec, QList() << RangeInRevision(2, 9, 2, 10) << RangeInRevision(2, 28, 2, 29)); dec = top->localDeclarations().at(2); QCOMPARE(dec->identifier(), Identifier("a")); StructureType::Ptr classType = dec->type(); QVERIFY(classType); QCOMPARE(classType->qualifiedIdentifier(), QualifiedIdentifier("a")); QVERIFY(classType->equals(dec->abstractType().data())); compareUses(dec, QList() << RangeInRevision(2, 14, 2, 16)); dec = top->childContexts().at(0)->localDeclarations().at(0); QVERIFY(dec); QCOMPARE(dec->identifier(), Identifier("foo")); compareUses(dec, QList() << RangeInRevision(2, 31, 2, 35) << RangeInRevision(2, 42, 2, 46)); dec = top->childContexts().at(1)->localDeclarations().at(0); QVERIFY(dec); QCOMPARE(dec->identifier(), Identifier("bar")); compareUses(dec, QList() << RangeInRevision(2, 37, 2, 40)); } void TestUses::instanceofVariableProperty() { // 0 1 2 3 4 5 // 012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("$foo->$bar->$foo;\n", DumpNone); QVERIFY(top); DUChainReleaser releaseTop(top); DUChainWriteLocker lock; QVERIFY(top->problems().isEmpty()); QVERIFY(!top->parentContext()); QCOMPARE(top->childContexts().count(), 2); QCOMPARE(top->localDeclarations().count(), 5); Declaration* dec = top->localDeclarations().at(0); QCOMPARE(dec->identifier(), Identifier("a")); compareUses(dec, QList() << RangeInRevision(3, 9, 3, 10)); dec = top->localDeclarations().at(4); QCOMPARE(dec->identifier(), Identifier("a")); StructureType::Ptr classType = dec->type(); QVERIFY(classType); QCOMPARE(classType->qualifiedIdentifier(), QualifiedIdentifier("a")); QVERIFY(classType->equals(dec->abstractType().data())); compareUses(dec, QList() << RangeInRevision(3, 14, 3, 16) << RangeInRevision(3, 28, 3, 30)); dec = top->localDeclarations().at(2); QCOMPARE(dec->identifier(), Identifier("foo")); compareUses(dec, QList() << RangeInRevision(3, 32, 3, 36) << RangeInRevision(3, 44, 3, 48)); dec = top->localDeclarations().at(3); QCOMPARE(dec->identifier(), Identifier("bar")); compareUses(dec, QList() << RangeInRevision(3, 38, 3, 42)); } +void TestUses::instanceofDynamicStaticProperty() +{ + // 0 1 2 3 4 5 + // 012345678901234567890123456789012345678901234567890123456789 + TopDUContext* top = parse("problems().isEmpty()); + + QVERIFY(!top->parentContext()); + QCOMPARE(top->childContexts().count(), 2); + QCOMPARE(top->localDeclarations().count(), 5); + + Declaration* dec = top->localDeclarations().at(0); + QCOMPARE(dec->identifier(), Identifier("a")); + compareUses(dec, QList() + << RangeInRevision(3, 9, 3, 10)); + + dec = top->localDeclarations().at(4); + QCOMPARE(dec->identifier(), Identifier("a")); + StructureType::Ptr classType = dec->type(); + QVERIFY(classType); + QCOMPARE(classType->qualifiedIdentifier(), QualifiedIdentifier("a")); + QVERIFY(classType->equals(dec->abstractType().data())); + compareUses(dec, QList() + << RangeInRevision(3, 14, 3, 16) + << RangeInRevision(3, 28, 3, 30)); + + dec = top->localDeclarations().at(2); + QCOMPARE(dec->identifier(), Identifier("foo")); + compareUses(dec, QList() + << RangeInRevision(3, 34, 3, 38) + << RangeInRevision(3, 52, 3, 56)); + + dec = top->localDeclarations().at(3); + QCOMPARE(dec->identifier(), Identifier("bar")); + compareUses(dec, QList() + << RangeInRevision(3, 43, 3, 47)); +} + +void TestUses::instanceofDynamicVariableProperty() +{ + // 0 1 2 3 4 5 + // 012345678901234567890123456789012345678901234567890123456789 + TopDUContext* top = parse("${$foo}->${$bar}->${$foo};\n", DumpNone); + + QVERIFY(top); + DUChainReleaser releaseTop(top); + DUChainWriteLocker lock; + + QVERIFY(top->problems().isEmpty()); + + QVERIFY(!top->parentContext()); + QCOMPARE(top->childContexts().count(), 2); + QCOMPARE(top->localDeclarations().count(), 5); + + Declaration* dec = top->localDeclarations().at(0); + QCOMPARE(dec->identifier(), Identifier("a")); + compareUses(dec, QList() + << RangeInRevision(3, 9, 3, 10)); + + dec = top->localDeclarations().at(4); + QCOMPARE(dec->identifier(), Identifier("a")); + StructureType::Ptr classType = dec->type(); + QVERIFY(classType); + QCOMPARE(classType->qualifiedIdentifier(), QualifiedIdentifier("a")); + QVERIFY(classType->equals(dec->abstractType().data())); + compareUses(dec, QList() + << RangeInRevision(3, 14, 3, 16) + << RangeInRevision(3, 28, 3, 30)); + + dec = top->localDeclarations().at(2); + QCOMPARE(dec->identifier(), Identifier("foo")); + compareUses(dec, QList() + << RangeInRevision(3, 34, 3, 38) + << RangeInRevision(3, 52, 3, 56)); + + dec = top->localDeclarations().at(3); + QCOMPARE(dec->identifier(), Identifier("bar")); + compareUses(dec, QList() + << RangeInRevision(3, 43, 3, 47)); +} + void TestUses::instanceofPropertyArrayAccess() { // 0 1 2 3 4 5 // 012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("foo[0]::$bar[0]->foo;\n", DumpNone); QVERIFY(top); DUChainReleaser releaseTop(top); DUChainWriteLocker lock; QVERIFY(top->problems().isEmpty()); QVERIFY(!top->parentContext()); QCOMPARE(top->childContexts().count(), 2); QCOMPARE(top->localDeclarations().count(), 3); Declaration* dec = top->localDeclarations().at(0); QCOMPARE(dec->identifier(), Identifier("a")); compareUses(dec, QList() << RangeInRevision(2, 9, 2, 10)); dec = top->localDeclarations().at(2); QCOMPARE(dec->identifier(), Identifier("a")); StructureType::Ptr classType = dec->type(); QVERIFY(classType); QCOMPARE(classType->qualifiedIdentifier(), QualifiedIdentifier("a")); QVERIFY(classType->equals(dec->abstractType().data())); compareUses(dec, QList() << RangeInRevision(2, 14, 2, 16) << RangeInRevision(2, 28, 2, 30)); dec = top->childContexts().at(0)->localDeclarations().at(0); QCOMPARE(dec->identifier(), Identifier("foo")); compareUses(dec, QList() << RangeInRevision(2, 32, 2, 35)); dec = top->childContexts().at(1)->localDeclarations().at(0); QCOMPARE(dec->identifier(), Identifier("bar")); QVERIFY(dec->uses().isEmpty()); } void TestUses::classNameString() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().at(0); QCOMPARE(foo->identifier().toString(), QString("foo")); compareUses(foo, RangeInRevision(0, 22, 0, 27)); } void TestUses::useTrait() { // 0 1 2 3 4 5 6 7 8 // 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("one(); $a->two();\n" "$b = new Bar(); $b->one(); $b->two(); $b->four();\n" "$c = new Baz(); $c->one(); $c->two(); $c->baz; $c::six();\n" "$d = new Boo(); $d->one(); $d->switch(); $d->three(); $d->static();\n", DumpAll); QVERIFY(top); DUChainReleaser releaseTop(top); DUChainWriteLocker lock; Declaration* dec; QVector topDecs = top->localDeclarations(); TraitMethodAliasDeclaration* method; TraitMemberAliasDeclaration* member; QCOMPARE(topDecs.size(), 11); dec = top->findDeclarations(QualifiedIdentifier(QStringLiteral("a"))).first(); compareUses(dec, QList() << RangeInRevision(4, 16, 4, 17) << RangeInRevision(5, 16, 5, 17) << RangeInRevision(5, 22, 5, 23) << RangeInRevision(6, 16, 6, 17) << RangeInRevision(6, 41, 6, 42) << RangeInRevision(7, 16, 7, 17) << RangeInRevision(7, 24, 7, 25) << RangeInRevision(7, 46, 7, 47) ); dec = top->findDeclarations(QualifiedIdentifier(QStringLiteral("b"))).first(); compareUses(dec, QList() << RangeInRevision(5, 18, 5, 19) << RangeInRevision(5, 39, 5, 40) << RangeInRevision(5, 42, 5, 43) << RangeInRevision(6, 18, 6, 19) << RangeInRevision(6, 43, 6, 44) << RangeInRevision(7, 18, 7, 19) << RangeInRevision(7, 41, 7, 42) << RangeInRevision(7, 64, 7, 65) ); dec = top->findDeclarations(QualifiedIdentifier(QStringLiteral("c"))).first(); compareUses(dec, QList() << RangeInRevision(6, 20, 6, 21) << RangeInRevision(6, 24, 6, 25) << RangeInRevision(6, 46, 6, 47) << RangeInRevision(7, 20, 7, 21) << RangeInRevision(7, 43, 7, 44) << RangeInRevision(7, 84, 7, 85) ); dec = topDecs[3]->internalContext()->findLocalDeclarations(Identifier(QStringLiteral("one"))).first(); method = dynamic_cast(dec); QVERIFY(method); QCOMPARE(method->aliasedDeclaration().data()->context()->owner()->identifier(), Identifier("a")); compareUses(dec, QList() << RangeInRevision(8, 20, 8, 23) ); dec = topDecs[3]->internalContext()->findLocalDeclarations(Identifier(QStringLiteral("two"))).first(); method = dynamic_cast(dec); QVERIFY(method); QCOMPARE(method->aliasedDeclaration().data()->context()->owner()->identifier(), Identifier("a")); compareUses(dec, QList() << RangeInRevision(8, 31, 8, 34) ); dec = topDecs[4]->internalContext()->findLocalDeclarations(Identifier(QStringLiteral("one"))).first(); method = dynamic_cast(dec); QVERIFY(method); QCOMPARE(method->aliasedDeclaration().data()->context()->owner()->identifier(), Identifier("a")); compareUses(dec, QList() << RangeInRevision(9, 20, 9, 23) ); dec = topDecs[4]->internalContext()->findLocalDeclarations(Identifier(QStringLiteral("two"))).first(); method = dynamic_cast(dec); QVERIFY(method); QCOMPARE(method->aliasedDeclaration().data()->context()->owner()->identifier(), Identifier("a")); compareUses(dec, QList() << RangeInRevision(9, 31, 9, 34) ); dec = topDecs[4]->internalContext()->findLocalDeclarations(Identifier(QStringLiteral("four"))).first(); method = dynamic_cast(dec); QVERIFY(method); QCOMPARE(method->aliasedDeclaration().data()->context()->owner()->identifier(), Identifier("b")); QCOMPARE(method->aliasedDeclaration().data()->identifier(), Identifier("three")); QCOMPARE(method->accessPolicy(), Declaration::AccessPolicy::Public); compareUses(dec, QList() << RangeInRevision(9, 42, 9, 46) ); dec = topDecs[5]->internalContext()->findLocalDeclarations(Identifier(QStringLiteral("one"))).first(); method = dynamic_cast(dec); QVERIFY(method); QCOMPARE(method->aliasedDeclaration().data()->context()->owner()->identifier(), Identifier("c")); compareUses(dec, QList() << RangeInRevision(10, 20, 10, 23) ); dec = topDecs[5]->internalContext()->findLocalDeclarations(Identifier(QStringLiteral("two"))).first(); method = dynamic_cast(dec); QVERIFY(method); QCOMPARE(method->aliasedDeclaration().data()->context()->owner()->identifier(), Identifier("a")); compareUses(dec, QList() << RangeInRevision(10, 31, 10, 34) ); dec = topDecs[5]->internalContext()->findLocalDeclarations(Identifier(QStringLiteral("baz"))).first(); member = dynamic_cast(dec); QVERIFY(member); QCOMPARE(member->aliasedDeclaration().data()->context()->owner()->identifier(), Identifier("c")); compareUses(dec, QList() << RangeInRevision(10, 42, 10, 45) ); dec = topDecs[5]->internalContext()->findLocalDeclarations(Identifier(QStringLiteral("six"))).first(); method = dynamic_cast(dec); QVERIFY(method); QCOMPARE(method->aliasedDeclaration().data()->context()->owner()->identifier(), Identifier("c")); QCOMPARE(method->aliasedDeclaration().data()->identifier(), Identifier("five")); QVERIFY(method->isStatic()); compareUses(dec, QList() << RangeInRevision(10, 51, 10, 54) ); dec = topDecs[6]->internalContext()->findLocalDeclarations(Identifier(QStringLiteral("one"))).first(); method = dynamic_cast(dec); QVERIFY(method); QCOMPARE(method->aliasedDeclaration().data()->context()->owner()->identifier(), Identifier("a")); compareUses(dec, QList() << RangeInRevision(11, 20, 11, 23) ); dec = topDecs[6]->internalContext()->findLocalDeclarations(Identifier(QStringLiteral("switch"))).first(); method = dynamic_cast(dec); QVERIFY(method); QCOMPARE(method->aliasedDeclaration().data()->context()->owner()->identifier(), Identifier("a")); QCOMPARE(method->aliasedDeclaration().data()->identifier(), Identifier("two")); compareUses(dec, QList() << RangeInRevision(11, 31, 11, 37) ); dec = topDecs[6]->internalContext()->findLocalDeclarations(Identifier(QStringLiteral("three"))).first(); method = dynamic_cast(dec); QVERIFY(method); QCOMPARE(method->aliasedDeclaration().data()->context()->owner()->identifier(), Identifier("b")); QCOMPARE(method->aliasedDeclaration().data()->identifier(), Identifier("three")); QCOMPARE(method->accessPolicy(), Declaration::AccessPolicy::Public); compareUses(dec, QList() << RangeInRevision(11, 45, 11, 50) ); dec = topDecs[6]->internalContext()->findLocalDeclarations(Identifier(QStringLiteral("static"))).first(); method = dynamic_cast(dec); QVERIFY(method); QCOMPARE(method->aliasedDeclaration().data()->context()->owner()->identifier(), Identifier("c")); QCOMPARE(method->aliasedDeclaration().data()->identifier(), Identifier("five")); compareUses(dec, QList() << RangeInRevision(11, 58, 11, 64) ); } void TestUses::exceptionFinally() { // 0 1 2 3 4 // 01234567890123456789012345678901234567890123456 QByteArray method("localDeclarations().at(0); QCOMPARE(a->identifier().toString(), QString("a")); compareUses(a, QList() << RangeInRevision(0, 17, 0, 19) << RangeInRevision(0, 37, 0, 39)); } void TestUses::returnTypeClassFunction() { QByteArray method("localDeclarations().at(0); QCOMPARE(a->identifier().toString(), QString("a")); compareUses(a, QList() << RangeInRevision(0, 29, 0, 30) << RangeInRevision(0, 50, 0, 54)); } void TestUses::returnTypeFunction() { QByteArray method("localDeclarations().at(0); QCOMPARE(a->identifier().toString(), QString("a")); compareUses(a, QList() << RangeInRevision(0, 30, 0, 31)); } void TestUses::defaultValue() { QByteArray method("localDeclarations().at(0); QCOMPARE(a->identifier().toString(), QString("a")); compareUses(a, QList() << RangeInRevision(0, 44, 0, 45)); Declaration *c = top->childContexts().at(0)->localDeclarations().at(0); QCOMPARE(c->identifier().toString(), QString("C")); compareUses(c, QList() << RangeInRevision(0, 47, 0, 48)); } } diff --git a/duchain/tests/uses.h b/duchain/tests/uses.h index f7d2718..4ae13b6 100644 --- a/duchain/tests/uses.h +++ b/duchain/tests/uses.h @@ -1,103 +1,106 @@ /* This file is part of KDevelop Copyright 2008 Niko Sams This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef TESTUSES_H #define TESTUSES_H #include "../../duchain/tests/duchaintestbase.h" namespace Php { class TestUses : public DUChainTestBase { Q_OBJECT public: TestUses(); private slots: void newObject(); void functionCall(); void memberFunctionCall(); void unsureMemberFunctionCall(); void memberVariable(); void implicitMemberVariable(); void variable(); void varInString(); void variableInNamespace(); void globalVariableInNamespace(); void variableInOtherNamespace(); void memberVarInString(); void memberFunctionInString(); void variableTypeChange(); void variableTypeChangeInFunction(); void classExtends(); void classImplements(); void classImplementsMultiple(); void interfaceExtends(); void interfaceExtendsMultiple(); void staticMemberFunctionCall(); void staticMemberVariable(); + void dynamicStaticMemberVariable(); void constant(); void classConstant(); void classParent(); void classSelf(); void classThis(); void objectWithClassName(); void classAndConstWithSameName(); void classAndFunctionWithSameName(); void constAndVariableWithSameName(); void functionAndClassWithSameName(); void constantInClassMember(); void useInAsignment(); void foreachArray(); void assignmentToMemberArray(); void staticArrayIndex(); void functionParamNewDeclaration(); void catchClass(); void variableRedeclaration(); void caseInsensitiveFunction(); void caseInsensitiveMethod(); void caseInsensitiveClass(); void functionUseBeforeDeclaration(); void propertyAndMethodWithSameName(); void nestedMethodCalls(); void unset(); void functionArguments(); void namespaces(); void useNamespace(); void lateStatic(); void closures(); void closureTypehints(); void instanceof(); void instanceofClassProperty(); void instanceofStaticProperty(); void instanceofMixedProperty(); void instanceofVariableProperty(); + void instanceofDynamicStaticProperty(); + void instanceofDynamicVariableProperty(); void instanceofPropertyArrayAccess(); void classNameString(); void useTrait(); void exceptionFinally(); void returnTypeClassFunction(); void returnTypeFunction(); void defaultValue(); }; } #endif diff --git a/kdevphpsupport.categories b/kdevphpsupport.categories index 5bd8253..3778a8a 100644 --- a/kdevphpsupport.categories +++ b/kdevphpsupport.categories @@ -1,10 +1,10 @@ # KDebugSettings data file # Format: # lognamedescription -kdevelop.languages.php KDevelop plugin: PHP language support -kdevelop.languages.php.completion KDevelop plugin: PHP language support - codecompletion -kdevelop.languages.php.docs KDevelop plugin: PHP language support - documentation -kdevelop.languages.php.duchain KDevelop plugin: PHP language support - duchain -kdevelop.languages.php.parser KDevelop plugin: PHP language support - parser -kdevelop.languages.php.testprovider KDevelop plugin: PHP language support - testprovider +kdevelop.plugins.php KDevelop plugin: PHP language support +kdevelop.plugins.php.completion KDevelop plugin: PHP language support - codecompletion +kdevelop.plugins.php.docs KDevelop plugin: PHP language support - documentation +kdevelop.plugins.php.duchain KDevelop plugin: PHP language support - duchain +kdevelop.plugins.php.parser KDevelop plugin: PHP language support - parser +kdevelop.plugins.php.testprovider KDevelop plugin: PHP language support - testprovider diff --git a/parser/CMakeLists.txt b/parser/CMakeLists.txt index a746742..92684d4 100644 --- a/parser/CMakeLists.txt +++ b/parser/CMakeLists.txt @@ -1,71 +1,71 @@ if (BUILD_TESTING) add_subdirectory(test) endif() set(parser_STAT_SRCS phplexer.cpp parsesession.cpp ) kdevpgqt_generate(parser_SRCS php NAMESPACE Php DEBUG_VISITOR TOKEN_TEXT "${CMAKE_CURRENT_SOURCE_DIR}/php.g" "${CMAKE_CURRENT_SOURCE_DIR}/phplexer.h" ) ecm_qt_declare_logging_category(parser_SRCS HEADER parserdebug.h IDENTIFIER PARSER - CATEGORY_NAME "kdevelop.languages.php.parser" + CATEGORY_NAME "kdevelop.plugins.php.parser" ) add_library(kdevphpparser SHARED ${parser_SRCS} ${parser_STAT_SRCS}) generate_export_header(kdevphpparser EXPORT_MACRO_NAME KDEVPHPPARSER_EXPORT EXPORT_FILE_NAME parserexport.h) target_link_libraries(kdevphpparser LINK_PRIVATE KDev::Language KF5::I18n ) # hack to make phpdebugvisitor.h (generated at compile time) # use the correct EXPORT tags when compiling in MinGW if (MINGW) add_definitions(-DMAKE_KDEV4PHPPARSER_LIB) endif (MINGW) target_include_directories(kdevphpparser PUBLIC $ PUBLIC $ PUBLIC $ ) if (BUILD_TESTING) add_executable(php-parser main.cpp) target_link_libraries(php-parser KDev::Tests KDev::Language kdevphpparser ) endif() install(TARGETS kdevphpparser EXPORT KDevPHPTargets DESTINATION ${INSTALL_TARGETS_DEFAULT_ARGS}) install(FILES parsesession.h phplexer.h tokenstream.h DESTINATION ${KDEVPHP_INCLUDE_DIR}/parser COMPONENT Devel ) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/parserexport.h ${CMAKE_CURRENT_BINARY_DIR}/phpast-fwd.h ${CMAKE_CURRENT_BINARY_DIR}/phpast.h ${CMAKE_CURRENT_BINARY_DIR}/phpdebugvisitor.h ${CMAKE_CURRENT_BINARY_DIR}/phpdefaultvisitor.h ${CMAKE_CURRENT_BINARY_DIR}/phpparser.h ${CMAKE_CURRENT_BINARY_DIR}/phptokentext.h ${CMAKE_CURRENT_BINARY_DIR}/phptokentype.h ${CMAKE_CURRENT_BINARY_DIR}/phpvisitor.h DESTINATION ${KDEVPHP_PRIVATE_INCLUDE_DIR}/parser COMPONENT Devel ) diff --git a/testprovider/CMakeLists.txt b/testprovider/CMakeLists.txt index 359a207..9be419b 100644 --- a/testprovider/CMakeLists.txt +++ b/testprovider/CMakeLists.txt @@ -1,28 +1,28 @@ set(kdevphpunitprovider_PLUGIN_SRCS phpunitprovider.cpp phpunittestsuite.cpp phpunitrunjob.cpp testdoxdelegate.cpp ) ecm_qt_declare_logging_category(kdevphpunitprovider_PLUGIN_SRCS HEADER testproviderdebug.h IDENTIFIER TESTPROVIDER - CATEGORY_NAME "kdevelop.languages.php.testprovider" + CATEGORY_NAME "kdevelop.plugins.php.testprovider" ) kdevplatform_add_plugin(kdevphpunitprovider JSON kdevphpunitprovider.json SOURCES ${kdevphpunitprovider_PLUGIN_SRCS} ) target_link_libraries(kdevphpunitprovider LINK_PRIVATE KDev::Interfaces KDev::Language KDev::Project KDev::OutputView KDev::Util ) install(FILES phpunitdeclarations.php DESTINATION ${KDE_INSTALL_DATADIR}/kdevphpsupport) diff --git a/testprovider/phpunitprovider.cpp b/testprovider/phpunitprovider.cpp index f6641e9..be48803 100644 --- a/testprovider/phpunitprovider.cpp +++ b/testprovider/phpunitprovider.cpp @@ -1,181 +1,179 @@ /*************************************************************************** * This file is part of KDevelop PHP support * * Copyright 2012 Miha Čančula * * * * 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 "phpunitprovider.h" #include "phpunittestsuite.h" #include "testproviderdebug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; K_PLUGIN_FACTORY_WITH_JSON(PhpUnitProviderFactory, "kdevphpunitprovider.json", registerPlugin(); ) PhpUnitProvider::PhpUnitProvider(QObject* parent, const QList< QVariant >& args) : IPlugin(QStringLiteral("kdevphpunitprovider"), parent) { Q_UNUSED(args); QString file = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kdevphpsupport/phpunitdeclarations.php")); m_phpUnitDeclarationsFile = IndexedString(file); DUChain::self()->updateContextForUrl(m_phpUnitDeclarationsFile, KDevelop::TopDUContext::AllDeclarationsContextsAndUses, this, -10); connect(DUChain::self(), &DUChain::updateReady, this, &PhpUnitProvider::updateReady); } void PhpUnitProvider::updateReady(const IndexedString& document, const ReferencedTopDUContext& context) { Q_UNUSED(document); DUChainReadLocker lock; if (!context) { qCDebug(TESTPROVIDER) << "Received null context for file: " << document; return; } if (document == m_phpUnitDeclarationsFile) { QVector declarations = context.data()->localDeclarations(); if (declarations.isEmpty()) { qCDebug(TESTPROVIDER) << "Update of the internal test file found no suitable declarations"; return; } m_testCaseDeclaration = IndexedDeclaration(declarations.first()); qCDebug(TESTPROVIDER) << "Found declaration" << declarations.first()->toString(); foreach (const ReferencedTopDUContext& context, m_pendingContexts) { processContext(context); } } else { if (!m_testCaseDeclaration.isValid()) { m_pendingContexts << context; } else { processContext(context); } } } void PhpUnitProvider::processContext(ReferencedTopDUContext referencedContext) { - qCDebug(TESTPROVIDER); - TopDUContext* context = referencedContext.data(); if (!context) { qCDebug(TESTPROVIDER) << "context went away"; return; } Declaration* testCase = m_testCaseDeclaration.data(); if (!testCase) { qCDebug(TESTPROVIDER) << "test case declaration went away"; return; } qCDebug(TESTPROVIDER) << "Number of declarations" << context->localDeclarations().size(); foreach (Declaration* declaration, context->localDeclarations()) { ClassDeclaration* classDeclaration = dynamic_cast(declaration); if (!classDeclaration || classDeclaration->classModifier() & ClassDeclarationData::Abstract || !classDeclaration->internalContext()) { continue; } if (classDeclaration->isPublicBaseClass(static_cast(m_testCaseDeclaration.data()), context)) { processTestCaseDeclaration(declaration); } } } void PhpUnitProvider::processTestCaseDeclaration(Declaration* d) { QString name = d->identifier().toString(); QUrl url = d->url().toUrl(); IProject* project = ICore::self()->projectController()->findProjectForUrl(url); qCDebug(TESTPROVIDER) << name << url << (project ? project->name() : QStringLiteral("No project")); if (!project) { return; } QStringList testCases; QHash testCaseDeclarations; ClassDeclaration* classDeclaration = dynamic_cast(d); if (!classDeclaration) { return; } if (!(classDeclaration->classModifier() & ClassDeclarationData::Abstract)) { foreach (Declaration* member, classDeclaration->internalContext()->localDeclarations()) { qCDebug(TESTPROVIDER) << "Trying test case declaration" << member; if (member->isFunctionDeclaration() && member->identifier().toString().startsWith(QLatin1String("test"))) { const QString caseName = member->identifier().toString(); testCases << caseName; testCaseDeclarations.insert(caseName, IndexedDeclaration(member)); } } if (!testCaseDeclarations.isEmpty()) { // NOTE: No declarations usually means the class in abstract // This should be resolved by the classDeclaration->isAbstract() check // But that always returns false. ICore::self()->testController()->addTestSuite(new PhpUnitTestSuite(name, url, IndexedDeclaration(classDeclaration), testCases, testCaseDeclarations, project)); return; } } uint steps = 100; foreach (Declaration* inheriter, DUChainUtils::inheriters(d, steps)) { processTestCaseDeclaration(inheriter); } } #include "phpunitprovider.moc"