diff --git a/CMakeLists.txt b/CMakeLists.txt index 77265f4..d653962 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,63 +1,63 @@ PROJECT(php) set(KDEVPHP_VERSION_MAJOR 4) set(KDEVPHP_VERSION_MINOR 90) -set(KDEVPHP_VERSION_PATCH 91) +set(KDEVPHP_VERSION_PATCH 92) # KDevplatform dependency version set( KDEVPLATFORM_VERSION "${KDEVPHP_VERSION_MAJOR}.${KDEVPHP_VERSION_MINOR}.${KDEVPHP_VERSION_PATCH}" ) configure_file( "${php_SOURCE_DIR}/kdevphpversion.h.cmake" "${php_BINARY_DIR}/kdevphpversion.h" @ONLY ) cmake_minimum_required(VERSION 2.8.12) find_package (ECM 0.0.9 REQUIRED NO_MODULE) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${ECM_MODULE_PATH}) include(ECMAddTests) include(KDEInstallDirs) include(KDECMakeSettings) include(KDECompilerSettings) include(GenerateExportHeader) include(FeatureSummary) find_package(Qt5 REQUIRED Core Widgets Test WebKitWidgets) find_package(KF5 REQUIRED COMPONENTS Archive ThreadWeaver TextEditor I18n ItemModels KCMUtils) find_package(KDevPlatform ${KDEVPLATFORM_VERSION} REQUIRED) find_package(KDevelop-PG-Qt REQUIRED) add_definitions( -DKDE_DEFAULT_DEBUG_AREA=9043 ) include_directories( ${KDEVPGQT_INCLUDE_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 phpdebug.cpp codegen/refactoring.cpp ) 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 ${DATA_INSTALL_DIR}/kdevphpsupport PERMISSIONS OWNER_READ GROUP_READ WORLD_READ) diff --git a/completion/codemodelitem.cpp b/completion/codemodelitem.cpp index 6be08af..0ca2874 100644 --- a/completion/codemodelitem.cpp +++ b/completion/codemodelitem.cpp @@ -1,122 +1,122 @@ /* * KDevelop Php Code Completion Support * * Copyright 2010 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 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 "codemodelitem.h" #include #include #include #include #include #include #include #include #include #include #include "completiondebug.h" using namespace KDevelop; namespace Php { CodeModelCompletionItem::CodeModelCompletionItem(const ParsingEnvironmentFilePointer &env, const CompletionCodeModelItem &item) : CompletionTreeItem(), m_item(item), m_env(env) { } QVariant CodeModelCompletionItem::data(const QModelIndex& index, int role, const KDevelop::CodeCompletionModel* model) const { DUChainReadLocker lock(DUChain::lock(), 500); if (!lock.locked()) { qCDebug(COMPLETION) << "Failed to lock the du-chain in time"; return QVariant(); } switch (role) { case Qt::DisplayRole: switch (index.column()) { case KDevelop::CodeCompletionModel::Name: return QVariant(m_item.prettyName.str()); case KDevelop::CodeCompletionModel::Prefix: - return QString("class"); + return QStringLiteral("class"); } break; case Qt::DecorationRole: if( index.column() == CodeCompletionModel::Icon ) { CodeCompletionModel::CompletionProperties p = completionProperties(); lock.unlock(); return DUChainUtils::iconForProperties(p); } break; case CodeCompletionModel::IsExpandable: return QVariant(true); case CodeCompletionModel::ExpandingWidget: { if (!declaration()) return QVariant(); QWidget *nav = new NavigationWidget(declaration(), model->currentTopContext()); model->addNavigationWidget(this, nav); QVariant v; v.setValue(nav); return v; } } return QVariant(); } CodeCompletionModel::CompletionProperties CodeModelCompletionItem::completionProperties() const { CodeCompletionModel::CompletionProperties ret = CodeCompletionModel::Class; return ret; } void CodeModelCompletionItem::execute(KTextEditor::View* view, const KTextEditor::Range& word) { KTextEditor::Document *document = view->document(); document->replaceText(word, m_item.prettyName.str()); if (declaration() && dynamic_cast(declaration().data())) { //Do some intelligent stuff for functions with the parens: KTextEditor::Cursor pos = word.start(); pos.setColumn(pos.column() + m_item.prettyName.length()); insertFunctionParenText(view, pos, declaration()); } } DeclarationPointer CodeModelCompletionItem::declaration() const { if (!m_decl) { KDevelop::DUChainReadLocker lock(KDevelop::DUChain::lock()); QList decls = m_env->topContext()->findDeclarations(m_item.id); if (decls.isEmpty()) return DeclarationPointer(); m_decl = decls.first(); } return m_decl; } } diff --git a/completion/context.cpp b/completion/context.cpp index b99c218..98895c1 100644 --- a/completion/context.cpp +++ b/completion/context.cpp @@ -1,1775 +1,1779 @@ /* Copyright 2007 David Nolden Copyright 2008 Hamish Rodda Copyright 2008 Niko Sams Copyright 2009 Milian Wolff This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "context.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../duchain/completioncodemodel.h" #include "../duchain/expressionparser.h" #include "../duchain/helper.h" #include "../duchain/declarations/variabledeclaration.h" #include "../duchain/declarations/classmethoddeclaration.h" #include "../duchain/types/structuretype.h" #include "../parser/phpparser.h" #include "../parser/phptokentext.h" #include "includefileitem.h" #include "codemodelitem.h" #include "completiondebug.h" #include "helpers.h" #include "implementationitem.h" #include "keyworditem.h" #include #define LOCKDUCHAIN DUChainReadLocker lock(DUChain::lock()) #define ifDebug(x) using namespace KDevelop; namespace Php { typedef QList TokenList; /** * Utility class which makes it easier to access the relevant parts * of the token stream for code completion. * * TODO: This class should be reviewed imo - I just hacked it together, quick'n'dirty */ class TokenAccess { public: /// Setup the token stream from the input code TokenAccess(const QString &code) : m_code(code) { Lexer lexer(&m_stream, code); int token; while ((token = lexer.nextTokenKind())) { Parser::Token &t = m_stream.push(); t.begin = lexer.tokenBegin(); t.end = lexer.tokenEnd(); t.kind = token; } // move to last token m_pos = m_stream.size() - 1; } /// returns Token_INVALID if the position is invalid /// else returns the type of the current token Parser::TokenType type() const { if ( m_pos == -1 ) { return Parser::Token_INVALID; } else { return (Parser::TokenType) m_stream.at(m_pos).kind; } } /// convenience comparison to a tokentype bool operator==(const Parser::TokenType& other) const { return other == type(); } /// move to previous token void pop() { if ( m_pos >= 0 ) { --m_pos; } } /// move relative to current token /// NOTE: make sure you honor the boundaries. void moveTo(const qint64 &relPos) { m_pos += relPos; Q_ASSERT(m_pos > 0); Q_ASSERT(m_pos < m_stream.size()); } /// get type of token relative to current position /// returns Token_INVALID if the position goes out of the boundaries int typeAt(const qint64 &relPos) const { const qint64 pos = m_pos + relPos; if ( pos >= 0 && pos < m_stream.size() ) { return m_stream.at(pos).kind; } else { return Parser::Token_INVALID; } } /// Get string for token at a given position relative to the current one. /// NOTE: Make sure you honor the boundaries. QString stringAt(const qint64 &relPos) const { Parser::Token token = at(relPos); return m_code.mid(token.begin, token.end - token.begin + 1); } /// check whether the current token is prepended by the list of tokens /// @return -1 when not prepended by the list, else the relative index-position qint64 prependedBy(const TokenList &list, bool skipWhitespace = false ) const { // this would be useless, hence forbid it Q_ASSERT ( !list.isEmpty() ); if ( m_pos < list.count() - 1 ) { // not enough tokens return -1; } else { uint pos = 1; - foreach ( const Parser::TokenType& type, list ) { + foreach ( Parser::TokenType type, list ) { if ( skipWhitespace && m_stream.at( m_pos - pos).kind == Parser::Token_WHITESPACE ) { ++pos; } if ( m_stream.at( m_pos - pos).kind == type ) { ++pos; continue; } else { return -1; } } return pos; } } /// Get the token relative to the current one. /// NOTE: Make sure you honor the boundaries. Parser::Token at(const qint64 &relPos) const { const qint64 pos = m_pos + relPos; Q_ASSERT(pos >= 0); Q_ASSERT(pos < m_stream.size()); return m_stream.at(pos); } private: const QString m_code; TokenStream m_stream; qint64 m_pos; }; /** * Pops all tokens from the @p lastToken and stops at the LPAREN. */ void removeOtherArguments(TokenAccess &lastToken) { Q_ASSERT(lastToken.type() == Parser::Token_COMMA); // remove all other arguments int openLParen = 0; do { lastToken.pop(); if ( lastToken.type() == Parser::Token_RPAREN ) { ++openLParen; } else if ( lastToken.type() == Parser::Token_LPAREN ) { if ( openLParen == 0 ) { return; } else { --openLParen; } } } while ( lastToken.type() != Parser::Token_INVALID ); } /** * if token at @p pos is whitespace, decrease pos by one. */ inline void skipWhiteSpace(const TokenAccess &lastToken, qint64 &pos) { if ( lastToken.typeAt(pos) == Parser::Token_WHITESPACE ) { --pos; } } /// add keyword to list of completion items -#define ADD_KEYWORD(x) items << CompletionTreeItemPointer( new KeywordItem( x, Php::CodeCompletionContext::Ptr(this) ) ) -#define ADD_KEYWORD2(x, y) items << CompletionTreeItemPointer( new KeywordItem( x, Php::CodeCompletionContext::Ptr(this), y ) ) +#define ADD_KEYWORD(x) items << CompletionTreeItemPointer( new KeywordItem( QStringLiteral(x), Php::CodeCompletionContext::Ptr(this) ) ) +#define ADD_KEYWORD2(x, y) items << CompletionTreeItemPointer( new KeywordItem( QStringLiteral(x), Php::CodeCompletionContext::Ptr(this), QStringLiteral(y) ) ) int completionRecursionDepth = 0; CodeCompletionContext::CodeCompletionContext(KDevelop::DUContextPointer context, const QString& text, const QString& followingText, const KDevelop::CursorInRevision& position, int depth) : KDevelop::CodeCompletionContext(context, text, position, depth) , m_memberAccessOperation(NoMemberAccess), m_parentAccess(false), m_isFileCompletionAfterDirname(false) { // use other ctor for parents Q_ASSERT(depth == 0); ifDebug(qCDebug(COMPLETION) << "non-processed text: " + text;) if ( context->type() == DUContext::Class || context->type() == DUContext::Function || context->type() == DUContext::Other || context->type() == DUContext::Namespace ) { if ( !m_parentContext && !m_text.startsWith(QLatin1String(" don't offer completion - if ( m_text.mid( lastTokenEnd, 2 ) == "/*" ) { + if ( m_text.mid( lastTokenEnd, 2 ) == QLatin1String("/*") ) { ifDebug(qCDebug(COMPLETION) << "no completion in comments"); m_valid = false; return; } ifDebug(qCDebug(COMPLETION) << tokenText(lastToken.type());) ///TODO: REFACTOR: push some stuff into its own methods /// and call them from inside the big switch. /// Then we can forget about having additional checks /// beforehand and can handle it all in one place. // The following tokens require a whitespace after them for code-completion: if ( !lastWasWhitespace ) { switch ( lastToken.type() ) { case Parser::Token_EXTENDS: case Parser::Token_IMPLEMENTS: case Parser::Token_NEW: case Parser::Token_THROW: ifDebug(qCDebug(COMPLETION) << "need whitespace after token for completion";) m_valid = false; return; default: break; } } ifDebug(qCDebug(COMPLETION) << tokenText(lastToken.type());) switch ( lastToken.type() ) { case Parser::Token_COMMENT: // don't offer code completion in comments, i.e. single line comments that don't end on \n // multi-line comments are handled above if ( !lastWasWhitespace && !lastToken.stringAt(0).endsWith('\n') && !lastToken.stringAt(0).startsWith(QLatin1String("/*")) ) { ifDebug(qCDebug(COMPLETION) << "no completion in comments";) m_valid = false; } break; case Parser::Token_EXTENDS: if ( lastToken.prependedBy(TokenList() << Parser::Token_WHITESPACE << Parser::Token_STRING << Parser::Token_WHITESPACE << Parser::Token_CLASS) != -1 ) { m_memberAccessOperation = ClassExtendsChoose; forbidIdentifier(lastToken.stringAt(-2)); } else if ( lastToken.prependedBy(TokenList() << Parser::Token_WHITESPACE << Parser::Token_STRING << Parser::Token_WHITESPACE << Parser::Token_INTERFACE) != -1 ) { m_memberAccessOperation = InterfaceChoose; forbidIdentifier(lastToken.stringAt(-2)); } else { ifDebug(qCDebug(COMPLETION) << "token prepended by bad tokens, don't do completion";) m_valid = false; } break; case Parser::Token_IMPLEMENTS: if ( lastToken.prependedBy(TokenList() << Parser::Token_WHITESPACE << Parser::Token_STRING << Parser::Token_WHITESPACE << Parser::Token_CLASS) != -1 ) { m_memberAccessOperation = InterfaceChoose; forbidIdentifier(lastToken.stringAt(-2)); } else { ifDebug(qCDebug(COMPLETION) << "token prepended by bad tokens, don't do completion";) m_valid = false; } break; case Parser::Token_COMMA: { // check if we are in the list after Token_IMPLEMENTS: qint64 relPos = -1; QList identifierPositions; while ( true ) { skipWhiteSpace(lastToken, relPos); if ( lastToken.typeAt(relPos) == Parser::Token_STRING ) { identifierPositions << relPos; --relPos; skipWhiteSpace(lastToken, relPos); // interfaces may extend more than one interface if ( ( lastToken.typeAt(relPos) == Parser::Token_EXTENDS && lastToken.typeAt(relPos - 1) == Parser::Token_WHITESPACE && lastToken.typeAt(relPos - 2) == Parser::Token_STRING && lastToken.typeAt(relPos - 3) == Parser::Token_WHITESPACE && lastToken.typeAt(relPos - 4) == Parser::Token_INTERFACE ) || // classes may implement more than one interface ( lastToken.typeAt(relPos) == Parser::Token_IMPLEMENTS && lastToken.typeAt(relPos - 1) == Parser::Token_WHITESPACE && lastToken.typeAt(relPos - 2) == Parser::Token_STRING && lastToken.typeAt(relPos - 3) == Parser::Token_WHITESPACE && lastToken.typeAt(relPos - 4) == Parser::Token_CLASS ) ) { identifierPositions << (relPos - 2); m_memberAccessOperation = InterfaceChoose; break; } else if ( lastToken.typeAt(relPos) == Parser::Token_COMMA ) { // skip to next entry --relPos; continue; } } else { break; } } if ( m_memberAccessOperation == InterfaceChoose ) { ifDebug(qCDebug(COMPLETION) << "in implementation list";) m_memberAccessOperation = InterfaceChoose; - foreach ( const qint64& pos, identifierPositions ) { + foreach ( qint64 pos, identifierPositions ) { forbidIdentifier(lastToken.stringAt(pos)); } } else { // else do function call completion m_memberAccessOperation = FunctionCallAccess; ///TODO: global, static etc. enumerations. removeOtherArguments(lastToken); if ( lastToken.type() == Parser::Token_INVALID ) { m_valid = false; } } } break; case Parser::Token_OPEN_TAG: // don't do completion if no whitespace is given and there is some text following, // esp. for stuff like type() == DUContext::Class ) { // when we are inside a class context, give overloadable members as completion m_memberAccessOperation = ClassMemberChoose; } else { m_memberAccessOperation = NoMemberAccess; } break; case Parser::Token_ABSTRACT: case Parser::Token_CONST: case Parser::Token_FINAL: case Parser::Token_PUBLIC: case Parser::Token_PRIVATE: case Parser::Token_PROTECTED: case Parser::Token_STATIC: case Parser::Token_VAR: if ( duContext() && duContext()->type() == DUContext::Class ) { // when we are inside a class context, give overloadable members as completion m_memberAccessOperation = ClassMemberChoose; } else { m_valid = false; } break; case Parser::Token_NAMESPACE: case Parser::Token_BACKSLASH: { QString identifier; qint64 relPos = 0; while (lastToken.typeAt(relPos) == Parser::Token_STRING || lastToken.typeAt(relPos) == Parser::Token_BACKSLASH) { if (lastToken.typeAt(relPos) == Parser::Token_BACKSLASH) { identifier.prepend("::"); } else { identifier.prepend(lastToken.stringAt(relPos)); } --relPos; } if ( lastToken.typeAt(relPos) == Parser::Token_NAMESPACE ) { m_memberAccessOperation = NamespaceChoose; } else { m_memberAccessOperation = BackslashAccess; } m_namespace = QualifiedIdentifier(identifier); break; } case Parser::Token_ARRAY: case Parser::Token_AS: case Parser::Token_BACKTICK: case Parser::Token_BREAK: case Parser::Token_CALLABLE: case Parser::Token_CASE: case Parser::Token_CATCH: case Parser::Token_CLASS: case Parser::Token_CLASS_C: case Parser::Token_CLONE: case Parser::Token_CLOSE_TAG: case Parser::Token_CONTINUE: case Parser::Token_DECLARE: case Parser::Token_DEFAULT: case Parser::Token_DNUMBER: case Parser::Token_DO: case Parser::Token_DOLLAR: case Parser::Token_ELSE: case Parser::Token_ELSEIF: case Parser::Token_EMPTY: case Parser::Token_ENDDECLARE: case Parser::Token_ENDFOR: case Parser::Token_ENDFOREACH: case Parser::Token_ENDIF: case Parser::Token_ENDSWITCH: case Parser::Token_ENDWHILE: case Parser::Token_END_HEREDOC: case Parser::Token_END_NOWDOC: case Parser::Token_EOF: case Parser::Token_EVAL: case Parser::Token_FILE: case Parser::Token_FINALLY: case Parser::Token_FOR: case Parser::Token_FOREACH: case Parser::Token_FUNCTION: case Parser::Token_FUNC_C: case Parser::Token_GLOBAL: case Parser::Token_HALT_COMPILER: case Parser::Token_IF: case Parser::Token_INCLUDE: case Parser::Token_INCLUDE_ONCE: case Parser::Token_INLINE_HTML: case Parser::Token_INSTEADOF: case Parser::Token_INTERFACE: case Parser::Token_INVALID: case Parser::Token_ISSET: case Parser::Token_LINE: case Parser::Token_LIST: case Parser::Token_LNUMBER: case Parser::Token_METHOD_C: case Parser::Token_NAMESPACE_C: case Parser::Token_NUM_STRING: case Parser::Token_REQUIRE: case Parser::Token_REQUIRE_ONCE: case Parser::Token_RBRACKET: case Parser::Token_RPAREN: case Parser::Token_STRING_VARNAME: case Parser::Token_SWITCH: case Parser::Token_TRAIT: case Parser::Token_TRY: case Parser::Token_UNSET: case Parser::Token_USE: case Parser::Token_VARIABLE: case Parser::Token_WHILE: case Parser::Token_WHITESPACE: /// TODO: code completion after goto case Parser::Token_GOTO: case Parser::TokenTypeSize: ifDebug(qCDebug(COMPLETION) << "no completion after this token";) m_valid = false; break; } ifDebug( switch ( m_memberAccessOperation ) { case FileChoose: qCDebug(COMPLETION) << "FileChoose"; break; case ExceptionInstanceChoose: qCDebug(COMPLETION) << "ExceptionInstanceChoose"; break; case ExceptionChoose: qCDebug(COMPLETION) << "ExceptionChoose"; break; case ClassMemberChoose: qCDebug(COMPLETION) << "ClassMemberChoose"; break; case NoMemberAccess: qCDebug(COMPLETION) << "NoMemberAccess"; break; case NewClassChoose: qCDebug(COMPLETION) << "NewClassChoose"; break; case FunctionCallAccess: qCDebug(COMPLETION) << "FunctionCallAccess"; break; case InterfaceChoose: qCDebug(COMPLETION) << "InterfaceChoose"; break; case ClassExtendsChoose: qCDebug(COMPLETION) << "ClassExtendsChoose"; break; case MemberAccess: qCDebug(COMPLETION) << "MemberAccess"; break; case StaticMemberAccess: qCDebug(COMPLETION) << "StaticMemberAccess"; break; case InstanceOfChoose: qCDebug(COMPLETION) << "InstanceOfChoose"; break; case NamespaceChoose: qCDebug(COMPLETION) << "NamespaceChoose"; break; case BackslashAccess: qCDebug(COMPLETION) << "BackslashAccess"; break; } ) ifDebug(qCDebug(COMPLETION) << tokenText(lastToken.type());) // if it's not valid, we should return early if ( !m_valid ) { ifDebug(qCDebug(COMPLETION) << "invalid completion";) return; } // trim the text to the end position of the current token m_text = m_text.left(lastToken.at(0).end + 1).trimmed(); ifDebug(qCDebug(COMPLETION) << "trimmed text: " << m_text;) // check whether we need the expression or have everything we need and can return early switch ( m_memberAccessOperation ) { // these access operations don't need the previous expression evaluated case FileChoose: case ClassMemberChoose: case InterfaceChoose: case NewClassChoose: case ExceptionChoose: case ExceptionInstanceChoose: case ClassExtendsChoose: case NoMemberAccess: case InstanceOfChoose: case NamespaceChoose: case BackslashAccess: ifDebug(qCDebug(COMPLETION) << "returning early";) return; case FunctionCallAccess: m_memberAccessOperation = NoMemberAccess; Q_ASSERT(lastToken.type() == Parser::Token_LPAREN); if ( lastToken.prependedBy(TokenList() << Parser::Token_STRING, true) == -1 && lastToken.prependedBy(TokenList() << Parser::Token_VARIABLE, true) == -1 ) { // handle for, foreach, while, etc. ifDebug(qCDebug(COMPLETION) << "NoMemberAccess (no function call)";) } else { //The first context should never be a function-call context, //so make this a NoMemberAccess context and the parent a function-call context. ifDebug(qCDebug(COMPLETION) << "NoMemberAccess (creating parentContext for function call)";) m_parentContext = new CodeCompletionContext(m_duContext, m_position, lastToken, depth + 1); } return; case MemberAccess: case StaticMemberAccess: // these types need the expression evaluated break; } evaluateExpression(lastToken); } CodeCompletionContext::CodeCompletionContext(KDevelop::DUContextPointer context, const KDevelop::CursorInRevision& position, TokenAccess& lastToken, int depth) : KDevelop::CodeCompletionContext(context, QString(), position, depth) , m_memberAccessOperation(NoMemberAccess), m_parentAccess(false), m_isFileCompletionAfterDirname(false) { switch ( lastToken.type() ) { case Parser::Token_LPAREN: m_memberAccessOperation = FunctionCallAccess; break; default: qCDebug(COMPLETION) << "unhandled token type for parent context" << tokenText(lastToken.typeAt(0)); Q_ASSERT(false); m_valid = false; return; } evaluateExpression(lastToken); } void CodeCompletionContext::evaluateExpression(TokenAccess& lastToken) { /// token pos qint64 startPos = 0; int openLParen = 0; if ( m_memberAccessOperation == FunctionCallAccess ) { Q_ASSERT(lastToken.type() == Parser::Token_LPAREN); // check ctor call qint64 pos = lastToken.prependedBy(TokenList() << Parser::Token_STRING << Parser::Token_NEW, true); if ( pos != -1 ) { startPos = -pos; ifDebug(qCDebug(COMPLETION) << "ctor call";) } else { // simple function call, get it's expression startPos = -1; ifDebug(qCDebug(COMPLETION) << "simple function call";) } } static const QList defaultStopTokens = QList() << Parser::Token_SEMICOLON << Parser::Token_INVALID << Parser::Token_OPEN_TAG << Parser::Token_OPEN_TAG_WITH_ECHO << Parser::Token_LBRACE << Parser::Token_RBRACE << Parser::Token_IF << Parser::Token_WHILE << Parser::Token_FOR << Parser::Token_FOREACH << Parser::Token_SWITCH << Parser::Token_ELSEIF; // find expression start while ( !defaultStopTokens.contains(lastToken.typeAt(startPos)) && (m_memberAccessOperation == FunctionCallAccess || lastToken.typeAt(startPos) != Parser::Token_COMMA) ) { if ( lastToken.typeAt(startPos) == Parser::Token_LPAREN ) { ++openLParen; if ( openLParen > 0 ) { break; } } else if ( lastToken.typeAt(startPos) == Parser::Token_RPAREN ) { --openLParen; } --startPos; } if ( openLParen < 0 ) { ifDebug(qCDebug(COMPLETION) << "too many closed parenthesis";) m_valid = false; return; } // we actually incorporate the not-wanted token, hence move forward ++startPos; if ( lastToken.typeAt(startPos) == Parser::Token_WHITESPACE ) { ++startPos; } if ( lastToken.typeAt(startPos) == Parser::Token_RETURN ) { ///TODO: match against function return type ++startPos; if ( lastToken.typeAt(startPos) == Parser::Token_WHITESPACE ) { ++startPos; } } if ( m_memberAccessOperation == StaticMemberAccess ) { if ( lastToken.typeAt(startPos) != Parser::Token_STRING ) { ifDebug(qCDebug(COMPLETION) << "unsupported token for start member access:" << tokenText(lastToken.typeAt(startPos));) m_valid = false; return; } const QString identifier(lastToken.stringAt(startPos).toLower()); - if ( identifier == "self" || identifier == "parent" || identifier == "static" ) { + if ( identifier == QLatin1String("self") || identifier == QLatin1String("parent") || identifier == QLatin1String("static") ) { // self and parent are only accessible from within a member function of a class if (DUContext* parent = m_duContext->parentContext()) { LOCKDUCHAIN; ClassDeclaration* classDec = dynamic_cast(parent->owner()); if (classDec) { - if (identifier == "parent") { + if (identifier == QLatin1String("parent")) { FOREACH_FUNCTION(const BaseClassInstance& base, classDec->baseClasses) { if (StructureType::Ptr classType = base.baseClass.type()) { if (ClassDeclaration* baseClass = dynamic_cast(classType->declaration(m_duContext->topContext()))) { if (baseClass->classType() == ClassDeclarationData::Class && baseClass->classModifier() != ClassDeclarationData::Abstract) { ifDebug(qCDebug(COMPLETION) << "correction: parent can do MemberAccess"); m_parentAccess = true; m_memberAccessOperation = MemberAccess; m_expressionResult.setDeclaration(baseClass); break; } } } } if (!m_parentAccess) { ifDebug(qCDebug(COMPLETION) << "class has no accessible parent class"); m_valid = false; return; } } else { m_expressionResult.setDeclaration(parent->owner()); } } } } else { QualifiedIdentifier id(identifier); m_expressionResult.setDeclaration(findDeclarationImportHelper(duContext(), id, ClassDeclarationType)); } } else { // Now get the string of the expression and evaluate it Q_ASSERT(m_expression.isEmpty()); for (qint64 i = startPos; i <= 0; ++i ) { m_expression += lastToken.stringAt(i); } m_expression = m_expression.trimmed(); // make sure the expression is valid if (m_memberAccessOperation == FunctionCallAccess) { m_expression.append(')'); } for ( int i = openLParen; i > 0; --i ) { m_expression.append(')'); } ifDebug(qCDebug(COMPLETION) << "expression: " << m_expression;) if ( !m_expression.isEmpty() ) { ExpressionParser expressionParser; m_expressionResult = expressionParser.evaluateType(m_expression.toUtf8(), m_duContext, m_position); } if (m_expressionResult.type()) { LOCKDUCHAIN; ifDebug(qCDebug(COMPLETION) << "expression type: " << m_expressionResult.type()->toString();) } else { ifDebug(qCDebug(COMPLETION) << QString("expression could not be evaluated")); if ( m_memberAccessOperation == FunctionCallAccess ) { ifDebug(qCDebug(COMPLETION) << "function not found";) return; } m_valid = false; return; } } lastToken.moveTo(startPos); // Handle recursive contexts (Example: "ret = function1(param1, function2(" ) if ( lastToken.typeAt(-1) == Parser::Token_LPAREN || lastToken.typeAt(-1) == Parser::Token_COMMA ) { //Our expression is within a function-call. We need to find out the possible argument-types we need to match, and show an argument-hint. lastToken.moveTo(-1); if ( lastToken.type() == Parser::Token_COMMA ) { removeOtherArguments(lastToken); if ( lastToken.type() == Parser::Token_INVALID ) { ifDebug(qCDebug(COMPLETION) << QString("Could not find start position for parent function-call. Aborting.");) m_valid = false; return; } } if ( lastToken.prependedBy(TokenList() << Parser::Token_STRING, true) == -1 ) { // handle for, foreach, while, etc. ifDebug(qCDebug(COMPLETION) << "No real function call";) return; } ifDebug(qCDebug(COMPLETION) << QString("Recursive function-call: creating parent context")); m_parentContext = new CodeCompletionContext(m_duContext, m_position, lastToken, m_depth + 1); if (!m_parentContext->isValid()) { m_parentContext = 0; m_valid = false; return; } } } void CodeCompletionContext::forbidIdentifier(const QString& identifier) { QualifiedIdentifier id(identifier.toLower()); ClassDeclaration *dec = dynamic_cast( findDeclarationImportHelper(m_duContext.data(), id, ClassDeclarationType).data() ); if (dec) { forbidIdentifier(dec); } else { // might be a class we are currently writing, i.e. without a proper declaration m_forbiddenIdentifiers << id.index(); } } void CodeCompletionContext::forbidIdentifier(ClassDeclaration* klass) { uint id; { LOCKDUCHAIN; // TODO: qualifiedIdentifier is marked as expensive - any other way // we can do what we are doing here? // TODO: maybe we should clar the m_fobiddenIdentifiers after we got // our list of items... id = klass->qualifiedIdentifier().index(); } if (m_forbiddenIdentifiers.contains(id)) { // nothing to do return; } m_forbiddenIdentifiers << id; // add parents so those are excluded from the completion items as well if (klass->baseClassesSize() > 0) { FOREACH_FUNCTION(const BaseClassInstance& base, klass->baseClasses) { StructureType::Ptr type = base.baseClass.type(); if (type.data()) { ClassDeclaration* parent; { LOCKDUCHAIN; parent = dynamic_cast( type->declaration(m_duContext->topContext()) ); } if (parent) { forbidIdentifier(parent); } } } } } CodeCompletionContext::~CodeCompletionContext() { } CodeCompletionContext::MemberAccessOperation CodeCompletionContext::memberAccessOperation() const { return m_memberAccessOperation; } ExpressionEvaluationResult CodeCompletionContext::memberAccessContainer() const { return m_expressionResult; } CodeCompletionContext* CodeCompletionContext::parentContext() { return static_cast(KDevelop::CodeCompletionContext::parentContext()); } QList CodeCompletionContext::memberAccessContainers() const { QList ret; QList types; AbstractType::Ptr expressionTarget = m_expressionResult.type(); if (UnsureType::Ptr unsureType = UnsureType::Ptr::dynamicCast(m_expressionResult.type())) { FOREACH_FUNCTION(const IndexedType& t, unsureType->types) { types << t.abstractType(); } } else if (ReferenceType::Ptr referencedType = ReferenceType::Ptr::dynamicCast(m_expressionResult.type()) ) { types << referencedType->baseType(); } else { types << expressionTarget; } foreach (const AbstractType::Ptr &type, types) { const IdentifiedType* idType = dynamic_cast(type.data()); Declaration* declaration = 0; if (idType) { declaration = idType->declaration(m_duContext->topContext()); } DUContext* ctx = 0; if (declaration) { ctx = declaration->logicalInternalContext(m_duContext->topContext()); } if (ctx) { ret << ctx; } else if (declaration) { //Print some debug-output qCDebug(COMPLETION) << "Could not get internal context from" << declaration->toString(); } else { //Print some debug-output qCDebug(COMPLETION) << "Could not get declaration"; } } return ret; } QList CodeCompletionContext::completionItems(bool& abort, bool fullCompletion) { //TODO: how should this be handled? Q_UNUSED(fullCompletion) /// Indexed string for 'Php', identifies environment files from this language plugin static const IndexedString phpLangString("Php"); LOCKDUCHAIN; QList items; if (!m_duContext) return items; typedef QPair DeclarationDepthPair; if ( memberAccessOperation() == FileChoose ) { if ( !ICore::self() ) { // in unit tests we can't do anything qCDebug(COMPLETION) << "no core found"; return items; } // file completion const Path currentDocument(m_duContext->url().str()); Path path; Path base; if ( !m_isFileCompletionAfterDirname ) { path = Path(currentDocument.parent(), m_expression); base = path; if ( !m_expression.isEmpty() && !m_expression.endsWith('/') ) { base = base.parent(); } } else { if ( m_expression.startsWith('/') ) { path = Path(currentDocument.parent(), m_expression.mid(1)); } else { path = currentDocument.parent(); } base = path; - if ( !m_expression.isEmpty() && !m_expression.endsWith('/') && m_expression != "/" ) { + if ( !m_expression.isEmpty() && !m_expression.endsWith('/') && m_expression != QLatin1String("/") ) { base = base.parent(); } } QList addedPaths; bool addedParentDir = false; const QUrl baseUrl = base.toUrl(); foreach ( ProjectBaseItem* item, ICore::self()->projectController()->projectModel()->itemsForPath(IndexedString(base.toUrl())) ) { if ( abort || !item->folder() ) { break; } auto folder = item->folder(); foreach ( ProjectFileItem* subFile, folder->fileList() ) { if ( abort ) { break; } if ( addedPaths.contains(subFile->path()) ) { continue; } else { addedPaths << subFile->path(); } IncludeItem item; item.isDirectory = false; item.basePath = baseUrl; item.name = subFile->fileName(); if ( m_isFileCompletionAfterDirname && !m_expression.startsWith('/') ) { item.name.prepend('/'); } items << CompletionTreeItemPointer(new IncludeFileItem(item)); } foreach ( ProjectFolderItem* subFolder, folder->folderList() ) { if ( abort ) { break; } if ( addedPaths.contains(subFolder->path()) ) { continue; } else { addedPaths << subFolder->path(); } IncludeItem item; item.isDirectory = true; item.basePath = baseUrl; item.name = subFolder->folderName(); if ( m_isFileCompletionAfterDirname && !m_expression.startsWith('/') ) { item.name.prepend('/'); } items << CompletionTreeItemPointer(new IncludeFileItem(item)); } if ( !folder->parent() && !addedParentDir && m_expression.isEmpty() ) { // expect a parent dir IncludeItem item; item.isDirectory = true; item.basePath = baseUrl; - item.name = ".."; + item.name = QStringLiteral(".."); items << CompletionTreeItemPointer(new IncludeFileItem(item)); } } return items; } else if (memberAccessOperation() == ClassMemberChoose) { // get current class if (ClassDeclaration * currentClass = dynamic_cast(m_duContext->owner())) { // whether we want to show a list of overloadable functions // i.e. not after we have typed one of the keywords var,const or abstract bool showOverloadable = true; // whether we want to remove static functions from the overloadable list // i.e. after we have typed "public function" bool filterStatic = false; // whether we want to remove non-static functions from the overloadable list // i.e. after we have typed "public static function" bool filterNonStatic = false; // private functions are always removed from the overloadable list // but when we type "protected function" only protected functions may be shown bool filterPublic = false; { // add typical keywords for class member definitions QStringList modifiers = getMethodTokens(m_text); // don't add keywords when "function" was already typed - bool addKeywords = !modifiers.contains("function"); + bool addKeywords = !modifiers.contains(QStringLiteral("function")); if (currentClass->classModifier() == ClassDeclarationData::Abstract) { // abstract is only allowed in abstract classes - if (modifiers.contains("abstract")) { + if (modifiers.contains(QStringLiteral("abstract"))) { // don't show overloadable functions when we are defining an abstract function showOverloadable = false; } else if (addKeywords) { ADD_KEYWORD("abstract"); } } else { // final is only allowed in non-abstract classes - if (addKeywords && !modifiers.contains("final")) { + if (addKeywords && !modifiers.contains(QStringLiteral("final"))) { ADD_KEYWORD("final"); } } - if (modifiers.contains("private")) { + if (modifiers.contains(QStringLiteral("private"))) { // overloadable functions must not be declared private showOverloadable = false; - } else if (modifiers.contains("protected")) { + } else if (modifiers.contains(QStringLiteral("protected"))) { // only show protected overloadable methods filterPublic = true; - } else if (addKeywords && !modifiers.contains("public")) { + } else if (addKeywords && !modifiers.contains(QStringLiteral("public"))) { ADD_KEYWORD("public"); ADD_KEYWORD("protected"); ADD_KEYWORD("private"); } - if (modifiers.contains("static")) { + if (modifiers.contains(QStringLiteral("static"))) { filterNonStatic = true; } else { if (addKeywords) { ADD_KEYWORD("static"); } else { filterStatic = true; } } if (addKeywords) { ADD_KEYWORD("function"); } if (modifiers.isEmpty()) { // var and const may not have any modifiers ADD_KEYWORD("var"); ADD_KEYWORD("const"); } } ifDebug( qCDebug(COMPLETION) << "showOverloadable" << showOverloadable; ) // complete overloadable methods from parents if (showOverloadable) { // TODO: use m_duContext instead of ctx // overloadable choose is only possible inside classes which extend/implement others if (currentClass->baseClassesSize()) { DUContext* ctx = currentClass->internalContext(); if (!ctx) { qCDebug(COMPLETION) << "invalid class context"; return items; } QList alreadyImplemented; //TODO: use the token stream here as well //TODO: always add __construct, __destruct and maby other magic functions // get all visible declarations and add inherited to the completion items - foreach(const DeclarationDepthPair& decl, ctx->allDeclarations(ctx->range().end, m_duContext->topContext(), false)) { + foreach(DeclarationDepthPair decl, ctx->allDeclarations(ctx->range().end, m_duContext->topContext(), false)) { ClassMemberDeclaration *member = dynamic_cast(decl.first); ClassFunctionDeclaration *classFunc = dynamic_cast(decl.first); if (member) { if (decl.second == 0) { // this function is already implemented alreadyImplemented << decl.first->indexedIdentifier().getIndex(); continue; } // skip already implemented functions if (alreadyImplemented.contains(decl.first->indexedIdentifier().getIndex())) { continue; } // skip non-static functions when requested if (filterNonStatic && !member->isStatic()) { continue; } // skip static functions when requested if (filterStatic && member->isStatic()) { continue; } // always skip private functions if (member->accessPolicy() == Declaration::Private) { continue; } // skip public functions when requested if (filterPublic && member->accessPolicy() == Declaration::Public) { // make sure no non-public base members are added alreadyImplemented << decl.first->indexedIdentifier().getIndex(); continue; } // skip final members if (classFunc && classFunc->isFinal()) { // make sure no non-final base members are added alreadyImplemented << decl.first->indexedIdentifier().getIndex(); continue; } // make sure we inherit or implement the base class of this member if (!member->context() || !member->context()->owner()) { qCDebug(COMPLETION) << "invalid parent context/owner:" << member->toString(); continue; } if (!currentClass->isPublicBaseClass(dynamic_cast(member->context()->owner()), m_duContext->topContext())) { continue; } ImplementationItem::HelperType itype; if (!member->isFunctionDeclaration()) { itype = ImplementationItem::OverrideVar; } else if (member->isAbstract()) { itype = ImplementationItem::Implement; } else { itype = ImplementationItem::Override; } ifDebug( qCDebug(COMPLETION) << "ImplementationItem" << itype; ) items << CompletionTreeItemPointer(new ImplementationItem(itype, DeclarationPointer(decl.first), Php::CodeCompletionContext::Ptr(this), decl.second)); // don't add identical items twice to the completion choices alreadyImplemented << decl.first->indexedIdentifier().getIndex(); } } } } else { qCDebug(COMPLETION) << "invalid owner declaration for overloadable completion"; } } } else if (m_memberAccessOperation == BackslashAccess || m_memberAccessOperation == NamespaceChoose) { DUContext* ctx = 0; if (m_namespace.isEmpty()) { ctx = m_duContext->topContext(); } else { foreach(Declaration* dec, m_duContext->topContext()->findDeclarations(m_namespace)) { if (dec->kind() == Declaration::Namespace) { ctx = dec->internalContext(); break; } } } if (!ctx) { qCDebug(COMPLETION) << "could not find namespace:" << m_namespace.toString(); return items; } foreach(Declaration* dec, ctx->localDeclarations()) { if (!isValidCompletionItem(dec)) { continue; } else { items << CompletionTreeItemPointer( new NormalDeclarationCompletionItem( DeclarationPointer(dec), Php::CodeCompletionContext::Ptr(this), depth() ) ); } } } else if (m_expressionResult.type()) { QList containers = memberAccessContainers(); qCDebug(COMPLETION) << "containers: " << containers.count(); if (!containers.isEmpty()) { // get the parent class when we are inside a method declaration ClassDeclaration* currentClass = 0; if (m_duContext->owner() && m_duContext->owner()->isFunctionDeclaration() && m_duContext->parentContext() && m_duContext->parentContext()->owner()) { currentClass = dynamic_cast(m_duContext->parentContext()->owner()); } foreach(DUContext* ctx, containers) { ClassDeclaration* accessedClass = dynamic_cast(ctx->owner()); if (abort) return items; - foreach(const DeclarationDepthPair& decl, ctx->allDeclarations( + foreach(DeclarationDepthPair decl, ctx->allDeclarations( ctx->range().end, m_duContext->topContext(), false)) { //If we have StaticMemberAccess, which means A::Bla, show only static members, //except if we're within a class that derives from the container ClassMemberDeclaration* classMember = dynamic_cast(decl.first); if (memberAccessOperation() != StaticMemberAccess) { if (classMember && classMember->isStatic()) continue; //Skip static class members when not doing static access } else { if (!classMember || !classMember->isStatic()) continue; //Skip static class members when not doing static access } // check access policy if (classMember && accessedClass) { // by default only show public declarations Declaration::AccessPolicy ap = Declaration::Public; if (currentClass) { // if we are inside a class, we might want to show protected or private members ClassDeclaration* memberClass = dynamic_cast(classMember->context()->owner()); if (memberClass) { if (currentClass == accessedClass) { if (currentClass == memberClass) { // we can show all members of the current class ap = Declaration::Private; } else if (currentClass->isPublicBaseClass(memberClass, m_duContext->topContext())) { // we can show all but private members of ancestors of the current class ap = Declaration::Protected; } } else if (currentClass->isPublicBaseClass(accessedClass, m_duContext->topContext()) && (accessedClass == memberClass || accessedClass->isPublicBaseClass(memberClass, m_duContext->topContext()))) { // we can show all but private members of ancestors of the current class ap = Declaration::Protected; } } } if (ap < classMember->accessPolicy()) { continue; } } if (!decl.first->identifier().isEmpty()) items << CompletionTreeItemPointer( new NormalDeclarationCompletionItem( DeclarationPointer( decl.first), Php::CodeCompletionContext::Ptr(this), decl.second ) ); } } } else { qCDebug(COMPLETION) << "setContext: no container-type"; } } else { //Show all visible declarations QSet existingIdentifiers; QList decls = m_duContext->allDeclarations( CursorInRevision::invalid(), m_duContext->topContext() ); qCDebug(COMPLETION) << "setContext: using all declarations visible:" << decls.size(); QListIterator i(decls); i.toBack(); while (i.hasPrevious()) { DeclarationDepthPair decl = i.previous(); Declaration* dec = decl.first; if (dec->kind() == Declaration::Instance) { // filter non-superglobal vars of other contexts if (dec->context() != m_duContext.data() && !m_duContext->imports(dec->context())) { VariableDeclaration* vDec = dynamic_cast(dec); if ( vDec && !vDec->isSuperglobal() ) { continue; } } if (existingIdentifiers.contains(dec->indexedIdentifier().getIndex())) continue; existingIdentifiers.insert(dec->indexedIdentifier().getIndex()); } if (abort) return items; if (!isValidCompletionItem(dec)) continue; items << CompletionTreeItemPointer( new NormalDeclarationCompletionItem( DeclarationPointer(dec), Php::CodeCompletionContext::Ptr(this), decl.second ) ); } foreach(QSet urlSets, completionFiles()) { foreach(const IndexedString &url, urlSets) { if (url == m_duContext->url()) { continue; } uint count = 0; const CodeModelItem* foundItems = 0; CodeModel::self().items(url, count, foundItems); for (uint i = 0; i < count; ++i) { CodeModelItem::Kind k = foundItems[i].kind; if (((k & CodeModelItem::Function) || (k & CodeModelItem::Variable)) && !(k & CodeModelItem::ClassMember)) { foreach(const ParsingEnvironmentFilePointer &env, DUChain::self()->allEnvironmentFiles(url)) { if (env->language() != phpLangString) continue; TopDUContext* top = env->topContext(); if(!top) continue; if (m_duContext->imports(top)) continue; QList decls = top->findDeclarations(foundItems[i].id); foreach(Declaration* decl, decls) { if (abort) return items; // we don't want to have class methods/properties, just normal functions // and other global stuff if ( decl->context() && decl->context()->type() == DUContext::Class ) { continue; } if (!isValidCompletionItem(decl)) continue; if ( VariableDeclaration* vDec = dynamic_cast(decl) ) { if ( !vDec->isSuperglobal() ) { continue; } } items << CompletionTreeItemPointer( new NormalDeclarationCompletionItem( DeclarationPointer(decl), Php::CodeCompletionContext::Ptr(this) ) ); } } } } } } foreach(QSet urlSets, completionFiles()) { foreach(const IndexedString &url, urlSets) { uint count = 0; const CompletionCodeModelItem* foundItems = 0; CompletionCodeModel::self().items(url, count, foundItems); for (uint i = 0; i < count; ++i) { if (abort) return items; if (m_memberAccessOperation == ExceptionChoose) { if (!(foundItems[i].kind & CompletionCodeModelItem::Exception)) continue; } - foreach(const ParsingEnvironmentFilePointer &env, DUChain::self()->allEnvironmentFiles(url)) { + auto files = DUChain::self()->allEnvironmentFiles(url); + items.reserve(files.size()); + foreach(const ParsingEnvironmentFilePointer &env, files) { Q_ASSERT(env->language() == phpLangString); items << CompletionTreeItemPointer ( new CodeModelCompletionItem(env, foundItems[i])); } } } } } ///Find all recursive function-calls that should be shown as call-tips CodeCompletionContext::Ptr parentContext(this); do { if (abort) return items; parentContext = parentContext->parentContext(); if (parentContext) { if (parentContext->memberAccessOperation() == CodeCompletionContext::FunctionCallAccess) { if (!parentContext->memberAccessContainer().allDeclarationIds().isEmpty()) { Declaration* decl = parentContext->memberAccessContainer().allDeclarationIds().first() .getDeclaration(m_duContext->topContext()); if (!isValidCompletionItem(decl)) { continue; } if ( !decl->isFunctionDeclaration() ) { if ( ClassDeclaration* classDec = dynamic_cast(decl) ) { // search for ctor decl = 0; foreach ( Declaration* dec, classDec->internalContext()->findDeclarations(Identifier("__construct")) ) { if ( dec->isFunctionDeclaration() ) { decl = dec; break; } } if ( !decl ) { foreach ( Declaration* dec, classDec->internalContext()->findDeclarations(classDec->identifier()) ) { if ( dec->isFunctionDeclaration() ) { decl = dec; break; } } } if ( !decl ) { continue; } } else if ( !decl->type() ) { qCDebug(COMPLETION) << "parent decl is neither function nor class nor closure, skipping" << decl->toString(); continue; } } items << CompletionTreeItemPointer( new NormalDeclarationCompletionItem( DeclarationPointer(decl), Php::CodeCompletionContext::Ptr(parentContext.data()) ) ); } } else { qCDebug(COMPLETION) << "parent-context has non function-call access type"; } } } while (parentContext); if ( m_memberAccessOperation == NoMemberAccess ) { ///TODO: function-like statements should just be handled as a function with declaration etc. /// e.g.: empty, eval, die, exit, isset, unset /// but _not_ echo, print, catch, include*, require* ///TODO: use user's style for indendation etc. ADD_KEYWORD2("abstract class", "abstract class %SELECT%NAME%ENDSELECT% {\n%INDENT%\n}\n"); ADD_KEYWORD2("final class", "final class %SELECT%NAME%ENDSELECT% {\n%INDENT%\n}\n"); ADD_KEYWORD2("class", "class %SELECT%NAME%ENDSELECT% {\n%INDENT%\n}\n"); ADD_KEYWORD2("interface", "interface %SELECT%NAME%ENDSELECT% {\n%INDENT%\n}\n"); ADD_KEYWORD2("array", "array(\n%INDENT%%CURSOR%\n)"); ADD_KEYWORD2("break", "break;\n"); ADD_KEYWORD2("case", "case %SELECT%CASE%ENDSELECT%:\n%INDENT%\n%INDENT%break;\n"); ADD_KEYWORD2("throw", "throw %CURSOR%;\n"); ADD_KEYWORD2("try", "try {\n%INDENT%%CURSOR%\n} catch() {\n$%INDENT%\n}\n"); ADD_KEYWORD2("catch", "catch(%CURSOR%) {\n%INDENT%\n}\n"); ADD_KEYWORD2("clone", "clone %CURSOR%;\n"); ADD_KEYWORD2("continue", "continue;\n"); ADD_KEYWORD2("declare", "declare(%CURSOR%);\n"); ADD_KEYWORD2("default", "default:\n%INDENT%%CURSOR%\n%INDENT%break;\n"); ADD_KEYWORD2("do", "do {\n%INDENT%%CURSOR%\n} while();\n"); ADD_KEYWORD2("echo", "echo %CURSOR%;\n"); ADD_KEYWORD2("else", "else {\n%INDENT%%CURSOR%\n}\n"); ADD_KEYWORD2("elseif", "elseif (%CURSOR%) {\n%INDENT%\n}\n"); ADD_KEYWORD2("endif", "endif;"); ADD_KEYWORD2("endforeach", "endforeach;"); ADD_KEYWORD2("endswitch", "endswitch;"); ADD_KEYWORD2("endwhile", "endwhile;"); ADD_KEYWORD2("endfor", "endfor;"); ADD_KEYWORD2("enddeclare", "enddeclare;"); ADD_KEYWORD2("empty", "empty(%CURSOR%)"); ADD_KEYWORD2("eval", "eval(%CURSOR%)"); ADD_KEYWORD2("die", "die(%CURSOR%);\n"); ADD_KEYWORD2("exit", "exit(%CURSOR%);\n"); ///TODO: only activate when after "class NAME " ADD_KEYWORD("extends"); ADD_KEYWORD("implements"); ADD_KEYWORD2("for", "for(%CURSOR%;;) {\n%INDENT%\n}\n"); ADD_KEYWORD2("foreach", "foreach(%CURSOR%) {\n%INDENT%\n}\n"); ADD_KEYWORD2("function", "function %SELECT%NAME%ENDSELECT%() {\n%INDENT%\n}\n"); ADD_KEYWORD2("global", "global $%CURSOR%;"); ADD_KEYWORD2("if", "if (%CURSOR%) {\n%INDENT%\n}\n"); ADD_KEYWORD2("include", "include '%CURSOR%';\n"); ADD_KEYWORD2("include_once", "include_once '%CURSOR%';\n"); ADD_KEYWORD2("require", "require '%CURSOR%';\n"); ADD_KEYWORD2("require_once", "require_once '%CURSOR%';\n"); ADD_KEYWORD2("isset", "isset(%CURSOR%)"); ADD_KEYWORD2("list", "list(%CURSOR%)"); ADD_KEYWORD2("print", "print %CURSOR%;\n"); ADD_KEYWORD2("return", "return %CURSOR%;\n"); ADD_KEYWORD2("static", "static $%CURSOR%%;\n"); ADD_KEYWORD2("unset", "unset(%CURSOR%);\n"); ADD_KEYWORD2("while", "while (%CURSOR%) {\n%INDENT%\n}\n"); ADD_KEYWORD2("switch", "switch (%CURSOR%) {\n%INDENT%\n}\n"); } return items; } inline bool CodeCompletionContext::isValidCompletionItem(Declaration* dec) { if ( !dec || dec->range().isEmpty() ) { // hack for included files return false; } if ( dec->kind() == Declaration::Type && dec->qualifiedIdentifier().isEmpty() ) { // filter closures return false; } static DUChainPointer exceptionDecl; if (!exceptionDecl) { /// Qualified identifier for 'exception' - static const KDevelop::QualifiedIdentifier exceptionQId("exception"); + static const KDevelop::QualifiedIdentifier exceptionQId(QStringLiteral("exception")); QList decs = dec->context()->findDeclarations(exceptionQId); Q_ASSERT(decs.count()); - if (decs.count()) { // additional safe-guard, see e.g. https://bugs.kde.org/show_bug.cgi?id=294218 + if (!decs.isEmpty()) { // additional safe-guard, see e.g. https://bugs.kde.org/show_bug.cgi?id=294218 exceptionDecl = dynamic_cast(decs.first()); Q_ASSERT(exceptionDecl); } } if (!exceptionDecl) { // safe-guard, see: https://bugs.kde.org/show_bug.cgi?id=294218 qWarning() << "could not find PHP-Exception declaration, related code completion will be broken."; } if (m_memberAccessOperation == ExceptionChoose || m_memberAccessOperation == NewClassChoose || m_memberAccessOperation == InterfaceChoose || m_memberAccessOperation == ClassExtendsChoose || m_memberAccessOperation == InstanceOfChoose) { // filter current class if (!m_forbiddenIdentifiers.isEmpty() && m_forbiddenIdentifiers.contains(dec->qualifiedIdentifier().index())) { return false; } ClassDeclaration* classDec = dynamic_cast(dec); // filter non-classes if (!classDec) { return false; } // show non-interface and non-abstract else if (m_memberAccessOperation == NewClassChoose) { return !(classDec->classModifier() & ClassDeclarationData::Abstract) && classDec->classType() == ClassDeclarationData::Class; } // filter non-exception classes else if (m_memberAccessOperation == ExceptionChoose) { if (!exceptionDecl) { // safe-guard, see: https://bugs.kde.org/show_bug.cgi?id=294218 return false; } return classDec->equalQualifiedIdentifier(exceptionDecl.data()) || classDec->isPublicBaseClass(exceptionDecl.data(), m_duContext->topContext()); } // show interfaces else if (m_memberAccessOperation == InterfaceChoose) { return classDec->classType() == ClassDeclarationData::Interface; } // show anything but final classes and interfaces else if (m_memberAccessOperation == ClassExtendsChoose) { return !(classDec->classModifier() & ClassDeclarationData::Final) && classDec->classType() == ClassDeclarationData::Class; } else if (m_memberAccessOperation == InstanceOfChoose) { return true; } } if (m_memberAccessOperation == ExceptionInstanceChoose) { if (!exceptionDecl) { // safe-guard, see: https://bugs.kde.org/show_bug.cgi?id=294218 return false; } if (dec->kind() != Declaration::Instance) { return false; } StructureType::Ptr structType = dec->type(); if (!structType) { return false; } ClassDeclaration* classDec = dynamic_cast(structType->declaration(dec->topContext())); if (!classDec) { return false; } return classDec->isPublicBaseClass(exceptionDecl.data(), m_duContext->topContext()); } if (m_memberAccessOperation == NoMemberAccess) { // filter private methods and class members when doing a global completion // when we are inside a class function, don't filter the private stuff // of the current class // NOTE: ClassFunctionDeclaration inherits ClassMemberDeclaration // NOTE: both have to have a parent context with type class if ( dec->context() && dec->context()->type() == DUContext::Class && m_duContext->parentContext() != dec->context() ) { if ( ClassMemberDeclaration* memberDec = dynamic_cast(dec) ) { if ( memberDec->accessPolicy() == Declaration::Private ) { return false; } } } if ( !dec->isFunctionDeclaration() && m_duContext.data() == dec->context() && m_position < dec->range().start ) { return false; } } if (m_memberAccessOperation == NamespaceChoose) { return dec->kind() == Declaration::Namespace; } return true; } QList > CodeCompletionContext::completionFiles() { QList > ret; if (ICore::self()) { - foreach(IProject* project, ICore::self()->projectController()->projects()) { + auto projects = ICore::self()->projectController()->projects(); + ret.reserve(projects.size()); + foreach(IProject* project, projects) { ret << project->fileSet(); } } return ret; } } diff --git a/completion/helpers.cpp b/completion/helpers.cpp index bc53f9f..beb0f90 100644 --- a/completion/helpers.cpp +++ b/completion/helpers.cpp @@ -1,213 +1,213 @@ /* * KDevelop Php Code Completion Support * * Copyright 2007-2008 David Nolden * 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 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 "helpers.h" #include "item.h" #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; namespace Php { void createArgumentList(const NormalDeclarationCompletionItem& item, QString& ret, QList* highlighting, bool phpTypeHinting) { ///@todo also highlight the matches of the previous arguments, they are given by ViableFunction Declaration* dec(item.declaration().data()); int textFormatStart = 0; QTextFormat normalFormat(QTextFormat::CharFormat); QTextFormat highlightFormat; //highlightFormat is invalid, so kate uses the match-quality dependent color. AbstractFunctionDeclaration* decl = dynamic_cast(dec); FunctionType::Ptr functionType = dec->type(); if (functionType && decl) { QVector parameters; if (DUChainUtils::getArgumentContext(dec)) parameters = DUChainUtils::getArgumentContext(dec)->localDeclarations(); uint defaultParamNum = 0; int firstDefaultParam = parameters.count() - decl->defaultParametersSize(); ret = '('; bool first = true; int num = 0; foreach(Declaration* dec, parameters) { if (first) first = false; else - ret += ", "; + ret += QLatin1String(", "); bool doHighlight = false; QTextFormat doFormat = normalFormat; // if( num < f.matchedArguments ) // { doHighlight = true; doFormat = QTextFormat(QTextFormat::CharFormat); // if( parameterConversion != conversions.end() ) { // //Interpolate the color // quint64 badMatchColor = 0xff7777ff; //Full blue // quint64 goodMatchColor = 0xff77ff77; //Full green // // uint totalColor = (badMatchColor*(Cpp::MaximumConversionResult-(*parameterConversion).rank) + goodMatchColor*(*parameterConversion).rank)/Cpp::MaximumConversionResult; // // doFormat.setBackground( QBrush(totalColor) ); // // ++parameterConversion; // } // } if (doHighlight) { if (highlighting && ret.length() != textFormatStart) { //Add a default-highlighting for the passed text *highlighting << QVariant(textFormatStart); *highlighting << QVariant(ret.length() - textFormatStart); *highlighting << QVariant(normalFormat); textFormatStart = ret.length(); } } if (num < functionType->arguments().count()) { if (AbstractType::Ptr type = functionType->arguments().at(num)) { // when php-like type hinting is requested only add types for arrays and classes if (!phpTypeHinting || (type->whichType() == AbstractType::TypeIntegral && type.cast()->dataType() == IntegralType::TypeArray) || type->whichType() == AbstractType::TypeStructure) { ret += type->toString() + ' '; } } } ret += '$' + dec->identifier().toString(); if (doHighlight) { if (highlighting && ret.length() != textFormatStart) { *highlighting << QVariant(textFormatStart); *highlighting << QVariant(ret.length() - textFormatStart); *highlighting << doFormat; textFormatStart = ret.length(); } } if (num >= firstDefaultParam) { ret += " = " + decl->defaultParameters()[defaultParamNum].str(); ++defaultParamNum; } ++num; } ret += ')'; if (highlighting && ret.length() != textFormatStart) { *highlighting << QVariant(textFormatStart); *highlighting << QVariant(ret.length()); *highlighting << normalFormat; textFormatStart = ret.length(); } return; } } QStringList getMethodTokens(QString text) { QStringList tokens; text = text.trimmed(); - if (text.endsWith(QString("function"), Qt::CaseInsensitive)) { - tokens << "function"; + if (text.endsWith(QStringLiteral("function"), Qt::CaseInsensitive)) { + tokens << QStringLiteral("function"); text = text.left(text.length() - 8); } QStringList possibleTokens; - possibleTokens << "private"; - possibleTokens << "public"; - possibleTokens << "protected"; - possibleTokens << "static"; - possibleTokens << "abstract"; - possibleTokens << "final"; + possibleTokens << QStringLiteral("private"); + possibleTokens << QStringLiteral("public"); + possibleTokens << QStringLiteral("protected"); + possibleTokens << QStringLiteral("static"); + possibleTokens << QStringLiteral("abstract"); + possibleTokens << QStringLiteral("final"); while (!possibleTokens.isEmpty()) { bool foundAnything = false; text = text.trimmed(); foreach(const QString &token, possibleTokens) { if (text.endsWith(token, Qt::CaseInsensitive)) { tokens << token; text = text.left(text.length() - token.length()); foundAnything = true; possibleTokens.removeOne(token); break; } } if (!foundAnything) { break; } } return tokens; } const QString indentString(KTextEditor::Document *document) { KTextEditor::ConfigInterface *iface = qobject_cast(document); if (!iface) - return " "; - if (iface->configValue("replace-tabs").toBool()) { - QVariant iWidth = iface->configValue("indent-width"); + return QStringLiteral(" "); + if (iface->configValue(QStringLiteral("replace-tabs")).toBool()) { + QVariant iWidth = iface->configValue(QStringLiteral("indent-width")); if (iWidth.isValid()) return QString(iWidth.toUInt(), ' '); /* * Provide a default implementation if current KatePart * does not handle "indent-width". */ - return " "; + return QStringLiteral(" "); } - return "\t"; + return QStringLiteral("\t"); } QString getIndendation( const QString &line ) { return line.left(line.indexOf(QRegExp("\\S"), 0)); } } diff --git a/completion/implementationitem.cpp b/completion/implementationitem.cpp index f29ee1f..092126a 100644 --- a/completion/implementationitem.cpp +++ b/completion/implementationitem.cpp @@ -1,275 +1,275 @@ /* * KDevelop Php Code Completion Support * * Copyright 2009 Milian Wolff * Basec on Cpp ImplementationHelperItem * * 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 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 "implementationitem.h" #include #include #include #include #include #include #include #include #include #include #include #include "declarations/classmethoddeclaration.h" #include "completiondebug.h" #include "helpers.h" using namespace KDevelop; namespace Php { #define RETURN_CACHED_ICON(name) {static QIcon icon(QIcon::fromTheme(QStringLiteral(name)).pixmap(QSize(16, 16))); return icon;} QVariant ImplementationItem::data(const QModelIndex& index, int role, const CodeCompletionModel* model) const { QVariant ret = NormalDeclarationCompletionItem::data(index, role, model); switch (role) { case Qt::DecorationRole: if (index.column() == KTextEditor::CodeCompletionModel::Icon) { switch (m_type) { case Override: case OverrideVar: RETURN_CACHED_ICON("CTparents"); case Implement: RETURN_CACHED_ICON("CTsuppliers"); } } break; case Qt::DisplayRole: if (index.column() == KTextEditor::CodeCompletionModel::Prefix) { QString prefix; switch (m_type) { case Override: case OverrideVar: prefix = i18n("Override"); break; case Implement: prefix = i18n("Implement"); break; } ret = prefix + ' ' + ret.toString(); } //TODO column == Name - required? break; case KTextEditor::CodeCompletionModel::ItemSelected: { DUChainReadLocker lock(DUChain::lock()); if (declaration().data()) { QualifiedIdentifier parentScope = declaration()->context()->scopeIdentifier(true); return i18n("From %1", parentScope.toString()); } } break; case KTextEditor::CodeCompletionModel::InheritanceDepth: return QVariant(0); default: //pass break; } return ret; } void ImplementationItem::execute(KTextEditor::View* view, const KTextEditor::Range& word) { DUChainReadLocker lock(DUChain::lock()); KTextEditor::Document *document = view->document(); QString replText; if (m_declaration) { //TODO:respect custom code styles // get existing modifiers so we can respect the user's choice of public/protected and final QStringList modifiers = getMethodTokens(document->text(KTextEditor::Range(KTextEditor::Cursor::start(), word.start()))); // get range to replace KTextEditor::Range replaceRange(word); if (!modifiers.isEmpty()) { // TODO: is there no easy API to map QString Index to a KTextEditor::Cursor ?! QString methodText = document->text(KTextEditor::Range(KTextEditor::Cursor::start(), word.start())); methodText = methodText.left(methodText.lastIndexOf(modifiers.last(), -1, Qt::CaseInsensitive)); replaceRange.start() = KTextEditor::Cursor(methodText.count('\n'), methodText.length() - methodText.lastIndexOf('\n') - 1); } // get indendation QString indendation; { QString currentLine = document->line(replaceRange.start().line()); indendation = getIndendation(currentLine); if ( !currentLine.isEmpty() && currentLine != indendation ) { // since theres some non-whitespace in this line, skip to the enxt one replText += '\n' + indendation; } if (indendation.isEmpty()) { // use a minimal indendation // TODO: respect code style - indendation = " "; + indendation = QStringLiteral(" "); replText += indendation; } } #if 0 //Disabled, because not everyone writes phpdoc for every function //TODO: move to a phpdoc helper // build phpdoc comment { QualifiedIdentifier parentClassIdentifier; if (DUContext* pctx = m_declaration->context()) { parentClassIdentifier = pctx->localScopeIdentifier(); } else { qCDebug(COMPLETION) << "completion item for implementation has no parent context!"; } replText += "/**\n" + indendation + " * "; // insert old comment: const QString indentationWithExtra = "\n" + indendation + " *"; replText += m_declaration->comment().replace('\n', indentationWithExtra.toAscii().constData()); replText += "\n" + indendation + " * @overload " + m_declaration->internalContext()->scopeIdentifier(true).toString(); replText += "\n" + indendation + " **/\n" + indendation; } #endif // write function signature // copy existing modifiers if (!modifiers.isEmpty()) { // the tokens are in a bad order and there's no reverse method or similar, so we can't simply join the tokens QStringList::const_iterator i = modifiers.constEnd() - 1; while (true) { replText += (*i) + ' '; if (i == modifiers.constBegin()) { break; } else { --i; } } } QString functionName; bool isConstructorOrDestructor = false; bool isInterface = false; if (ClassMemberDeclaration* member = dynamic_cast(m_declaration.data())) { // NOTE: it should _never_ be private - but that's the completionmodel / context / worker's job - if (!modifiers.contains("public") && !modifiers.contains("protected")) { + if (!modifiers.contains(QStringLiteral("public")) && !modifiers.contains(QStringLiteral("protected"))) { if (member->accessPolicy() == Declaration::Protected) { - replText += "protected "; + replText += QLatin1String("protected "); } else { - replText += "public "; + replText += QLatin1String("public "); } } - if (!modifiers.contains("static") && member->isStatic()) { - replText += "static "; + if (!modifiers.contains(QStringLiteral("static")) && member->isStatic()) { + replText += QLatin1String("static "); } functionName = member->identifier().toString(); ClassMethodDeclaration* method = dynamic_cast(m_declaration.data()); if (method) { functionName = method->prettyName().str(); isConstructorOrDestructor = method->isConstructor() || method->isDestructor(); } if (member->context() && member->context()->owner()) { ClassDeclaration* classDec = dynamic_cast(member->context()->owner()); if (classDec) { isInterface = (classDec->classType() == ClassDeclarationData::Interface); } } } else { qCDebug(COMPLETION) << "completion item for implementation was not a classfunction declaration!"; functionName = m_declaration->identifier().toString(); } if (m_type == ImplementationItem::OverrideVar) { replText += "$" + functionName + " = "; } else { - if (!modifiers.contains("function")) { - replText += "function "; + if (!modifiers.contains(QStringLiteral("function"))) { + replText += QLatin1String("function "); } replText += functionName; { // get argument list QString arguments; createArgumentList(*this, arguments, 0, true); replText += arguments; } QString arguments; QVector parameters; if (DUChainUtils::getArgumentContext(m_declaration.data())) parameters = DUChainUtils::getArgumentContext(m_declaration.data())->localDeclarations(); arguments = '('; bool first = true; foreach(Declaration* dec, parameters) { if (first) first = false; else - arguments += ", "; + arguments += QLatin1String(", "); arguments += '$' + dec->identifier().toString(); } arguments += ')'; bool voidReturnType = false; if (FunctionType::Ptr::dynamicCast(m_declaration->abstractType())) { AbstractType::Ptr retType = FunctionType::Ptr::staticCast(m_declaration->abstractType())->returnType(); if (retType->equals(new IntegralType(IntegralType::TypeVoid))) { voidReturnType = true; } } - replText += QString("\n%1{\n%1 ").arg(indendation); + replText += QStringLiteral("\n%1{\n%1 ").arg(indendation); if (isInterface || m_type == ImplementationItem::Implement) { } else if (!isConstructorOrDestructor && !voidReturnType) { - replText += QString("$ret = parent::%2%3;\n%1 return $ret;").arg(indendation).arg(functionName).arg(arguments); + replText += QStringLiteral("$ret = parent::%2%3;\n%1 return $ret;").arg(indendation, functionName, arguments); } else { - replText += QString("parent::%1%2;").arg(functionName).arg(arguments); + replText += QStringLiteral("parent::%1%2;").arg(functionName, arguments); } - replText += QString("\n%1}\n%1") + replText += QStringLiteral("\n%1}\n%1") .arg(indendation); } //TODO: properly place the cursor inside the {} part document->replaceText(replaceRange, replText); } else { qCDebug(COMPLETION) << "Declaration disappeared"; } } } diff --git a/completion/includefileitem.cpp b/completion/includefileitem.cpp index b9c2696..bead91a 100644 --- a/completion/includefileitem.cpp +++ b/completion/includefileitem.cpp @@ -1,102 +1,102 @@ /* * KDevelop Php Code Completion Support * * Copyright 2009 Milian Wolff * * 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 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 "includefileitem.h" #include #include using namespace KTextEditor; namespace Php { void IncludeFileItem::execute(View* view, const Range& _word) { KTextEditor::Document *document = view->document(); Range word(_word); QString newText; if ( includeItem.isDirectory ) { newText = includeItem.name + '/'; } else { newText = includeItem.name; } // Add suffix QChar closeChar; { const QString textBefore = document->text(Range(Cursor(0, 0), _word.start())); QRegExp regexp("(?:include|require)(?:_once)?(\\s*)(\\(?)(\\s*)(?:dirname\\s*\\(\\s*__FILE__\\s*\\)\\s*\\.\\s*)?([\"'])", Qt::CaseInsensitive); if ( regexp.lastIndexIn(textBefore) != -1 ) { - closeChar = regexp.cap(4)[0]; + closeChar = regexp.cap(4).at(0); newText.append(closeChar); if ( !regexp.cap(2).isEmpty() ) { newText.append(regexp.cap(3)); newText.append(')'); } newText.append(';'); } } // Adapt range and replace existing stuff { const QString textAfter = document->text(Range(_word.end(), document->documentEnd())); if ( !textAfter.isEmpty() ) { int pos = 0; for (; pos < textAfter.length(); ++pos ) { if ( textAfter[pos].isSpace() ) { break; } else if ( textAfter[pos] == closeChar ) { // remove close char ++pos; // find semicolon (if existing) for (int i = pos; i < textAfter.length(); ++i ) { if ( textAfter[i] == ';' ) { // remove semicolon pos = i + 1; break; } else if ( !textAfter[i].isSpace() && textAfter[i] != ')' ) { break; } } break; } } if ( pos > 0 ) { word.setEnd(word.end() + Cursor(0, pos)); } } } document->replaceText(word, newText); // when we complete a directory, move the cursor behind it so we can continue with auto-completion if ( includeItem.isDirectory ) { if (view) { view->setCursorPosition(Cursor(_word.start().line(), _word.start().column() + includeItem.name.size() + 1)); } } } } diff --git a/completion/item.cpp b/completion/item.cpp index 8e3bf7a..e03dcff 100644 --- a/completion/item.cpp +++ b/completion/item.cpp @@ -1,204 +1,204 @@ /* * KDevelop Php Code Completion Support * * Copyright 2007-2008 David Nolden * Copyright 2008 Hamish Rodda * 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 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 "item.h" #include #include #include #include #include #include #include #include #include #include #include #include "../duchain/declarations/classdeclaration.h" #include "../duchain/declarations/classmethoddeclaration.h" #include "../duchain/declarations/variabledeclaration.h" #include "../duchain/types/structuretype.h" #include "../duchain/navigation/navigationwidget.h" #include "../duchain/helper.h" #include "helpers.h" #include "context.h" #include "completiondebug.h" using namespace KDevelop; namespace Php { QString NormalDeclarationCompletionItem::declarationName() const { QString ret = prettyName(m_declaration.data()); if ( ret.isEmpty() ) { - return ""; + return QStringLiteral(""); } bool isStatic = false; if (!m_declaration->isFunctionDeclaration()) { if (dynamic_cast(m_declaration.data())) { ret = '$' + ret; } else if (ClassMemberDeclaration* memberDec = dynamic_cast(m_declaration.data())) { isStatic = memberDec->isStatic(); if (memberDec->isStatic() && memberDec->abstractType() && ! memberDec->abstractType()->modifiers() & AbstractType::ConstModifier) { // PHP is strange, $obj->asdf, class::const but class::$static ... ret = '$' + ret; } } } else if ( ClassFunctionDeclaration* funDec = dynamic_cast(m_declaration.data()) ) { isStatic = funDec->isStatic(); } const QExplicitlySharedDataPointer& ctx = completionContext(); if ( ctx->memberAccessOperation() == CodeCompletionContext::NoMemberAccess ) { // if we complete a class member or method (inside a method) // we might have to add "self::", "parent::" or "$this->" if ( ctx->duContext() && ctx->duContext()->parentContext() && ctx->duContext()->parentContext()->type() == DUContext::Class ) { if ( m_declaration->context() && m_declaration->context()->type() == DUContext::Class ) { if ( isStatic ) { ret = "self::" + ret; } else { ret = "$this->" + ret; } } } } return ret; } void NormalDeclarationCompletionItem::executed(KTextEditor::View* view, const KTextEditor::Range& word) { if (m_declaration && dynamic_cast(m_declaration.data())) { //Do some intelligent stuff for functions with the parens: insertFunctionParenText(view, word.end(), m_declaration); } } QVariant NormalDeclarationCompletionItem::data(const QModelIndex& index, int role, const KDevelop::CodeCompletionModel* model) const { DUChainReadLocker lock(DUChain::lock(), 500); if (!lock.locked()) { qCDebug(COMPLETION) << "Failed to lock the du-chain in time"; return QVariant(); } if (!m_declaration) { return QVariant(); } Declaration* dec = const_cast(m_declaration.data()); switch (role) { case CodeCompletionModel::ItemSelected: return QVariant(NavigationWidget::shortDescription(dec)); case Qt::DisplayRole: switch (index.column()) { case CodeCompletionModel::Postfix: return QVariant(); case CodeCompletionModel::Prefix: if (dec->kind() == Declaration::Type && !dec->isTypeAlias()) { if (dec->isFunctionDeclaration()) { FunctionType::Ptr funcType = dec->type(); if ( funcType && funcType->returnType() ) { return funcType->returnType()->toString(); } else { return ""; } } else if (dec->internalContext() && dec->internalContext()->type() == DUContext::Class) { ClassDeclaration* classDec = dynamic_cast(dec); if (classDec) { if (classDec->classType() == ClassDeclarationData::Interface) { return "interface"; } else { return "class"; } } } return QVariant(); } else if (dec->kind() == Declaration::Namespace) { return "namespace"; } break; case CodeCompletionModel::Arguments: if (FunctionType::Ptr functionType = dec->type()) { QString ret; createArgumentList(*this, ret, 0); return ret; } break; } break; case CodeCompletionModel::HighlightingMethod: if (index.column() == CodeCompletionModel::Arguments) { if (completionContext()->memberAccessOperation() == CodeCompletionContext::FunctionCallAccess) { return QVariant(CodeCompletionModel::CustomHighlighting); } else { return QVariant(); } break; } break; case CodeCompletionModel::CustomHighlight: if (index.column() == CodeCompletionModel::Arguments && completionContext()->memberAccessOperation() == CodeCompletionContext::FunctionCallAccess) { QString ret; QList highlight; createArgumentList(*this, ret, &highlight); return QVariant(highlight); } break; } lock.unlock(); return KDevelop::NormalDeclarationCompletionItem::data(index, role, model); } QWidget* NormalDeclarationCompletionItem::createExpandingWidget(const KDevelop::CodeCompletionModel* model) const { return new NavigationWidget(m_declaration, model->currentTopContext()); } bool NormalDeclarationCompletionItem::createsExpandingWidget() const { return true; } QExplicitlySharedDataPointer NormalDeclarationCompletionItem::completionContext() const { auto context = dynamic_cast(m_completionContext.data()); return QExplicitlySharedDataPointer(context); } } diff --git a/completion/keyworditem.cpp b/completion/keyworditem.cpp index 7e335ec..1eb5581 100644 --- a/completion/keyworditem.cpp +++ b/completion/keyworditem.cpp @@ -1,122 +1,122 @@ /* * KDevelop Php Code Completion Support * * Copyright 2009 Milian Wolff * Based on Cpp ImplementationHelperItem * * 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 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 "keyworditem.h" #include #include #include #include #include #include #include #include #include "../duchain/declarations/classmethoddeclaration.h" #include "helpers.h" using namespace KDevelop; namespace Php { QVariant KeywordItem::data(const QModelIndex& index, int role, const CodeCompletionModel* model) const { switch (role) { case CodeCompletionModel::IsExpandable: return QVariant(false); case Qt::DisplayRole: if (index.column() == KTextEditor::CodeCompletionModel::Name) { return QVariant(m_keyword); } else { return QVariant(""); } break; case KTextEditor::CodeCompletionModel::ItemSelected: return QVariant(""); case KTextEditor::CodeCompletionModel::InheritanceDepth: return QVariant(0); default: //pass break; } return NormalDeclarationCompletionItem::data(index, role, model); } void KeywordItem::execute(KTextEditor::View* view, const KTextEditor::Range& word) { KTextEditor::Document *document = view->document(); if ( !m_replacement.isEmpty() ) { QString replacement = m_replacement; replacement = replacement.replace('\n', '\n' + getIndendation(document->line(word.start().line()))); - replacement = replacement.replace("%INDENT%", indentString(document)); + replacement = replacement.replace(QLatin1String("%INDENT%"), indentString(document)); - int cursorPos = replacement.indexOf("%CURSOR%"); + int cursorPos = replacement.indexOf(QStringLiteral("%CURSOR%")); int selectionEnd = -1; if ( cursorPos != -1 ) { - replacement.remove("%CURSOR%"); + replacement.remove(QStringLiteral("%CURSOR%")); } else { - cursorPos = replacement.indexOf("%SELECT%"); + cursorPos = replacement.indexOf(QStringLiteral("%SELECT%")); if ( cursorPos != -1 ) { - replacement.remove("%SELECT%"); - selectionEnd = replacement.indexOf("%ENDSELECT%", cursorPos + 1); + replacement.remove(QStringLiteral("%SELECT%")); + selectionEnd = replacement.indexOf(QStringLiteral("%ENDSELECT%"), cursorPos + 1); if ( selectionEnd == -1 ) { selectionEnd = replacement.length(); } - replacement.remove("%ENDSELECT%"); + replacement.remove(QStringLiteral("%ENDSELECT%")); } } document->replaceText(word, replacement); if ( cursorPos != -1 ) { if (view) { replacement = replacement.left(cursorPos); KTextEditor::Cursor newPos( word.start().line() + replacement.count('\n'), word.start().column() + replacement.length() - replacement.lastIndexOf('\n') - 1 ); view->setCursorPosition(newPos); if ( selectionEnd != -1 ) { ///TODO: maybe we want to support multi-line selections in the future? view->setSelection( KTextEditor::Range( newPos, KTextEditor::Cursor( newPos.line(), newPos.column() + selectionEnd - cursorPos ) ) ); } } } } else { document->replaceText(word, m_keyword + ' '); } } } diff --git a/completion/model.cpp b/completion/model.cpp index a85c68f..101930e 100644 --- a/completion/model.cpp +++ b/completion/model.cpp @@ -1,97 +1,97 @@ /* * KDevelop Php Code Completion Support * * Copyright 2006-2008 Hamish Rodda * Copyright 2007-2008 David Nolden * 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 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 "model.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../duchain/declarations/classmethoddeclaration.h" #include "context.h" #include "worker.h" #include "completiondebug.h" using namespace KTextEditor; using namespace KDevelop; namespace Php { CodeCompletionModel::CodeCompletionModel(QObject * parent) : KDevelop::CodeCompletionModel(parent) { } KDevelop::CodeCompletionWorker* CodeCompletionModel::createCompletionWorker() { return new CodeCompletionWorker(this); } CodeCompletionModel::~CodeCompletionModel() { } Range CodeCompletionModel::completionRange(View* view, const Cursor &position) { Range range = KDevelop::CodeCompletionModel::completionRange(view, position); if (range.start().column() > 0) { KTextEditor::Range preRange(Cursor(range.start().line(), range.start().column() - 1), Cursor(range.start().line(), range.start().column())); qCDebug(COMPLETION) << preRange << view->document()->text(preRange); const QString contents = view->document()->text(preRange); - if ( contents == "$" ) { + if ( contents == QLatin1String("$") ) { range.expandToRange(preRange); qCDebug(COMPLETION) << "using custom completion range" << range; } } return range; } bool CodeCompletionModel::shouldAbortCompletion(View* view, const Range &range, const QString ¤tCompletion) { if(view->cursorPosition() < range.start() || view->cursorPosition() > range.end()) return true; //Always abort when the completion-range has been left //Do not abort completions when the text has been empty already before and a newline has been entered static const QRegExp allowedText("^\\$?(\\w*)"); return !allowedText.exactMatch(currentCompletion); } } diff --git a/completion/tests/bench_completion.cpp b/completion/tests/bench_completion.cpp index e978b6b..34ff0c2 100644 --- a/completion/tests/bench_completion.cpp +++ b/completion/tests/bench_completion.cpp @@ -1,148 +1,148 @@ /* This file is part of KDevelop Copyright 2010 Milian Wolff This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "bench_completion.h" #include "helper.h" #include "../context.h" #include "../completiondebug.h" #include #ifdef USE_VALGRIND #include #endif #include #include #include using namespace Php; using namespace KDevelop; QTEST_MAIN(Php::BenchmarkCodeCompletion) namespace Php { QFile* getFile(const QString& path) { QFile* file = new QFile(QFINDTESTDATA(path)); qDebug() << file->fileName(); Q_ASSERT(file->exists()); file->open(QIODevice::ReadOnly); Q_ASSERT(!file->error()); Q_ASSERT(file->isReadable()); return file; } typedef CodeCompletionItemTester PhpCompletionTester; void BenchmarkCodeCompletion::initTestCase() { AutoTestShell::init(); TestCore* core = new TestCore(); core->initialize(KDevelop::Core::NoUi); DUChain::self()->disablePersistentStorage(); // make sure we have a valid duchain for the global file DUChainReadLocker lock(DUChain::lock()); if ( !DUChain::self()->chainForDocument(internalFunctionFile()) ) { qDebug() << "no internal function file found in DUChain, loading it manually"; QString fileName = internalFunctionFile().str(); QScopedPointer file(new QFile(fileName)); if ( !file->open(QIODevice::ReadOnly) ) { qDebug() << "Could not open file" << fileName; return; } lock.unlock(); parseAdditionalFile(internalFunctionFile(), file->readAll()); } } void BenchmarkCodeCompletion::globalCompletion() { qDebug() << "benching global completion"; TopDUContext* top = parse("readAll() ); delete file; TopDUContext* top = parse(contents.toUtf8(), DumpNone); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); #ifdef USE_VALGRIND CALLGRIND_TOGGLE_COLLECT #endif QBENCHMARK { PhpCompletionTester tester(top, contents); } #ifdef USE_VALGRIND CALLGRIND_TOGGLE_COLLECT #endif } void BenchmarkCodeCompletion::completionData() { qDebug() << "benching global completion"; TopDUContext* top = parse(" 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 "test_completion.h" #include #include #include #include #include #include #include #include #include #include #include "../../duchain/types/structuretype.h" #include "../../duchain/declarations/functiondeclaration.h" #include "context.h" #include "item.h" #include "helpers.h" #include "model.h" using namespace KTextEditor; using namespace KDevelop; QTEST_MAIN(Php::TestCompletion) namespace Php { class TestCodeCompletionModel : public CodeCompletionModel { public: using CodeCompletionModel::foundDeclarations; //normally set by worker, but in test we don't have a worker void foundDeclarations(QList items, CodeCompletionContext* completionContext) { beginResetModel(); m_completionItems.clear(); foreach(const CompletionTreeItemPointer &i, items) { m_completionItems << QExplicitlySharedDataPointer(i); } m_completionContext = KDevelop::CodeCompletionContext::Ptr(completionContext); endResetModel(); } }; /** * declaration of class A with a number of completion items * * also introduces a instance of class A named $instA; */ const QByteArray testClassA( "class A {" // start non-static // public " public function pubf() {}" // at(0) " public $pub;" // at(1) // protected " protected function protf() {}" // at(2) " protected $prot;" // at(3) // private " private function privf() {}" // at(4) " private $priv;" // at(5) // start static // public " static public function spubf() {}" // at(6) " static public $spub;" // at(7) // const == static public " const c = 0;" // at(8) // protected " static protected function sprotf() {}" // at(9) " static protected $sprot;" // at(10) // private " static private function sprivf() {}" // at(11) " static private $spriv;" // at(12) "} $instA = new A; " ); /** * declaration of class B which extends class A * B has one new public member function * * also introduces a instance of class B named $instB; */ const QByteArray testClassB( "class B extends A {" "public function __construct(){}" // at(0) "} $instB = new B; " ); class TestCodeCompletionContext : public CodeCompletionContext { public: TestCodeCompletionContext(KDevelop::DUContextPointer context, const QString& text, const QString& followingText, const CursorInRevision &position, int depth = 0) : CodeCompletionContext(context, text, followingText, position, depth) { } protected: QList > completionFiles() { QList > ret; QSet set; set << IndexedString("file:///internal/projecttest0"); set << IndexedString("file:///internal/projecttest1"); ret << set; return ret; } }; typedef CodeCompletionItemTester BasePhpCompletionTester; /** * Automatically prepent the test string with " items) { qDebug() << items.count() << "completion items:"; foreach(const CompletionTreeItemPointer &item, items) { qDebug() << item->declaration()->toString(); } } void TestCompletion::publicObjectCompletion() { TopDUContext* top = parse(""); + PhpCompletionTester tester(top, QStringLiteral("$blah; $instA->")); QCOMPARE(tester.completionContext->memberAccessOperation(), CodeCompletionContext::MemberAccess); QCOMPARE(tester.names, QStringList() << "pubf" << "pub"); } void TestCompletion::publicStaticObjectCompletion() { TopDUContext* top = parse("memberAccessOperation(), CodeCompletionContext::StaticMemberAccess); QCOMPARE(tester.names, QStringList() << "spubf" << "$spub" << "c"); } void TestCompletion::privateObjectCompletion() { TopDUContext* top = parse("childContexts().first()->localDeclarations().first()->internalContext(); - PhpCompletionTester tester(funContext, "$this->"); + PhpCompletionTester tester(funContext, QStringLiteral("$this->")); QCOMPARE(tester.completionContext->memberAccessOperation(), CodeCompletionContext::MemberAccess); QCOMPARE(tester.names, QStringList() << "pubf" << "pub" << "protf" << "prot" << "privf" << "priv"); } void TestCompletion::privateStaticObjectCompletion() { TopDUContext* top = parse("childContexts().first()->localDeclarations().first()->internalContext(); { - PhpCompletionTester tester(funContext, "self::"); + PhpCompletionTester tester(funContext, QStringLiteral("self::")); QCOMPARE(tester.completionContext->memberAccessOperation(), CodeCompletionContext::StaticMemberAccess); QCOMPARE(tester.names, QStringList() << "spubf" << "$spub" << "c" << "sprotf" << "$sprot" << "sprivf" << "$spriv"); } { - PhpCompletionTester tester(funContext, "static::"); + PhpCompletionTester tester(funContext, QStringLiteral("static::")); QCOMPARE(tester.completionContext->memberAccessOperation(), CodeCompletionContext::StaticMemberAccess); QCOMPARE(tester.names, QStringList() << "spubf" << "$spub" << "c" << "sprotf" << "$sprot" << "sprivf" << "$spriv"); } } void TestCompletion::protectedObjectCompletion() { TopDUContext* top = parse("childContexts().at(1)->localDeclarations().first()->internalContext(); { - PhpCompletionTester tester(funContext, "$this->"); + PhpCompletionTester tester(funContext, QStringLiteral("$this->")); QCOMPARE(tester.completionContext->memberAccessOperation(), CodeCompletionContext::MemberAccess); QCOMPARE(tester.names, QStringList() << "__construct" << "pubf" << "pub" << "protf" << "prot"); } { - PhpCompletionTester tester(funContext, ""); + PhpCompletionTester tester(funContext, {}); QCOMPARE(tester.completionContext->memberAccessOperation(), CodeCompletionContext::NoMemberAccess); qDebug() << tester.names; QVERIFY(tester.names.contains("$this->__construct")); QVERIFY(tester.names.contains("$this->pubf")); QVERIFY(tester.names.contains("$this->pub")); QVERIFY(tester.names.contains("self::spubf")); QVERIFY(tester.names.contains("self::$spub")); QVERIFY(tester.names.contains("$this->protf")); QVERIFY(tester.names.contains("$this->prot")); QVERIFY(tester.names.contains("self::sprotf")); QVERIFY(tester.names.contains("self::$sprot")); QVERIFY(tester.names.contains("self::c")); QVERIFY(!tester.names.contains("self::sprivf")); QVERIFY(!tester.names.contains("self::$spriv")); QVERIFY(!tester.names.contains("$this->privf")); QVERIFY(!tester.names.contains("$this->$priv")); } } void TestCompletion::protectedStaticObjectCompletion() { TopDUContext* top = parse("childContexts().at(1)->localDeclarations().first()->internalContext(); - PhpCompletionTester tester(funContext, "self::"); + PhpCompletionTester tester(funContext, QStringLiteral("self::")); QCOMPARE(tester.completionContext->memberAccessOperation(), CodeCompletionContext::StaticMemberAccess); QCOMPARE(tester.names, QStringList() << "spubf" << "$spub" << "c" << "sprotf" << "$sprot"); } void TestCompletion::methodCall() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("foo("); + PhpCompletionTester tester(top, QStringLiteral("$blah; $i->foo(")); QCOMPARE(tester.completionContext->memberAccessOperation(), CodeCompletionContext::NoMemberAccess); QVERIFY(tester.completionContext->parentContext()); QCOMPARE(tester.completionContext->parentContext()->memberAccessOperation(), CodeCompletionContext::FunctionCallAccess); CompletionTreeItemPointer item = searchDeclaration(tester.items, top->childContexts().at(0)->localDeclarations().at(0)); QVERIFY(item); NormalDeclarationCompletionItem* item2 = dynamic_cast(item.data()); QString ret; createArgumentList(*item2, ret, 0); QCOMPARE(ret, QString("(A $a, null $b = null)")); } { - PhpCompletionTester tester(top, "blah; $i->foo(new A(), "); + PhpCompletionTester tester(top, QStringLiteral("blah; $i->foo(new A(), ")); QVERIFY(searchDeclaration(tester.items, top->childContexts().at(0)->localDeclarations().at(0))); } } void TestCompletion::functionCall() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("parentContext()); QVERIFY(tester.completionContext->parentContext()); QVERIFY(!tester.completionContext->parentContext()->parentContext()); QCOMPARE(tester.completionContext->parentContext()->memberAccessOperation(), CodeCompletionContext::FunctionCallAccess); QVERIFY(searchDeclaration(tester.items, top->localDeclarations().at(1))); } void TestCompletion::nestedFunctionCall_data() { QTest::addColumn("text"); - QTest::newRow("nested") << QString("bar(foo("); - QTest::newRow("nested prev arg") << QString("bar(1, foo("); - QTest::newRow("nested prev func call") << QString("bar(foo(1), foo("); - QTest::newRow("nested prev arg comma") << QString("bar(1, bar(1, "); - QTest::newRow("nested prev func comma") << QString("bar(1, bar(foo(1), "); + QTest::newRow("nested") << QStringLiteral("bar(foo("); + QTest::newRow("nested prev arg") << QStringLiteral("bar(1, foo("); + QTest::newRow("nested prev func call") << QStringLiteral("bar(foo(1), foo("); + QTest::newRow("nested prev arg comma") << QStringLiteral("bar(1, bar(1, "); + QTest::newRow("nested prev func comma") << QStringLiteral("bar(1, bar(foo(1), "); } void TestCompletion::nestedFunctionCall() { QFETCH(QString, text); // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("parentContext()); QVERIFY(tester.completionContext->parentContext()->parentContext()); QVERIFY(!tester.completionContext->parentContext()->parentContext()->parentContext()); QCOMPARE(tester.completionContext->parentContext()->memberAccessOperation(), CodeCompletionContext::FunctionCallAccess); QCOMPARE(tester.completionContext->parentContext()->parentContext()->memberAccessOperation(), CodeCompletionContext::FunctionCallAccess); QVERIFY(searchDeclaration(tester.items, top->localDeclarations().at(0))); QVERIFY(searchDeclaration(tester.items, top->localDeclarations().at(1))); } void TestCompletion::newObjectFromOtherFile() { TopDUContext* addTop = parseAdditionalFile(IndexedString("/duchaintest/foo.php"), ""); + PhpCompletionTester tester(top, QStringLiteral("blah; $a->")); QCOMPARE(tester.items.count(), 1); QCOMPARE(tester.items.first()->declaration().data(), addTop->childContexts().first()->localDeclarations().first()); } void TestCompletion::constantFromOtherFile() { TopDUContext* addTop = parseAdditionalFile( IndexedString("file:///internal/projecttest0"), "localDeclarations().size(), 2); Declaration* findMe = addTop->localDeclarations().first(); Declaration* dontFindMe = addTop->localDeclarations().last(); - PhpCompletionTester tester(top, ""); + PhpCompletionTester tester(top, {}); QVERIFY(searchDeclaration(tester.items, findMe)); QVERIFY(!searchDeclaration(tester.items, dontFindMe)); } void TestCompletion::baseClass() { QByteArray method(""); + PhpCompletionTester tester(top, QStringLiteral("$a->")); QCOMPARE(tester.names, QStringList() << "avar"); } { - PhpCompletionTester tester(top, "$b->"); + PhpCompletionTester tester(top, QStringLiteral("$b->")); QCOMPARE(tester.names, QStringList() << "bvar" << "avar"); } } void TestCompletion::extendsFromOtherFile() { TopDUContext* addTop = parseAdditionalFile(IndexedString("/duchaintest/foo.php"), ""); + PhpCompletionTester tester(top, QStringLiteral("$b->")); QCOMPARE(tester.items.count(), 2); QCOMPARE(tester.items.at(1)->declaration().data(), addTop->childContexts().first()->localDeclarations().first()); QCOMPARE(tester.items.at(0)->declaration().data(), top->childContexts().first()->localDeclarations().first()); } void TestCompletion::globalClassFromOtherFile() { TopDUContext* addTop = parseAdditionalFile(IndexedString("/duchaintest/foo.php"), "localDeclarations().first())); */ } void TestCompletion::codeModel() { DUChainWriteLocker lock(DUChain::lock()); uint count; const CodeModelItem* items; - CodeModel::self().addItem(IndexedString("file:///foo"), QualifiedIdentifier("identifier"), CodeModelItem::Class); + CodeModel::self().addItem(IndexedString("file:///foo"), QualifiedIdentifier(QStringLiteral("identifier")), CodeModelItem::Class); CodeModel::self().items(IndexedString("file:///foo"), count, items); bool found = false; for (uint i = 0;i < count;++i) { - if (items[0].id.identifier() == QualifiedIdentifier("identifier")) { + if (items[0].id.identifier() == QualifiedIdentifier(QStringLiteral("identifier"))) { found = true; QCOMPARE(items[i].kind, CodeModelItem::Class); } } QVERIFY(found); } void TestCompletion::projectFileClass() { TopDUContext* addTop = parseAdditionalFile(IndexedString("file:///internal/projecttest0"), "localDeclarations().first())); } { // inside of class foo, i.e. in its bar() method - PhpCompletionTester tester(top->childContexts().first()->childContexts().first(), "childContexts().first()->childContexts().first(), QStringLiteral("localDeclarations().first())); // but not its methods QVERIFY(!searchDeclaration(tester.items, addTop->childContexts().first()->localDeclarations().first())); } } void TestCompletion::variable() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("memberAccessOperation(), CodeCompletionContext::NoMemberAccess); QVERIFY(searchDeclaration(tester.items, top->localDeclarations().at(1))); } void TestCompletion::nameNormalVariable() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("memberAccessOperation(), CodeCompletionContext::NoMemberAccess); foreach(const QString &id, QStringList() << "ghi" << "def" << "$abc" << "$arr") { QVERIFY(tester.names.contains(id, Qt::CaseSensitive)); } } void TestCompletion::nameClassMember() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method(""); + PhpCompletionTester tester(top, QStringLiteral("$b->")); TestCodeCompletionModel *model = new TestCodeCompletionModel; model->foundDeclarations(tester.items, tester.completionContext.data()); QCOMPARE(tester.completionContext->memberAccessOperation(), CodeCompletionContext::MemberAccess); CompletionTreeItemPointer itm = searchDeclaration(tester.items, top->childContexts().first()->localDeclarations().first()); QVERIFY(itm); QCOMPARE(itm->data(model->index(0, Php::CodeCompletionModel::Name), Qt::DisplayRole, model).toString(), QString("abc")); //don't delete model as its constructor does bad things (quit the current thread - we don't want that in test) //TODO find better solution that doesn't leak } void TestCompletion::exceptions() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("memberAccessOperation(), CodeCompletionContext::ExceptionInstanceChoose); QCOMPARE(tester.items.count(), 1); QVERIFY(searchDeclaration(tester.items, top->localDeclarations().at(1))); } { - PhpCompletionTester tester(top, "throw new "); + PhpCompletionTester tester(top, QStringLiteral("throw new ")); QCOMPARE(tester.completionContext->memberAccessOperation(), CodeCompletionContext::ExceptionChoose); QCOMPARE(tester.items.count(), 2); QVERIFY(searchDeclaration(tester.items, top->localDeclarations().at(0))); } { - PhpCompletionTester tester(top, "try { } catch("); + PhpCompletionTester tester(top, QStringLiteral("try { } catch(")); QCOMPARE(tester.completionContext->memberAccessOperation(), CodeCompletionContext::ExceptionChoose); QCOMPARE(tester.items.count(), 2); QVERIFY(searchDeclaration(tester.items, top->localDeclarations().at(0))); } } void TestCompletion::exceptionOtherFile() { TopDUContext* addTop = parseAdditionalFile(IndexedString("file:///internal/projecttest0"), "memberAccessOperation(), CodeCompletionContext::ExceptionChoose); QCOMPARE(tester.items.count(), 2); QVERIFY(searchDeclaration(tester.items, addTop->localDeclarations().at(0))); } } void TestCompletion::abstractMethods() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("childContexts().first()->localDeclarations().last()->internalContext(); - PhpCompletionTester tester(funContext, "$this->"); + PhpCompletionTester tester(funContext, QStringLiteral("$this->")); QCOMPARE(tester.names, QStringList() << "foo" << "bar"); } void TestCompletion::interfaceMethods() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("childContexts().last()->localDeclarations().first()->internalContext(); - PhpCompletionTester tester(funContext, "$this->"); + PhpCompletionTester tester(funContext, QStringLiteral("$this->")); QCOMPARE(tester.names, QStringList() << "bar" << "foo"); } void TestCompletion::interfaceMethods2() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method(""); + PhpCompletionTester tester(funContext, QStringLiteral("$a->")); QCOMPARE(tester.names, QStringList() << "foo"); } void TestCompletion::implementMethods() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("childContexts().last(); { - PhpCompletionTester tester(classContext, "{"); + PhpCompletionTester tester(classContext, QStringLiteral("{")); QStringList compItems; - compItems << "foo"; - compItems << "const"; - compItems << "final"; - compItems << "function"; - compItems << "public"; - compItems << "private"; - compItems << "protected"; - compItems << "static"; - compItems << "var"; + compItems << QStringLiteral("foo"); + compItems << QStringLiteral("const"); + compItems << QStringLiteral("final"); + compItems << QStringLiteral("function"); + compItems << QStringLiteral("public"); + compItems << QStringLiteral("private"); + compItems << QStringLiteral("protected"); + compItems << QStringLiteral("static"); + compItems << QStringLiteral("var"); compItems.sort(); tester.names.sort(); QCOMPARE(tester.names, compItems); } //TODO: verify actual completion text } void TestCompletion::overrideMethods() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("childContexts().last(); { - PhpCompletionTester tester(classContext, "{"); + PhpCompletionTester tester(classContext, QStringLiteral("{")); QStringList compItems; - compItems << "a"; - compItems << "const"; - compItems << "final"; - compItems << "function"; - compItems << "public"; - compItems << "private"; - compItems << "protected"; - compItems << "static"; - compItems << "var"; + compItems << QStringLiteral("a"); + compItems << QStringLiteral("const"); + compItems << QStringLiteral("final"); + compItems << QStringLiteral("function"); + compItems << QStringLiteral("public"); + compItems << QStringLiteral("private"); + compItems << QStringLiteral("protected"); + compItems << QStringLiteral("static"); + compItems << QStringLiteral("var"); compItems.sort(); tester.names.sort(); QCOMPARE(tester.names, compItems); } { - PhpCompletionTester tester(classContext, "public static"); + PhpCompletionTester tester(classContext, QStringLiteral("public static")); QStringList compItems; - compItems << "final"; - compItems << "function"; + compItems << QStringLiteral("final"); + compItems << QStringLiteral("function"); compItems.sort(); tester.names.sort(); QCOMPARE(tester.names, compItems); } { - PhpCompletionTester tester(classContext, "private function"); + PhpCompletionTester tester(classContext, QStringLiteral("private function")); QVERIFY(tester.items.isEmpty()); } { - PhpCompletionTester tester(classContext, "final public "); + PhpCompletionTester tester(classContext, QStringLiteral("final public ")); QStringList compItems; - compItems << "a"; - compItems << "function"; - compItems << "static"; + compItems << QStringLiteral("a"); + compItems << QStringLiteral("function"); + compItems << QStringLiteral("static"); compItems.sort(); tester.names.sort(); QCOMPARE(tester.names, compItems); } //TODO: verify actual completion text } void TestCompletion::overrideVars() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("childContexts().last(); { - PhpCompletionTester tester(classContext, "{"); + PhpCompletionTester tester(classContext, QStringLiteral("{")); QStringList compItems; - compItems << "x"; - compItems << "const"; - compItems << "final"; - compItems << "function"; - compItems << "public"; - compItems << "private"; - compItems << "protected"; - compItems << "static"; - compItems << "var"; + compItems << QStringLiteral("x"); + compItems << QStringLiteral("const"); + compItems << QStringLiteral("final"); + compItems << QStringLiteral("function"); + compItems << QStringLiteral("public"); + compItems << QStringLiteral("private"); + compItems << QStringLiteral("protected"); + compItems << QStringLiteral("static"); + compItems << QStringLiteral("var"); compItems.sort(); tester.names.sort(); QCOMPARE(tester.names, compItems); } } void TestCompletion::inArray() { TopDUContext* top = parse("", DumpNone); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); - PhpCompletionTester tester(top, " 0); // TODO: compare to global completion list } void TestCompletion::verifyExtendsOrImplements(const QString &codeStr, const QString &completionStr, ClassDeclarationData::ClassType type, const CursorInRevision& cursor, QStringList forbiddenIdentifiers) { if (cursor.isValid()) { qDebug() << codeStr.mid(0, cursor.column) + completionStr + '|' + codeStr.mid(cursor.column); } else { qDebug() << codeStr + completionStr + '|'; } TopDUContext *top = parse(codeStr.toUtf8(), DumpNone); DUChainReleaser releaseTop(top); DUContext *ctx; if (cursor.isValid()) { DUChainWriteLocker lock(DUChain::lock()); ctx = top->findContextAt(cursor); QVERIFY(ctx); QVERIFY(ctx->owner()); QVERIFY(dynamic_cast(ctx->owner())); } else { ctx = top; } PhpCompletionTester tester(ctx, completionStr); QVERIFY(!tester.items.isEmpty()); // make sure the items are unique QCOMPARE(tester.names.size(), tester.names.toSet().size()); foreach(const CompletionTreeItemPointer &item, tester.items) { ClassDeclaration* klass = dynamic_cast(item->declaration().data()); QVERIFY(klass); QVERIFY(klass->classModifier() != ClassDeclarationData::Final); QCOMPARE(klass->classType(), type); if (!forbiddenIdentifiers.isEmpty()) { QVERIFY(! forbiddenIdentifiers.contains(item->declaration()->identifier().toString())); } } } void TestCompletion::newExtends() { - verifyExtendsOrImplements(""); + PhpCompletionTester tester(top, QStringLiteral("$f->")); QCOMPARE(tester.completionContext->memberAccessOperation(), CodeCompletionContext::MemberAccess); qDebug() << tester.names; foreach(const QString &id, QStringList() << "vA" << "vB") { QVERIFY(tester.names.contains(id, Qt::CaseSensitive)); } } void TestCompletion::completionAfterComments() { TopDUContext* top = parse("isValid()); QVERIFY(tester.items.count() > 0); } // TODO: compare to global completion list } void TestCompletion::completionInComments() { TopDUContext* top = parse("isValid()); } } void TestCompletion::phpStartTag() { // some context with a function (or anything else for that matter) starting with "php" substring TopDUContext* top = parse("=", DumpDUChain); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); - PhpCompletionTester tester(top, "="); + PhpCompletionTester tester(top, QStringLiteral("=")); QVERIFY(tester.items.isEmpty()); } void TestCompletion::nonGlobalInFunction() { TopDUContext* top = parse("childContexts().first(), ""); + PhpCompletionTester tester(top->childContexts().first(), {}); - QList decs = top->findLocalDeclarations(Identifier("outside")); + QList decs = top->findLocalDeclarations(Identifier(QStringLiteral("outside"))); QCOMPARE(decs.count(), 1); QVERIFY(!searchDeclaration(tester.items, decs.first())); } void TestCompletion::fileCompletion() { TopDUContext* top = parse("memberAccessOperation(), CodeCompletionContext::FileChoose); } } void TestCompletion::instanceof() { TopDUContext* top = parse(" decs = top->findLocalDeclarations(Identifier(name)); QCOMPARE(decs.size(), 1); ClassDeclaration* cdec = dynamic_cast(decs.first()); QVERIFY(cdec); QVERIFY(searchDeclaration(tester.items, cdec)); } foreach ( const CompletionTreeItemPointer &item, tester.items ) { QVERIFY(dynamic_cast(item->declaration().data())); } } void TestCompletion::afterFunctionArg() { TopDUContext* top = parse("" << "while ($a->" << "foobar($a->" ) { qDebug() << code; PhpCompletionTester tester(top, code); QCOMPARE(tester.names.size(), 1); QCOMPARE(tester.names.first(), QString("b")); } } void TestCompletion::functionBeforeDeclaration() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("localDeclarations().size(), 1); - PhpCompletionTester tester(top, "", "", CursorInRevision(0, 3)); + PhpCompletionTester tester(top, {}, {}, CursorInRevision(0, 3)); // function _should_ be found QVERIFY(searchDeclaration(tester.items, top->localDeclarations().first())); } void TestCompletion::classBeforeDeclaration() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("localDeclarations().size(), 1); - PhpCompletionTester tester(top, "", "", CursorInRevision(0, 3)); + PhpCompletionTester tester(top, {}, {}, CursorInRevision(0, 3)); // class _should_ be found QVERIFY(searchDeclaration(tester.items, top->localDeclarations().first())); } void TestCompletion::constantBeforeDeclaration() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("localDeclarations().size(), 1); - PhpCompletionTester tester(top, "", "", CursorInRevision(0, 3)); + PhpCompletionTester tester(top, {}, {}, CursorInRevision(0, 3)); // constant should _not_ be found QVERIFY(!searchDeclaration(tester.items, top->localDeclarations().first())); } void TestCompletion::variableBeforeDeclaration() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("localDeclarations().size(), 1); - PhpCompletionTester tester(top, "", "", CursorInRevision(0, 3)); + PhpCompletionTester tester(top, {}, {}, CursorInRevision(0, 3)); // variable should _not_ be found QVERIFY(!searchDeclaration(tester.items, top->localDeclarations().first())); } void TestCompletion::functionArguments() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("localDeclarations().first(); QVERIFY(fDec); FunctionType::Ptr fType = fDec->type(); QVERIFY(fType); // params QVector< Declaration* > args = top->childContexts().first()->localDeclarations(); QCOMPARE(args.size(), 2); - PhpCompletionTester tester(top->childContexts().last(), ""); + PhpCompletionTester tester(top->childContexts().last(), {}); // should get two local and the func itself QVERIFY(searchDeclaration(tester.items, fDec)); foreach( Declaration* dec, args ) { qDebug() << dec->toString(); QVERIFY(searchDeclaration(tester.items, dec)); } } void TestCompletion::referencedClass() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse(" decs = top->findDeclarations(Identifier("a")); + QList decs = top->findDeclarations(Identifier(QStringLiteral("a"))); QCOMPARE(decs.size(), 1); ClassDeclaration* aDec = dynamic_cast(decs.first()); QVERIFY(aDec); - decs = top->findDeclarations(Identifier("foo")); + decs = top->findDeclarations(Identifier(QStringLiteral("foo"))); QCOMPARE(decs.size(), 1); FunctionDeclaration* funcDec = dynamic_cast(decs.first()); QVERIFY(funcDec); QVERIFY(funcDec->internalContext()); QVERIFY(funcDec->internalFunctionContext()); QVERIFY(funcDec->internalContext()->imports(funcDec->internalFunctionContext())); - PhpCompletionTester tester(funcDec->internalContext(), "$arg->"); + PhpCompletionTester tester(funcDec->internalContext(), QStringLiteral("$arg->")); QVERIFY(tester.completionContext->memberAccessOperation() == CodeCompletionContext::MemberAccess); QCOMPARE(tester.names, QStringList() << "pubf" << "pub"); } void TestCompletion::ctorCall() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("childContexts().first()->localDeclarations().first(); Declaration* bCtor = top->childContexts().last()->localDeclarations().first(); { - PhpCompletionTester tester(top, "new A("); + PhpCompletionTester tester(top, QStringLiteral("new A(")); QCOMPARE(tester.completionContext->memberAccessOperation(), CodeCompletionContext::NoMemberAccess); QVERIFY(tester.completionContext->parentContext()); QCOMPARE(tester.completionContext->parentContext()->memberAccessOperation(), CodeCompletionContext::FunctionCallAccess); CompletionTreeItemPointer item = searchDeclaration(tester.items, aCtor); QVERIFY(item); NormalDeclarationCompletionItem* item2 = dynamic_cast(item.data()); QString ret; createArgumentList(*item2, ret, 0); QCOMPARE(ret, QString("(string $bar)")); } { - PhpCompletionTester tester(top, "new B("); + PhpCompletionTester tester(top, QStringLiteral("new B(")); QCOMPARE(tester.completionContext->memberAccessOperation(), CodeCompletionContext::NoMemberAccess); QVERIFY(tester.completionContext->parentContext()); QCOMPARE(tester.completionContext->parentContext()->memberAccessOperation(), CodeCompletionContext::FunctionCallAccess); CompletionTreeItemPointer item = searchDeclaration(tester.items, bCtor); QVERIFY(item); NormalDeclarationCompletionItem* item2 = dynamic_cast(item.data()); QString ret; createArgumentList(*item2, ret, 0); QCOMPARE(ret, QString("(bool $asdf)")); } } void TestCompletion::chainedCalling() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("b()->"); + PhpCompletionTester tester(top, QStringLiteral("$a->b()->")); QVERIFY(tester.completionContext->memberAccessOperation() == CodeCompletionContext::MemberAccess); QCOMPARE(tester.names, QStringList() << "b"); } void TestCompletion::funcCallInConditional() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("memberAccessOperation(), CodeCompletionContext::NoMemberAccess); QVERIFY(tester.completionContext->parentContext()); QCOMPARE(tester.completionContext->parentContext()->memberAccessOperation(), CodeCompletionContext::FunctionCallAccess); CompletionTreeItemPointer item = searchDeclaration(tester.items, top->localDeclarations().at(0)); QVERIFY(item); NormalDeclarationCompletionItem* item2 = dynamic_cast(item.data()); QString ret; createArgumentList(*item2, ret, 0); QCOMPARE(ret, QString("(mixed $a, int $b = 1)")); } } void TestCompletion::namespaces() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("memberAccessOperation(), CodeCompletionContext::NamespaceChoose); QVERIFY(!tester.completionContext->parentContext()); QCOMPARE(tester.names, QStringList() << "foo"); } } void TestCompletion::inNamespace() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("childContexts().at(0), ""); + PhpCompletionTester tester(top->childContexts().at(0), {}); QCOMPARE(tester.completionContext->memberAccessOperation(), CodeCompletionContext::NoMemberAccess); QVERIFY(!tester.completionContext->parentContext()); QVERIFY(searchDeclaration(tester.items, top->localDeclarations().first())); QVERIFY(searchDeclaration(tester.items, top->childContexts().first()->localDeclarations().first())); QVERIFY(searchDeclaration(tester.items, top->localDeclarations().last())); QVERIFY(!searchDeclaration(tester.items, top->childContexts().last()->localDeclarations().first())); } { - PhpCompletionTester tester(top->childContexts().at(0), "\\"); + PhpCompletionTester tester(top->childContexts().at(0), QStringLiteral("\\")); QCOMPARE(tester.completionContext->memberAccessOperation(), CodeCompletionContext::BackslashAccess); QVERIFY(!tester.completionContext->parentContext()); QCOMPARE(tester.items.count(), 2); QVERIFY(searchDeclaration(tester.items, top->localDeclarations().first())); QVERIFY(!searchDeclaration(tester.items, top->childContexts().first()->localDeclarations().first())); QVERIFY(searchDeclaration(tester.items, top->localDeclarations().last())); QVERIFY(!searchDeclaration(tester.items, top->childContexts().last()->localDeclarations().first())); } { - PhpCompletionTester tester(top->childContexts().at(0), "\\foo\\"); + PhpCompletionTester tester(top->childContexts().at(0), QStringLiteral("\\foo\\")); QCOMPARE(tester.completionContext->memberAccessOperation(), CodeCompletionContext::BackslashAccess); QVERIFY(!tester.completionContext->parentContext()); QCOMPARE(tester.items.count(), 1); QVERIFY(!searchDeclaration(tester.items, top->localDeclarations().first())); QVERIFY(searchDeclaration(tester.items, top->childContexts().first()->localDeclarations().first())); QVERIFY(!searchDeclaration(tester.items, top->localDeclarations().last())); QVERIFY(!searchDeclaration(tester.items, top->childContexts().last()->localDeclarations().first())); } { - PhpCompletionTester tester(top->childContexts().at(0), "\\yxc\\"); + PhpCompletionTester tester(top->childContexts().at(0), QStringLiteral("\\yxc\\")); QCOMPARE(tester.completionContext->memberAccessOperation(), CodeCompletionContext::BackslashAccess); QVERIFY(!tester.completionContext->parentContext()); QCOMPARE(tester.items.count(), 1); QVERIFY(!searchDeclaration(tester.items, top->localDeclarations().first())); QVERIFY(!searchDeclaration(tester.items, top->childContexts().first()->localDeclarations().first())); QVERIFY(!searchDeclaration(tester.items, top->localDeclarations().last())); QVERIFY(searchDeclaration(tester.items, top->childContexts().last()->localDeclarations().first())); } } void TestCompletion::closures() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().first(); Declaration* c = top->localDeclarations().last(); { - PhpCompletionTester tester(top, ""); + PhpCompletionTester tester(top, {}); QVERIFY(tester.containsDeclaration(l)); QVERIFY(!tester.containsDeclaration(c)); } { - PhpCompletionTester tester(top, "$l("); + PhpCompletionTester tester(top, QStringLiteral("$l(")); QVERIFY(tester.containsDeclaration(l)); QVERIFY(!tester.containsDeclaration(c)); QVERIFY(tester.completionContext->parentContext()); QVERIFY(!tester.completionContext->parentContext()->parentContext()); QCOMPARE(tester.completionContext->parentContext()->memberAccessOperation(), CodeCompletionContext::FunctionCallAccess); } } } diff --git a/docs/kdevphpdocs.json b/docs/kdevphpdocs.json index 2225334..1d9653f 100644 --- a/docs/kdevphpdocs.json +++ b/docs/kdevphpdocs.json @@ -1,60 +1,62 @@ { "KPlugin": { "Authors": [ { "Name": "Milian Wolff", "Name[x-test]": "xxMilian Wolffxx" } ], "Category": "Documentation", "Description": "This plugin integrates PHP.net online documentation.", "Description[ast]": "Esti complementu integra la documentación en llinia de PHP.net", "Description[ca@valencia]": "Este connector integra la documentació en línia de PHP.net.", "Description[ca]": "Aquest connector integra la documentació en línia de PHP.net.", "Description[de]": "Dieses Modul integriert die Online-Dokumentation von PHP.net.", "Description[es]": "Este complemento integra la documentación en línea de PHP.net.", + "Description[et]": "See plugin lõimib PHP veebidokumentatsiooni.", "Description[fi]": "Tämä liitännäinen integroi PHP.net-dokumentaation.", "Description[gl]": "Esta extensión integra a documentación en liña de PHP.net", "Description[ko]": "PHP.net 온라인 문서를 통합합니다.", "Description[nl]": "Deze plugin integreert PHP.net online-documentatie.", "Description[pl]": "Wtyczka ta integruje dokumentację internetową PHP.net.", "Description[pt]": "Este 'plugin' integra-se com a documentação 'online' do PHP.net.", "Description[pt_BR]": "Este plugin integra a documentação online do PHP.net.", "Description[sk]": "Tento plugin integruje online dokumentáciu PHP.net.", "Description[sl]": "Ta vstavek vključuje spletno dokumentacijo za PHP.", "Description[sv]": "Insticksprogrammet integrerar Internet-dokumentationen från PHP.net.", "Description[uk]": "За допомогою цього додатка можна інтегрувати документацію PHP.net.", "Description[x-test]": "xxThis plugin integrates PHP.net online documentation.xx", "Id": "kdevphpdocs", "License": "GPL", "Name": "PHP Documentation", "Name[ast]": "Documentación de PHP", "Name[ca@valencia]": "Documentació de PHP", "Name[ca]": "Documentació de PHP", "Name[de]": "PHP-Dokumentation", "Name[es]": "Documentación de PHP", + "Name[et]": "PHP dokumentatsioon", "Name[fi]": "PHP-dokumentaatio", "Name[gl]": "Documentación de PHP", "Name[ko]": "PHP 문서", "Name[nl]": "PHP-documentatie", "Name[pl]": "Dokumentacja PHP", "Name[pt]": "Documentação de PHP", "Name[pt_BR]": "Documentação de PHP", "Name[sk]": "Dokumentácia PHP", "Name[sl]": "Dokumentacija za PHP", "Name[sv]": "PHP-dokumentation", "Name[uk]": "Документація з PHP", "Name[x-test]": "xxPHP Documentationxx", "ServiceTypes": [ "KDevelop/Plugin" ] }, "X-KDevelop-Category": "Global", "X-KDevelop-IRequired": [ "org.kdevelop.PHPSupport" ], "X-KDevelop-Interfaces": [ "org.kdevelop.IDocumentationProvider" ], "X-KDevelop-Mode": "GUI" } \ No newline at end of file diff --git a/docs/phpdocsconfig.cpp b/docs/phpdocsconfig.cpp index 9e574b3..0f8b8a2 100644 --- a/docs/phpdocsconfig.cpp +++ b/docs/phpdocsconfig.cpp @@ -1,71 +1,71 @@ /* This file is part of the KDevelop PHP Documentation Plugin Copyright 2012 Milian Wolff This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "phpdocsconfig.h" #include #include #include #include #include #include #include #include #include "phpdocssettings.h" #include "ui_phpdocsconfig.h" K_PLUGIN_FACTORY_WITH_JSON(PhpDocsConfigFactory, "kcm_kdevphpdocs.json", registerPlugin();) PhpDocsConfig::PhpDocsConfig(QWidget *parent, const QVariantList &args) - : KCModule(KAboutData::pluginData("kcm_kdevphpdocs"), parent, args) + : KCModule(KAboutData::pluginData(QStringLiteral("kcm_kdevphpdocs")), parent, args) { QVBoxLayout * l = new QVBoxLayout( this ); QWidget* w = new QWidget; m_configWidget = new Ui::PhpDocsConfigUI; m_configWidget->setupUi( w ); m_configWidget->kcfg_phpDocLocation->setMode( KFile::Directory | KFile::ExistingOnly ); l->addWidget( w ); addConfig( PhpDocsSettings::self(), w ); load(); } void PhpDocsConfig::save() { KCModule::save(); // looks like we have to force a write so readConfig() can get the new values PhpDocsSettings::self()->save(); KSettings::Dispatcher::reparseConfiguration( componentData().componentName() ); } PhpDocsConfig::~PhpDocsConfig() { delete m_configWidget; } #include "phpdocsconfig.moc" diff --git a/docs/phpdocsmodel.cpp b/docs/phpdocsmodel.cpp index e0ceda2..99d43a2 100644 --- a/docs/phpdocsmodel.cpp +++ b/docs/phpdocsmodel.cpp @@ -1,148 +1,148 @@ /* This file is part of the KDevelop PHP Documentation Plugin Copyright 2012 Milian Wolff This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "phpdocsmodel.h" #include "phpdocsdebug.h" #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; PhpDocsModel::PhpDocsModel(QObject* parent) - : QAbstractListModel(parent), m_internalFunctionsFile(QStandardPaths::locate(QStandardPaths::GenericDataLocation, "kdevphpsupport/phpfunctions.php")) + : QAbstractListModel(parent), m_internalFunctionsFile(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kdevphpsupport/phpfunctions.php"))) { // make sure the php plugin is loaded - auto phpLangPlugin = ICore::self()->languageController()->language("Php"); + auto phpLangPlugin = ICore::self()->languageController()->language(QStringLiteral("Php")); if ( !phpLangPlugin ) { qCWarning(DOCS) << "could not load PHP language support plugin"; return; } DUChain::self()->updateContextForUrl(internalFunctionFile(), TopDUContext::AllDeclarationsAndContexts, this, -10); } PhpDocsModel::~PhpDocsModel() { } IndexedString PhpDocsModel::internalFunctionFile() const { return m_internalFunctionsFile; } void PhpDocsModel::updateReady(const IndexedString& file, const ReferencedTopDUContext& top) { if ( file == m_internalFunctionsFile && top ) { fillModel(top); } } void PhpDocsModel::fillModel(const ReferencedTopDUContext& top) { DUChainReadLocker lock; if (!top) { return; } qCDebug(DOCS) << "filling model"; typedef QPair DeclDepthPair; - foreach ( const DeclDepthPair& declpair, top->allDeclarations(top->range().end, top) ) { + foreach ( DeclDepthPair declpair, top->allDeclarations(top->range().end, top) ) { if ( declpair.first->abstractType() && declpair.first->abstractType()->modifiers() & AbstractType::ConstModifier ) { // filter global constants, since they are hard to find in the documentation continue; } m_declarations << DeclarationPointer(declpair.first); if ( StructureType::Ptr type = declpair.first->type() ) { foreach ( Declaration* dec, type->internalContext(top)->localDeclarations() ) { m_declarations << DeclarationPointer(dec); } } } } bool PhpDocsModel::hasChildren(const QModelIndex& parent) const { // only the top-level has children return parent == QModelIndex(); } QVariant PhpDocsModel::data(const QModelIndex& index, int role) const { switch ( role ) { case Qt::DisplayRole: case Qt::EditRole: { DUChainReadLocker lock; DeclarationPointer dec = declarationForIndex(index); if (!dec.data()) { return i18n(""); } QString ret = dec->toString(); if ( dec->isFunctionDeclaration() ) { // remove function arguments - ret = ret.replace(QRegExp("\\(.+\\)"), "()"); + ret = ret.replace(QRegExp("\\(.+\\)"), QStringLiteral("()")); // remove return type ret = ret.remove(QRegExp("^[^ ]+ ")); } return ret; } case DeclarationRole: { DeclarationPointer dec = declarationForIndex(index); return QVariant::fromValue(dec); } /* case Qt::ToolTipRole: case Qt::AccessibleTextRole: case Qt::AccessibleDescriptionRole: */ default: return QVariant(); } } int PhpDocsModel::rowCount(const QModelIndex& parent) const { if (parent.isValid()) return 0; return m_declarations.count(); } DeclarationPointer PhpDocsModel::declarationForIndex(const QModelIndex& index) const { Q_ASSERT(m_declarations.size() > index.row()); return m_declarations[index.row()]; } diff --git a/docs/phpdocsplugin.cpp b/docs/phpdocsplugin.cpp index 39db689..d12141b 100644 --- a/docs/phpdocsplugin.cpp +++ b/docs/phpdocsplugin.cpp @@ -1,225 +1,225 @@ /* This file is part of the KDevelop PHP Documentation Plugin Copyright 2012 Milian Wolff This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "phpdocsplugin.h" #include "phpdocsmodel.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "phpdocsdebug.h" #include "phpdocumentation.h" #include "phpdocssettings.h" using namespace KDevelop; K_PLUGIN_FACTORY_WITH_JSON(PhpDocsFactory, "kdevphpdocs.json", registerPlugin();) PhpDocsPlugin::PhpDocsPlugin(QObject* parent, const QVariantList& args) - : IPlugin("kdevphpdocs", parent) + : IPlugin(QStringLiteral("kdevphpdocs"), parent) , m_model(new PhpDocsModel(this)) { KDEV_USE_EXTENSION_INTERFACE( KDevelop::IDocumentationProvider ) Q_UNUSED(args); readConfig(); - KSettings::Dispatcher::registerComponent( "kdevphpdocs_config", this, "readConfig" ); + KSettings::Dispatcher::registerComponent( QStringLiteral("kdevphpdocs_config"), this, "readConfig" ); } PhpDocsPlugin::~PhpDocsPlugin() { } QString PhpDocsPlugin::name() const { return QStringLiteral("PHP"); } QIcon PhpDocsPlugin::icon() const { - static QIcon icon = QIcon::fromTheme("application-x-php"); + static QIcon icon = QIcon::fromTheme(QStringLiteral("application-x-php")); return icon; } void PhpDocsPlugin::readConfig() { // since PhpDocsSettings pointer in this plugin is distinct from the one in the KCM // we have to trigger reading manually PhpDocsSettings::self()->load(); } ///TODO: possibly return multiple filenames (i.e. fallbacks) when doing local lookups QString PhpDocsPlugin::getDocumentationFilename( KDevelop::Declaration* dec, const bool& isLocal ) const { QString fname; //TODO: most of the SPL stuff is not found for me in the deb package php-doc // => check newer documentation or give a fallback to ref.spl.html if ( ClassFunctionDeclaration* fdec = dynamic_cast( dec ) ) { // class methods -> php.net/CLASS.METHOD // local: either CLASS.METHOD.html or function.CLASS-METHOD.html... really sucks :-/ // for now, use the latter... if ( dec->context() && dec->context()->type() == DUContext::Class && dec->context()->owner() ) { QString className = dec->context()->owner()->identifier().toString(); if ( !isLocal ) { fname = className + '.' + fdec->identifier().toString(); } else { if ( fdec->isConstructor() ) { - fname = "construct"; + fname = QStringLiteral("construct"); } else if ( fdec->isDestructor() ) { - fname = "destruct"; + fname = QStringLiteral("destruct"); } else { fname = fdec->identifier().toString(); } //TODO: CLASS.METHOD.html e.g. for xmlreader etc. pp. fname = "function." + className + '-' + fname; } } } else if ( dynamic_cast(dec) ) { fname = "class." + dec->identifier().toString(); } else if ( dynamic_cast(dec) ) { fname = "function." + dec->identifier().toString(); } // check for superglobals / reserved variables - else if ( dec->identifier() == Identifier("GLOBALS") || - dec->identifier() == Identifier("php_errormsg") || - dec->identifier() == Identifier("HTTP_RAW_POST_DATA") || - dec->identifier() == Identifier("http_response_header") || - dec->identifier() == Identifier("argc") || - dec->identifier() == Identifier("argv") || - dec->identifier() == Identifier("_GET") || - dec->identifier() == Identifier("_POST") || - dec->identifier() == Identifier("_FILES") || - dec->identifier() == Identifier("_REQUEST") || - dec->identifier() == Identifier("_SESSION") || - dec->identifier() == Identifier("_ENV") || - dec->identifier() == Identifier("_COOKIE") ) { + else if ( dec->identifier() == Identifier(QStringLiteral("GLOBALS")) || + dec->identifier() == Identifier(QStringLiteral("php_errormsg")) || + dec->identifier() == Identifier(QStringLiteral("HTTP_RAW_POST_DATA")) || + dec->identifier() == Identifier(QStringLiteral("http_response_header")) || + dec->identifier() == Identifier(QStringLiteral("argc")) || + dec->identifier() == Identifier(QStringLiteral("argv")) || + dec->identifier() == Identifier(QStringLiteral("_GET")) || + dec->identifier() == Identifier(QStringLiteral("_POST")) || + dec->identifier() == Identifier(QStringLiteral("_FILES")) || + dec->identifier() == Identifier(QStringLiteral("_REQUEST")) || + dec->identifier() == Identifier(QStringLiteral("_SESSION")) || + dec->identifier() == Identifier(QStringLiteral("_ENV")) || + dec->identifier() == Identifier(QStringLiteral("_COOKIE")) ) { if ( isLocal ) { - fname = QString("reserved.variables.") + dec->identifier().toString().remove('_'); + fname = QStringLiteral("reserved.variables.") + dec->identifier().toString().remove('_'); } else { fname = dec->identifier().toString(); } } qCDebug(DOCS) << fname; if ( !fname.isEmpty() && isLocal ) { fname = fname.toLower(); fname.replace('_', '-'); fname.append(".html"); } return fname; } IDocumentation::Ptr PhpDocsPlugin::documentationForDeclaration( Declaration* dec ) const { if ( dec ) { DUChainReadLocker lock( DUChain::lock() ); // filter non global or non-php declarations if ( dec->topContext()->url() != m_model->internalFunctionFile() ) { return {}; } QUrl url = PhpDocsSettings::phpDocLocation(); qCDebug(DOCS) << url; QString file = getDocumentationFilename( dec, url.isLocalFile() ); if ( file.isEmpty() ) { qCDebug(DOCS) << "no documentation pattern found for" << dec->toString(); return {}; } url.setPath( url.path() + '/' + file); if ( url.isLocalFile() && !QFile::exists( url.toLocalFile() ) ) { qCDebug(DOCS) << "bad path" << url << "for documentation of" << dec->toString() << " - aborting"; return {}; } qCDebug(DOCS) << "php documentation located at " << url << "for" << dec->toString(); return documentationForUrl(url, dec->qualifiedIdentifier().toString(), dec->comment()); } return {}; } QAbstractListModel* PhpDocsPlugin::indexModel() const { return m_model; } IDocumentation::Ptr PhpDocsPlugin::documentationForIndex(const QModelIndex& index) const { return documentationForDeclaration(static_cast( index.data(PhpDocsModel::DeclarationRole).value().data() )); } void PhpDocsPlugin::loadUrl(const QUrl& url) const { qCDebug(DOCS) << "loading URL" << url; auto doc = documentationForUrl(url, QString()); ICore::self()->documentationController()->showDocumentation(doc); } void PhpDocsPlugin::addToHistory(const QUrl& url) { auto doc = documentationForUrl(url, url.toString()); emit addHistory(doc); } IDocumentation::Ptr PhpDocsPlugin::documentationForUrl(const QUrl& url, const QString& name, const QByteArray& description) const { return IDocumentation::Ptr(new PhpDocumentation( url, name, description, const_cast(this))); } IDocumentation::Ptr PhpDocsPlugin::homePage() const { QUrl url = PhpDocsSettings::phpDocLocation(); if ( url.isLocalFile() ) { url.setPath(url.path() + "/index.html"); } else { url.setPath(url.path() + "/manual"); } return documentationForUrl(url, i18n("PHP Documentation")); } #include "phpdocsplugin.moc" diff --git a/docs/phpdocumentation.h b/docs/phpdocumentation.h index b7c584d..0834047 100644 --- a/docs/phpdocumentation.h +++ b/docs/phpdocumentation.h @@ -1,54 +1,55 @@ /* This file is part of the KDevelop PHP Documentation Plugin Copyright 2012 Milian Wolff This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef PHPDOCUMENTATION_H #define PHPDOCUMENTATION_H #include #include #include #include class QWidget; class QStackedWidget; class PhpDocsPlugin; class PhpDocumentation : public KDevelop::IDocumentation { - public: + Q_OBJECT +public: explicit PhpDocumentation(const QUrl& url, const QString& name, const QByteArray& description, PhpDocsPlugin* parent); ~PhpDocumentation(); virtual QString name() const override; virtual QString description() const override; virtual QWidget* documentationWidget(KDevelop::DocumentationFindWidget* findWidget, QWidget* parent = 0) override; virtual KDevelop::IDocumentationProvider* provider() const override; - private: +private: const QUrl m_url; const QString m_name; const QByteArray m_description; PhpDocsPlugin* m_parent; }; #endif // PHPDOCUMENTATION_H diff --git a/docs/phpdocumentationwidget.cpp b/docs/phpdocumentationwidget.cpp index dd644e6..3f9258c 100644 --- a/docs/phpdocumentationwidget.cpp +++ b/docs/phpdocumentationwidget.cpp @@ -1,99 +1,106 @@ /* This file is part of the KDevelop PHP Documentation Plugin Copyright 2012 Milian Wolff This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "phpdocumentationwidget.h" #include #include #include #include #include #include #include "phpdocsplugin.h" #include QTemporaryFile* createStyleSheet(QObject* parent) { QTemporaryFile* file = new QTemporaryFile(parent); bool ret = file->open(); Q_ASSERT(ret); Q_UNUSED(ret); QTextStream ts(file); ts << ".page-tools { float: none !important; };"; return file; } PhpDocumentationWidget::PhpDocumentationWidget(KDevelop::DocumentationFindWidget* find, const QUrl &url, PhpDocsPlugin* provider, QWidget* parent) : QStackedWidget(parent) , m_loading(new QWidget(this)) , m_styleSheet(createStyleSheet(this)) , m_provider(provider) { m_part = new KDevelop::StandardDocumentationView(find, this); m_part->page()->setLinkDelegationPolicy(QWebPage::DelegateAllLinks); addWidget(m_part); addWidget(m_loading); QProgressBar* progressbar = new QProgressBar; progressbar->setValue(0); progressbar->setMinimum(0); progressbar->setMaximum(100); progressbar->setAlignment(Qt::AlignCenter); - connect( m_part, SIGNAL(loadProgress(int)), - progressbar, SLOT(setValue(int)) ); + connect( m_part, &KDevelop::StandardDocumentationView::loadProgress, + progressbar, &QProgressBar::setValue ); QVBoxLayout* layout = new QVBoxLayout; layout->addStretch(); QLabel* label = new QLabel(i18n("...loading documentation...")); label->setAlignment(Qt::AlignCenter); layout->addWidget(label); layout->addWidget(progressbar); layout->addStretch(); m_loading->setLayout(layout); setCurrentWidget(m_loading); - connect(m_part, SIGNAL(linkClicked(QUrl)), this, SLOT(linkClicked(QUrl))); - connect(m_part, SIGNAL(loadFinished(bool)), this, SLOT(documentLoaded()) ); + connect(m_part, &KDevelop::StandardDocumentationView::linkClicked, this, &PhpDocumentationWidget::linkClicked); + connect(m_part, &KDevelop::StandardDocumentationView::loadFinished, this, &PhpDocumentationWidget::documentLoaded); m_part->load( url ); } +PhpDocumentationWidget::~PhpDocumentationWidget() +{ + // make sure we don't get called by any of the m_part signals on shutdown, see also: + // https://codereview.qt-project.org/#/c/83800/ + disconnect(m_part, 0, this, 0); +} + void PhpDocumentationWidget::linkClicked(const QUrl& url) { m_part->load(url); m_provider->addToHistory(url); } void PhpDocumentationWidget::documentLoaded() { m_part->settings()->setUserStyleSheetUrl(QUrl::fromLocalFile(m_styleSheet->fileName())); setCurrentWidget(m_part); removeWidget(m_loading); delete m_loading; m_loading = 0; } diff --git a/docs/phpdocumentationwidget.h b/docs/phpdocumentationwidget.h index 2322b3b..a7b83b6 100644 --- a/docs/phpdocumentationwidget.h +++ b/docs/phpdocumentationwidget.h @@ -1,57 +1,58 @@ /* This file is part of the KDevelop PHP Documentation Plugin Copyright 2012 Milian Wolff This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef PHPDOCUMENTATIONWIDGET_H #define PHPDOCUMENTATIONWIDGET_H #include class QTemporaryFile; class QUrl; namespace KDevelop { class StandardDocumentationView; class DocumentationFindWidget; } class PhpDocsPlugin; class PhpDocumentationWidget : public QStackedWidget { Q_OBJECT public: explicit PhpDocumentationWidget(KDevelop::DocumentationFindWidget* find, const QUrl &url, PhpDocsPlugin* provider, QWidget* parent = 0); + ~PhpDocumentationWidget(); private slots: /// used to inject some custom CSS to alter the remote php.net site void documentLoaded(); void linkClicked(const QUrl& url); private: KDevelop::StandardDocumentationView* m_part; QWidget* m_loading; QTemporaryFile* m_styleSheet; PhpDocsPlugin* m_provider; }; #endif // PHPDOCUMENTATIONWIDGET_H diff --git a/duchain/builders/declarationbuilder.cpp b/duchain/builders/declarationbuilder.cpp index 3c1ac82..32b3d3a 100644 --- a/duchain/builders/declarationbuilder.cpp +++ b/duchain/builders/declarationbuilder.cpp @@ -1,1460 +1,1460 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2008 Niko Sams * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "declarationbuilder.h" #include #include #include #include #include #include #include #include #include #include "../declarations/variabledeclaration.h" #include "../declarations/classmethoddeclaration.h" #include "../declarations/classdeclaration.h" #include "../declarations/functiondeclaration.h" #include "../declarations/namespacedeclaration.h" #include "../declarations/namespacealiasdeclaration.h" #include "../declarations/traitmethodaliasdeclaration.h" #include "../declarations/traitmemberaliasdeclaration.h" #include "../parser/phpast.h" #include "../parser/parsesession.h" #include "../helper.h" #include "../duchaindebug.h" #include "../expressionvisitor.h" #include "predeclarationbuilder.h" #define ifDebug(x) using namespace KDevelop; namespace Php { DeclarationBuilder::FindVariableResults::FindVariableResults() : find(true) , isArray(false) , node(0) { } void DeclarationBuilder::getVariableIdentifier(VariableAst* node, QualifiedIdentifier &identifier, QualifiedIdentifier &parent, AstNode* &targetNode, bool &arrayAccess) { parent = QualifiedIdentifier(); if ( node->variablePropertiesSequence ) { // at least one "->" in the assigment target // => find he parent of the target // => find the target (last object property) if ( node->variablePropertiesSequence->count() == 1 ) { // $parent->target ///TODO: $parent[0]->target = ... (we don't know the type of [0] yet, need proper array handling first) if ( node->var && node->var->baseVariable && node->var->baseVariable->var && !node->var->baseVariable->offsetItemsSequence ) { parent = identifierForNode( node->var->baseVariable->var->variable ); } } else { // $var->...->parent->target ///TODO: $var->...->parent[0]->target = ... (we don't know the type of [0] yet, need proper array handling first) const KDevPG::ListNode< VariableObjectPropertyAst* >* parentNode = node->variablePropertiesSequence->at( node->variablePropertiesSequence->count() - 2 ); if ( parentNode->element && parentNode->element->variableProperty && parentNode->element->variableProperty->objectProperty && parentNode->element->variableProperty->objectProperty->objectDimList && parentNode->element->variableProperty->objectProperty->objectDimList->variableName && !parentNode->element->variableProperty->objectProperty->objectDimList->offsetItemsSequence ) { parent = identifierForNode( parentNode->element->variableProperty->objectProperty->objectDimList->variableName->name ); } } if ( !parent.isEmpty() ) { const KDevPG::ListNode< VariableObjectPropertyAst* >* tNode = node->variablePropertiesSequence->at( node->variablePropertiesSequence->count() - 1 ); if ( tNode->element && tNode->element->variableProperty && tNode->element->variableProperty->objectProperty && tNode->element->variableProperty->objectProperty->objectDimList && tNode->element->variableProperty->objectProperty->objectDimList->variableName ) { arrayAccess = (bool) tNode->element->variableProperty->objectProperty->objectDimList->offsetItemsSequence; identifier = identifierForNode( tNode->element->variableProperty->objectProperty->objectDimList->variableName->name ); targetNode = tNode->element->variableProperty->objectProperty->objectDimList->variableName->name; } } } else { // simple assignment to $var if ( node->var && node->var->baseVariable && node->var->baseVariable->var ) { arrayAccess = (bool) node->var->baseVariable->offsetItemsSequence; identifier = identifierForNode( node->var->baseVariable->var->variable ); targetNode = node->var->baseVariable->var->variable; } } } ReferencedTopDUContext DeclarationBuilder::build(const IndexedString& url, AstNode* node, ReferencedTopDUContext updateContext) { //Run DeclarationBuilder twice, to find uses of declarations that are //declared after the use. ($a = new Foo; class Foo {}) { PreDeclarationBuilder prebuilder(&m_types, &m_functions, &m_namespaces, &m_upcomingClassVariables, m_editor); updateContext = prebuilder.build(url, node, updateContext); m_actuallyRecompiling = prebuilder.didRecompile(); } // now skip through some things the DeclarationBuilderBase (ContextBuilder) would do, // most significantly don't clear imported parent contexts m_isInternalFunctions = url == internalFunctionFile(); if ( m_isInternalFunctions ) { m_reportErrors = false; } else if ( ICore::self() ) { m_reportErrors = ICore::self()->languageController()->completionSettings()->highlightSemanticProblems(); } return ContextBuilderBase::build(url, node, updateContext); } void DeclarationBuilder::startVisiting(AstNode* node) { setRecompiling(m_actuallyRecompiling); setCompilingContexts(false); DeclarationBuilderBase::startVisiting(node); } void DeclarationBuilder::closeDeclaration() { if (currentDeclaration() && lastType()) { DUChainWriteLocker lock(DUChain::lock()); currentDeclaration()->setType(lastType()); } eventuallyAssignInternalContext(); DeclarationBuilderBase::closeDeclaration(); } void DeclarationBuilder::classContextOpened(DUContext* context) { DUChainWriteLocker lock(DUChain::lock()); currentDeclaration()->setInternalContext(context); } void DeclarationBuilder::visitClassDeclarationStatement(ClassDeclarationStatementAst * node) { ClassDeclaration* classDec = openTypeDeclaration(node->className, ClassDeclarationData::Class); openType(classDec->abstractType()); DeclarationBuilderBase::visitClassDeclarationStatement(node); { DUChainWriteLocker lock; classDec->updateCompletionCodeModelItem(); } closeType(); closeDeclaration(); m_upcomingClassVariables.clear(); } void DeclarationBuilder::visitInterfaceDeclarationStatement(InterfaceDeclarationStatementAst *node) { ClassDeclaration* interfaceDec = openTypeDeclaration(node->interfaceName, ClassDeclarationData::Interface); openType(interfaceDec->abstractType()); DeclarationBuilderBase::visitInterfaceDeclarationStatement(node); closeType(); closeDeclaration(); } void DeclarationBuilder::visitTraitDeclarationStatement(TraitDeclarationStatementAst * node) { ClassDeclaration* traitDec = openTypeDeclaration(node->traitName, ClassDeclarationData::Trait); openType(traitDec->abstractType()); DeclarationBuilderBase::visitTraitDeclarationStatement(node); closeType(); closeDeclaration(); m_upcomingClassVariables.clear(); } ClassDeclaration* DeclarationBuilder::openTypeDeclaration(IdentifierAst* name, ClassDeclarationData::ClassType type) { ClassDeclaration* classDec = m_types.value(name->string, 0); Q_ASSERT(classDec); isGlobalRedeclaration(identifierForNode(name), name, ClassDeclarationType); Q_ASSERT(classDec->classType() == type); Q_UNUSED(type); // seems like we have to do that manually, else the usebuilder crashes... setEncountered(classDec); openDeclarationInternal(classDec); return classDec; } bool DeclarationBuilder::isBaseMethodRedeclaration(const IdentifierPair &ids, ClassDeclaration *curClass, ClassStatementAst *node) { DUChainWriteLocker lock(DUChain::lock()); while (curClass->baseClassesSize() > 0) { StructureType::Ptr type; FOREACH_FUNCTION(const BaseClassInstance& base, curClass->baseClasses) { DUChainReadLocker lock(DUChain::lock()); type = base.baseClass.type(); if (!type) { continue; } ClassDeclaration *nextClass = dynamic_cast(type->declaration(currentContext()->topContext())); if (!nextClass || nextClass->classType() != ClassDeclarationData::Class) { type.reset(); continue; } curClass = nextClass; break; } if (!type) { break; } { if (!type->internalContext(currentContext()->topContext())) { continue; } foreach(Declaration * dec, type->internalContext(currentContext()->topContext())->findLocalDeclarations(ids.second.first(), startPos(node))) { if (dec->isFunctionDeclaration()) { ClassMethodDeclaration* func = dynamic_cast(dec); if (!func || !wasEncountered(func)) { continue; } // we cannot redeclare final classes ever if (func->isFinal()) { reportRedeclarationError(dec, node->methodName); return true; } // also we may not redeclare an already abstract method, we would have to implement it // TODO: original error message? // -> Can't inherit abstract function class::func() (previously declared in otherclass) else if (func->isAbstract() && node->modifiers->modifiers & ModifierAbstract) { reportRedeclarationError(dec, node->methodName); return true; } } } } } return false; } void DeclarationBuilder::visitClassStatement(ClassStatementAst *node) { setComment(formatComment(node, m_editor)); ClassDeclaration *parent = dynamic_cast(currentDeclaration()); Q_ASSERT(parent); if (node->methodName) { //method declaration IdentifierPair ids = identifierPairForNode(node->methodName); if (m_reportErrors) { // check for redeclarations Q_ASSERT(currentContext()->type() == DUContext::Class); bool localError = false; { DUChainWriteLocker lock(DUChain::lock()); foreach(Declaration * dec, currentContext()->findLocalDeclarations(ids.second.first(), startPos(node->methodName))) { if (wasEncountered(dec) && dec->isFunctionDeclaration() && !dynamic_cast(dec)) { reportRedeclarationError(dec, node->methodName); localError = true; break; } } } if (!localError) { // if we have no local error, check that we don't try to overwrite a final method of a baseclass isBaseMethodRedeclaration(ids, parent, node); } } { DUChainWriteLocker lock(DUChain::lock()); ClassMethodDeclaration* dec = openDefinition(ids.second, editorFindRange(node->methodName, node->methodName)); dec->setPrettyName(ids.first); dec->clearDefaultParameters(); dec->setKind(Declaration::Type); if (node->modifiers->modifiers & ModifierPublic) { dec->setAccessPolicy(Declaration::Public); } else if (node->modifiers->modifiers & ModifierProtected) { dec->setAccessPolicy(Declaration::Protected); } else if (node->modifiers->modifiers & ModifierPrivate) { dec->setAccessPolicy(Declaration::Private); } if (node->modifiers->modifiers & ModifierStatic) { dec->setStatic(true); } if (parent->classType() == ClassDeclarationData::Interface) { if (m_reportErrors) { if (node->modifiers->modifiers & ModifierFinal || node->modifiers->modifiers & ModifierAbstract) { reportError(i18n("Access type for interface method %1 must be omitted.", dec->toString()), node->modifiers); } if (!isEmptyMethodBody(node->methodBody)) { reportError(i18n("Interface function %1 cannot contain body.", dec->toString()), node->methodBody); } } // handle interface methods like abstract methods dec->setIsAbstract(true); } else { if (node->modifiers->modifiers & ModifierAbstract) { if (!m_reportErrors) { dec->setIsAbstract(true); } else { if (parent->classModifier() != ClassDeclarationData::Abstract && parent->classType() != ClassDeclarationData::Trait) { reportError(i18n("Class %1 contains abstract method %2 and must therefore be declared abstract " "or implement the method.", parent->identifier().toString(), dec->identifier().toString()), node->modifiers); } else if (!isEmptyMethodBody(node->methodBody)) { reportError(i18n("Abstract function %1 cannot contain body.", dec->toString()), node->methodBody); } else if (node->modifiers->modifiers & ModifierFinal) { reportError(i18n("Cannot use the final modifier on an abstract class member."), node->modifiers); } else { dec->setIsAbstract(true); } } } else if (node->modifiers->modifiers & ModifierFinal) { dec->setIsFinal(true); } if (m_reportErrors && !dec->isAbstract() && isEmptyMethodBody(node->methodBody)) { reportError(i18n("Non-abstract method %1 must contain body.", dec->toString()), node->methodBody); } } } DeclarationBuilderBase::visitClassStatement(node); closeDeclaration(); } else if (node->traitsSequence) { DeclarationBuilderBase::visitClassStatement(node); importTraitMethods(node); } else { if (node->modifiers) { m_currentModifers = node->modifiers->modifiers; if (m_reportErrors) { // have to report the errors here to get a good problem range if (m_currentModifers & ModifierFinal) { reportError(i18n("Properties cannot be declared final."), node->modifiers); } if (m_currentModifers & ModifierAbstract) { reportError(i18n("Properties cannot be declared abstract."), node->modifiers); } } } else { m_currentModifers = 0; } DeclarationBuilderBase::visitClassStatement(node); m_currentModifers = 0; } } void DeclarationBuilder::importTraitMethods(ClassStatementAst *node) { // Add trait members that don't need special handling const KDevPG::ListNode< NamespacedIdentifierAst* >* it = node->traitsSequence->front(); DUChainWriteLocker lock; forever { DeclarationPointer dec = findDeclarationImport(ClassDeclarationType, identifierForNamespace(it->element, m_editor)); if (!dec || !dec->internalContext()) { break; } QVector declarations = dec.data()->internalContext()->localDeclarations(0); QVector localDeclarations = currentContext()->localDeclarations(0); ifDebug(qCDebug(DUCHAIN) << "Importing from" << dec.data()->identifier().toString() << "to" << currentContext()->localScopeIdentifier().toString();) foreach (Declaration* import, declarations) { Declaration* found = nullptr; foreach (Declaration* local, localDeclarations) { ifDebug(qCDebug(DUCHAIN) << "Comparing" << import->identifier().toString() << "with" << local->identifier().toString();) if (auto trait = dynamic_cast(local)) { if (trait->aliasedDeclaration().data() == import) { ifDebug(qCDebug(DUCHAIN) << "Already imported";) found = local; break; } if (local->identifier() == import->identifier()) { ClassMethodDeclaration* importMethod = dynamic_cast(import); if (trait->isOverriding(import->context()->indexedLocalScopeIdentifier())) { ifDebug(qCDebug(DUCHAIN) << "Is overridden";) found = local; break; } else if (importMethod) { reportError( i18n("Trait method %1 has not been applied, because there are collisions with other trait methods on %2") - .arg(importMethod->prettyName().str()) - .arg(dynamic_cast(currentDeclaration())->prettyName().str()) + .arg(importMethod->prettyName().str(), + dynamic_cast(currentDeclaration())->prettyName().str()) , it->element, IProblem::Error ); found = local; break; } } } if (auto trait = dynamic_cast(local)) { if (trait->aliasedDeclaration().data() == import) { ifDebug(qCDebug(DUCHAIN) << "Already imported";) found = local; break; } } if (local->identifier() == import->identifier()) { if (dynamic_cast(local) && dynamic_cast(import)) { found = local; break; } } } if (found) { setEncountered(found); continue; } ifDebug(qCDebug(DUCHAIN) << "Importing new declaration";) CursorInRevision cursor = m_editor->findRange(it->element).start; if (auto olddec = dynamic_cast(import)) { TraitMethodAliasDeclaration* newdec = openDefinition(olddec->qualifiedIdentifier(), RangeInRevision(cursor, cursor)); openAbstractType(olddec->abstractType()); newdec->setPrettyName(olddec->prettyName()); newdec->setAccessPolicy(olddec->accessPolicy()); newdec->setKind(Declaration::Type); newdec->setAliasedDeclaration(IndexedDeclaration(olddec)); newdec->setStatic(olddec->isStatic()); closeType(); closeDeclaration(); } else if (auto olddec = dynamic_cast(import)) { TraitMemberAliasDeclaration* newdec = openDefinition(olddec->qualifiedIdentifier(), RangeInRevision(cursor, cursor)); openAbstractType(olddec->abstractType()); newdec->setAccessPolicy(olddec->accessPolicy()); newdec->setKind(Declaration::Instance); newdec->setAliasedDeclaration(IndexedDeclaration(olddec)); newdec->setStatic(olddec->isStatic()); closeType(); closeDeclaration(); } } if ( it->hasNext() ) { it = it->next; } else { break; } } } void DeclarationBuilder::visitClassExtends(ClassExtendsAst *node) { addBaseType(node->identifier); } void DeclarationBuilder::visitClassImplements(ClassImplementsAst *node) { const KDevPG::ListNode *__it = node->implementsSequence->front(), *__end = __it; do { addBaseType(__it->element); __it = __it->next; } while (__it != __end); DeclarationBuilderBase::visitClassImplements(node); } void DeclarationBuilder::visitClassVariable(ClassVariableAst *node) { QualifiedIdentifier name = identifierForNode(node->variable); if (m_reportErrors) { // check for redeclarations DUChainWriteLocker lock(DUChain::lock()); Q_ASSERT(currentContext()->type() == DUContext::Class); foreach(Declaration * dec, currentContext()->findLocalDeclarations(name.first(), startPos(node))) { if (wasEncountered(dec) && !dec->isFunctionDeclaration() && ! dec->abstractType()->modifiers() & AbstractType::ConstModifier) { reportRedeclarationError(dec, node); break; } } } openClassMemberDeclaration(node->variable, name); DeclarationBuilderBase::visitClassVariable(node); closeDeclaration(); } void DeclarationBuilder::openClassMemberDeclaration(AstNode* node, const QualifiedIdentifier &name) { DUChainWriteLocker lock(DUChain::lock()); // dirty hack: declarations of class members outside the class context would // make the class context encompass the newRange. This is not what we want. RangeInRevision oldRange = currentContext()->range(); RangeInRevision newRange = editorFindRange(node, node); openDefinition(name, newRange); ClassMemberDeclaration* dec = dynamic_cast(currentDeclaration()); Q_ASSERT(dec); if (m_currentModifers & ModifierPublic) { dec->setAccessPolicy(Declaration::Public); } else if (m_currentModifers & ModifierProtected) { dec->setAccessPolicy(Declaration::Protected); } else if (m_currentModifers & ModifierPrivate) { dec->setAccessPolicy(Declaration::Private); } if (m_currentModifers & ModifierStatic) { dec->setStatic(true); } dec->setKind(Declaration::Instance); currentContext()->setRange(oldRange); } void DeclarationBuilder::declareClassMember(DUContext *parentCtx, AbstractType::Ptr type, const QualifiedIdentifier& identifier, AstNode* node ) { if ( m_upcomingClassVariables.contains(identifier) ) { if (m_actuallyRecompiling) { DUChainWriteLocker lock; if (Declaration* dec = currentContext()->findDeclarationAt(startPos(node))) { if (dynamic_cast(dec)) { // invalidate declaration, it got added // see also bug https://bugs.kde.org/show_bug.cgi?id=241750 delete dec; } } } return; } DUChainWriteLocker lock(DUChain::lock()); // check for redeclaration of private or protected stuff { // only interesting context might be the class context when we are inside a method DUContext *ctx = currentContext()->parentContext(); foreach ( Declaration* dec, parentCtx->findDeclarations(identifier) ) { if ( ClassMemberDeclaration* cdec = dynamic_cast(dec) ) { if ( cdec->accessPolicy() == Declaration::Private && cdec->context() != ctx ) { reportError(i18n("Cannot redeclare private property %1 from this context.", cdec->toString()), node); return; } else if ( cdec->accessPolicy() == Declaration::Protected && cdec->context() != ctx && ( !ctx || !ctx->imports(cdec->context()) ) ) { reportError(i18n("Cannot redeclare protected property %1 from this context.", cdec->toString()), node); return; } if ( cdec->abstractType()->indexed() == type->indexed() ) { encounter(dec); return; } } } } // this member should be public and non-static m_currentModifers = ModifierPublic; injectContext(parentCtx); openClassMemberDeclaration(node, identifier); m_currentModifers = 0; //own closeDeclaration() that doesn't use lastType() currentDeclaration()->setType(type); eventuallyAssignInternalContext(); DeclarationBuilderBase::closeDeclaration(); closeInjectedContext(); } void DeclarationBuilder::visitConstantDeclaration(ConstantDeclarationAst *node) { if (m_reportErrors) { // Check for constants in traits if (isMatch(currentDeclaration(), ClassDeclarationType)) { ClassDeclaration *parent = dynamic_cast(currentDeclaration()); Q_ASSERT(parent); if (parent->classType() == ClassDeclarationData::Trait) { reportError(i18n("Traits cannot have constants."), node); } } // check for redeclarations DUChainWriteLocker lock(DUChain::lock()); foreach(Declaration * dec, currentContext()->findLocalDeclarations(identifierForNode(node->identifier).first(), startPos(node->identifier))) { if (wasEncountered(dec) && !dec->isFunctionDeclaration() && dec->abstractType()->modifiers() & AbstractType::ConstModifier) { reportRedeclarationError(dec, node->identifier); break; } } } ClassMemberDeclaration* dec = openDefinition(node->identifier, node->identifier); { DUChainWriteLocker lock(DUChain::lock()); dec->setAccessPolicy(Declaration::Public); dec->setStatic(true); dec->setKind(Declaration::Instance); } DeclarationBuilderBase::visitConstantDeclaration(node); closeDeclaration(); if ( m_reportErrors ) { // const class members may only be ints, floats, bools or strings bool badType = true; if ( IntegralType* type = fastCast(lastType().data()) ) { switch( type->dataType() ) { case IntegralType::TypeBoolean: case IntegralType::TypeFloat: case IntegralType::TypeInt: case IntegralType::TypeString: case IntegralType::TypeNull: badType = false; break; default: // every other type is a badType (see above) break; } } if ( badType ) { reportError(i18n("Only booleans, ints, floats and strings are allowed for class constants."), node->scalar); } } } void DeclarationBuilder::visitTraitAliasStatement(TraitAliasStatementAst *node) { DUChainWriteLocker lock; DeclarationPointer dec = findDeclarationImport(ClassDeclarationType, identifierForNamespace(node->importIdentifier->identifier, m_editor)); if (dec && dec.data()->internalContext()) { createTraitAliasDeclarations(node, dec); } lock.unlock(); DeclarationBuilderBase::visitTraitAliasStatement(node); } void DeclarationBuilder::createTraitAliasDeclarations(TraitAliasStatementAst *node, DeclarationPointer dec) { QualifiedIdentifier original = identifierPairForNode(node->importIdentifier->methodIdentifier).second; QList list = dec.data()->internalContext()->findLocalDeclarations(original.last(), dec.data()->internalContext()->range().start); QualifiedIdentifier alias; if (node->aliasIdentifier) { alias = identifierPairForNode(node->aliasIdentifier).second; } else { alias = original; } if (!list.isEmpty()) { ClassMethodDeclaration* olddec = dynamic_cast(list.first()); TraitMethodAliasDeclaration* newdec; // no existing declaration found, create one if (node->aliasIdentifier) { newdec = openDefinition(alias, m_editor->findRange(node->aliasIdentifier)); newdec->setPrettyName(identifierPairForNode(node->aliasIdentifier).first); newdec->setAccessPolicy(olddec->accessPolicy()); openAbstractType(olddec->abstractType()); if (node->modifiers) { if (node->modifiers->modifiers & ModifierPublic) { newdec->setAccessPolicy(Declaration::Public); } else if (node->modifiers->modifiers & ModifierProtected) { newdec->setAccessPolicy(Declaration::Protected); } else if (node->modifiers->modifiers & ModifierPrivate) { newdec->setAccessPolicy(Declaration::Private); } if (node->modifiers->modifiers & ModifierFinal) { reportError(i18n("Cannot use 'final' as method modifier"), node->modifiers, IProblem::Error); } if (node->modifiers->modifiers & ModifierStatic) { reportError(i18n("Cannot use 'static' as method modifier"), node->modifiers, IProblem::Error); } } } else { CursorInRevision cursor = m_editor->findRange(node->importIdentifier).start; newdec = openDefinition(alias, RangeInRevision(cursor, cursor)); newdec->setPrettyName(identifierPairForNode(node->importIdentifier->methodIdentifier).first); newdec->setAccessPolicy(olddec->accessPolicy()); openAbstractType(olddec->abstractType()); } newdec->setKind(Declaration::Type); newdec->setAliasedDeclaration(IndexedDeclaration(olddec)); newdec->setStatic(olddec->isStatic()); QVector ids; if (node->conflictIdentifierSequence) { const KDevPG::ListNode< NamespacedIdentifierAst* >* it = node->conflictIdentifierSequence->front(); forever { DeclarationPointer dec = findDeclarationImport(ClassDeclarationType, identifierForNamespace(it->element, m_editor)); if (dec) { ids.append(IndexedQualifiedIdentifier(dec.data()->qualifiedIdentifier())); } if ( it->hasNext() ) { it = it->next; } else { break; } } newdec->setOverrides(ids); } closeType(); closeDeclaration(); } } void DeclarationBuilder::visitParameter(ParameterAst *node) { AbstractFunctionDeclaration* funDec = dynamic_cast(currentDeclaration()); Q_ASSERT(funDec); if (node->defaultValue) { QString symbol = m_editor->parseSession()->symbol(node->defaultValue); funDec->addDefaultParameter(IndexedString(symbol)); if ( node->parameterType && symbol.compare(QLatin1String("null"), Qt::CaseInsensitive) != 0 ) { reportError(i18n("Default value for parameters with a class type hint can only be NULL."), node->defaultValue); } } else if ( !node->defaultValue && funDec->defaultParametersSize() ) { reportError(i18n("Following parameters must have a default value assigned."), node); } { // create variable declaration for argument DUChainWriteLocker lock(DUChain::lock()); RangeInRevision newRange = editorFindRange(node->variable, node->variable); openDefinition(identifierForNode(node->variable), newRange); currentDeclaration()->setKind(Declaration::Instance); } DeclarationBuilderBase::visitParameter(node); closeDeclaration(); } void DeclarationBuilder::visitFunctionDeclarationStatement(FunctionDeclarationStatementAst* node) { isGlobalRedeclaration(identifierForNode(node->functionName), node->functionName, FunctionDeclarationType); FunctionDeclaration* dec = m_functions.value(node->functionName->string, 0); Q_ASSERT(dec); // seems like we have to set that, else the usebuilder crashes DeclarationBuilderBase::setEncountered(dec); openDeclarationInternal(dec); openType(dec->abstractType()); DeclarationBuilderBase::visitFunctionDeclarationStatement(node); closeType(); closeDeclaration(); } void DeclarationBuilder::visitClosure(ClosureAst* node) { setComment(formatComment(node, editor())); { DUChainWriteLocker lock; FunctionDeclaration *dec = openDefinition(QualifiedIdentifier(), editor()->findRange(node->startToken)); dec->setKind(Declaration::Type); dec->clearDefaultParameters(); } DeclarationBuilderBase::visitClosure(node); closeDeclaration(); } void DeclarationBuilder::visitLexicalVar(LexicalVarAst* node) { DeclarationBuilderBase::visitLexicalVar(node); QualifiedIdentifier id = identifierForNode(node->variable); DUChainWriteLocker lock; if ( recompiling() ) { // sadly we can't use findLocalDeclarations() here, since it un-aliases declarations foreach ( Declaration* dec, currentContext()->localDeclarations() ) { if ( dynamic_cast(dec) && dec->identifier() == id.first() ) { // don't redeclare but reuse the existing declaration encounter(dec); return; } } } // no existing declaration found, create one foreach(Declaration* aliasedDeclaration, currentContext()->findDeclarations(id)) { if (aliasedDeclaration->kind() == Declaration::Instance) { AliasDeclaration* dec = openDefinition(id, editor()->findRange(node->variable)); dec->setAliasedDeclaration(aliasedDeclaration); closeDeclaration(); break; } } } bool DeclarationBuilder::isGlobalRedeclaration(const QualifiedIdentifier &identifier, AstNode* node, DeclarationType type) { if (!m_reportErrors) { return false; } ///TODO: method redeclaration etc. if (type != ClassDeclarationType && type != FunctionDeclarationType && type != ConstantDeclarationType) { // the other types can be redeclared return false; } DUChainWriteLocker lock(DUChain::lock()); QList declarations = currentContext()->topContext()->findDeclarations( identifier, startPos(node) ); foreach(Declaration* dec, declarations) { if (wasEncountered(dec) && isMatch(dec, type)) { reportRedeclarationError(dec, node); return true; } } return false; } void DeclarationBuilder::reportRedeclarationError(Declaration* declaration, AstNode* node) { if (declaration->range().contains(startPos(node))) { // make sure this is not a wrongly reported redeclaration error return; } if (declaration->context()->topContext()->url() == internalFunctionFile()) { reportError(i18n("Cannot redeclare PHP internal %1.", declaration->toString()), node); } else if (auto trait = dynamic_cast(declaration)) { reportError( i18n("%1 and %2 define the same property (%3) in the composition of %1. This might be incompatible, to improve maintainability consider using accessor methods in traits instead.") - .arg(dynamic_cast(currentDeclaration())->prettyName().str()) - .arg(dynamic_cast(trait->aliasedDeclaration().data()->context()->owner())->prettyName().str()) - .arg(dynamic_cast(trait)->identifier().toString()), node, IProblem::Warning + .arg(dynamic_cast(currentDeclaration())->prettyName().str(), + dynamic_cast(trait->aliasedDeclaration().data()->context()->owner())->prettyName().str(), + dynamic_cast(trait)->identifier().toString()), node, IProblem::Warning ); } else { ///TODO: try to shorten the filename by removing the leading path to the current project reportError( i18n("Cannot redeclare %1, already declared in %2 on line %3.", declaration->toString(), declaration->context()->topContext()->url().str(), declaration->range().start.line + 1 ), node ); } } void DeclarationBuilder::visitOuterTopStatement(OuterTopStatementAst* node) { //docblock of an AssignmentExpression setComment(formatComment(node, m_editor)); m_lastTopStatementComment = m_editor->parseSession()->docComment(node->startToken); DeclarationBuilderBase::visitOuterTopStatement(node); } void DeclarationBuilder::visitAssignmentExpression(AssignmentExpressionAst* node) { if ( node->assignmentExpressionEqual ) { PushValue restore(m_findVariable); DeclarationBuilderBase::visitAssignmentExpression(node); } else { DeclarationBuilderBase::visitAssignmentExpression(node); } } void DeclarationBuilder::visitVariable(VariableAst* node) { if ( m_findVariable.find ) { getVariableIdentifier(node, m_findVariable.identifier, m_findVariable.parentIdentifier, m_findVariable.node, m_findVariable.isArray); m_findVariable.find = false; } DeclarationBuilderBase::visitVariable(node); } void DeclarationBuilder::declareVariable(DUContext* parentCtx, AbstractType::Ptr type, const QualifiedIdentifier& identifier, AstNode* node) { // we must not re-assign $this in a class context /// Qualified identifier for 'this' - static const QualifiedIdentifier thisQId("this"); + static const QualifiedIdentifier thisQId(QStringLiteral("this")); if ( identifier == thisQId && currentContext()->parentContext() && currentContext()->parentContext()->type() == DUContext::Class ) { reportError(i18n("Cannot re-assign $this."), QList() << node); return; } DUChainWriteLocker lock(DUChain::lock()); const RangeInRevision newRange = editorFindRange(node, node); // check if this variable is already declared { QList< Declaration* > decs = parentCtx->findDeclarations(identifier.first(), startPos(node), 0, DUContext::DontSearchInParent); if ( !decs.isEmpty() ) { QList< Declaration* >::const_iterator it = decs.constEnd() - 1; while ( true ) { // we expect that the list of declarations has the newest declaration at back if ( dynamic_cast( *it ) ) { if (!wasEncountered(*it)) { encounter(*it); // force new range https://bugs.kde.org/show_bug.cgi?id=262189, // might be wrong when we had syntax errors in there before (*it)->setRange(newRange); } if ( (*it)->abstractType() && !(*it)->abstractType()->equals(type.data()) ) { // if it's currently mixed and we now get something more definite, use that instead if ( ReferenceType::Ptr rType = ReferenceType::Ptr::dynamicCast((*it)->abstractType()) ) { if ( IntegralType::Ptr integral = IntegralType::Ptr::dynamicCast(rType->baseType()) ) { if ( integral->dataType() == IntegralType::TypeMixed ) { // referenced mixed to referenced @p type ReferenceType::Ptr newType(new ReferenceType()); newType->setBaseType(type); (*it)->setType(newType); return; } } } if ( IntegralType::Ptr integral = IntegralType::Ptr::dynamicCast((*it)->abstractType()) ) { if ( integral->dataType() == IntegralType::TypeMixed ) { // mixed to @p type (*it)->setType(type); return; } } // else make it unsure UnsureType::Ptr unsure = UnsureType::Ptr::dynamicCast((*it)->abstractType()); // maybe it's referenced? ReferenceType::Ptr rType = ReferenceType::Ptr::dynamicCast((*it)->abstractType()); if ( !unsure && rType ) { unsure = UnsureType::Ptr::dynamicCast(rType->baseType()); } if ( !unsure ) { unsure = UnsureType::Ptr(new UnsureType()); if ( rType ) { unsure->addType(rType->baseType()->indexed()); } else { unsure->addType((*it)->indexedType()); } } unsure->addType(type->indexed()); if ( rType ) { rType->setBaseType(AbstractType::Ptr(unsure.data())); (*it)->setType(rType); } else { (*it)->setType(unsure); } } return; } if ( it == decs.constBegin() ) { break; } --it; } } } VariableDeclaration *dec = openDefinition(identifier, newRange); dec->setKind(Declaration::Instance); if (!m_lastTopStatementComment.isEmpty()) { QRegExp rx("(\\*|///)\\s*@superglobal"); if (rx.indexIn(m_lastTopStatementComment) != -1) { dec->setSuperglobal(true); } } //own closeDeclaration() that doesn't use lastType() dec->setType(type); // variable declarations are not namespaced in PHP if (currentContext()->type() == DUContext::Namespace) { dec->setContext(currentContext()->topContext()); } eventuallyAssignInternalContext(); DeclarationBuilderBase::closeDeclaration(); } DUContext* getClassContext(const QualifiedIdentifier &identifier, DUContext* currentCtx) { /// Qualified identifier for 'this' - static const QualifiedIdentifier thisQId("this"); + static const QualifiedIdentifier thisQId(QStringLiteral("this")); if ( identifier == thisQId ) { if ( currentCtx->parentContext() && currentCtx->parentContext()->type() == DUContext::Class ) { return currentCtx->parentContext(); } } else { DUChainReadLocker lock(DUChain::lock()); foreach( Declaration* parent, currentCtx->topContext()->findDeclarations(identifier) ) { if ( StructureType::Ptr ctype = parent->type() ) { return ctype->internalContext(currentCtx->topContext()); } } ///TODO: if we can't find anything here we might have to use the findDeclarationImport helper } return 0; } ///TODO: we need to handle assignment to array-members properly /// currently we just make sure the array is declared, but don't know /// anything about its contents void DeclarationBuilder::visitAssignmentExpressionEqual(AssignmentExpressionEqualAst *node) { DeclarationBuilderBase::visitAssignmentExpressionEqual(node); if ( !m_findVariable.identifier.isEmpty() && currentAbstractType()) { //create new declaration assignments to not-yet declared variables and class members AbstractType::Ptr type; if ( m_findVariable.isArray ) { // implicit array declaration type = AbstractType::Ptr(new IntegralType(IntegralType::TypeArray)); } else { type = currentAbstractType(); } if ( !m_findVariable.parentIdentifier.isEmpty() ) { // assignment to class members if ( DUContext* ctx = getClassContext(m_findVariable.parentIdentifier, currentContext()) ) { declareClassMember(ctx, type, m_findVariable.identifier, m_findVariable.node); } } else { // assigment to other variables declareVariable(currentContext(), type, m_findVariable.identifier, m_findVariable.node ); } } } void DeclarationBuilder::visitFunctionCall(FunctionCallAst* node) { QualifiedIdentifier id; if (!m_isInternalFunctions) { FunctionType::Ptr oldFunction = m_currentFunctionType; DeclarationPointer dec; if ( node->stringFunctionName ) { dec = findDeclarationImport(FunctionDeclarationType, node->stringFunctionName); } else if ( node->stringFunctionNameOrClass ) { id = identifierForNamespace(node->stringFunctionNameOrClass, m_editor); dec = findDeclarationImport(FunctionDeclarationType, id); } else { ///TODO: node->varFunctionName } if ( dec ) { m_currentFunctionType = dec->type(); } else { m_currentFunctionType = 0; } DeclarationBuilderBase::visitFunctionCall(node); m_currentFunctionType = oldFunction; } else { // optimize for internal function file DeclarationBuilderBase::visitFunctionCall(node); } if (node->stringFunctionNameOrClass && !node->stringFunctionName && !node->varFunctionName) { - if (id.toString(true) == "define" + if (id.toString(true) == QLatin1String("define") && node->stringParameterList && node->stringParameterList->parametersSequence && node->stringParameterList->parametersSequence->count() > 0) { //constant, defined through define-function //find name of the constant (first argument of the function call) CommonScalarAst* scalar = findCommonScalar(node->stringParameterList->parametersSequence->at(0)->element); if (scalar && scalar->string != -1) { QString constant = m_editor->parseSession()->symbol(scalar->string); constant = constant.mid(1, constant.length() - 2); RangeInRevision newRange = editorFindRange(scalar, scalar); AbstractType::Ptr type; if (node->stringParameterList->parametersSequence->count() > 1) { type = getTypeForNode(node->stringParameterList->parametersSequence->at(1)->element); Q_ASSERT(type); type->setModifiers(type->modifiers() | AbstractType::ConstModifier); } // TODO: else report error? DUChainWriteLocker lock; // find fitting context to put define in, // pick first namespace or global context otherwise DUContext* ctx = currentContext(); while (ctx->type() != DUContext::Namespace && ctx->parentContext()) { ctx = ctx->parentContext(); } injectContext(ctx); //constants are always global QualifiedIdentifier identifier(constant); isGlobalRedeclaration(identifier, scalar, ConstantDeclarationType); Declaration* dec = openDefinition(identifier, newRange); dec->setKind(Declaration::Instance); if (type) { dec->setType(type); injectType(type); } closeDeclaration(); closeInjectedContext(); } } } } void DeclarationBuilder::visitFunctionCallParameterList(FunctionCallParameterListAst* node) { int oldPos = m_functionCallParameterPos; m_functionCallParameterPos = 0; DeclarationBuilderBase::visitFunctionCallParameterList(node); m_functionCallParameterPos = oldPos; } void DeclarationBuilder::visitFunctionCallParameterListElement(FunctionCallParameterListElementAst* node) { PushValue restore(m_findVariable); DeclarationBuilderBase::visitFunctionCallParameterListElement(node); if ( m_findVariable.node && m_currentFunctionType && m_currentFunctionType->arguments().count() > m_functionCallParameterPos) { ReferenceType::Ptr refType = m_currentFunctionType->arguments() .at(m_functionCallParameterPos).cast(); if ( refType ) { // this argument is referenced, so if the node contains undeclared variables we have // to declare them with a NULL type, see also: // http://de.php.net/manual/en/language.references.whatdo.php // declare with NULL type, just like PHP does declareFoundVariable(AbstractType::Ptr(new IntegralType(IntegralType::TypeNull))); } } ++m_functionCallParameterPos; } void DeclarationBuilder::visitAssignmentListElement(AssignmentListElementAst* node) { PushValue restore(m_findVariable); DeclarationBuilderBase::DefaultVisitor::visitAssignmentListElement(node); if ( m_findVariable.node ) { ///TODO: get a proper type here, if possible declareFoundVariable(AbstractType::Ptr(new IntegralType(IntegralType::TypeMixed))); } } void DeclarationBuilder::declareFoundVariable(AbstractType::Ptr type) { Q_ASSERT(m_findVariable.node); ///TODO: support something like: foo($var[0]) if ( !m_findVariable.isArray ) { DUContext *ctx = 0; if ( m_findVariable.parentIdentifier.isEmpty() ) { ctx = currentContext(); } else { ctx = getClassContext(m_findVariable.parentIdentifier, currentContext()); } if ( ctx ) { bool isDeclared = false; { DUChainWriteLocker lock(DUChain::lock()); RangeInRevision range = m_editor->findRange(m_findVariable.node); foreach ( Declaration* dec, ctx->findDeclarations(m_findVariable.identifier) ) { if ( dec->kind() == Declaration::Instance ) { if (!wasEncountered(dec) || (dec->context() == ctx && range < dec->range())) { // just like a "redeclaration", hence we must update the range // TODO: do the same for all other uses of "encounter"? dec->setRange(editorFindRange(m_findVariable.node)); encounter(dec); } isDeclared = true; break; } } } if ( !isDeclared && m_findVariable.parentIdentifier.isEmpty() ) { // check also for global vars isDeclared = findDeclarationImport(GlobalVariableDeclarationType, m_findVariable.identifier); } if ( !isDeclared ) { // couldn't find the dec, declare it if ( !m_findVariable.parentIdentifier.isEmpty() ) { declareClassMember(ctx, type, m_findVariable.identifier, m_findVariable.node); } else { declareVariable(ctx, type, m_findVariable.identifier, m_findVariable.node); } } } } } void DeclarationBuilder::visitStatement(StatementAst* node) { DeclarationBuilderBase::visitStatement(node); if (node->foreachVariable) { PushValue restore(m_findVariable); visitForeachVariable(node->foreachVariable); if (m_findVariable.node) { declareFoundVariable(lastType()); } } if (node->foreachVarAsVar) { PushValue restore(m_findVariable); visitForeachVariable(node->foreachVarAsVar); if (m_findVariable.node) { declareFoundVariable(lastType()); } } if (node->foreachExprAsVar) { PushValue restore(m_findVariable); visitVariable(node->foreachExprAsVar); if (m_findVariable.node) { declareFoundVariable(lastType()); } } } void DeclarationBuilder::visitStaticVar(StaticVarAst* node) { DeclarationBuilderBase::visitStaticVar(node); DUChainWriteLocker lock(DUChain::lock()); openDefinition(identifierForNode(node->var), editorFindRange(node->var, node->var)); currentDeclaration()->setKind(Declaration::Instance); closeDeclaration(); } void DeclarationBuilder::visitGlobalVar(GlobalVarAst* node) { DeclarationBuilderBase::visitGlobalVar(node); if (node->var) { QualifiedIdentifier id = identifierForNode(node->var); if ( recompiling() ) { DUChainWriteLocker lock(DUChain::lock()); // sadly we can't use findLocalDeclarations() here, since it un-aliases declarations foreach ( Declaration* dec, currentContext()->localDeclarations() ) { if ( dynamic_cast(dec) && dec->identifier() == id.first() ) { // don't redeclare but reuse the existing declaration encounter(dec); return; } } } // no existing declaration found, create one DeclarationPointer aliasedDeclaration = findDeclarationImport(GlobalVariableDeclarationType, node->var); if (aliasedDeclaration) { DUChainWriteLocker lock(DUChain::lock()); AliasDeclaration* dec = openDefinition(id, m_editor->findRange(node->var)); dec->setAliasedDeclaration(aliasedDeclaration.data()); closeDeclaration(); } } } void DeclarationBuilder::visitCatchItem(CatchItemAst *node) { DeclarationBuilderBase::visitCatchItem(node); DUChainWriteLocker lock(DUChain::lock()); openDefinition(identifierForNode(node->var), editorFindRange(node->var, node->var)); currentDeclaration()->setKind(Declaration::Instance); closeDeclaration(); } void DeclarationBuilder::visitUnaryExpression(UnaryExpressionAst* node) { DeclarationBuilderBase::visitUnaryExpression(node); IndexedString includeFile = getIncludeFileForNode(node, m_editor); if ( !includeFile.isEmpty() ) { DUChainWriteLocker lock; TopDUContext* includedCtx = DUChain::self()->chainForDocument(includeFile); if ( !includedCtx ) { // invalid include return; } QualifiedIdentifier identifier(includeFile.str()); foreach ( Declaration* dec, includedCtx->findDeclarations(identifier, CursorInRevision(0, 1)) ) { if ( dec->kind() == Declaration::Import ) { encounter(dec); return; } } injectContext(includedCtx); openDefinition(identifier, RangeInRevision(0, 0, 0, 0)); currentDeclaration()->setKind(Declaration::Import); eventuallyAssignInternalContext(); DeclarationBuilderBase::closeDeclaration(); closeInjectedContext(); } } void DeclarationBuilder::openNamespace(NamespaceDeclarationStatementAst* parent, IdentifierAst* node, const IdentifierPair& identifier, const RangeInRevision& range) { NamespaceDeclaration* dec = m_namespaces.value(node->string, 0); Q_ASSERT(dec); DeclarationBuilderBase::setEncountered(dec); openDeclarationInternal(dec); DeclarationBuilderBase::openNamespace(parent, node, identifier, range); } void DeclarationBuilder::closeNamespace(NamespaceDeclarationStatementAst* parent, IdentifierAst* node, const IdentifierPair& identifier) { DeclarationBuilderBase::closeNamespace(parent, node, identifier); closeDeclaration(); } void DeclarationBuilder::visitUseNamespace(UseNamespaceAst* node) { DUChainWriteLocker lock; if ( currentContext()->type() != DUContext::Namespace && !node->aliasIdentifier && node->identifier->namespaceNameSequence->count() == 1 ) { reportError(i18n("The use statement with non-compound name '%1' has no effect.", identifierForNode(node->identifier->namespaceNameSequence->front()->element).toString()), node->identifier, IProblem::Warning); return; } IdentifierAst* idNode = node->aliasIdentifier ? node->aliasIdentifier : node->identifier->namespaceNameSequence->back()->element; IdentifierPair id = identifierPairForNode(idNode); ///TODO: case insensitive! QualifiedIdentifier qid = identifierForNamespace(node->identifier, m_editor); ///TODO: find out why this must be done (see mail to kdevelop-devel on jan 18th 2011) qid.setExplicitlyGlobal( false ); DeclarationPointer dec = findDeclarationImport(ClassDeclarationType, qid); if (dec) { // Check for a name conflict DeclarationPointer dec2 = findDeclarationImport(ClassDeclarationType, id.second); if (dec2 && dec2->context()->scopeIdentifier() == currentContext()->scopeIdentifier() && dec2->context()->topContext() == currentContext()->topContext() && dec2->identifier().toString() == id.second.toString()) { reportError(i18n("Cannot use '%1' as '%2' because the name is already in use.", dec.data()->identifier().toString(), id.second.toString()), node->identifier, IProblem::Error); return; } AliasDeclaration* decl = openDefinition(id.second, m_editor->findRange(idNode)); decl->setAliasedDeclaration(dec.data()); } else { NamespaceAliasDeclaration* decl = openDefinition(id.second, m_editor->findRange(idNode)); decl->setImportIdentifier( qid ); decl->setPrettyName( id.first ); decl->setKind(Declaration::NamespaceAlias); } closeDeclaration(); } void DeclarationBuilder::updateCurrentType() { DUChainWriteLocker lock(DUChain::lock()); currentDeclaration()->setAbstractType(currentAbstractType()); } void DeclarationBuilder::supportBuild(AstNode* node, DUContext* context) { // generally we are the second pass through the doc (see PreDeclarationBuilder) // so notify our base about it setCompilingContexts(false); DeclarationBuilderBase::supportBuild(node, context); } void DeclarationBuilder::closeContext() { if (currentContext()->type() == DUContext::Function) { Q_ASSERT(currentDeclaration()); currentDeclaration()->setInternalFunctionContext(currentContext()); } // We don't want the first pass to clean up stuff, since // there is lots of stuff we visit/encounter here first. // So we clean things up here. setCompilingContexts(true); DeclarationBuilderBase::closeContext(); setCompilingContexts(false); } void DeclarationBuilder::encounter(Declaration* dec) { // when we are recompiling, it's important to mark decs as encountered // and update their comments if ( recompiling() && !wasEncountered(dec) ) { dec->setComment(comment()); setEncountered(dec); } } } diff --git a/duchain/builders/typebuilder.cpp b/duchain/builders/typebuilder.cpp index 61a53f6..14afd05 100644 --- a/duchain/builders/typebuilder.cpp +++ b/duchain/builders/typebuilder.cpp @@ -1,576 +1,577 @@ /*************************************************************************** * 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 "typebuilder.h" #include #include #include #include #include #include #include "../declarations/classdeclaration.h" #include "../types/integraltypeextended.h" #include "../types/structuretype.h" #include "../duchaindebug.h" #include "editorintegrator.h" #include "parsesession.h" #include "phpdebugvisitor.h" #include "expressionparser.h" #include "expressionvisitor.h" #include "../declarations/classmethoddeclaration.h" #include using namespace KDevelop; namespace Php { TypeBuilder::TypeBuilder() : TypeBuilderBase() , m_gotTypeFromDocComment(false) , m_gotReturnTypeFromDocComment(false) { } TypeBuilder::~TypeBuilder() { } AbstractType::Ptr TypeBuilder::parseType(QString type, AstNode* node) { uint iType = 0; type = type.trimmed(); if (!type.compare(QLatin1String("int"), Qt::CaseInsensitive) || !type.compare(QLatin1String("integer"), Qt::CaseInsensitive)) { iType = IntegralType::TypeInt; } else if (!type.compare(QLatin1String("float"), Qt::CaseInsensitive) || !type.compare(QLatin1String("double"), Qt::CaseInsensitive)) { iType = IntegralType::TypeFloat; } else if (!type.compare(QLatin1String("bool"), Qt::CaseInsensitive) || !type.compare(QLatin1String("boolean"), Qt::CaseInsensitive) || !type.compare(QLatin1String("false"), Qt::CaseInsensitive) || !type.compare(QLatin1String("true"), Qt::CaseInsensitive)) { iType = IntegralType::TypeBoolean; } else if (!type.compare(QLatin1String("string"), Qt::CaseInsensitive)) { iType = IntegralType::TypeString; } else if (!type.compare(QLatin1String("mixed"), Qt::CaseInsensitive)) { iType = IntegralType::TypeMixed; } else if (!type.compare(QLatin1String("array"), Qt::CaseInsensitive)) { iType = IntegralType::TypeArray; } else if (!type.compare(QLatin1String("resource"), Qt::CaseInsensitive)) { return AbstractType::Ptr(new IntegralTypeExtended(IntegralTypeExtended::TypeResource)); } else if (!type.compare(QLatin1String("null"), Qt::CaseInsensitive)) { iType = IntegralType::TypeNull; } else if (!type.compare(QLatin1String("void"), Qt::CaseInsensitive)) { iType = IntegralType::TypeVoid; } else if (!type.compare(QLatin1String("self"), Qt::CaseInsensitive) || !type.compare(QLatin1String("this"), Qt::CaseInsensitive) || !type.compare(QLatin1String("static"), Qt::CaseInsensitive)) { DUChainReadLocker lock(DUChain::lock()); if ( currentContext()->type() == DUContext::Class && currentContext()->owner() ) { return currentContext()->owner()->abstractType(); } } else { if (!type.compare(QLatin1String("object"), Qt::CaseInsensitive)) { - type = "stdclass"; + type = QStringLiteral("stdclass"); } //don't use openTypeFromName as it uses cursor for findDeclarations DeclarationPointer decl = findDeclarationImport(ClassDeclarationType, QualifiedIdentifier(type.toLower())); if (decl && decl->abstractType()) { return decl->abstractType(); } if (type.contains('|')) { QList types; foreach (const QString& t, type.split('|')) { AbstractType::Ptr subType = parseType(t, node); if (!(IntegralType::Ptr::dynamicCast(subType) && IntegralType::Ptr::staticCast(subType)->dataType() == IntegralType::TypeMixed)) { types << parseType(t, node); } } if (!type.isEmpty()) { UnsureType::Ptr ret(new UnsureType()); foreach (const AbstractType::Ptr& t, types) { ret->addType(t->indexed()); } //qCDebug(DUCHAIN) << type << ret->toString(); return AbstractType::Ptr::staticCast(ret); } } iType = IntegralType::TypeMixed; } AbstractType::Ptr ret(new IntegralType(iType)); //qCDebug(DUCHAIN) << type << ret->toString(); return ret; } AbstractType::Ptr TypeBuilder::injectParseType(QString type, AstNode* node) { AbstractType::Ptr ret = parseType(type, node); injectType(ret); //qCDebug(DUCHAIN) << type << ret->toString(); return ret; } /** * Find all (or only one - see @p docCommentName) values for a given needle * in a doc-comment. Needle has to start a line in the doccomment, * i.e.: * * * @docCommentName value * * or * * /// @docCommentName value */ QStringList findInDocComment(const QString &docComment, const QString &docCommentName, const bool onlyOne) { QStringList matches; // optimization that does not require potentially slow regexps // old code was something like this: /* if (!docComment.isEmpty()) { QRegExp rx("\\*\\s+@param\\s([^\\s]*)"); int pos = 0; while ((pos = rx.indexIn(docComment, pos)) != -1) { ret << parseType(rx.cap(1), node); pos += rx.matchedLength(); } } */ for ( int i = 0, size = docComment.size(); i < size; ++i ) { if ( docComment[i].isSpace() || docComment[i] == '*' || docComment[i] == '/' ) { // skip whitespace and comment-marker at beginning of line continue; } else if ( docComment[i] == '@' && docComment.midRef(i + 1, docCommentName.size()) == docCommentName ) { // find @return or similar i += docCommentName.size() + 1; // skip whitespace (at least one is required) if ( i >= size || !docComment[i].isSpace() ) { // skip to next line i = docComment.indexOf('\n', i); if ( i == -1 ) { break; } continue; } else if ( docComment[i] == '\n' ) { continue; } ++i; // at least one whitespace while ( i < size && docComment[i].isSpace() ) { ++i; } // finally get the typename int pos = i; while ( pos < size && !docComment[pos].isSpace() ) { ++pos; } if ( pos > i ) { matches << docComment.mid(i, pos - i); if ( onlyOne ) { break; } else { i = pos; } } } // skip to next line i = docComment.indexOf('\n', i); if ( i == -1 ) { break; } } return matches; } AbstractType::Ptr TypeBuilder::parseDocComment(AstNode* node, const QString& docCommentName) { m_gotTypeFromDocComment = false; const QString& docComment = editor()->parseSession()->docComment(node->startToken); if ( !docComment.isEmpty() ) { const QStringList& matches = findInDocComment(docComment, docCommentName, true); if ( !matches.isEmpty() ) { AbstractType::Ptr type; - if (matches.first() == "$this") { + if (matches.first() == QLatin1String("$this")) { DUChainReadLocker lock(DUChain::lock()); if (currentContext()->owner()) { type = currentContext()->owner()->abstractType(); } } else { type = injectParseType(matches.first(), node); } if (type) { m_gotTypeFromDocComment = true; } return type; } } return AbstractType::Ptr(); } QList TypeBuilder::parseDocCommentParams(AstNode* node) { QList ret; QString docComment = editor()->parseSession()->docComment(node->startToken); if ( !docComment.isEmpty() ) { - const QStringList& matches = findInDocComment(docComment, "param", false); + const QStringList& matches = findInDocComment(docComment, QStringLiteral("param"), false); if ( !matches.isEmpty() ) { + ret.reserve(matches.size()); foreach ( const QString& type, matches ) { ret << parseType(type, node); } } } return ret; } AbstractType::Ptr TypeBuilder::getTypeForNode(AstNode* node) { AbstractType::Ptr type; if (node) { - type = parseDocComment(node, "var"); //we fully trust in @var typehint and don't try to evaluate ourself + type = parseDocComment(node, QStringLiteral("var")); //we fully trust in @var typehint and don't try to evaluate ourself if (!type) { node->ducontext = currentContext(); ExpressionParser ep; ep.setCreateProblems(true); ExpressionEvaluationResult res = ep.evaluateType(node, editor()); if (res.hadUnresolvedIdentifiers()) { m_hadUnresolvedIdentifiers = true; } type = res.type(); } } if (!type) { type = AbstractType::Ptr(new IntegralType(IntegralType::TypeMixed)); } return type; } FunctionType::Ptr TypeBuilder::openFunctionType(AstNode* node) { FunctionType::Ptr functionType = FunctionType::Ptr(new FunctionType()); openType(functionType); - functionType->setReturnType(parseDocComment(node, "return")); + functionType->setReturnType(parseDocComment(node, QStringLiteral("return"))); m_gotReturnTypeFromDocComment = functionType->returnType(); updateCurrentType(); return functionType; } void TypeBuilder::visitClassDeclarationStatement(ClassDeclarationStatementAst* node) { // the predeclaration builder should have set up a type already // and the declarationbuilder should have set that as current type Q_ASSERT(hasCurrentType() && currentType()); TypeBuilderBase::visitClassDeclarationStatement(node); } void TypeBuilder::visitInterfaceDeclarationStatement(InterfaceDeclarationStatementAst* node) { // the predeclaration builder should have set up a type already // and the declarationbuilder should have set that as current type Q_ASSERT(hasCurrentType() && currentType()); TypeBuilderBase::visitInterfaceDeclarationStatement(node); } void TypeBuilder::visitTraitDeclarationStatement(TraitDeclarationStatementAst* node) { // the predeclaration builder should have set up a type already // and the declarationbuilder should have set that as current type Q_ASSERT(hasCurrentType() && currentType()); TypeBuilderBase::visitTraitDeclarationStatement(node); } void TypeBuilder::visitClassStatement(ClassStatementAst *node) { if (node->methodName) { //method declaration m_currentFunctionParams = parseDocCommentParams(node); openFunctionType(node); TypeBuilderBase::visitClassStatement(node); if (currentType() && !currentType()->returnType()) { currentType()->setReturnType(AbstractType::Ptr(new IntegralType(IntegralType::TypeVoid))); } closeType(); } else { //member-variable - parseDocComment(node, "var"); + parseDocComment(node, QStringLiteral("var")); TypeBuilderBase::visitClassStatement(node); if (m_gotTypeFromDocComment) { clearLastType(); m_gotTypeFromDocComment = false; } } } void TypeBuilder::visitClassVariable(ClassVariableAst *node) { if (!m_gotTypeFromDocComment) { openAbstractType(getTypeForNode(node->value)); TypeBuilderBase::visitClassVariable(node); closeType(); } else { TypeBuilderBase::visitClassVariable(node); } } void TypeBuilder::visitConstantDeclaration(ConstantDeclarationAst* node) { if (!m_gotTypeFromDocComment || !currentAbstractType()) { AbstractType::Ptr type = getTypeForNode(node->scalar); type->setModifiers(type->modifiers() | AbstractType::ConstModifier); openAbstractType(type); TypeBuilderBase::visitConstantDeclaration(node); closeType(); } else { currentAbstractType()->setModifiers(currentAbstractType()->modifiers() & AbstractType::ConstModifier); TypeBuilderBase::visitConstantDeclaration(node); } } void TypeBuilder::visitParameter(ParameterAst *node) { AbstractType::Ptr type; if (node->parameterType) { //don't use openTypeFromName as it uses cursor for findDeclarations DeclarationPointer decl = findDeclarationImport(ClassDeclarationType, identifierForNamespace(node->parameterType, editor())); if (decl) { type = decl->abstractType(); } } else if (node->arrayType != -1) { type = AbstractType::Ptr(new IntegralType(IntegralType::TypeArray)); } else if (node->defaultValue) { ExpressionVisitor v(editor()); node->defaultValue->ducontext = currentContext(); v.visitNode(node->defaultValue); type = v.result().type(); } if (!type) { if (m_currentFunctionParams.count() > currentType()->arguments().count()) { type = m_currentFunctionParams.at(currentType()->arguments().count()); } else { type = AbstractType::Ptr(new IntegralType(IntegralType::TypeMixed)); } } if ( node->isRef != -1 ) { ReferenceType::Ptr p( new ReferenceType() ); p->setBaseType( type ); type = p.cast(); } openAbstractType(type); TypeBuilderBase::visitParameter(node); closeType(); DUChainWriteLocker lock(DUChain::lock()); currentType()->addArgument(type); } void TypeBuilder::visitFunctionDeclarationStatement(FunctionDeclarationStatementAst* node) { m_currentFunctionParams = parseDocCommentParams(node); // the predeclarationbuilder should have already built the type // and the declarationbuilder should have set it to open Q_ASSERT(hasCurrentType()); FunctionType::Ptr type = currentType(); Q_ASSERT(type); - type->setReturnType(parseDocComment(node, "return")); + type->setReturnType(parseDocComment(node, QStringLiteral("return"))); m_gotReturnTypeFromDocComment = type->returnType(); updateCurrentType(); TypeBuilderBase::visitFunctionDeclarationStatement(node); if (!type->returnType()) { type->setReturnType(AbstractType::Ptr(new IntegralType(IntegralType::TypeVoid))); } } void TypeBuilder::visitClosure(ClosureAst* node) { m_currentFunctionParams = parseDocCommentParams(node); FunctionType::Ptr type = FunctionType::Ptr(new FunctionType()); openType(type); - type->setReturnType(parseDocComment(node, "return")); + type->setReturnType(parseDocComment(node, QStringLiteral("return"))); m_gotReturnTypeFromDocComment = type->returnType(); updateCurrentType(); TypeBuilderBase::visitClosure(node); if (!type->returnType()) { type->setReturnType(AbstractType::Ptr(new IntegralType(IntegralType::TypeVoid))); } closeType(); } void TypeBuilder::visitAssignmentExpression(AssignmentExpressionAst* node) { // performance: only try to find type when we are actually in an assignment expr if (node->assignmentExpression || node->assignmentExpressionEqual) { openAbstractType(getTypeForNode(node)); } TypeBuilderBase::visitAssignmentExpression(node); if (node->assignmentExpression || node->assignmentExpressionEqual) { closeType(); } } void TypeBuilder::visitStaticVar(StaticVarAst *node) { openAbstractType(getTypeForNode(node->value)); TypeBuilderBase::visitStaticVar(node); closeType(); } void TypeBuilder::visitStatement(StatementAst* node) { TypeBuilderBase::visitStatement(node); if ( !m_gotReturnTypeFromDocComment && node->returnExpr && hasCurrentType() && currentType()) { FunctionType::Ptr ft = currentType(); // qCDebug(DUCHAIN) << "return" << (ft->returnType() ? ft->returnType()->toString() : "none") << lastType()->toString(); AbstractType::Ptr type = getTypeForNode(node->returnExpr); if (type) { // ignore references for return values, PHP does so as well if ( ReferenceType::Ptr rType = ReferenceType::Ptr::dynamicCast(type) ) { type = rType->baseType(); } if (ft->returnType() && !ft->returnType()->equals(type.data())) { if (ft->returnType().cast() && ft->returnType().cast()->dataType() == IntegralType::TypeMixed) { //don't add TypeMixed to the list, just ignore ft->setReturnType(type); } else { UnsureType::Ptr retT; if (ft->returnType().cast()) { //qCDebug(DUCHAIN) << "we already have an unsure type"; retT = ft->returnType().cast(); if (type.cast()) { //qCDebug(DUCHAIN) << "add multiple to returnType"; FOREACH_FUNCTION(const IndexedType& t, type.cast()->types) { retT->addType(t); } } else { //qCDebug(DUCHAIN) << "add to returnType"; retT->addType(type->indexed()); } } else { if (type.cast()) { retT = type.cast(); } else { retT = new UnsureType(); retT->addType(type->indexed()); } retT->addType(ft->returnType()->indexed()); } ft->setReturnType(AbstractType::Ptr::staticCast(retT)); } } else { ft->setReturnType(type); } updateCurrentType(); } } AstNode *foreachNode = 0; if (node->foreachVar) { foreachNode = node->foreachVar; } else if (node->foreachExpr) { foreachNode = node->foreachExpr; } else if (node->foreachExprAsVar) { foreachNode = node->foreachExprAsVar; } if (foreachNode) { ExpressionVisitor v(editor()); foreachNode->ducontext = currentContext(); v.visitNode(foreachNode); DUChainReadLocker lock(DUChain::lock()); bool foundType = false; if (StructureType::Ptr type = StructureType::Ptr::dynamicCast(v.result().type())) { ClassDeclaration *classDec = dynamic_cast(type->declaration(currentContext()->topContext())); if (!classDec) { ///FIXME: this is just a hack for https://bugs.kde.org/show_bug.cgi?id=269369 /// a proper fix needs full fledged two-pass, i.e. get rid of PreDeclarationBuilder // 0 == global lookup and the delcaration is found again... classDec = dynamic_cast(type->declaration(0)); } if (classDec) { /// Qualified identifier for 'iterator' - static const QualifiedIdentifier iteratorQId("iterator"); + static const QualifiedIdentifier iteratorQId(QStringLiteral("iterator")); ClassDeclaration* iteratorDecl = dynamic_cast( findDeclarationImport(ClassDeclarationType, iteratorQId).data() ); Q_ASSERT(iteratorDecl); if (classDec->isPublicBaseClass(iteratorDecl, currentContext()->topContext())) { /// Qualified identifier for 'current' - static const QualifiedIdentifier currentQId("current"); + static const QualifiedIdentifier currentQId(QStringLiteral("current")); foreach (Declaration *d, classDec->internalContext()->findDeclarations(currentQId)) { if (!dynamic_cast(d)) continue; Q_ASSERT(d->type()); injectType(d->type()->returnType()); foundType = true; // qCDebug(DUCHAIN) << "that's it: " << d->type()->returnType()->toString(); } } } } if (!foundType) { injectType(AbstractType::Ptr(new IntegralType(IntegralType::TypeMixed))); } } } void TypeBuilder::visitCatchItem(Php::CatchItemAst *node) { TypeBuilderBase::visitCatchItem(node); DeclarationPointer dec = findDeclarationImport(ClassDeclarationType, identifierForNamespace(node->catchClass, m_editor)); if (dec && dec->abstractType()) { openAbstractType(dec->abstractType()); closeType(); } } void TypeBuilder::updateCurrentType() { // do nothing } } diff --git a/duchain/completioncodemodel.cpp b/duchain/completioncodemodel.cpp index 65f0c34..d8207c6 100644 --- a/duchain/completioncodemodel.cpp +++ b/duchain/completioncodemodel.cpp @@ -1,364 +1,364 @@ /* This file is part of KDevelop Copyright 2008 David Nolden 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 "completioncodemodel.h" #include #include #include #include #include #include #include #include #include "duchaindebug.h" #define ifDebug(x) namespace Php { class CompletionCodeModelItemHandler { public: static int leftChild(const CompletionCodeModelItem& m_data) { return (int)m_data.referenceCount; } static void setLeftChild(CompletionCodeModelItem& m_data, int child) { m_data.referenceCount = (uint)child; } static int rightChild(const CompletionCodeModelItem& m_data) { return (int)m_data.uKind; } static void setRightChild(CompletionCodeModelItem& m_data, int child) { m_data.uKind = (uint)child; } //Copies this item into the given one static void copyTo(const CompletionCodeModelItem& m_data, CompletionCodeModelItem& data) { data = m_data; } static void createFreeItem(CompletionCodeModelItem& data) { data = CompletionCodeModelItem(); data.referenceCount = (uint)-1; data.uKind = (uint)-1; } static bool isFree(const CompletionCodeModelItem& m_data) { return !m_data.id.isValid(); } static const CompletionCodeModelItem& data(const CompletionCodeModelItem& m_data) { return m_data; } static bool equals(const CompletionCodeModelItem& m_data, const CompletionCodeModelItem& rhs) { return m_data.id == rhs.id; } }; DEFINE_LIST_MEMBER_HASH(CompletionCodeModelRepositoryItem, items, CompletionCodeModelItem) class CompletionCodeModelRepositoryItem { public: CompletionCodeModelRepositoryItem() : centralFreeItem(-1) { initializeAppendedLists(); } CompletionCodeModelRepositoryItem(const CompletionCodeModelRepositoryItem& rhs, bool dynamic = true) : file(rhs.file), centralFreeItem(rhs.centralFreeItem) { initializeAppendedLists(dynamic); copyListsFrom(rhs); } ~CompletionCodeModelRepositoryItem() { freeAppendedLists(); } unsigned int hash() const { //We only compare the declaration. This allows us implementing a map, although the item-repository //originally represents a set. return file.index(); } uint itemSize() const { return dynamicSize(); } uint classSize() const { return sizeof(CompletionCodeModelRepositoryItem); } KDevelop::IndexedString file; int centralFreeItem; START_APPENDED_LISTS(CompletionCodeModelRepositoryItem); APPENDED_LIST_FIRST(CompletionCodeModelRepositoryItem, CompletionCodeModelItem, items); END_APPENDED_LISTS(CompletionCodeModelRepositoryItem, items); }; class CodeModelRequestItem { public: CodeModelRequestItem(const CompletionCodeModelRepositoryItem& item) : m_item(item) { } enum { AverageSize = 30+8 //This should be the approximate average size of an Item }; unsigned int hash() const { return m_item.hash(); } uint itemSize() const { return m_item.itemSize(); } void createItem(CompletionCodeModelRepositoryItem* item) const { Q_ASSERT(KDevelop::shouldDoDUChainReferenceCounting(item)); Q_ASSERT(KDevelop::shouldDoDUChainReferenceCounting(((char*)item) + (itemSize()-1))); new (item) CompletionCodeModelRepositoryItem(m_item, false); } static void destroy(CompletionCodeModelRepositoryItem* item, KDevelop::AbstractItemRepository&) { Q_ASSERT(KDevelop::shouldDoDUChainReferenceCounting(item)); // Q_ASSERT(shouldDoDUChainReferenceCounting(((char*)item) + (itemSize()-1))); item->~CompletionCodeModelRepositoryItem(); } static bool persistent(const CompletionCodeModelRepositoryItem* item) { Q_UNUSED(item); return true; } bool equals(const CompletionCodeModelRepositoryItem* item) const { return m_item.file == item->file; } const CompletionCodeModelRepositoryItem& m_item; }; class CompletionCodeModelPrivate { public: - CompletionCodeModelPrivate() : m_repository("Php Completion Code Model") { + CompletionCodeModelPrivate() : m_repository(QStringLiteral("Php Completion Code Model")) { } //Maps declaration-ids to items KDevelop::ItemRepository m_repository; }; CompletionCodeModel::CompletionCodeModel() : d(new CompletionCodeModelPrivate()) { } CompletionCodeModel::~CompletionCodeModel() { delete d; } void CompletionCodeModel::addItem(const KDevelop::IndexedString& file, const KDevelop::IndexedQualifiedIdentifier& id, const KDevelop::IndexedString & prettyName, CompletionCodeModelItem::Kind kind) { ifDebug( qCDebug(DUCHAIN) << "addItem" << file.str() << id.identifier().toString() << id.index; ) if(!id.isValid()) return; CompletionCodeModelRepositoryItem item; item.file = file; CodeModelRequestItem request(item); uint index = d->m_repository.findIndex(item); CompletionCodeModelItem newItem; newItem.id = id; newItem.kind = kind; newItem.prettyName = prettyName; newItem.referenceCount = 1; if(index) { const CompletionCodeModelRepositoryItem* oldItem = d->m_repository.itemFromIndex(index); KDevelop::EmbeddedTreeAlgorithms alg(oldItem->items(), oldItem->itemsSize(), oldItem->centralFreeItem); int listIndex = alg.indexOf(newItem); QMutexLocker lock(d->m_repository.mutex()); KDevelop::DynamicItem editableItem = d->m_repository.dynamicItemFromIndex(index); CompletionCodeModelItem* items = const_cast(editableItem->items()); if(listIndex != -1) { //Only update the reference-count ++items[listIndex].referenceCount; items[listIndex].kind = kind; items[listIndex].prettyName = prettyName; return; }else{ //Add the item to the list KDevelop::EmbeddedTreeAddItem add(items, editableItem->itemsSize(), editableItem->centralFreeItem, newItem); if(add.newItemCount() != editableItem->itemsSize()) { //The data needs to be transferred into a bigger list. That list is within "item". item.itemsList().resize(add.newItemCount()); add.transferData(item.itemsList().data(), item.itemsList().size(), &item.centralFreeItem); d->m_repository.deleteItem(index); }else{ //We're fine: The item fits into the existing list. return; } } }else{ //We're creating a new index item.itemsList().append(newItem); } Q_ASSERT(!d->m_repository.findIndex(request)); //This inserts the changed item volatile uint newIndex = d->m_repository.index(request); Q_UNUSED(newIndex); ifDebug( qCDebug(DUCHAIN) << "new index" << newIndex; ) Q_ASSERT(d->m_repository.findIndex(request)); } void CompletionCodeModel::updateItem(const KDevelop::IndexedString& file, const KDevelop::IndexedQualifiedIdentifier& id, const KDevelop::IndexedString & prettyName, CompletionCodeModelItem::Kind kind) { ifDebug( qCDebug(DUCHAIN) << file.str() << id.identifier().toString() << kind; ) if(!id.isValid()) return; CompletionCodeModelRepositoryItem item; item.file = file; CodeModelRequestItem request(item); CompletionCodeModelItem newItem; newItem.id = id; newItem.kind = kind; newItem.prettyName = prettyName; newItem.referenceCount = 1; uint index = d->m_repository.findIndex(item); if(index) { //Check whether the item is already in the mapped list, else copy the list into the new created item QMutexLocker lock(d->m_repository.mutex()); KDevelop::DynamicItem oldItem = d->m_repository.dynamicItemFromIndex(index); KDevelop::EmbeddedTreeAlgorithms alg(oldItem->items(), oldItem->itemsSize(), oldItem->centralFreeItem); int listIndex = alg.indexOf(newItem); Q_ASSERT(listIndex != -1); CompletionCodeModelItem* items = const_cast(oldItem->items()); Q_ASSERT(items[listIndex].id == id); items[listIndex].kind = kind; items[listIndex].prettyName = prettyName; return; } Q_ASSERT(0); //The updated item as not in the symbol table! } void CompletionCodeModel::removeItem(const KDevelop::IndexedString& file, const KDevelop::IndexedQualifiedIdentifier& id) { if(!id.isValid()) return; ifDebug( qCDebug(DUCHAIN) << "removeItem" << file.str() << id.identifier().toString(); ) CompletionCodeModelRepositoryItem item; item.file = file; CodeModelRequestItem request(item); uint index = d->m_repository.findIndex(item); if(index) { CompletionCodeModelItem searchItem; searchItem.id = id; QMutexLocker lock(d->m_repository.mutex()); KDevelop::DynamicItem oldItem = d->m_repository.dynamicItemFromIndex(index); KDevelop::EmbeddedTreeAlgorithms alg(oldItem->items(), oldItem->itemsSize(), oldItem->centralFreeItem); int listIndex = alg.indexOf(searchItem); if(listIndex == -1) return; CompletionCodeModelItem* items = const_cast(oldItem->items()); --items[listIndex].referenceCount; if(oldItem->items()[listIndex].referenceCount) return; //Nothing to remove, there's still a reference-count left //We have reduced the reference-count to zero, so remove the item from the list KDevelop::EmbeddedTreeRemoveItem remove(items, oldItem->itemsSize(), oldItem->centralFreeItem, searchItem); uint newItemCount = remove.newItemCount(); if(newItemCount != oldItem->itemsSize()) { if(newItemCount == 0) { //Has become empty, delete the item d->m_repository.deleteItem(index); return; }else{ //Make smaller item.itemsList().resize(newItemCount); remove.transferData(item.itemsList().data(), item.itemsSize(), &item.centralFreeItem); //Delete the old list d->m_repository.deleteItem(index); //Add the new list d->m_repository.index(request); return; } } } } void CompletionCodeModel::items(const KDevelop::IndexedString& file, uint& count, const CompletionCodeModelItem*& items) const { ifDebug( qCDebug(DUCHAIN) << "items" << file.str(); ) CompletionCodeModelRepositoryItem item; item.file = file; CodeModelRequestItem request(item); uint index = d->m_repository.findIndex(item); if(index) { const CompletionCodeModelRepositoryItem* repositoryItem = d->m_repository.itemFromIndex(index); ifDebug( qCDebug(DUCHAIN) << "found index" << index << repositoryItem->itemsSize(); ) count = repositoryItem->itemsSize(); items = repositoryItem->items(); }else{ ifDebug( qCDebug(DUCHAIN) << "found no index"; ) count = 0; items = 0; } } CompletionCodeModel& CompletionCodeModel::self() { static CompletionCodeModel ret; return ret; } } diff --git a/duchain/declarations/classdeclaration.cpp b/duchain/declarations/classdeclaration.cpp index 19bf903..e9edffd 100644 --- a/duchain/declarations/classdeclaration.cpp +++ b/duchain/declarations/classdeclaration.cpp @@ -1,138 +1,138 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2008 Milian Wolff * * * * 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 "classdeclaration.h" #include #include #include "helper.h" namespace Php { REGISTER_DUCHAIN_ITEM(ClassDeclaration); ClassDeclaration::ClassDeclaration(const ClassDeclaration& rhs) : KDevelop::ClassDeclaration(*new ClassDeclarationData(*rhs.d_func())) { } ClassDeclaration::ClassDeclaration(const KDevelop::RangeInRevision& range, KDevelop::DUContext* context) : KDevelop::ClassDeclaration(*new ClassDeclarationData, range, context) { d_func_dynamic()->setClassId(this); if (context) { setContext(context); } } ClassDeclaration::ClassDeclaration(ClassDeclarationData& data) : KDevelop::ClassDeclaration(data) { } ClassDeclaration::ClassDeclaration(ClassDeclarationData& data, const KDevelop::RangeInRevision& range, KDevelop::DUContext* context) : KDevelop::ClassDeclaration(data, range, context) { } ClassDeclaration::~ClassDeclaration() { } KDevelop::Declaration* ClassDeclaration::clonePrivate() const { return new ClassDeclaration(*this); } KDevelop::IndexedString ClassDeclaration::prettyName() const { return d_func()->prettyName; } void ClassDeclaration::setPrettyName( const KDevelop::IndexedString& name ) { d_func_dynamic()->prettyName = name; } QString ClassDeclaration::toString() const { QString ret; switch ( classModifier() ) { case ClassDeclarationData::None: //nothing break; case ClassDeclarationData::Abstract: - ret += "abstract "; + ret += QLatin1String("abstract "); break; case ClassDeclarationData::Final: - ret += "final "; + ret += QLatin1String("final "); break; } switch ( classType() ) { case ClassDeclarationData::Class: - ret += "class "; + ret += QLatin1String("class "); break; case ClassDeclarationData::Interface: - ret += "interface "; + ret += QLatin1String("interface "); break; case ClassDeclarationData::Trait: - ret += "trait "; + ret += QLatin1String("trait "); break; case ClassDeclarationData::Union: - ret += "union "; + ret += QLatin1String("union "); break; case ClassDeclarationData::Struct: - ret += "struct "; + ret += QLatin1String("struct "); break; } return ret + prettyName().str(); } void ClassDeclaration::updateCompletionCodeModelItem() { if (d_func()->prettyName.isEmpty()) { return; } if (d_func()->m_inSymbolTable) { CompletionCodeModelItem::Kind flags = CompletionCodeModelItem::Unknown; - static const KDevelop::QualifiedIdentifier exceptionQId("exception"); + static const KDevelop::QualifiedIdentifier exceptionQId(QStringLiteral("exception")); if (qualifiedIdentifier() == exceptionQId) { flags = (CompletionCodeModelItem::Kind)(flags | CompletionCodeModelItem::Exception); } else { static KDevelop::DUChainPointer exceptionDecl; if (!exceptionDecl) { QList decs = context()->topContext()->findDeclarations(exceptionQId); Q_ASSERT(decs.count()); exceptionDecl = dynamic_cast(decs.first()); Q_ASSERT(exceptionDecl); } if (equalQualifiedIdentifier(exceptionDecl.data()) || isPublicBaseClass(exceptionDecl.data(), context()->topContext()) ) { flags = (CompletionCodeModelItem::Kind)(flags | CompletionCodeModelItem::Exception); } } CompletionCodeModel::self().addItem(url(), qualifiedIdentifier(), d_func_dynamic()->prettyName, flags); } else { CompletionCodeModel::self().removeItem(url(), qualifiedIdentifier()); } } } diff --git a/duchain/declarations/classmethoddeclaration.cpp b/duchain/declarations/classmethoddeclaration.cpp index 2106b7a..dd0dd42 100644 --- a/duchain/declarations/classmethoddeclaration.cpp +++ b/duchain/declarations/classmethoddeclaration.cpp @@ -1,110 +1,110 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2008 Milian Wolff * * * * 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 "classmethoddeclaration.h" #include #include #include "../duchaindebug.h" using namespace KDevelop; namespace Php { REGISTER_DUCHAIN_ITEM(ClassMethodDeclaration); ClassMethodDeclaration::ClassMethodDeclaration(const ClassMethodDeclaration& rhs) : KDevelop::ClassFunctionDeclaration(*new ClassMethodDeclarationData(*rhs.d_func())) { } ClassMethodDeclaration::ClassMethodDeclaration(const KDevelop::RangeInRevision& range, KDevelop::DUContext* context) : KDevelop::ClassFunctionDeclaration(*new ClassMethodDeclarationData, range, context) { d_func_dynamic()->setClassId(this); if (context) setContext(context); } ClassMethodDeclaration::ClassMethodDeclaration(ClassMethodDeclarationData& data) : KDevelop::ClassFunctionDeclaration(data) { } ClassMethodDeclaration::ClassMethodDeclaration(ClassMethodDeclarationData& data, const KDevelop::RangeInRevision& range, KDevelop::DUContext* context) : KDevelop::ClassFunctionDeclaration(data, range, context) { } ClassMethodDeclaration::~ClassMethodDeclaration() { } bool ClassMethodDeclaration::isConstructor() const { static const auto constructId = IndexedIdentifier(Identifier(QStringLiteral("__construct"))); const auto indexed = indexedIdentifier(); return indexed == constructId || indexed == context()->indexedLocalScopeIdentifier().identifier().indexedFirst(); } bool ClassMethodDeclaration::isDestructor() const { //TODO: register_shutdown_function static const auto destructId = IndexedIdentifier(Identifier(QStringLiteral("__destruct"))); const auto indexed = indexedIdentifier(); return indexed == destructId; } Declaration* ClassMethodDeclaration::clonePrivate() const { return new ClassMethodDeclaration(*this); } KDevelop::IndexedString ClassMethodDeclaration::prettyName() const { return d_func()->prettyName; } void ClassMethodDeclaration::setPrettyName( const KDevelop::IndexedString& name ) { d_func_dynamic()->prettyName = name; } QString ClassMethodDeclaration::toString() const { if( !abstractType() ) return ClassMemberDeclaration::toString(); TypePtr function = type(); if(function) { - return QString("%1 %2 %3").arg(function->partToString( FunctionType::SignatureReturn )) - .arg(prettyName().str()) - .arg(function->partToString( FunctionType::SignatureArguments )); + return QStringLiteral("%1 %2 %3").arg(function->partToString( FunctionType::SignatureReturn ), + prettyName().str(), + function->partToString( FunctionType::SignatureArguments )); } else { - QString type = abstractType() ? abstractType()->toString() : QString(""); + QString type = abstractType() ? abstractType()->toString() : QStringLiteral(""); qCDebug(DUCHAIN) << "A function has a bad type attached:" << type; - return QString("invalid member-function %1 type %2").arg(prettyName().str()).arg(type); + return QStringLiteral("invalid member-function %1 type %2").arg(prettyName().str(),type); } } } diff --git a/duchain/declarations/functiondeclaration.cpp b/duchain/declarations/functiondeclaration.cpp index 66c97bf..06b7edc 100644 --- a/duchain/declarations/functiondeclaration.cpp +++ b/duchain/declarations/functiondeclaration.cpp @@ -1,88 +1,88 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2008 Milian Wolff * * * * 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 "functiondeclaration.h" #include #include namespace Php { REGISTER_DUCHAIN_ITEM(FunctionDeclaration); FunctionDeclaration::FunctionDeclaration(const FunctionDeclaration& rhs) : KDevelop::FunctionDeclaration(*new FunctionDeclarationData(*rhs.d_func())) { } FunctionDeclaration::FunctionDeclaration(const KDevelop::RangeInRevision& range, KDevelop::DUContext* context) : KDevelop::FunctionDeclaration(*new FunctionDeclarationData, range) { d_func_dynamic()->setClassId(this); if (context) { setContext(context); } } FunctionDeclaration::FunctionDeclaration(FunctionDeclarationData& data) : KDevelop::FunctionDeclaration(data) { } FunctionDeclaration::FunctionDeclaration(FunctionDeclarationData& data, const KDevelop::RangeInRevision& range, KDevelop::DUContext* context) : KDevelop::FunctionDeclaration(data, range) { if (context) { setContext(context); } } FunctionDeclaration::~FunctionDeclaration() { } KDevelop::Declaration* FunctionDeclaration::clonePrivate() const { return new FunctionDeclaration(*this); } KDevelop::IndexedString FunctionDeclaration::prettyName() const { return d_func()->prettyName; } void FunctionDeclaration::setPrettyName( const KDevelop::IndexedString& name ) { d_func_dynamic()->prettyName = name; } QString FunctionDeclaration::toString() const { if( !abstractType() ) return Declaration::toString(); KDevelop::TypePtr function = type(); Q_ASSERT(function); - return QString("%1 %2 %3").arg(function->partToString( KDevelop::FunctionType::SignatureReturn )) - .arg(prettyName().str()) - .arg(function->partToString( KDevelop::FunctionType::SignatureArguments )); + return QStringLiteral("%1 %2 %3").arg(function->partToString( KDevelop::FunctionType::SignatureReturn ), + prettyName().str(), + function->partToString( KDevelop::FunctionType::SignatureArguments )); } } diff --git a/duchain/declarations/namespacealiasdeclaration.cpp b/duchain/declarations/namespacealiasdeclaration.cpp index f52012c..eb33724 100644 --- a/duchain/declarations/namespacealiasdeclaration.cpp +++ b/duchain/declarations/namespacealiasdeclaration.cpp @@ -1,78 +1,78 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2008 Milian Wolff * * * * 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 "namespacealiasdeclaration.h" #include #include #include "helper.h" namespace Php { REGISTER_DUCHAIN_ITEM(NamespaceAliasDeclaration); NamespaceAliasDeclaration::NamespaceAliasDeclaration(const NamespaceAliasDeclaration& rhs) : KDevelop::NamespaceAliasDeclaration(*new NamespaceAliasDeclarationData(*rhs.d_func())) { } NamespaceAliasDeclaration::NamespaceAliasDeclaration(const KDevelop::RangeInRevision& range, KDevelop::DUContext* context) : KDevelop::NamespaceAliasDeclaration(*new NamespaceAliasDeclarationData) { setRange(range); d_func_dynamic()->setClassId(this); if (context) { setContext(context); } } NamespaceAliasDeclaration::NamespaceAliasDeclaration(NamespaceAliasDeclarationData& data) : KDevelop::NamespaceAliasDeclaration(data) { } NamespaceAliasDeclaration::~NamespaceAliasDeclaration() { } KDevelop::Declaration* NamespaceAliasDeclaration::clonePrivate() const { return new NamespaceAliasDeclaration(*this); } KDevelop::IndexedString NamespaceAliasDeclaration::prettyName() const { return d_func()->prettyName; } void NamespaceAliasDeclaration::setPrettyName( const KDevelop::IndexedString& name ) { bool wasInSymbolTable = d_func()->m_inSymbolTable; setInSymbolTable(false); d_func_dynamic()->prettyName = name; setInSymbolTable(wasInSymbolTable); } QString NamespaceAliasDeclaration::toString() const { - return QString("Import %1 as %2").arg(d_func()->m_importIdentifier.identifier().toString()).arg(prettyName().str()); + return QStringLiteral("Import %1 as %2").arg(d_func()->m_importIdentifier.identifier().toString(), prettyName().str()); } } diff --git a/duchain/declarations/namespacedeclaration.cpp b/duchain/declarations/namespacedeclaration.cpp index aa12542..a0d1dcf 100644 --- a/duchain/declarations/namespacedeclaration.cpp +++ b/duchain/declarations/namespacedeclaration.cpp @@ -1,78 +1,78 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2008 Milian Wolff * * * * 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 "namespacedeclaration.h" #include #include #include "helper.h" namespace Php { REGISTER_DUCHAIN_ITEM(NamespaceDeclaration); NamespaceDeclaration::NamespaceDeclaration(const NamespaceDeclaration& rhs) : KDevelop::Declaration(*new NamespaceDeclarationData(*rhs.d_func())) { } NamespaceDeclaration::NamespaceDeclaration(const KDevelop::RangeInRevision& range, KDevelop::DUContext* context) : KDevelop::Declaration(*new NamespaceDeclarationData, range) { d_func_dynamic()->setClassId(this); if (context) { setContext(context); } } NamespaceDeclaration::NamespaceDeclaration(NamespaceDeclarationData& data) : KDevelop::Declaration(data) { } NamespaceDeclaration::~NamespaceDeclaration() { } KDevelop::Declaration* NamespaceDeclaration::clonePrivate() const { return new NamespaceDeclaration(*this); } KDevelop::IndexedString NamespaceDeclaration::prettyName() const { return d_func()->prettyName; } void NamespaceDeclaration::setPrettyName( const KDevelop::IndexedString& name ) { bool wasInSymbolTable = d_func()->m_inSymbolTable; setInSymbolTable(false); d_func_dynamic()->prettyName = name; setInSymbolTable(wasInSymbolTable); } QString NamespaceDeclaration::toString() const { - QString ret("namespace "); + QString ret(QStringLiteral("namespace ")); return ret + prettyName().str(); } } diff --git a/duchain/expressionevaluationresult.cpp b/duchain/expressionevaluationresult.cpp index da41e78..e109ee7 100644 --- a/duchain/expressionevaluationresult.cpp +++ b/duchain/expressionevaluationresult.cpp @@ -1,122 +1,123 @@ /* This file is part of KDevelop Copyright 2008 David Nolden 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 "expressionevaluationresult.h" #include #include #include #include #include #include #include #include "duchaindebug.h" #define ifDebug(x) using namespace KDevelop; namespace Php { ExpressionEvaluationResult::~ExpressionEvaluationResult() { } ExpressionEvaluationResult::ExpressionEvaluationResult() : m_hadUnresolvedIdentifiers(false)/*, isInstance(false)*/ { } void ExpressionEvaluationResult::setDeclaration( Declaration* declaration ) { ENSURE_CHAIN_READ_LOCKED setDeclaration(DeclarationPointer(declaration)); } void ExpressionEvaluationResult::setDeclaration( DeclarationPointer declaration) { QList decs; if (declaration) { decs << declaration; } setDeclarations(decs); } void ExpressionEvaluationResult::setDeclarations( QList< Declaration* > declarations ) { ENSURE_CHAIN_READ_LOCKED QList decs; + decs.reserve(declarations.size()); foreach(Declaration* dec, declarations) { decs << DeclarationPointer(dec); } setDeclarations(decs); } void ExpressionEvaluationResult::setDeclarations(QList declarations) { ifDebug(qCDebug(DUCHAIN) << "setting declarations" << declarations.size();) m_allDeclarations = declarations; if (!m_allDeclarations.isEmpty()) { setType(m_allDeclarations.last()->abstractType()); } else { setType(AbstractType::Ptr()); } m_allDeclarationIds.clear(); DUChainReadLocker lock(DUChain::lock()); foreach(const DeclarationPointer& dec, m_allDeclarations) { m_allDeclarationIds << dec->id(); ifDebug(qCDebug(DUCHAIN) << dec->toString();) } } AbstractType::Ptr ExpressionEvaluationResult::type() const { return m_type; } QList ExpressionEvaluationResult::allDeclarations() const { return m_allDeclarations; } QList ExpressionEvaluationResult::allDeclarationIds() const { return m_allDeclarationIds; } void ExpressionEvaluationResult::setType(AbstractType::Ptr type) { ifDebug(qCDebug(DUCHAIN) << "setting type" << (type ? type->toString() : QString("no type"));) m_type = type; } void ExpressionEvaluationResult::setHadUnresolvedIdentifiers(bool v) { m_hadUnresolvedIdentifiers = v; } bool ExpressionEvaluationResult::hadUnresolvedIdentifiers() const { return m_hadUnresolvedIdentifiers; } } diff --git a/duchain/expressionvisitor.cpp b/duchain/expressionvisitor.cpp index 48474ef..62faf29 100644 --- a/duchain/expressionvisitor.cpp +++ b/duchain/expressionvisitor.cpp @@ -1,809 +1,809 @@ /*************************************************************************** * 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 "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(0), 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("this"))) { + 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("this") ) + 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::visitVarExpressionNewObject(VarExpressionNewObjectAst *node) { DefaultVisitor::visitVarExpressionNewObject(node); if (node->className->staticIdentifier != -1) { - static const QualifiedIdentifier id("static"); + static const QualifiedIdentifier id(QStringLiteral("static")); DeclarationPointer dec = findDeclarationImport(ClassDeclarationType, id); usingDeclaration(node->className, dec); m_result.setDeclaration(dec); } else if (node->className->identifier) { const QualifiedIdentifier id = identifierForNamespace(node->className->identifier, m_editor); DeclarationPointer dec = findDeclarationImport(ClassDeclarationType, id); usingDeclaration(node->className->identifier->namespaceNameSequence->back()->element, dec); buildNamespaceUses(node->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) { FunctionType* closureType = new FunctionType; m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeVoid))); if (node->functionBody) { visitInnerStatementList(node->functionBody); } closureType->setReturnType(m_result.type()); if (node->parameters->parametersSequence) { const KDevPG::ListNode< ParameterAst* >* it = node->parameters->parametersSequence->front(); forever { AbstractType::Ptr type; if (it->element->parameterType) { //don't use openTypeFromName as it uses cursor for findDeclarations DeclarationPointer decl = findDeclarationImport(ClassDeclarationType, identifierForNamespace(it->element->parameterType, m_editor)); if (decl) { type = decl->abstractType(); } } else if (it->element->arrayType != -1) { type = AbstractType::Ptr(new IntegralType(IntegralType::TypeArray)); } else if (it->element->callableType != -1) { type = AbstractType::Ptr(new IntegralType(IntegralType::TypeMixed)); } else if (it->element->defaultValue) { ExpressionVisitor v(m_editor); it->element->defaultValue->ducontext = m_currentContext; v.visitNode(it->element->defaultValue); type = v.result().type(); } if (!type) { type = AbstractType::Ptr(new IntegralType(IntegralType::TypeMixed)); } if ( it->element->isRef != -1 ) { ReferenceType::Ptr p( new ReferenceType() ); p->setBaseType( type ); type = p.cast(); } closureType->addArgument(type); 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)); } 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(true) == "define" + if (id.toString(true) == 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 { //global function call foo(); const QualifiedIdentifier id = identifierForNamespace(node->stringFunctionNameOrClass, m_editor); DeclarationPointer 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 = 0; 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 = 0; 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()); } } else { m_result.setType(AbstractType::Ptr()); } } else { QString str(stringForNode(node->constant).toLower()); - if (str == "true" || str == "false") { + if (str == QLatin1String("true") || str == QLatin1String("false")) { m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeBoolean))); - } else if (str == "null") { + } 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) { ///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() if (m_result.type() && StructureType::Ptr::dynamicCast(m_result.type())) { DUChainReadLocker lock(DUChain::lock()); Declaration* declaration = StructureType::Ptr::staticCast(m_result.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() ) { decs << dec; ifDebug(qCDebug(DUCHAIN) << "found:" << dec->toString();) } } } m_result.setDeclarations(decs); lock.unlock(); if (!m_result.allDeclarations().isEmpty()) { if ( !m_isAssignmentExpressionEqual ) { usingDeclaration(node->objectProperty->objectDimList->variableName, m_result.allDeclarations().last()); } if (node->isFunctionCall != -1) { FunctionType::Ptr function = m_result.allDeclarations().last()->type(); if (function) { m_result.setType(function->returnType()); } else { m_result.setType(AbstractType::Ptr()); } } } else { if ( !m_isAssignmentExpressionEqual ) { usingDeclaration(node->objectProperty->objectDimList->variableName, DeclarationPointer()); } m_result.setType(AbstractType::Ptr()); } } else { m_result.setType(AbstractType::Ptr()); } } else { m_result.setType(AbstractType::Ptr()); } } } DefaultVisitor::visitVariableProperty(node); } void ExpressionVisitor::visitStaticMember(StaticMemberAst* node) { //don't call DefaultVisitor::visitStaticMember(node); //because we would end up in visitCompoundVariableWithSimpleIndirectReference if (node->variable->variable->variable) { DUContext* context = findClassContext(node->className); if (context) { DUChainReadLocker lock(DUChain::lock()); m_result.setDeclarations(context->findDeclarations(identifierForNode(node->variable->variable->variable))); lock.unlock(); if (!m_result.allDeclarations().isEmpty()) { usingDeclaration(node->variable->variable->variable, m_result.allDeclarations().last()); } else { usingDeclaration(node->variable->variable->variable, DeclarationPointer()); } } else { usingDeclaration(node->className, DeclarationPointer()); m_result.setType(AbstractType::Ptr()); } if (node->variable->offsetItemsSequence) { const KDevPG::ListNode< DimListItemAst* >* it = node->variable->offsetItemsSequence->front(); do { visitDimListItem(it->element); } 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("stdclass"); + static const QualifiedIdentifier stdclassQId(QStringLiteral("stdclass")); DUChainReadLocker lock(DUChain::lock()); m_result.setDeclarations(m_currentContext->findDeclarations(stdclassQId)); break; } case CastBool: type = IntegralType::TypeBoolean; break; case CastUnset: //TODO break; } if (type) { m_result.setType(AbstractType::Ptr(new IntegralType(type))); } } } void ExpressionVisitor::visitAdditiveExpressionRest(AdditiveExpressionRestAst* node) { DefaultVisitor::visitAdditiveExpressionRest(node); if (node->operation == OperationPlus || node->operation == OperationMinus) { m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeInt))); } else if (node->operation == OperationConcat) { m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeString))); } } void ExpressionVisitor::visitRelationalExpression(RelationalExpressionAst *node) { DefaultVisitor::visitRelationalExpression(node); if (node->instanceofType && node->instanceofType->identifier) { const QualifiedIdentifier id = identifierForNamespace(node->instanceofType->identifier, m_editor); DeclarationPointer dec = findDeclarationImport(ClassDeclarationType, id); usingDeclaration(node->instanceofType->identifier->namespaceNameSequence->back()->element, dec); buildNamespaceUses(node->instanceofType->identifier, id); m_result.setDeclaration(dec); } } 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); } } 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, 0, 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 NULL; } } diff --git a/duchain/helper.cpp b/duchain/helper.cpp index 471acae..19305ef 100644 --- a/duchain/helper.cpp +++ b/duchain/helper.cpp @@ -1,421 +1,421 @@ /*************************************************************************** * 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 "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 "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; } DeclarationPointer findDeclarationImportHelper(DUContext* currentContext, const QualifiedIdentifier& id, DeclarationType declarationType) { /// Qualified identifier for 'self' - static const QualifiedIdentifier selfQId("self"); + static const QualifiedIdentifier selfQId(QStringLiteral("self")); /// Qualified identifier for 'parent' - static const QualifiedIdentifier parentQId("parent"); + static const QualifiedIdentifier parentQId(QStringLiteral("parent")); /// Qualified identifier for 'static' - static const QualifiedIdentifier staticQId("static"); + static const QualifiedIdentifier staticQId(QStringLiteral("static")); ifDebug(qCDebug(DUCHAIN) << id.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 = 0; 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(id); if (foundDeclarations.isEmpty()) { // If it's not in the top context, try the current context (namespaces...) // this fixes the bug: https://bugs.kde.org/show_bug.cgi?id=322274 foundDeclarations = currentContext->findDeclarations(id); } if (foundDeclarations.isEmpty()) { // If it is neither in the top not the current context it might be defined in a different context // Look up with fully qualified identifier foundDeclarations = currentContext->topContext()->findDeclarations(identifierWithNamespace(id, currentContext)); } 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, id, declarationType); if (!decl) { decl = findDeclarationInPST(currentContext, identifierWithNamespace(id, currentContext), 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 = 0; 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(0) {} CommonScalarAst* node() const { return m_node; } private: virtual void visitCommonScalar(CommonScalarAst* node) { 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 == "." || str == ".." || str.endsWith('/') ) { + 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, "kdevphpsupport/phpfunctions.php")); + 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, "kdevphpsupport/phpunitdeclarations.php")); + 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; } } } diff --git a/duchain/navigation/declarationnavigationcontext.cpp b/duchain/navigation/declarationnavigationcontext.cpp index 44d4383..a3f453d 100644 --- a/duchain/navigation/declarationnavigationcontext.cpp +++ b/duchain/navigation/declarationnavigationcontext.cpp @@ -1,178 +1,178 @@ /* Copyright 2007 David Nolden 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 "declarationnavigationcontext.h" #include #include #include #include #include #include #include #include "../declarations/classdeclaration.h" #include #include #include #include "helper.h" namespace Php { using namespace KDevelop; DeclarationNavigationContext::DeclarationNavigationContext(DeclarationPointer decl, KDevelop::TopDUContextPointer topContext, AbstractNavigationContext* previousContext) : AbstractDeclarationNavigationContext(decl, topContext, previousContext) { } NavigationContextPointer DeclarationNavigationContext::registerChild(DeclarationPointer declaration) { return AbstractDeclarationNavigationContext::registerChild(new DeclarationNavigationContext(declaration, m_topContext, this)); } void DeclarationNavigationContext::htmlClass() { StructureType::Ptr klass = m_declaration->abstractType().cast(); Q_ASSERT(klass); ClassDeclaration* classDecl = dynamic_cast(klass->declaration(m_topContext.data())); if (classDecl) { // write class modifier switch (classDecl->classModifier()) { case ClassDeclarationData::Abstract: - modifyHtml() += "abstract "; + modifyHtml() += QStringLiteral("abstract "); break; case ClassDeclarationData::Final: - modifyHtml() += "final "; + modifyHtml() += QStringLiteral("final "); break; default: //nothing break; } // write class type if (classDecl->classType() == ClassDeclarationData::Interface) { - modifyHtml() += "interface "; + modifyHtml() += QStringLiteral("interface "); } else if (classDecl->classType() == ClassDeclarationData::Trait) { - modifyHtml() += "trait "; + modifyHtml() += QStringLiteral("trait "); } else { - modifyHtml() += "class "; + modifyHtml() += QStringLiteral("class "); } // write class identifier eventuallyMakeTypeLinks(m_declaration->abstractType()); // write inheritance if (classDecl->baseClassesSize() > 0) { AbstractType::Ptr extends; QList implements; FOREACH_FUNCTION(const BaseClassInstance& base, classDecl->baseClasses) { StructureType::Ptr stype = base.baseClass.type(); if (stype) { ClassDeclaration *classDecl = dynamic_cast(stype->declaration(m_topContext.data())); if (classDecl) { if (classDecl->classType() == ClassDeclarationData::Interface) { implements.append(base.baseClass.abstractType()); } else { extends = base.baseClass.abstractType(); } } } } // write parent class if (extends) { - modifyHtml() += " extends "; + modifyHtml() += QStringLiteral(" extends "); eventuallyMakeTypeLinks(extends); } // write implemented interfaces if (!implements.isEmpty()) { - modifyHtml() += " implements "; + modifyHtml() += QStringLiteral(" implements "); for (QList::iterator i = implements.begin(); ;) { eventuallyMakeTypeLinks(*i); ++i; if (i != implements.end()) { - modifyHtml() += ", "; + modifyHtml() += QStringLiteral(", "); } else { break; } } } } - modifyHtml() += " "; + modifyHtml() += QStringLiteral(" "); } } void DeclarationNavigationContext::htmlAdditionalNavigation() { if (auto member = dynamic_cast(m_declaration.data())) { Declaration *dec = member->aliasedDeclaration().data(); if (dec && dec->context() && dec->context()->owner()) { modifyHtml() += i18n("Use of %1 from %2
") .arg(createLink(prettyQualifiedIdentifier(DeclarationPointer(dec)).toString(), - QString("jump_to_used"), + QStringLiteral("jump_to_used"), NavigationAction(DeclarationPointer(dec), KDevelop::NavigationAction::NavigateDeclaration))) .arg(createLink(prettyQualifiedIdentifier(DeclarationPointer(dec->context()->owner())).toString(), - QString("jump_to_used_container"), + QStringLiteral("jump_to_used_container"), NavigationAction(DeclarationPointer(dec->context()->owner()), KDevelop::NavigationAction::NavigateDeclaration))); } } else if (auto member = dynamic_cast(m_declaration.data())) { Declaration *dec = member->aliasedDeclaration().data(); if (dec && dec->context() && dec->context()->owner()) { modifyHtml() += i18n("Use of %1 from %2
") .arg(createLink(prettyQualifiedIdentifier(DeclarationPointer(dec)).toString(), - QString("jump_to_used"), + QStringLiteral("jump_to_used"), NavigationAction(DeclarationPointer(dec), KDevelop::NavigationAction::NavigateDeclaration))) .arg(createLink(prettyQualifiedIdentifier(DeclarationPointer(dec->context()->owner())).toString(), - QString("jump_to_used_container"), + QStringLiteral("jump_to_used_container"), NavigationAction(DeclarationPointer(dec->context()->owner()), KDevelop::NavigationAction::NavigateDeclaration))); } else { modifyHtml() += i18n("Broken member alias trait."); } } KDevelop::AbstractDeclarationNavigationContext::htmlAdditionalNavigation(); } QualifiedIdentifier DeclarationNavigationContext::prettyQualifiedIdentifier( DeclarationPointer decl ) const { return QualifiedIdentifier(prettyName(decl.data())); } void DeclarationNavigationContext::makeLink(const QString& name, DeclarationPointer declaration, NavigationAction::Type actionType) { if ( actionType == NavigationAction::JumpToSource && declaration->url() == internalFunctionFile() ) { modifyHtml() += i18n("PHP internal"); return; } AbstractDeclarationNavigationContext::makeLink(name, declaration, actionType); } QString DeclarationNavigationContext::declarationKind(DeclarationPointer decl) { if ( decl->kind() == Declaration::Instance && decl->abstractType() && decl->abstractType()->modifiers() & AbstractType::ConstModifier ) { return i18nc("kind of a php-constant, as shown in the declaration tooltip", "Constant"); } return AbstractDeclarationNavigationContext::declarationKind(decl); } } diff --git a/duchain/navigation/magicconstantnavigationcontext.cpp b/duchain/navigation/magicconstantnavigationcontext.cpp index 0a7849e..f3434de 100644 --- a/duchain/navigation/magicconstantnavigationcontext.cpp +++ b/duchain/navigation/magicconstantnavigationcontext.cpp @@ -1,124 +1,124 @@ /* Copyright 2009 Milian Wolff This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "magicconstantnavigationcontext.h" #include #include #include #include #include using namespace KDevelop; namespace Php { MagicConstantNavigationContext::MagicConstantNavigationContext(TopDUContextPointer topContext, const KTextEditor::Cursor& position, const QString& constant) : AbstractNavigationContext(topContext, 0), m_position(position.line(), position.column()), m_constant(constant) { } QString MagicConstantNavigationContext::name() const { return m_constant; } DUContext* findContext(TopDUContextPointer topCtx, const CursorInRevision& pos, DUContext::ContextType type) { DUContext* ctx = topCtx->findContextAt(pos); while ( ctx && ctx->type() != type ) { ctx = ctx->parentContext(); } if ( !ctx || ctx->type() != type ) { return 0; } else { return ctx; } } QString MagicConstantNavigationContext::html(bool /*shorten*/) { - QString html = "

"; + QString html = QStringLiteral("

"); html += typeHighlight(i18n("magic constant")); html += ' '; html += nameHighlight(m_constant.toHtmlEscaped()); - html += "
\n"; + html += QLatin1String("
\n"); QString value; ///TODO: php 5.3: __DIR__, __NAMESPACE__ - if ( m_constant == "__FILE__" ) { + if ( m_constant == QLatin1String("__FILE__") ) { value = m_topContext->url().str().toHtmlEscaped(); - } else if ( m_constant == "__LINE__" ) { + } else if ( m_constant == QLatin1String("__LINE__") ) { value.setNum(m_position.line + 1); - } else if ( m_constant == "__CLASS__" ) { + } else if ( m_constant == QLatin1String("__CLASS__") ) { if ( DUContext* ctx = findContext(m_topContext, m_position, DUContext::Class) ) { value = codeHighlight(ctx->localScopeIdentifier().toString().toHtmlEscaped()); } else { value = commentHighlight(i18n("empty (not inside a class)")); } - } else if ( m_constant == "__METHOD__" ) { + } else if ( m_constant == QLatin1String("__METHOD__") ) { CursorInRevision pos = m_position; while ( DUContext* ctx = findContext(m_topContext, pos, DUContext::Other) ) { if ( !ctx->parentContext() ) { break; } if ( ctx->parentContext()->type() == DUContext::Class ) { value = codeHighlight(QString( ctx->parentContext()->localScopeIdentifier().toString() + "::" + ctx->localScopeIdentifier().toString() ).toHtmlEscaped()); break; } // might be a "normal" function inside a method... pos = ctx->range().start; } if ( value.isEmpty() ) { value = commentHighlight(i18n("empty (not inside a method)")); } - } else if ( m_constant == "__FUNCTION__" ) { + } else if ( m_constant == QLatin1String("__FUNCTION__") ) { CursorInRevision pos = m_position; if ( DUContext* ctx = findContext(m_topContext, pos, DUContext::Other) ) { if ( ctx->owner() && ctx->owner()->type() ) { value = codeHighlight(ctx->localScopeIdentifier().toString().toHtmlEscaped()); } } if ( value.isEmpty() ) { value = commentHighlight(i18n("empty (not inside a function)")); } - } else if ( m_constant == "__NAMESPACE__" ) { + } else if ( m_constant == QLatin1String("__NAMESPACE__") ) { if ( DUContext* ctx = findContext(m_topContext, m_position, DUContext::Namespace) ) { if ( ctx->owner() && ctx->owner()->kind() == Declaration::Namespace ) { value = codeHighlight(ctx->localScopeIdentifier().toString().toHtmlEscaped()); } } if ( value.isEmpty() ) { value = commentHighlight(i18n("empty (not inside a namespace)")); } } html += i18n("current value: %1", value); - html += "

"; + html += QLatin1String("

"); return html; } } diff --git a/duchain/tests/benchmarks.cpp b/duchain/tests/benchmarks.cpp index 5bb63db..6ae68b8 100644 --- a/duchain/tests/benchmarks.cpp +++ b/duchain/tests/benchmarks.cpp @@ -1,101 +1,101 @@ /* This file is part of KDevelop Copyright 2009 Milian Wolff This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "benchmarks.h" #include #include #include #include #include "../../parser/parsesession.h" #include "../builders/declarationbuilder.h" #include "../builders/usebuilder.h" using namespace KDevelop; QTEST_MAIN(Php::Benchmarks) namespace Php { Benchmarks::Benchmarks() { } QIODevice* getInternalFile() { QIODevice* file = new QFile(internalFunctionFile().str()); bool opened = file->open(QIODevice::ReadOnly); Q_ASSERT(opened); Q_UNUSED(opened); return file; } void Benchmarks::parser() { QIODevice* file = getInternalFile(); QBENCHMARK { ParseSession session = ParseSession(); session.setContents(file->readAll()); StartAst* ast = 0; session.parse(&ast); } delete file; } void Benchmarks::declarationBuilder() { QIODevice* file = getInternalFile(); ParseSession session; session.setContents(file->readAll()); delete file; StartAst* ast = 0; session.parse(&ast); EditorIntegrator editor(&session); QBENCHMARK { DeclarationBuilder builder(&editor); builder.build(internalFunctionFile(), ast); } } void Benchmarks::useBuilder() { - const auto document = IndexedString(QUrl("file:///internal/BigTestFile.php")); + const auto document = IndexedString(QUrl(QStringLiteral("file:///internal/BigTestFile.php"))); QIODevice* file = getInternalFile(); ParseSession session = ParseSession(); session.setCurrentDocument(document); session.setContents(file->readAll()); delete file; StartAst* ast = 0; session.parse(&ast); EditorIntegrator editor(&session); DeclarationBuilder builder(&editor); KDevelop::ReferencedTopDUContext chain = builder.build(document, ast); QBENCHMARK { UseBuilder useBuilder(&editor); useBuilder.buildUses(ast); } } } diff --git a/duchain/tests/duchain.cpp b/duchain/tests/duchain.cpp index b0b62f8..8144487 100644 --- a/duchain/tests/duchain.cpp +++ b/duchain/tests/duchain.cpp @@ -1,2925 +1,2925 @@ /* This file is part of KDevelop Copyright 2008 Niko Sams This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "duchain.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "helper.h" #include "../declarations/classdeclaration.h" #include "../declarations/classmethoddeclaration.h" #include "../declarations/functiondeclaration.h" #include "../declarations/variabledeclaration.h" #include "../types/structuretype.h" #include "../types/integraltypeextended.h" #include using namespace KDevelop; using namespace Php; QTEST_MAIN(Php::TestDUChain) TestDUChain::TestDUChain() { } void TestDUChain::declareFunction() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("childContexts().count(), 2); QCOMPARE(top->localDeclarations().count(), 1); Declaration* dec = top->localDeclarations().at(0); QVERIFY(dec); QCOMPARE(dec->context(), top); QCOMPARE(dec->internalContext(), top->childContexts().at(1)); // no return means void as return type FunctionType::Ptr ftype = FunctionType::Ptr::dynamicCast(dec->abstractType()); QVERIFY(ftype); IntegralType::Ptr itype = IntegralType::Ptr::dynamicCast(ftype->returnType()); QVERIFY(itype->dataType() == IntegralType::TypeVoid); QCOMPARE(top->childContexts().at(0)->type(), DUContext::Function); QCOMPARE(top->childContexts().at(1)->type(), DUContext::Other); } void TestDUChain::declareVar() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("parentContext()); QCOMPARE(top->childContexts().count(), 2); QCOMPARE(top->localDeclarations().count(), 5); //class A Declaration* dec = top->localDeclarations().at(0); QCOMPARE(dec->uses().count(), 1); QCOMPARE(dec->uses().begin()->count(), 1); //$i Declaration* decVar = top->localDeclarations().at(2); QCOMPARE(decVar->identifier(), Identifier("i")); qDebug() << decVar->abstractType()->toString(); UnsureType::Ptr unsureType = decVar->type(); QVERIFY(unsureType); QCOMPARE(unsureType->typesSize(), 3u); // = new A(); QCOMPARE(unsureType->types()[0].abstractType().cast()->qualifiedIdentifier(), QualifiedIdentifier("a")); QVERIFY(unsureType->types()[0].abstractType()->equals(dec->abstractType().data())); // = new B(); //class B dec = top->localDeclarations().at(1); QCOMPARE(dec->uses().count(), 1); QCOMPARE(dec->uses().begin()->count(), 2); QCOMPARE(unsureType->types()[1].abstractType().cast()->qualifiedIdentifier(), QualifiedIdentifier("b")); QVERIFY(unsureType->types()[1].abstractType()->equals(dec->abstractType().data())); // = 'foo'; QVERIFY(unsureType->types()[2].abstractType().cast()); QVERIFY(unsureType->types()[2].abstractType().cast()->dataType() == IntegralType::TypeString); //$j decVar = top->localDeclarations().at(3); QCOMPARE(decVar->identifier(), Identifier("j")); StructureType::Ptr classType = decVar->type(); QVERIFY(classType); QCOMPARE(classType->qualifiedIdentifier(), QualifiedIdentifier("b")); QVERIFY(classType->equals(dec->abstractType().data())); // $a decVar = top->localDeclarations().at(4); QCOMPARE(decVar->identifier(), Identifier("a")); QVERIFY(decVar->type()); } void TestDUChain::varTypehint() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().at(0); //$i Declaration* decVar = top->localDeclarations().at(1); QCOMPARE(decVar->identifier(), Identifier("i")); StructureType::Ptr classType = decVar->type(); QVERIFY(classType); QCOMPARE(classType->qualifiedIdentifier(), QualifiedIdentifier("a")); QVERIFY(classType->equals(dec->abstractType().data())); } void TestDUChain::declareClass() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("parentContext()); QCOMPARE(top->childContexts().count(), 1); DUContext* contextClassA = top->childContexts().first(); QCOMPARE(top->localDeclarations().count(), 1); Declaration* dec = top->localDeclarations().first(); QCOMPARE(dec->kind(), Declaration::Type); QCOMPARE(dec->toString(), QString("class A")); QCOMPARE(dec->qualifiedIdentifier(), QualifiedIdentifier("a")); QCOMPARE(dec->isDefinition(), true); QCOMPARE(dec->logicalInternalContext(top), contextClassA); qDebug() << contextClassA->localScopeIdentifier().toString(); QCOMPARE(contextClassA->localScopeIdentifier(), QualifiedIdentifier("a")); QCOMPARE(contextClassA->childContexts().count(), 8); QCOMPARE(contextClassA->childContexts().first()->localScopeIdentifier(), QualifiedIdentifier("foo")); DUContext* contextMethodBodyFoo = contextClassA->childContexts().at(1); QCOMPARE(contextMethodBodyFoo->localScopeIdentifier(), QualifiedIdentifier("foo")); QCOMPARE(contextMethodBodyFoo->importedParentContexts().count(), 1); QCOMPARE(contextMethodBodyFoo->childContexts().count(), 0); QVERIFY(contextMethodBodyFoo->importedParentContexts().first().context(top) == contextClassA->childContexts().first()); //foo() dec = contextClassA->localDeclarations().at(0); ClassFunctionDeclaration* funDec = dynamic_cast(dec); QVERIFY(funDec); QCOMPARE(funDec->kind(), Declaration::Type); QCOMPARE(funDec->identifier(), Identifier("foo")); QCOMPARE(funDec->accessPolicy(), Declaration::Public); QCOMPARE(funDec->isStatic(), false); { // no return means void as return type FunctionType::Ptr ftype = FunctionType::Ptr::dynamicCast(dec->abstractType()); QVERIFY(ftype); IntegralType::Ptr itype = IntegralType::Ptr::dynamicCast(ftype->returnType()); QVERIFY(itype->dataType() == IntegralType::TypeVoid); } //bar() dec = contextClassA->localDeclarations().at(1); funDec = dynamic_cast(dec); QVERIFY(funDec); QCOMPARE(funDec->identifier(), Identifier("bar")); QCOMPARE(funDec->accessPolicy(), Declaration::Protected); QCOMPARE(funDec->isStatic(), true); //baz() dec = contextClassA->localDeclarations().at(2); funDec = dynamic_cast(dec); QVERIFY(funDec); QCOMPARE(funDec->identifier(), Identifier("baz")); QCOMPARE(funDec->accessPolicy(), Declaration::Private); QCOMPARE(funDec->isStatic(), false); //boo() dec = contextClassA->localDeclarations().at(3); funDec = dynamic_cast(dec); QVERIFY(funDec); QCOMPARE(funDec->identifier(), Identifier("boo")); QCOMPARE(funDec->accessPolicy(), Declaration::Public); } void TestDUChain::classMemberVar() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("parentContext()); QCOMPARE(top->childContexts().count(), 1); DUContext* contextClassA = top->childContexts().first(); QCOMPARE(top->localDeclarations().count(), 1); Declaration* dec = top->localDeclarations().first(); QCOMPARE(dec->qualifiedIdentifier(), QualifiedIdentifier("a")); QCOMPARE(dec->isDefinition(), true); QCOMPARE(dec->logicalInternalContext(top), contextClassA); QCOMPARE(contextClassA->localScopeIdentifier(), QualifiedIdentifier("a")); QCOMPARE(contextClassA->childContexts().count(), 0); QCOMPARE(contextClassA->localDeclarations().count(), 4); //$foo ClassMemberDeclaration* var = dynamic_cast(contextClassA->localDeclarations().first()); QVERIFY(var); QCOMPARE(var->identifier(), Identifier("foo")); QCOMPARE(var->accessPolicy(), Declaration::Public); QCOMPARE(var->isStatic(), false); QVERIFY(var->type()); QVERIFY(var->type()->dataType() == IntegralType::TypeMixed); //$bar var = dynamic_cast(contextClassA->localDeclarations().at(1)); QVERIFY(var); QCOMPARE(var->identifier(), Identifier("bar")); QCOMPARE(var->accessPolicy(), Declaration::Protected); QCOMPARE(var->isStatic(), false); StructureType::Ptr type = var->type(); QVERIFY(type); QCOMPARE(type->qualifiedIdentifier(), QualifiedIdentifier("a")); //$baz var = dynamic_cast(contextClassA->localDeclarations().at(2)); QVERIFY(var); QCOMPARE(var->identifier(), Identifier("baz")); QCOMPARE(var->accessPolicy(), Declaration::Private); QCOMPARE(var->isStatic(), true); QVERIFY(var->type()); QVERIFY(var->type()->dataType() == IntegralType::TypeString); //$boo var = dynamic_cast(contextClassA->localDeclarations().at(3)); QVERIFY(var); QCOMPARE(var->identifier(), Identifier("boo")); QCOMPARE(var->accessPolicy(), Declaration::Public); QCOMPARE(var->isStatic(), false); QVERIFY(var->type()); QVERIFY(var->type()->dataType() == IntegralType::TypeInt); } void TestDUChain::returnTypeClass() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("parentContext()); QCOMPARE(top->childContexts().count(), 5); QCOMPARE(top->localDeclarations().count(), 3); Declaration* dec = top->localDeclarations().at(1); QCOMPARE(dec->qualifiedIdentifier(), QualifiedIdentifier("foo")); FunctionType::Ptr functionType = dec->type(); QVERIFY(functionType); StructureType::Ptr retType = StructureType::Ptr::dynamicCast(functionType->returnType()); QVERIFY(retType); QCOMPARE(retType->qualifiedIdentifier(), QualifiedIdentifier("a")); dec = top->localDeclarations().at(2); QCOMPARE(dec->qualifiedIdentifier(), QualifiedIdentifier("bar")); functionType = dec->type(); QVERIFY(functionType); retType = StructureType::Ptr::dynamicCast(functionType->returnType()); QVERIFY(retType); QCOMPARE(retType->qualifiedIdentifier(), QualifiedIdentifier("a")); } void TestDUChain::declarationReturnType() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("parentContext()); QCOMPARE(top->childContexts().count(), 3); QCOMPARE(top->localDeclarations().count(), 3); Declaration* dec = top->localDeclarations().at(1); FunctionType::Ptr fType = dec->type(); QVERIFY(fType); QVERIFY(StructureType::Ptr::dynamicCast(fType->returnType())); QCOMPARE(StructureType::Ptr::dynamicCast(fType->returnType())->qualifiedIdentifier(), QualifiedIdentifier("a")); dec = top->localDeclarations().at(2); QCOMPARE(dec->identifier(), Identifier("i")); StructureType::Ptr type = dec->type(); QVERIFY(type); QCOMPARE(type->qualifiedIdentifier(), QualifiedIdentifier("a")); } void TestDUChain::declarationReturnTypeInRecursingFunction() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method(" decs = top->childContexts().last()->findDeclarations(Identifier("i")); + QList< Declaration* > decs = top->childContexts().last()->findDeclarations(Identifier(QStringLiteral("i"))); QCOMPARE(decs.size(), 1); Declaration* dec = decs.first(); StructureType::Ptr type = dec->type(); QVERIFY(type); QCOMPARE(type->qualifiedIdentifier(), QualifiedIdentifier("a")); } void TestDUChain::declarationMultipleReturnTypes() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().at(1)->type(); QVERIFY(fType); qDebug() << fType->toString(); TypePtr ut = UnsureType::Ptr::dynamicCast(fType->returnType()); QVERIFY(ut); QCOMPARE(2u, ut->typesSize()); ///TODO: why are the types not in the correct order, i.e. null, A QVERIFY(ut->types()[0].type()); QVERIFY(ut->types()[0].type()->declaration(top)); QCOMPARE(ut->types()[0].type()->declaration(top)->qualifiedIdentifier(), QualifiedIdentifier("a")); QVERIFY(ut->types()[1].type()); QVERIFY(ut->types()[1].type()->dataType() == IntegralType::TypeNull); fType = top->localDeclarations().at(2)->type(); QVERIFY(fType); qDebug() << fType->toString(); QVERIFY(IntegralType::Ptr::dynamicCast(fType->returnType())); QVERIFY(IntegralType::Ptr::dynamicCast(fType->returnType())->dataType() == IntegralType::TypeInt); } void TestDUChain::returnTypeViaMember() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("fa($param); }\n" " function fb2($param) { $i = $this->anormal->fa($param); } }"); TopDUContext* top = parse(method, DumpNone); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); QVector decs = top->localDeclarations(); QCOMPARE(decs.size(), 2); ClassDeclaration* aDec = dynamic_cast(decs.first()); QVERIFY(aDec); ClassDeclaration* bDec = dynamic_cast(decs.last()); QVERIFY(bDec); QCOMPARE(bDec->logicalInternalContext(top)->localDeclarations().size(), 4); typedef QPair idPair; foreach ( const idPair & pair, QList< idPair >() << qMakePair(QString("fb1"), QString("astatic")) << qMakePair(QString("fb2"), QString("anormal")) ) { qDebug() << pair.first << pair.second; ClassMethodDeclaration* fDec = dynamic_cast( bDec->logicalInternalContext(top)->findDeclarations(Identifier(pair.first)).first() ); QVERIFY(fDec); ClassMemberDeclaration* mDec = dynamic_cast( bDec->logicalInternalContext(top)->findDeclarations(Identifier(pair.second)).first() ); QVERIFY(mDec); QVERIFY(mDec->type()); QCOMPARE(mDec->type()->declaration(top), aDec); QCOMPARE(fDec->logicalInternalContext(top)->localDeclarations().size(), 1); Declaration* iDec = fDec->logicalInternalContext(top)->localDeclarations().first(); QCOMPARE(iDec->identifier().toString(), QString("i")); QVERIFY(iDec->type()); QCOMPARE(iDec->type()->declaration(top), aDec); } } void TestDUChain::declarationReturnTypeDocBlock() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("childContexts().at(0)->localDeclarations().at(0); FunctionType::Ptr fType = dec->type(); QVERIFY(fType); QVERIFY(StructureType::Ptr::dynamicCast(fType->returnType())); QCOMPARE(StructureType::Ptr::dynamicCast(fType->returnType())->qualifiedIdentifier(), QualifiedIdentifier("a")); //function foo dec = top->localDeclarations().at(2); fType = dec->type(); QVERIFY(fType); QVERIFY(StructureType::Ptr::dynamicCast(fType->returnType())); QCOMPARE(StructureType::Ptr::dynamicCast(fType->returnType())->qualifiedIdentifier(), QualifiedIdentifier("a")); //function bar dec = top->localDeclarations().at(3); fType = dec->type(); QVERIFY(fType); QVERIFY(StructureType::Ptr::dynamicCast(fType->returnType())); QCOMPARE(StructureType::Ptr::dynamicCast(fType->returnType())->qualifiedIdentifier(), QualifiedIdentifier("stdclass")); //test hint in internal functions file of a type that is added later on // function - QList decs = top->findDeclarations(Identifier("should_return_exception")); + QList decs = top->findDeclarations(Identifier(QStringLiteral("should_return_exception"))); QCOMPARE(decs.size(), 1); dec = decs.first(); fType = dec->type(); QVERIFY(fType); QVERIFY(StructureType::Ptr::dynamicCast(fType->returnType())); QCOMPARE(StructureType::Ptr::dynamicCast(fType->returnType())->qualifiedIdentifier(), QualifiedIdentifier("exception")); // method - decs = top->findDeclarations(Identifier("internal_test_class")); + decs = top->findDeclarations(Identifier(QStringLiteral("internal_test_class"))); QCOMPARE(decs.size(), 1); ClassDeclaration* cdec = dynamic_cast(decs.first()); QVERIFY(cdec); - decs = cdec->logicalInternalContext(top)->findDeclarations(Identifier("should_return_exception")); + decs = cdec->logicalInternalContext(top)->findDeclarations(Identifier(QStringLiteral("should_return_exception"))); QCOMPARE(decs.size(), 1); dec = decs.first(); fType = dec->type(); QVERIFY(fType); QVERIFY(StructureType::Ptr::dynamicCast(fType->returnType())); QCOMPARE(StructureType::Ptr::dynamicCast(fType->returnType())->qualifiedIdentifier(), QualifiedIdentifier("exception")); } void TestDUChain::declarationReturnTypeDocBlockIntegral() { QByteArray method("localDeclarations().at(0)->type(); QVERIFY(fType); QVERIFY(IntegralType::Ptr::dynamicCast(fType->returnType())); QVERIFY(IntegralType::Ptr::dynamicCast(fType->returnType())->dataType() == IntegralType::TypeString); //function bar fType = top->localDeclarations().at(1)->type(); QVERIFY(fType); QVERIFY(IntegralType::Ptr::dynamicCast(fType->returnType())); QVERIFY(IntegralType::Ptr::dynamicCast(fType->returnType())->dataType() == IntegralType::TypeMixed); //function aaa fType = top->childContexts().at(4)->localDeclarations().first()->type(); QVERIFY(fType); QVERIFY(IntegralType::Ptr::dynamicCast(fType->returnType())); QVERIFY(IntegralType::Ptr::dynamicCast(fType->returnType())->dataType() == IntegralType::TypeInt); } void TestDUChain::declarationReturnTypeClassChain() { QByteArray method("childContexts().first(); QCOMPARE(ctx->type(), DUContext::Class); QVERIFY(ctx->owner()); QVERIFY(StructureType::Ptr::dynamicCast(ctx->owner()->abstractType())); //function a // FIXME QEXPECT_FAIL("", "This test fails after porting the plugin to KF5.", Abort); QVERIFY(/* func a (this) */ ctx->localDeclarations().at(0)->type().data() == ctx->owner()->abstractType().data()); QVERIFY(/* func b (self) */ ctx->localDeclarations().at(1)->type().data() == ctx->owner()->abstractType().data()); } void TestDUChain::declareTypehintFunction() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("parentContext()); QCOMPARE(top->childContexts().count(), 3); QCOMPARE(top->localDeclarations().count(), 2); Declaration* dec = top->localDeclarations().at(0); QCOMPARE(dec->internalContext(), top->childContexts().at(0)); QCOMPARE(dec->uses().count(), 1); QCOMPARE(dec->uses().begin()->count(), 1); QCOMPARE(top->childContexts().at(0)->localScopeIdentifier(), QualifiedIdentifier("a")); QCOMPARE(top->childContexts().at(0)->childContexts().count(), 0); DUContext* contextFunctionFoo = top->childContexts().at(1); QCOMPARE(contextFunctionFoo->localScopeIdentifier(), QualifiedIdentifier("foo")); DUContext* contextFunctionBodyFoo = top->childContexts().at(2); QCOMPARE(contextFunctionBodyFoo->localScopeIdentifier(), QualifiedIdentifier("foo")); QCOMPARE(contextFunctionBodyFoo->importedParentContexts().count(), 1); QCOMPARE(contextFunctionBodyFoo->childContexts().count(), 0); QVERIFY(contextFunctionBodyFoo->importedParentContexts().first().context(top) == contextFunctionFoo); QVERIFY(top->childContexts().at(1)->localDeclarations().first()->type()); QCOMPARE(top->childContexts().at(1)->localDeclarations().first()->type()->qualifiedIdentifier(), QualifiedIdentifier("a")); FunctionType::Ptr fType = top->localDeclarations().at(1)->type(); QVERIFY(fType); QVERIFY(StructureType::Ptr::dynamicCast(fType->returnType())); QCOMPARE(StructureType::Ptr::dynamicCast(fType->returnType())->qualifiedIdentifier(), QualifiedIdentifier("a")); } void TestDUChain::declareTypehintArrayFunction() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().first()->type(); QVERIFY(fun); QCOMPARE(fun->arguments().count(), 1); QVERIFY(IntegralType::Ptr::dynamicCast(fun->arguments().first())); QVERIFY(IntegralType::Ptr::dynamicCast(fun->arguments().first())->dataType() == IntegralType::TypeArray); IntegralType::Ptr type = top->childContexts().first()->localDeclarations().first()->type(); QVERIFY(type); QVERIFY(type->dataType() == IntegralType::TypeArray); } void TestDUChain::declareTypehintCallableFunction() { // 0 1 2 3 // 0123456789012345678901234567890123 QByteArray method("localDeclarations().first()->type(); QVERIFY(fun); QCOMPARE(fun->arguments().count(), 1); QVERIFY(IntegralType::Ptr::dynamicCast(fun->arguments().first())); QVERIFY(IntegralType::Ptr::dynamicCast(fun->arguments().first())->dataType() == IntegralType::TypeMixed); IntegralType::Ptr type = top->childContexts().first()->localDeclarations().first()->type(); QVERIFY(type); QVERIFY(type->dataType() == IntegralType::TypeMixed); } void TestDUChain::classImplementsInterface() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("childContexts().count(), 2); QCOMPARE(top->localDeclarations().count(), 2); //interface I Declaration* dec = top->localDeclarations().at(0); QVERIFY(dec->isDefinition()); QCOMPARE(dec->identifier(), Identifier("i")); QCOMPARE(dec->toString(), QString("interface I")); StructureType::Ptr typeI = dec->type(); QCOMPARE(typeI->qualifiedIdentifier(), QualifiedIdentifier("i")); QVERIFY(typeI->declaration(top) == dec); ClassDeclaration* classDec = dynamic_cast(dec); QVERIFY(classDec); QCOMPARE(classDec->classType(), ClassDeclarationData::Interface); QCOMPARE(dec->internalContext(), top->childContexts().at(0)); QCOMPARE(dec->internalContext()->childContexts().count(), 0); QCOMPARE(dec->internalContext()->importedParentContexts().count(), 0); QCOMPARE(dec->internalContext()->localScopeIdentifier(), QualifiedIdentifier("i")); QCOMPARE(dec->uses().count(), 1); QCOMPARE(dec->uses().begin()->count(), 1); IndexedType indexedTypeI = classDec->indexedType(); //class A dec = top->localDeclarations().at(1); QVERIFY(dec->isDefinition()); QCOMPARE(dec->identifier(), Identifier("a")); StructureType::Ptr typeA = dec->type(); QCOMPARE(typeA->qualifiedIdentifier(), QualifiedIdentifier("a")); QVERIFY(typeA->declaration(top) == dec); classDec = dynamic_cast(dec); QVERIFY(classDec); QCOMPARE(classDec->classType(), ClassDeclarationData::Class); QCOMPARE(dec->internalContext(), top->childContexts().at(1)); QCOMPARE(dec->internalContext()->childContexts().count(), 0); QCOMPARE(dec->internalContext()->localScopeIdentifier(), QualifiedIdentifier("a")); //class A imports interface I context QCOMPARE(dec->internalContext()->importedParentContexts().count(), 1); QVERIFY(dec->internalContext()->importedParentContexts().at(0).context(top) == top->childContexts().at(0)); QCOMPARE(classDec->baseClassesSize(), 1u); QCOMPARE(classDec->baseClasses()[0].baseClass, indexedTypeI); QCOMPARE(dec->uses().count(), 0); } void TestDUChain::classExtends() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("childContexts().count(), 2); QCOMPARE(top->localDeclarations().count(), 2); //class A Declaration* dec = top->localDeclarations().at(0); QVERIFY(dec->isDefinition()); QCOMPARE(dec->identifier(), Identifier("a")); StructureType::Ptr typeA = dec->type(); QCOMPARE(typeA->qualifiedIdentifier(), QualifiedIdentifier("a")); QVERIFY(typeA->declaration(top) == dec); ClassDeclaration* classDec = dynamic_cast(dec); QVERIFY(classDec); QCOMPARE(classDec->classType(), ClassDeclarationData::Class); QCOMPARE(dec->internalContext(), top->childContexts().at(0)); QCOMPARE(dec->internalContext()->childContexts().count(), 0); QCOMPARE(dec->internalContext()->importedParentContexts().count(), 0); QCOMPARE(dec->internalContext()->localScopeIdentifier(), QualifiedIdentifier("a")); QCOMPARE(dec->uses().count(), 1); QCOMPARE(dec->uses().begin()->count(), 1); IndexedType indexedTypeA = classDec->indexedType(); //class B dec = top->localDeclarations().at(1); QVERIFY(dec->isDefinition()); QCOMPARE(dec->identifier(), Identifier("b")); StructureType::Ptr typeB = dec->type(); QCOMPARE(typeB->qualifiedIdentifier(), QualifiedIdentifier("b")); QVERIFY(typeB->declaration(top) == dec); classDec = dynamic_cast(dec); QVERIFY(classDec); QCOMPARE(classDec->classType(), ClassDeclarationData::Class); QCOMPARE(dec->internalContext(), top->childContexts().at(1)); QCOMPARE(dec->internalContext()->childContexts().count(), 0); QCOMPARE(dec->internalContext()->localScopeIdentifier(), QualifiedIdentifier("b")); //class B imports class A context QCOMPARE(dec->internalContext()->importedParentContexts().count(), 1); QVERIFY(dec->internalContext()->importedParentContexts().at(0).context(top) == top->childContexts().at(0)); QCOMPARE(classDec->baseClassesSize(), 1u); QCOMPARE(classDec->baseClasses()[0].baseClass, indexedTypeA); QCOMPARE(dec->uses().count(), 0); } void TestDUChain::staticMethod() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().at(2)->type(); QVERIFY(type); QCOMPARE(type->qualifiedIdentifier(), QualifiedIdentifier("b")); } void TestDUChain::ownStaticMethod() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("childContexts().at(1)); QVERIFY(top->childContexts().at(1)->localDeclarations().at(0)); QVERIFY(top->childContexts().at(1)->localDeclarations().at(0)->type()); AbstractType::Ptr ret = top->childContexts().at(1)->localDeclarations().at(0) ->type()->returnType(); QVERIFY(StructureType::Ptr::dynamicCast(ret)); QCOMPARE(StructureType::Ptr::dynamicCast(ret)->declaration(top), top->localDeclarations().at(0)); QVERIFY(top->childContexts().at(1)->childContexts().at(1 + 2)); QVERIFY(top->childContexts().at(1)->childContexts().at(1 + 2)->localDeclarations().at(0)); QVERIFY(top->childContexts().at(1)->childContexts().at(1 + 2)->localDeclarations().at(0)->type()); QCOMPARE(top->childContexts().at(1)->childContexts().at(1 + 2)->localDeclarations().at(0) ->type()->qualifiedIdentifier(), QualifiedIdentifier("b")); QCOMPARE(top->childContexts().at(1)->childContexts().at(1 + 2)->localDeclarations().at(1) ->type()->qualifiedIdentifier(), QualifiedIdentifier("b")); } void TestDUChain::thisVar() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("x(); } } "); TopDUContext* top = parse(method, DumpNone); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); FunctionType::Ptr fn = top->childContexts().at(0)->localDeclarations().at(0)->type(); QVERIFY(fn); StructureType::Ptr cls = StructureType::Ptr::dynamicCast(fn->returnType()); QVERIFY(cls); QCOMPARE(cls->qualifiedIdentifier(), QualifiedIdentifier("a")); fn = top->childContexts().at(0)->localDeclarations().at(1)->type(); QVERIFY(fn); cls = StructureType::Ptr::dynamicCast(fn->returnType()); QVERIFY(cls); QCOMPARE(cls->qualifiedIdentifier(), QualifiedIdentifier("a")); } void TestDUChain::objectFunctionCall() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("x(); } } "); TopDUContext* top = parse(method, DumpNone); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); FunctionType::Ptr fn = top->childContexts().at(1)->localDeclarations().at(0)->type(); QVERIFY(fn); StructureType::Ptr cls = StructureType::Ptr::dynamicCast(fn->returnType()); QVERIFY(cls); QCOMPARE(cls->qualifiedIdentifier(), QualifiedIdentifier("b")); fn = top->childContexts().at(1)->localDeclarations().at(1)->type(); QVERIFY(fn); cls = StructureType::Ptr::dynamicCast(fn->returnType()); QVERIFY(cls); QCOMPARE(cls->qualifiedIdentifier(), QualifiedIdentifier("b")); } void TestDUChain::objectFunctionCall2() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("x()->c(); } } "); TopDUContext* top = parse(method, DumpNone); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); FunctionType::Ptr fn = top->childContexts().at(2)->localDeclarations().at(1)->type(); QVERIFY(fn); StructureType::Ptr cls = StructureType::Ptr::dynamicCast(fn->returnType()); QVERIFY(cls); QCOMPARE(cls->qualifiedIdentifier(), QualifiedIdentifier("c")); } void TestDUChain::objectFunctionCall3() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("b();"); TopDUContext* top = parse(method, DumpNone); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(top->localDeclarations().at(2)->qualifiedIdentifier(), QualifiedIdentifier("i")); QCOMPARE(top->localDeclarations().at(2)->type()->qualifiedIdentifier(), QualifiedIdentifier("a"));; QCOMPARE(top->localDeclarations().at(3)->qualifiedIdentifier(), QualifiedIdentifier("j")); QCOMPARE(top->localDeclarations().at(3)->type()->qualifiedIdentifier(), QualifiedIdentifier("b"));; } void TestDUChain::objectVariable() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("foo;"); TopDUContext* top = parse(method, DumpNone); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(top->localDeclarations().at(3)->qualifiedIdentifier(), QualifiedIdentifier("i")); QCOMPARE(top->localDeclarations().at(3)->type()->qualifiedIdentifier(), QualifiedIdentifier("b"));; } void TestDUChain::staticMemberVariable() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().at(2)->qualifiedIdentifier(), QualifiedIdentifier("i")); QCOMPARE(top->localDeclarations().at(2)->type()->qualifiedIdentifier(), QualifiedIdentifier("b"));; } void TestDUChain::ownStaticMemberVariable() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("childContexts().at(1)->childContexts().at(1); QCOMPARE(barContext->localDeclarations().at(0)->type()->qualifiedIdentifier(), QualifiedIdentifier("b")); QCOMPARE(barContext->localDeclarations().at(1)->type()->qualifiedIdentifier(), QualifiedIdentifier("b")); } void TestDUChain::classConst_data() { QTest::addColumn("classBody"); QTest::addColumn("problems"); QTest::newRow("int") << "const C = 1;" << 0; QTest::newRow("string") << "const C = 'asdf';" << 0; QTest::newRow("float") << "const C = 0.5;" << 0; QTest::newRow("bool") << "const C = true;" << 0; QTest::newRow("selfConst") << "const C2 = 1; const C = self::C2;" << 0; QTest::newRow("parentConst") << "const C = parent::P;" << 0; QTest::newRow("null") << "const C = null;" << 0; QTest::newRow("array") << "const C = array();" << 1; } void TestDUChain::classConst() { QFETCH(QString, classBody); QFETCH(int, problems); QString fullClass("childContexts().count(), 2); QCOMPARE(top->problems().count(), problems); QCOMPARE(top->findDeclarations(QualifiedIdentifier("a::C")).count(), 1); QCOMPARE(top->findDeclarations(QualifiedIdentifier("a::C")).first()->context(), top->childContexts().last()); } void TestDUChain::fileConst_data() { QTest::addColumn("code"); QTest::addColumn("problems"); QTest::addColumn("dataType"); QTest::newRow("int") << "const C = 1;" << 0 << (uint) IntegralType::TypeInt; QTest::newRow("string") << "const C = 'asdf';" << 0 << (uint) IntegralType::TypeString; QTest::newRow("float") << "const C = 0.5;" << 0 << (uint) IntegralType::TypeFloat; QTest::newRow("bool") << "const C = true;" << 0 << (uint) IntegralType::TypeBoolean; QTest::newRow("array") << "const C = array();" << 1 << (uint) IntegralType::TypeArray; } void TestDUChain::fileConst() { QFETCH(QString, code); QFETCH(int, problems); QFETCH(uint, dataType); code.prepend("problems().count(), problems); - QList< Declaration* > decs = top->findDeclarations(QualifiedIdentifier("C")); + QList< Declaration* > decs = top->findDeclarations(QualifiedIdentifier(QStringLiteral("C"))); QCOMPARE(decs.count(), 1); IntegralType::Ptr type = decs.first()->abstractType().cast(); QVERIFY(type); QCOMPARE(type->dataType(), dataType); QVERIFY(type->modifiers() & AbstractType::ConstModifier); } void TestDUChain::define() { // the last define tests that we don't crash under that circumstance // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("findDeclarations(QualifiedIdentifier("FOO")).count(), 1); QCOMPARE(top->findDeclarations(QualifiedIdentifier("BAR")).count(), 1); QCOMPARE(top->findDeclarations(QualifiedIdentifier("FOO")).first()->context(), top); QCOMPARE(top->findDeclarations(QualifiedIdentifier("BAR")).first()->context(), top); QVERIFY(top->findDeclarations(QualifiedIdentifier("FOO")).first()->abstractType()->modifiers() & AbstractType::ConstModifier); QVERIFY(top->findDeclarations(QualifiedIdentifier("BAR")).first()->abstractType()->modifiers() & AbstractType::ConstModifier); } void TestDUChain::defaultFunctionParam() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("(top->localDeclarations().first()); QVERIFY(fun); QCOMPARE(fun->defaultParametersSize(), 2u); QCOMPARE(fun->defaultParameters()[0].str(), QString("false")); QCOMPARE(fun->defaultParameters()[1].str(), QString("null")); } void TestDUChain::globalFunction() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("importedParentContexts().count(), 1); QVERIFY(DUChain::self()->chainForDocument(internalFunctionFile())); QCOMPARE(DUChain::self()->chainForDocument(internalFunctionFile()), top->importedParentContexts().first().context(top)); QCOMPARE(top->findDeclarations(QualifiedIdentifier("substr")).count(), 1); } void TestDUChain::globalVariableFromInternalFunctions() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("importedParentContexts().count(), 1); QVERIFY(DUChain::self()->chainForDocument(internalFunctionFile())); QCOMPARE(DUChain::self()->chainForDocument(internalFunctionFile()), top->importedParentContexts().first().context(top)); QCOMPARE(top->findDeclarations(QualifiedIdentifier("_GET")).count(), 1); } void TestDUChain::newObjectFromOtherFile() { TopDUContext* addTop = parseAdditionalFile(IndexedString("/duchaintest/foo.php"), "localDeclarations().first()->type()->declaration(top), addTop->localDeclarations().first()); } void TestDUChain::unknownReturnType() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().at(0); FunctionType::Ptr fType = dec->type(); QVERIFY(fType); QVERIFY(IntegralType::Ptr::dynamicCast(fType->returnType())); QVERIFY(IntegralType::Ptr::staticCast(fType->returnType())->dataType() == IntegralType::TypeVoid); } void TestDUChain::staticFunctionCallFromOtherFile() { TopDUContext* addTop = parseAdditionalFile(IndexedString("/duchaintest/foo2.php"), "childContexts().first()->localDeclarations().first()->type(); QVERIFY(fun); StructureType::Ptr ret = StructureType::Ptr::dynamicCast(fun->returnType()); qDebug() << fun->returnType()->toString(); QVERIFY(ret); QCOMPARE(ret->declaration(top), top->localDeclarations().first()); } void TestDUChain::internalFunctions() { return; //disabled because it is too slow - QString fileName = QStandardPaths::locate(QStandardPaths::GenericDataLocation, "kdevphpsupport/phpfunctions.php"); + QString fileName = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kdevphpsupport/phpfunctions.php")); QFile file(fileName); file.open(QIODevice::ReadOnly | QIODevice::Text); TopDUContext* top = parse(file.readAll(), DumpNone); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); } void TestDUChain::trueFalse() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().at(0)->type()->dataType() == IntegralType::TypeBoolean); QVERIFY(top->localDeclarations().at(1)->type()->dataType() == IntegralType::TypeBoolean); } void TestDUChain::null() { QByteArray method("localDeclarations().at(0)->type()->dataType() == IntegralType::TypeNull); } void TestDUChain::array() { QByteArray method("localDeclarations().at(0)->type()->dataType() == IntegralType::TypeArray); QVERIFY(top->localDeclarations().at(1)->type()->dataType() == IntegralType::TypeArray); // $b[] = 'test'; is not a redeclaration of b! Esp. it's type should not change. QCOMPARE(top->findDeclarations(Identifier("b")).count(), 1); } void TestDUChain::functionDocBlock() { { TopDUContext* top = parse("localDeclarations().first()->comment(), QByteArray("Foo")); } { TopDUContext* top = parse("localDeclarations().first()->comment(), QByteArray("Bar")); QCOMPARE(top->childContexts().first()->localDeclarations().first()->comment(), QByteArray("Foo")); } { TopDUContext* top = parse("localDeclarations().first()->comment(), QByteArray("Foo")); } { TopDUContext* top = parse("childContexts().first()->localDeclarations().first()->comment(), QByteArray("Foo")); } { TopDUContext* top = parse("childContexts().first()->localDeclarations().first()->comment(), QByteArray("Foo")); } { TopDUContext* top = parse("localDeclarations().first()->comment(), QByteArray("Foo\n Bar")); } { // same as above but with indendation TopDUContext* top = parse("localDeclarations().first()->comment(), QByteArray("Foo\n Bar")); } } void TestDUChain::variableDocBlock() { { TopDUContext* top = parse("localDeclarations().first()->comment(), QByteArray("Foo")); QCOMPARE(top->localDeclarations().at(1)->comment(), QByteArray("Foo")); } { TopDUContext* top = parse("localDeclarations().first()->comment(), QByteArray("Foo")); QCOMPARE(top->localDeclarations().at(1)->comment(), QByteArray("Foo")); } } void TestDUChain::functionDocBlockParams() { TopDUContext* top = parse("localDeclarations().at(1)->type()->arguments().count(), 4); AbstractType::Ptr arg = top->localDeclarations().at(1)->type()->arguments().at(0); QVERIFY(IntegralType::Ptr::dynamicCast(arg)); QVERIFY(IntegralType::Ptr::dynamicCast(arg)->dataType() == IntegralType::TypeInt); QVERIFY(top->childContexts().at(1)->localDeclarations().at(0)->type()); QVERIFY(top->childContexts().at(1)->localDeclarations().at(0)->type()->dataType() == IntegralType::TypeInt); arg = top->localDeclarations().at(1)->type()->arguments().at(1); QVERIFY(StructureType::Ptr::dynamicCast(arg)); QCOMPARE(StructureType::Ptr::dynamicCast(arg)->declaration(top), top->localDeclarations().at(0)); QCOMPARE(top->childContexts().at(1)->localDeclarations().at(1)->type()->declaration(top), top->localDeclarations().at(0)); arg = top->localDeclarations().at(1)->type()->arguments().at(2); QVERIFY(IntegralType::Ptr::dynamicCast(arg)); QVERIFY(IntegralType::Ptr::dynamicCast(arg)->dataType() == IntegralType::TypeMixed); arg = top->localDeclarations().at(1)->type()->arguments().at(3); QVERIFY(IntegralType::Ptr::dynamicCast(arg)); QVERIFY(IntegralType::Ptr::dynamicCast(arg)->dataType() == IntegralType::TypeMixed); } } void TestDUChain::memberFunctionDocBlockParams() { TopDUContext* top = parse("childContexts().first()->localDeclarations().first()->type()->arguments().count(), 3); AbstractType::Ptr arg = top->childContexts().first()->localDeclarations().first()->type()->arguments().at(0); QVERIFY(IntegralType::Ptr::dynamicCast(arg)); QVERIFY(IntegralType::Ptr::dynamicCast(arg)->dataType() == IntegralType::TypeBoolean); arg = top->childContexts().first()->localDeclarations().first()->type()->arguments().at(1); QVERIFY(StructureType::Ptr::dynamicCast(arg)); QCOMPARE(StructureType::Ptr::dynamicCast(arg)->declaration(top), top->localDeclarations().at(0)); arg = top->childContexts().first()->localDeclarations().first()->type()->arguments().at(2); QVERIFY(IntegralType::Ptr::dynamicCast(arg)); QVERIFY(IntegralType::Ptr::dynamicCast(arg)->dataType() == IntegralType::TypeArray); } } void TestDUChain::foreachLoop() { { TopDUContext* top = parse("$i) { $i; }", DumpAll); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(top->localDeclarations().count(), 3); QCOMPARE(top->localDeclarations().at(1)->qualifiedIdentifier(), QualifiedIdentifier("k")); QVERIFY(top->localDeclarations().at(1)->abstractType().cast()); QCOMPARE(top->localDeclarations().at(1)->abstractType().cast()->dataType(), static_cast(IntegralType::TypeMixed)); QCOMPARE(top->localDeclarations().at(2)->qualifiedIdentifier(), QualifiedIdentifier("i")); QVERIFY(top->localDeclarations().at(2)->abstractType().cast()); QCOMPARE(top->localDeclarations().at(2)->abstractType().cast()->dataType(), static_cast(IntegralType::TypeMixed)); } { // bug: https://bugs.kde.org/show_bug.cgi?id=237110 TopDUContext* top = parse("localDeclarations().count(), 3); QCOMPARE(top->localDeclarations().at(1)->qualifiedIdentifier(), QualifiedIdentifier("b")); qDebug() << top->localDeclarations().at(1)->toString(); QVERIFY(top->localDeclarations().at(1)->abstractType().cast()); QCOMPARE(top->localDeclarations().at(1)->abstractType().cast()->dataType(), static_cast(IntegralType::TypeMixed)); QCOMPARE(top->localDeclarations().at(2)->qualifiedIdentifier(), QualifiedIdentifier("c")); QVERIFY(top->localDeclarations().at(2)->abstractType().cast()); QCOMPARE(top->localDeclarations().at(2)->abstractType().cast()->qualifiedIdentifier().toString(), QString("stdclass")); } } void TestDUChain::php4StyleConstructor() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("bb(); } } "); TopDUContext* top = parse(method, DumpAll); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); Declaration* dec = top->childContexts().first()->localDeclarations().at(0); QVERIFY(dec); QCOMPARE(dec->qualifiedIdentifier(), QualifiedIdentifier("aa::aa")); ClassFunctionDeclaration* classFuncDec = dynamic_cast(dec); QVERIFY(classFuncDec); QVERIFY(classFuncDec->isConstructor()); } void TestDUChain::constructor() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 { QByteArray method("childContexts().first()->localDeclarations().at(0); QVERIFY(dec); ClassFunctionDeclaration* classFuncDec = dynamic_cast(dec); QVERIFY(classFuncDec); QVERIFY(!classFuncDec->isDestructor()); QVERIFY(classFuncDec->isConstructor()); } { QByteArray method("childContexts().first()->localDeclarations().at(0); QVERIFY(dec); ClassFunctionDeclaration* classFuncDec = dynamic_cast(dec); QVERIFY(classFuncDec); QVERIFY(!classFuncDec->isDestructor()); QVERIFY(classFuncDec->isConstructor()); } } void TestDUChain::destructor() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("childContexts().first()->localDeclarations().at(0); QVERIFY(dec); ClassFunctionDeclaration* classFuncDec = dynamic_cast(dec); QVERIFY(classFuncDec); QVERIFY(classFuncDec->isDestructor()); QVERIFY(!classFuncDec->isConstructor()); } void TestDUChain::functionInFunction() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().at(0)->qualifiedIdentifier(), QualifiedIdentifier("aaa")); } void TestDUChain::objectWithClassName() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("foo();"); - TopDUContext* top = parse(method, DumpNone, QUrl("file:///internal/testObjectWithClassName.php")); + TopDUContext* top = parse(method, DumpNone, QUrl(QStringLiteral("file:///internal/testObjectWithClassName.php"))); DUChainReleaser releaseTop(top); // update top (the pointer will be the same) QByteArray method2("foo();"); - TopDUContext* top2 = parse(method2, DumpNone, QUrl("file:///internal/testObjectWithClassName.php")); + TopDUContext* top2 = parse(method2, DumpNone, QUrl(QStringLiteral("file:///internal/testObjectWithClassName.php"))); QVERIFY(top2 == top); } void TestDUChain::largeNumberOfDeclarations() { - TopDUContext* top = new TopDUContext(IndexedString(QUrl("file:///internal/testurl")), RangeInRevision(0, 0, 6000, 0), 0); + TopDUContext* top = new TopDUContext(IndexedString(QUrl(QStringLiteral("file:///internal/testurl"))), RangeInRevision(0, 0, 6000, 0), 0); DUChain::self()->addDocumentChain(top); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); for (int i = 0; i < 6000; ++i) { RangeInRevision newRange(i, 0, i, 1); Declaration* dec = new Declaration(newRange, top); - dec->setIdentifier(Identifier(QString("dec%0").arg(i))); + dec->setIdentifier(Identifier(QStringLiteral("dec%0").arg(i))); dec->setAbstractType(AbstractType::Ptr(0)); } } void TestDUChain::staticVariable() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("childContexts().at(1)->localDeclarations().count(), 6); QCOMPARE(top->childContexts().at(1)->localDeclarations().first()->qualifiedIdentifier(), QualifiedIdentifier("aaa::foo")); QVERIFY(top->childContexts().at(1)->localDeclarations().first()->type()); QCOMPARE(top->childContexts().at(1)->localDeclarations().first()->type()->dataType(), (uint)IntegralType::TypeMixed); QCOMPARE(top->childContexts().at(1)->localDeclarations().at(1)->qualifiedIdentifier(), QualifiedIdentifier("aaa::bar")); QVERIFY(top->childContexts().at(1)->localDeclarations().at(1)->type()); QCOMPARE(top->childContexts().at(1)->localDeclarations().at(1)->type()->dataType(), (uint)IntegralType::TypeInt); QCOMPARE(top->childContexts().at(1)->localDeclarations().at(2)->qualifiedIdentifier(), QualifiedIdentifier("aaa::baz")); QVERIFY(top->childContexts().at(1)->localDeclarations().at(2)->type()); QCOMPARE(top->childContexts().at(1)->localDeclarations().at(2)->type()->dataType(), (uint)IntegralType::TypeString); QVERIFY(top->childContexts().at(1)->localDeclarations().at(3)->type()); QCOMPARE(top->childContexts().at(1)->localDeclarations().at(3)->type()->dataType(), (uint)IntegralType::TypeArray); QVERIFY(top->childContexts().at(1)->localDeclarations().at(4)->type()); QCOMPARE(top->childContexts().at(1)->localDeclarations().at(4)->type()->dataType(), (uint)IntegralType::TypeInt); QVERIFY(top->childContexts().at(1)->localDeclarations().at(5)->type()); QCOMPARE(top->childContexts().at(1)->localDeclarations().at(5)->type()->dataType(), (uint)IntegralType::TypeInt); } void TestDUChain::returnTypeTwoDeclarations() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().at(0); FunctionType::Ptr functionType = dec->type(); QVERIFY(functionType); UnsureType::Ptr retType = UnsureType::Ptr::dynamicCast(functionType->returnType()); QVERIFY(retType); QCOMPARE(retType->typesSize(), 2u); QVERIFY(retType->types()[0].abstractType().cast()); QCOMPARE(retType->types()[0].abstractType().cast()->dataType(), (uint)IntegralType::TypeString); QVERIFY(retType->types()[1].abstractType().cast()); QCOMPARE(retType->types()[1].abstractType().cast()->dataType(), (uint)IntegralType::TypeInt); } void TestDUChain::globalVariableNotVisibleInFunction() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("findDeclarations(QualifiedIdentifier("a")).first()->uses().count(), 0); } void TestDUChain::globalVariableInFunction() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("findDeclarations(QualifiedIdentifier("a")).first()->uses().count(), 1); } void TestDUChain::nonGlobalVariableInFunction() { // bug: https://bugs.kde.org/show_bug.cgi?id=240920 // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("findLocalDeclarations(Identifier("a")).count(), 1); QCOMPARE(top->findLocalDeclarations(Identifier("a")).first()->uses().count(), 0); QCOMPARE(top->childContexts().count(), 2); QCOMPARE(top->childContexts().last()->findLocalDeclarations(Identifier("a")).count(), 1); QCOMPARE(top->childContexts().last()->findLocalDeclarations(Identifier("a")).first()->uses().count(), 0); } void TestDUChain::superglobalInFunction() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("findDeclarations(QualifiedIdentifier("_GET")).count(), 1); - Declaration* dec = top->findDeclarations(QualifiedIdentifier("_GET")).first(); + Declaration* dec = top->findDeclarations(QualifiedIdentifier(QStringLiteral("_GET"))).first(); QVERIFY(dynamic_cast(dec)); QVERIFY(static_cast(dec)->isSuperglobal()); QCOMPARE(dec->uses().keys().count(), 1); QCOMPARE(dec->uses().values().count(), 1); QCOMPARE(dec->uses().values().first().count(), 2); QCOMPARE(dec->uses().values().first().first(), RangeInRevision(0, 3, 0, 8)); QCOMPARE(dec->uses().values().first().at(1), RangeInRevision(0, 27, 0, 32)); } void TestDUChain::returnWithoutFunction() { //yes, this is possible in php, you then have $a as return value of an include call QByteArray method("localDeclarations().at(2)->internalContext()->importedParentContexts().empty()); QCOMPARE(top->localDeclarations().at(1)->internalContext()->importedParentContexts().count(), 1); QCOMPARE(top->localDeclarations().at(1)->internalContext()->importedParentContexts().first().context(top), top->localDeclarations().at(2)->internalContext()); QCOMPARE(top->localDeclarations().at(0)->internalContext()->importedParentContexts().count(), 1); QCOMPARE(top->localDeclarations().at(0)->internalContext()->importedParentContexts().first().context(top), top->localDeclarations().at(1)->internalContext()); } void TestDUChain::findDeclarations() { DUChainWriteLocker lock(DUChain::lock()); - TopDUContext* top1 = new TopDUContext(IndexedString(QUrl("file:///internal/testfile1")), RangeInRevision(0, 0, 0, 10), 0); + TopDUContext* top1 = new TopDUContext(IndexedString(QUrl(QStringLiteral("file:///internal/testfile1"))), RangeInRevision(0, 0, 0, 10), 0); DUChainReleaser releaseTop1(top1); DUChain::self()->addDocumentChain(top1); - TopDUContext* top2 = new TopDUContext(IndexedString(QUrl("file:///internal/testfile2")), RangeInRevision(0, 0, 0, 10), 0); + TopDUContext* top2 = new TopDUContext(IndexedString(QUrl(QStringLiteral("file:///internal/testfile2"))), RangeInRevision(0, 0, 0, 10), 0); DUChainReleaser releaseTop2(top2); DUChain::self()->addDocumentChain(top2); Declaration* declaration = new Declaration(RangeInRevision(0, 0, 0, 3), top1); - declaration->setIdentifier(Identifier("foo")); + declaration->setIdentifier(Identifier(QStringLiteral("foo"))); QVERIFY(!top1->usingImportsCache()); QVERIFY(!top2->usingImportsCache()); QCOMPARE(1, top1->findDeclarations(Identifier("foo")).count()); QCOMPARE(0, top2->findDeclarations(Identifier("foo")).count()); top2->addImportedParentContext(top1); QVERIFY(!top1->usingImportsCache()); QVERIFY(!top2->usingImportsCache()); QCOMPARE(1, top2->findDeclarations(Identifier("foo")).count()); top2->clearImportedParentContexts(); QCOMPARE(top2->importedParentContexts().size(), 0); QVERIFY(!top1->usingImportsCache()); QVERIFY(!top2->usingImportsCache()); QCOMPARE(0, top2->findDeclarations(Identifier("foo")).count()); top2->addImportedParentContext(top1); QCOMPARE(1, top2->findDeclarations(Identifier("foo")).count()); } void TestDUChain::memberTypeAfterMethod() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("childContexts().first(); // function foo { ClassMemberDeclaration* var = dynamic_cast(contextClassA->localDeclarations().first()); QVERIFY(var); QCOMPARE(var->identifier(), Identifier("foo")); QCOMPARE(var->accessPolicy(), Declaration::Public); QCOMPARE(var->isStatic(), false); QVERIFY(var->type()); IntegralType::Ptr ret = var->type()->returnType().cast(); QVERIFY(ret); QVERIFY(ret->dataType() == IntegralType::TypeVoid); } // public $bar { ClassMemberDeclaration* var = dynamic_cast(contextClassA->localDeclarations().at(1)); QVERIFY(var); QCOMPARE(var->identifier(), Identifier("bar")); QCOMPARE(var->accessPolicy(), Declaration::Public); QCOMPARE(var->isStatic(), false); QVERIFY(var->type()); QVERIFY(var->type()->dataType() == IntegralType::TypeMixed); } } void TestDUChain::catchDeclaration() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("(top->localDeclarations().first()); QVERIFY(ex); QCOMPARE(ex->identifier(), Identifier("e")); QVERIFY(ex->type()); QCOMPARE(QualifiedIdentifier("exception"), ex->type()->declaration(top)->qualifiedIdentifier()); } void TestDUChain::resourceType() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("(top->localDeclarations().first()); QVERIFY(fun); FunctionType::Ptr ftype = FunctionType::Ptr::dynamicCast(fun->abstractType()); QVERIFY(ftype); IntegralType::Ptr rtype = IntegralType::Ptr::dynamicCast(ftype->returnType()); QVERIFY(rtype); QCOMPARE(rtype->toString(), QString("resource")); QVERIFY(rtype->dataType() == IntegralTypeExtended::TypeResource); } void TestDUChain::foreachIterator() { QByteArray code; code.append("localDeclarations().at(3); QCOMPARE(iDec->qualifiedIdentifier(), QualifiedIdentifier("i")); QVERIFY(iDec->type()); QCOMPARE(iDec->type()->qualifiedIdentifier(), QualifiedIdentifier("b")); QVERIFY(top->localDeclarations().first() == iDec->type()->declaration(top)); } void TestDUChain::foreachIterator2() { QByteArray code; code.append("localDeclarations().size(), 3); Declaration* iDec = top->localDeclarations().at(2); QCOMPARE(iDec->qualifiedIdentifier(), QualifiedIdentifier("i")); qDebug() << iDec->abstractType()->toString(); QVERIFY(iDec->type()); QCOMPARE(iDec->type()->qualifiedIdentifier(), QualifiedIdentifier("b")); QVERIFY(top->localDeclarations().first() == iDec->type()->declaration(top)); } void TestDUChain::foreachIterator3() { QByteArray code; code.append("localDeclarations().at(3); QCOMPARE(iDec->qualifiedIdentifier(), QualifiedIdentifier("i")); QVERIFY(iDec->type()); QCOMPARE(iDec->type()->qualifiedIdentifier(), QualifiedIdentifier("b")); QVERIFY(top->localDeclarations().first() == iDec->type()->declaration(top)); } void TestDUChain::foreachIterator4() { // see also: https://bugs.kde.org/show_bug.cgi?id=276603 QByteArray code = "i){}\n" " foreach(array(1,2) as $this->k => $this->v){}\n" " foreach(array(1,2) as A::$s){}\n" " }\n" "}\n"; TopDUContext* top = parse(code, DumpNone); DUChainReleaser releaseTop(top); DUChainWriteLocker lock; QVERIFY(top->problems().isEmpty()); Declaration* aDec = top->localDeclarations().first(); DUContext* fooCtx = top->childContexts().first()->childContexts().last(); QVERIFY(fooCtx->owner()); QCOMPARE(aDec->uses().size(), 1); QCOMPARE(aDec->uses().begin()->size(), 4); } void TestDUChain::returnThis() { QByteArray code("childContexts().first()->localDeclarations().first(); QVERIFY(dec->type()); AbstractType::Ptr t = dec->type()->returnType(); qDebug() << t->toString(); QVERIFY(StructureType::Ptr::dynamicCast(t)); QVERIFY(StructureType::Ptr::dynamicCast(t)->declaration(top) == top->localDeclarations().first()); } void TestDUChain::unsureReturnType() { QByteArray code("localDeclarations().first(); QVERIFY(dec->type()); TypePtr ut = dec->type()->returnType().cast(); QVERIFY(ut); QCOMPARE((uint)2, ut->typesSize()); QVERIFY(ut->types()[0].type()); QVERIFY(ut->types()[0].type()->dataType() == IntegralType::TypeBoolean); QVERIFY(ut->types()[1].type()); QVERIFY(ut->types()[1].type()->dataType() == IntegralType::TypeInt); } void TestDUChain::unsureReturnType2() { QByteArray code("localDeclarations().at(2); QVERIFY(dec->type()); TypePtr ut = dec->type()->returnType().cast(); QVERIFY(ut); QCOMPARE((uint)2, ut->typesSize()); QVERIFY(ut->types()[0].type()); QCOMPARE(ut->types()[0].type()->toString(), QString("A")); QVERIFY(ut->types()[1].type()); QCOMPARE(ut->types()[1].type()->toString(), QString("B")); } void TestDUChain::unsureReturnType3() { QByteArray code("localDeclarations().at(0); QVERIFY(dec->type()); qDebug() << dec->type()->returnType()->toString(); TypePtr ut = dec->type()->returnType().cast(); QVERIFY(ut); QCOMPARE((uint)3, ut->typesSize()); QVERIFY(ut->types()[0].type()); QVERIFY(ut->types()[0].type()->dataType() == IntegralType::TypeInt); QVERIFY(ut->types()[1].type()); QVERIFY(ut->types()[1].type()->dataType() == IntegralType::TypeBoolean); QVERIFY(ut->types()[2].type()); QVERIFY(ut->types()[2].type()->dataType() == IntegralType::TypeString); } void TestDUChain::unsureReturnType4() { QByteArray code("localDeclarations().first(); QVERIFY(dec->type()); TypePtr ut = dec->type()->returnType().cast(); QVERIFY(ut); QCOMPARE((uint)2, ut->typesSize()); QVERIFY(ut->types()[0].type()); QVERIFY(ut->types()[0].type()->dataType() == IntegralType::TypeBoolean); QVERIFY(ut->types()[1].type()); QVERIFY(ut->types()[1].type()->dataType() == IntegralType::TypeInt); } void TestDUChain::referencedArgument() { // php does not return references QByteArray code("localDeclarations().first(); QVERIFY(dec->type()); qDebug() << dec->abstractType()->toString(); IntegralType::Ptr aType = dec->type()->returnType().cast(); QVERIFY(aType); QCOMPARE(aType->dataType(), (uint)IntegralType::TypeInt); QCOMPARE(top->childContexts().first()->type(), DUContext::Function); ReferenceType::Ptr rType = top->childContexts().first()->localDeclarations().first()->abstractType().cast(); QVERIFY(rType); QVERIFY(rType->baseType()->equals(aType.data())); } void TestDUChain::unsureReferencedArgument() { // php does not return references QByteArray code("localDeclarations().first(); QVERIFY(dec->type()); qDebug() << dec->abstractType()->toString(); UnsureType::Ptr aType = dec->type()->returnType().cast(); QVERIFY(aType); QCOMPARE(aType->typesSize(), 2u); QCOMPARE(aType->types()[0].abstractType().cast()->dataType(), (uint)IntegralType::TypeInt); QCOMPARE(aType->types()[1].abstractType().cast()->dataType(), (uint)IntegralType::TypeString); QCOMPARE(top->childContexts().first()->type(), DUContext::Function); ReferenceType::Ptr rType = top->childContexts().first()->localDeclarations().first()->abstractType().cast(); QVERIFY(rType); QVERIFY(rType->baseType()->equals(aType.data())); } void TestDUChain::defaultArgument() { // php does not return references QByteArray code("childContexts().first()->localDeclarations().first(); QVERIFY(dec->type()); QCOMPARE(dec->type()->dataType(), (uint)IntegralType::TypeInt); } void TestDUChain::declareMemberOutOfClass() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray code("asdf = true; $bar->asdf = false;\n" // not allowed: "$bar->prot = 1;\n" // not allowed: "$bar->priv = 1;"); TopDUContext* top = parse(code, DumpAST); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); { // $bar is only declared once - QList decs = top->findLocalDeclarations(Identifier("bar")); + QList decs = top->findLocalDeclarations(Identifier(QStringLiteral("bar"))); QCOMPARE(decs.size(), 1); Declaration *dec = decs.first(); QVERIFY(dec->type()); QVERIFY(dec->type()->declaration(top)->identifier().nameEquals(Identifier("foo"))); // while we are at it, compare uses QCOMPARE(dec->uses().keys().count(), 1); QCOMPARE(dec->uses().values().count(), 1); QCOMPARE(dec->uses().values().first().count(), 4); qDebug() << dec->uses().values().first().at(0).castToSimpleRange(); QCOMPARE(dec->uses().values().first().at(0), RangeInRevision(1, 16, 1, 20)); qDebug() << dec->uses().values().first().at(1).castToSimpleRange(); QCOMPARE(dec->uses().values().first().at(1), RangeInRevision(1, 35, 1, 39)); qDebug() << dec->uses().values().first().at(2).castToSimpleRange(); QCOMPARE(dec->uses().values().first().at(2), RangeInRevision(2, 0, 2, 4)); qDebug() << dec->uses().values().first().at(3).castToSimpleRange(); QCOMPARE(dec->uses().values().first().at(3), RangeInRevision(3, 0, 3, 4)); } { // check if asdf got declared - QList decs = top->childContexts().first()->findDeclarations(Identifier("asdf")); + QList decs = top->childContexts().first()->findDeclarations(Identifier(QStringLiteral("asdf"))); // the type of both assignments to $bar->asdf are the same, hence it should only be declared once QCOMPARE(decs.size(), 1); ClassMemberDeclaration* cmdec = dynamic_cast(decs.first()); QVERIFY(cmdec); QVERIFY(cmdec->accessPolicy() == Declaration::Public); QVERIFY(!cmdec->isStatic()); QVERIFY(cmdec->type()); QVERIFY(cmdec->type()->dataType() == IntegralType::TypeBoolean); } // check that prot and priv don't get redeclared QCOMPARE(top->problems().count(), 2); QCOMPARE(top->problems().at(0)->finalLocation().start().line(), 2); QCOMPARE(top->problems().at(1)->finalLocation().start().line(), 3); } void TestDUChain::declareMemberOutOfClass2() { // see also: https://bugs.kde.org/show_bug.cgi?id=283356 // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray code("x = 1;\n" "class A { var $x = 1; }"); TopDUContext* top = parse(code, DumpAST); QVERIFY(top); // update top = parse(code, DumpNone, top->url().toUrl(), ReferencedTopDUContext(top)); DUChainReleaser releaseTop(top); DUChainWriteLocker lock; QVERIFY(top->problems().isEmpty()); - QList decs = top->findLocalDeclarations(Identifier("a")); + QList decs = top->findLocalDeclarations(Identifier(QStringLiteral("a"))); QCOMPARE(decs.size(), 2); { Declaration *dec = decs.first(); QVERIFY(dynamic_cast(dec)); QVERIFY(dec->type()); QVERIFY(dec->type()->declaration(top)->identifier().nameEquals(Identifier("a"))); } { Declaration *dec = decs.last(); QVERIFY(dynamic_cast(dec)); QVERIFY(dec->type()); QVERIFY(dec->type()->declaration(top)->identifier().nameEquals(Identifier("a"))); } { // check if x got declared - QList decs = top->childContexts().first()->findDeclarations(Identifier("x")); + QList decs = top->childContexts().first()->findDeclarations(Identifier(QStringLiteral("x"))); // the type of both assignments to $bar->asdf are the same, hence it should only be declared once QCOMPARE(decs.size(), 1); ClassMemberDeclaration* cmdec = dynamic_cast(decs.first()); QVERIFY(cmdec); QVERIFY(cmdec->accessPolicy() == Declaration::Public); QVERIFY(!cmdec->isStatic()); QVERIFY(cmdec->type()); QCOMPARE(cmdec->type()->dataType(), (uint) IntegralType::TypeInt); } } void TestDUChain::declareMemberInClassMethod() { QByteArray code("asdf = true; $this->asdf = false; }\n" // should only declare bar once as private " private $xyz = 0; function test2() { $this->xyz = 42; }\n" // should not create any local declarations, and issue an error for trying to // assign something to a private member variable of a parent class " function test3() { $this->prot = 42;\n$this->priv = 42; }\n" " }"); TopDUContext* top = parse(code, DumpAST); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); { // asdf - QList decs = top->childContexts().last()->findLocalDeclarations(Identifier("asdf")); + QList decs = top->childContexts().last()->findLocalDeclarations(Identifier(QStringLiteral("asdf"))); QCOMPARE(decs.size(), 1); ClassMemberDeclaration *dec = dynamic_cast(decs.first()); QVERIFY(dec); QVERIFY(dec->accessPolicy() == Declaration::Public); QVERIFY(!dec->isStatic()); QVERIFY(dec->type()); QVERIFY(dec->type()->dataType() == IntegralType::TypeBoolean); } { // xyz - QList decs = top->childContexts().last()->findLocalDeclarations(Identifier("xyz")); + QList decs = top->childContexts().last()->findLocalDeclarations(Identifier(QStringLiteral("xyz"))); QCOMPARE(decs.size(), 1); ClassMemberDeclaration *dec = dynamic_cast(decs.first()); QVERIFY(dec); QVERIFY(dec->accessPolicy() == Declaration::Private); QVERIFY(!dec->isStatic()); QVERIFY(dec->type()); QVERIFY(dec->type()->dataType() == IntegralType::TypeInt); } { // prot and priv QVERIFY(top->childContexts().last()->findLocalDeclarations(Identifier("prot")).isEmpty()); QVERIFY(top->childContexts().last()->findLocalDeclarations(Identifier("priv")).isEmpty()); } // only one problem: error trying to assign to a private member of a parent class QCOMPARE(top->problems().count(), 1); QCOMPARE(top->problems().first()->finalLocation().start().line(), 4); } void TestDUChain::thisRedeclaration() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray code("test = true; $this = false;} }"); TopDUContext* top = parse(code, DumpAST); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); // only $this = false is a problem, $this->test = true is perfectly valid QCOMPARE(top->problems().count(), 1); qDebug() << top->problems().first()->finalLocation(); QVERIFY(top->problems().first()->finalLocation() == KDevelop::DocumentRange(top->url(), KTextEditor::Range(0, 50, 0, 55))); } void TestDUChain::implicitArrayDeclaration() { ///TODO: adapt to unsure type once it's supported { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray code(" decs = top->findDeclarations(Identifier("a")); + QList decs = top->findDeclarations(Identifier(QStringLiteral("a"))); QCOMPARE(decs.size(), 1); VariableDeclaration* vdec = dynamic_cast(decs.first()); QVERIFY(vdec); QVERIFY(vdec->type()); QVERIFY(vdec->type()->dataType() == IntegralType::TypeArray); } { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray code(" decs = top->findDeclarations(Identifier("a")); + QList decs = top->findDeclarations(Identifier(QStringLiteral("a"))); QCOMPARE(decs.size(), 1); VariableDeclaration* vdec = dynamic_cast(decs.first()); QVERIFY(vdec); QVERIFY(vdec->type()); QVERIFY(vdec->type()->dataType() == IntegralType::TypeArray); } { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray code("a[1] = true;"); TopDUContext* top = parse(code, DumpAST); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); - QList decs = top->childContexts().first()->findDeclarations(Identifier("a")); + QList decs = top->childContexts().first()->findDeclarations(Identifier(QStringLiteral("a"))); QCOMPARE(decs.size(), 1); ClassMemberDeclaration* cmdec = dynamic_cast(decs.first()); QVERIFY(cmdec); QVERIFY(cmdec->type()); QVERIFY(cmdec->type()->dataType() == IntegralType::TypeArray); } { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray code("a[$b] = true;"); TopDUContext* top = parse(code, DumpAST); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); - QList decs = top->childContexts().first()->findDeclarations(Identifier("a")); + QList decs = top->childContexts().first()->findDeclarations(Identifier(QStringLiteral("a"))); QCOMPARE(decs.size(), 1); ClassMemberDeclaration* cmdec = dynamic_cast(decs.first()); QVERIFY(cmdec); QVERIFY(cmdec->type()); QVERIFY(cmdec->type()->dataType() == IntegralType::TypeArray); } } void TestDUChain::implicitReferenceDeclaration() { { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray code(" decs = top->findDeclarations(Identifier("bar")); + QList decs = top->findDeclarations(Identifier(QStringLiteral("bar"))); QCOMPARE(decs.size(), 1); QVERIFY(dynamic_cast(decs.first())); QVERIFY(decs.first()->type()); qDebug() << decs.first()->type()->dataType() << decs.first()->toString(); QVERIFY(decs.first()->type()->dataType() == IntegralType::TypeNull); } { // a user reported a crash with the code example below // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray code("a);} }"); TopDUContext* top = parse(code, DumpAST); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); QVERIFY( top->childContexts().last()->localScopeIdentifier() == QualifiedIdentifier("foo")); // a is already declared - QList decs = top->childContexts().last()->findDeclarations(Identifier("a")); + QList decs = top->childContexts().last()->findDeclarations(Identifier(QStringLiteral("a"))); QCOMPARE(decs.size(), 1); ClassMemberDeclaration* cmdec = dynamic_cast(decs.first()); QVERIFY(cmdec); QVERIFY(cmdec->type()); qDebug() << cmdec->type()->dataType() << cmdec->toString(); QVERIFY(cmdec->type()->dataType() == IntegralType::TypeMixed); } } void TestDUChain::classContextRange() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray code(" foobar = 1; $a->barFoo= 0;"); TopDUContext* top = parse(code, DumpAST); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(top->childContexts().first()->range(), KDevelop::RangeInRevision(0, 6, 0, 17)); QCOMPARE(top->childContexts().first()->localDeclarations().count(), 2); } void TestDUChain::lateClassMembers() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray code("val = 'b'; } private $val = 'a'; } "); TopDUContext* top = parse(code, DumpAST); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); ClassDeclaration* cdec = dynamic_cast(top->localDeclarations().first()); QVERIFY(cdec); - QList decs = cdec->logicalInternalContext(top)->findDeclarations(Identifier("val")); + QList decs = cdec->logicalInternalContext(top)->findDeclarations(Identifier(QStringLiteral("val"))); QCOMPARE(decs.count(), 1); ClassMemberDeclaration* cmdec = dynamic_cast(decs.first()); QVERIFY(cmdec); QCOMPARE(cmdec->accessPolicy(), Declaration::Private); } void TestDUChain::list() { foreach ( const QString& code, QStringList() << " decs = top->findDeclarations(Identifier(identifier)); QCOMPARE(decs.size(), 1); Declaration *dec = decs.first(); QVERIFY(dec->type()); QCOMPARE(dec->type()->dataType(), (uint) IntegralType::TypeMixed); ///TODO: support arrays better and compare to actual type } } } void TestDUChain::alternateDocCommentTypeHints() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("(top->localDeclarations().first()); QVERIFY(cdec); QVERIFY(cdec->type()); QVector decs = cdec->logicalInternalContext(top)->localDeclarations(); QCOMPARE(decs.size(), 1); Declaration *dec = decs.first(); QVERIFY(dec->type()); QCOMPARE(dec->type()->declaration(top), cdec); } void TestDUChain::findFunctionArgs() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("(top->localDeclarations().first()); QVERIFY(funcDec); QVERIFY(funcDec->internalContext()); QVERIFY(funcDec->internalFunctionContext()); QVERIFY(funcDec->internalContext()->imports(funcDec->internalFunctionContext())); QList decs; foreach ( Declaration* arg, funcDec->internalFunctionContext()->localDeclarations() ) { decs = funcDec->internalContext()->findDeclarations(arg->identifier()); QCOMPARE(decs.size(), 1); decs = funcDec->internalContext()->findDeclarations(arg->qualifiedIdentifier()); qDebug() << arg->qualifiedIdentifier().toString(); QEXPECT_FAIL("", "strangely the arg dec is only found with its identifier, not by its qualifiedidentifier...", Continue); QCOMPARE(decs.size(), 1); } } void TestDUChain::undeclaredPropertyInString() { // testcase for bug 209814 // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("baz\"; } }", DumpNone); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(top->childContexts().size(), 1); DUContext* classCtx = top->childContexts().first(); QVERIFY(classCtx->type() == DUContext::Class); QCOMPARE(classCtx->localDeclarations().size(), 2); QCOMPARE(classCtx->findDeclarations(Identifier("foo")).size(), 1); QCOMPARE(classCtx->findDeclarations(Identifier("bar")).size(), 1); } void TestDUChain::undeclaredVarPropertyInString() { // testcase for bug 210043 // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("baz\";", DumpNone); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); // just don't crash } void TestDUChain::upcommingClassInString() { // testcase for bug 232687 // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("blah\";\n" " }\n" "}\n" "class B {\n" " var $blah;\n" "}\n", DumpNone); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); // just don't crash } void TestDUChain::namespaces() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("problems().count(), 0); QCOMPARE(top->childContexts().size(), 4); QCOMPARE(top->childContexts().at(0)->localScopeIdentifier().toString(), QString("asdf")); QCOMPARE(top->childContexts().at(1)->localScopeIdentifier().toString(), QString("ns1")); QCOMPARE(top->childContexts().at(2)->type(), DUContext::Function); QCOMPARE(top->childContexts().at(3)->localScopeIdentifier().toString(), QString("a")); QCOMPARE(top->localDeclarations().size(), 3); QCOMPARE(top->localDeclarations().at(0)->kind(), Declaration::Namespace); QCOMPARE(top->localDeclarations().at(1)->kind(), Declaration::Namespace); QCOMPARE(top->localDeclarations().at(2)->kind(), Declaration::Type); QCOMPARE(top->findDeclarations(QualifiedIdentifier("asdf")).size(), 1); QCOMPARE(top->childContexts().at(0)->localDeclarations().size(), 3); QCOMPARE(top->findDeclarations(QualifiedIdentifier("asdf::a")).size(), 1); QCOMPARE(top->findDeclarations(QualifiedIdentifier("asdf::b")).size(), 1); QCOMPARE(top->findDeclarations(QualifiedIdentifier("asdf::c")).size(), 1); QCOMPARE(top->findDeclarations(QualifiedIdentifier("ns1")).size(), 1); QCOMPARE(top->childContexts().at(1)->localDeclarations().size(), 1); QCOMPARE(top->childContexts().at(1)->localDeclarations().first()->kind(), Declaration::Namespace); ///TODO: support \ as separator QCOMPARE(top->childContexts().at(1)->localDeclarations().first()->qualifiedIdentifier().toString(), QString("ns1::ns2")); QCOMPARE(top->findDeclarations(QualifiedIdentifier("ns1::ns2")).size(), 1); QCOMPARE(top->findDeclarations(QualifiedIdentifier("ns1::ns2")).first()->logicalInternalContext(top)->localDeclarations().size(), 3); QCOMPARE(top->childContexts().at(1)->childContexts().size(), 1); QCOMPARE(top->childContexts().at(1)->childContexts().first()->localDeclarations().size(), 3); QCOMPARE(top->findDeclarations(QualifiedIdentifier("ns1::ns2")).first()->logicalInternalContext(top)->localDeclarations().first()->qualifiedIdentifier().toString(), QString("ns1::ns2::a")); QCOMPARE(top->findDeclarations(QualifiedIdentifier("ns1::ns2::a")).size(), 1); QCOMPARE(top->findDeclarations(QualifiedIdentifier("ns1::ns2::b")).size(), 1); QCOMPARE(top->findDeclarations(QualifiedIdentifier("ns1::ns2::c")).size(), 1); QCOMPARE(top->findDeclarations(QualifiedIdentifier("a")).size(), 1); QCOMPARE(top->findDeclarations(QualifiedIdentifier("b")).size(), 0); QCOMPARE(top->findDeclarations(QualifiedIdentifier("c")).size(), 0); ///TODO: prevent redeclarations of namespaces } void TestDUChain::namespacesNoCurly() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("problems().count(), 0); foreach(ProblemPointer p, top->problems()) { qDebug() << p->description() << p->explanation() << p->finalLocation(); } QCOMPARE(top->childContexts().size(), 2); QCOMPARE(top->childContexts().at(0)->localScopeIdentifier().toString(), QString("asdf")); QCOMPARE(top->childContexts().at(1)->localScopeIdentifier().toString(), QString("ns1")); QCOMPARE(top->localDeclarations().size(), 2); QCOMPARE(top->localDeclarations().at(0)->kind(), Declaration::Namespace); QCOMPARE(top->localDeclarations().at(1)->kind(), Declaration::Namespace); } void TestDUChain::useNamespace() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("localDeclarations().count(), 5); Declaration* dec = top->localDeclarations().at(2); QCOMPARE(dec->qualifiedIdentifier().toString(), QString("ns2")); QVERIFY(dynamic_cast(dec)); dec = top->localDeclarations().at(3); QCOMPARE(dec->qualifiedIdentifier().toString(), QString("ns5")); QVERIFY(dynamic_cast(dec)); dec = top->localDeclarations().at(4); QCOMPARE(dec->qualifiedIdentifier().toString(), QString("ns6")); QVERIFY(dynamic_cast(dec)); ///TODO: find out why this is explictly required QVERIFY(!dynamic_cast(dec)->importIdentifier().explicitlyGlobal()); } void TestDUChain::namespaceStaticVar() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("problems().isEmpty()); - Declaration* fooDec = top->findDeclarations(QualifiedIdentifier("ns::c::foo")).first(); + Declaration* fooDec = top->findDeclarations(QualifiedIdentifier(QStringLiteral("ns::c::foo"))).first(); QVERIFY(fooDec); QVERIFY(!fooDec->uses().isEmpty()); QVERIFY(!fooDec->uses().begin()->isEmpty()); QCOMPARE(fooDec->uses().begin()->begin()->start.line, 5); } void TestDUChain::namespacedCatch() { // see also: https://bugs.kde.org/show_bug.cgi?id=281451 // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("problems().isEmpty()); - Declaration* eDec = top->findDeclarations(QualifiedIdentifier("ns::e")).first(); + Declaration* eDec = top->findDeclarations(QualifiedIdentifier(QStringLiteral("ns::e"))).first(); QVERIFY(eDec); QVERIFY(!eDec->uses().isEmpty()); QVERIFY(!eDec->uses().begin()->isEmpty()); QCOMPARE(eDec->uses().begin()->begin()->start.line, 6); } struct TestUse { TestUse(const QString& _id, Declaration::Kind _kind, int _uses) : id(_id), kind(_kind), uses(_uses) {} TestUse() {} QualifiedIdentifier id; Declaration::Kind kind; int uses; }; Q_DECLARE_METATYPE ( TestUse ) Q_DECLARE_METATYPE ( QList ) void TestDUChain::errorRecovery_data() { QTest::addColumn("code"); QTest::addColumn< QList >("usesMap"); - QTest::newRow("conditional") << QString("() - << TestUse("a", Declaration::Instance, 1)); + << TestUse(QStringLiteral("a"), Declaration::Instance, 1)); - QTest::newRow("namespace") << QString("() - << TestUse("foo", Declaration::Namespace, 1) - << TestUse("y", Declaration::Namespace, 0) - << TestUse("foo::a", Declaration::Instance, 1)); + << TestUse(QStringLiteral("foo"), Declaration::Namespace, 1) + << TestUse(QStringLiteral("y"), Declaration::Namespace, 0) + << TestUse(QStringLiteral("foo::a"), Declaration::Instance, 1)); - QTest::newRow("class") << QString("() - << TestUse("foo", Declaration::Type, 0) - << TestUse("foo::bar", Declaration::Instance, 1) - << TestUse("foo::func", Declaration::Type, 1) + << TestUse(QStringLiteral("foo"), Declaration::Type, 0) + << TestUse(QStringLiteral("foo::bar"), Declaration::Instance, 1) + << TestUse(QStringLiteral("foo::func"), Declaration::Type, 1) ); } void TestDUChain::errorRecovery() { QFETCH(QString, code); QFETCH(QList, usesMap); TopDUContext* top = parse(code.toLocal8Bit(), DumpAll); QVERIFY(top); DUChainReleaser releaseTop(top); DUChainWriteLocker lock; foreach ( const TestUse& use, usesMap ) { QList< Declaration* > decs = top->findDeclarations(use.id); QCOMPARE(decs.count(), 1); Declaration* dec = decs.first(); QCOMPARE(dec->kind(), use.kind); if (use.uses) { QCOMPARE(dec->uses().count(), 1); QCOMPARE(dec->uses().begin()->count(), use.uses); } } } void TestDUChain::varStatic() { //bug: https://bugs.kde.org/244076 // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("problems().empty()); // we cannot support anything though :( } void TestDUChain::staticNowdoc() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("problems().empty()); QCOMPARE(top->childContexts().first()->localDeclarations().count(), 2); QCOMPARE(top->childContexts().first()->localDeclarations().first()->type()->dataType(), static_cast(IntegralType::TypeString)); QCOMPARE(top->childContexts().first()->localDeclarations().last()->type()->dataType(), static_cast(IntegralType::TypeString)); } void TestDUChain::curlyVarAfterObj() { // bug: https://bugs.kde.org/show_bug.cgi?id=241645 // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("{$a->bar}();\n" "$a->{$a->asdf};\n" , DumpNone); QVERIFY(top); DUChainReleaser releaseTop(top); DUChainWriteLocker lock; QVERIFY(top->problems().empty()); } void TestDUChain::embeddedHTML_data() { QTest::addColumn("code"); - QTest::newRow("if") << QString("\n"); - QTest::newRow("elseif") << QString("\n\n"); - QTest::newRow("foreach") << QString("\n\n"); - QTest::newRow("switch") << QString("\n\n"); - QTest::newRow("for") << QString("\n\n"); - QTest::newRow("while") << QString("\n\n"); - QTest::newRow("else") << QString(""); + QTest::newRow("if") << QStringLiteral("\n"); + QTest::newRow("elseif") << QStringLiteral("\n\n"); + QTest::newRow("foreach") << QStringLiteral("\n\n"); + QTest::newRow("switch") << QStringLiteral("\n\n"); + QTest::newRow("for") << QStringLiteral("\n\n"); + QTest::newRow("while") << QStringLiteral("\n\n"); + QTest::newRow("else") << QStringLiteral(""); } void TestDUChain::embeddedHTML() { QFETCH(QString, code); TopDUContext* top = parse(code.toLocal8Bit(), DumpNone); QVERIFY(top); DUChainReleaser releaseTop(top); DUChainWriteLocker lock; QVERIFY(top->problems().empty()); } void TestDUChain::cases() { // testcase for bug https://bugs.kde.org/show_bug.cgi?id=245832 TopDUContext* top = parse("problems().empty()); } void TestDUChain::closureParser() { // testcase for the parser after closures where introduced, // to make sure nothing brakes and all parser conflicts are resolved TopDUContext* top = parse("problems().empty()); } void TestDUChain::closures() { TopDUContext* top = parse("problems().isEmpty()); QCOMPARE(top->localDeclarations().count(), 2); Declaration* l = top->localDeclarations().first(); QCOMPARE(l->identifier().toString(), QString("l")); Declaration* closure = top->localDeclarations().last(); QVERIFY(closure->identifier().isEmpty()); FunctionType::Ptr funcType = closure->type(); QVERIFY(funcType); QCOMPARE(funcType->arguments().count(), 2); QVERIFY(funcType->arguments().at(0).cast()); QCOMPARE(funcType->arguments().at(0).cast()->dataType(), static_cast(IntegralType::TypeMixed)); QVERIFY(funcType->arguments().at(1).cast()); QCOMPARE(funcType->arguments().at(1).cast()->qualifiedIdentifier().toString(), QString("stdclass")); QVERIFY(funcType->returnType().cast()); QCOMPARE(funcType->returnType().cast()->dataType(), static_cast(IntegralType::TypeInt)); QVERIFY(l->abstractType()->equals(closure->abstractType().constData())); } void TestDUChain::closureEmptyUse() { // test case for: https://bugs.kde.org/show_bug.cgi?id=267105 // don't crash but report parse error TopDUContext* top = parse(" 2; };\n", DumpNone); QVERIFY(top); DUChainReleaser releaseTop(top); DUChainWriteLocker lock; QCOMPARE(top->problems().size(), 1); } void TestDUChain::gotoTest() { TopDUContext* top = parse("problems().isEmpty()); ///TODO: create declaration for destination label ///TODO: create use for goto label ///TODO: report error when trying to jump into loop or switch statement } void TestDUChain::ternary() { TopDUContext* top = parse("problems().isEmpty()); } void TestDUChain::bug296709() { // see also: https://bugs.kde.org/show_bug.cgi?id=296709 // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse( "problems().isEmpty()); - QList< Declaration* > decs = top->findLocalDeclarations(Identifier("a")); + QList< Declaration* > decs = top->findLocalDeclarations(Identifier(QStringLiteral("a"))); QCOMPARE(decs.size(), 1); QCOMPARE(decs.at(0)->range(), RangeInRevision(1, 19, 1, 21)); QCOMPARE(decs.at(0)->uses().count(), 1); QCOMPARE(decs.at(0)->uses().begin()->count(), 1); QCOMPARE(decs.at(0)->uses().begin()->first(), RangeInRevision(2, 2, 2, 4)); } void TestDUChain::declareFinalMethod() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("parentContext()); QCOMPARE(top->childContexts().count(), 1); DUContext* contextClassA = top->childContexts().first(); Declaration* dec = contextClassA->localDeclarations().at(0); ClassFunctionDeclaration* funDec = dynamic_cast(dec); QVERIFY(funDec); QCOMPARE(funDec->qualifiedIdentifier(), QualifiedIdentifier("a::foo")); QVERIFY(funDec->isFinal()); } void Php::TestDUChain::testTodoExtractor() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("languageController()->completionSettings()->todoMarkerWords().contains("TODO")); QVERIFY(KDevelop::ICore::self()->languageController()->completionSettings()->todoMarkerWords().contains("FIXME")); TopDUContext* top = parse(method, DumpAll); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); QVERIFY(top); QCOMPARE(top->problems().size(), 2); QCOMPARE(top->problems().at(0)->description(), QString("TODO: bla")); QCOMPARE(top->problems().at(0)->range(), RangeInRevision(1, 3, 1, 12)); QCOMPARE(top->problems().at(1)->description(), QString("FIXME blub")); QCOMPARE(top->problems().at(1)->range(), RangeInRevision(2, 4, 2, 14)); } diff --git a/duchain/tests/duchain_multiplefiles.cpp b/duchain/tests/duchain_multiplefiles.cpp index d1d44fa..1ae51cb 100644 --- a/duchain/tests/duchain_multiplefiles.cpp +++ b/duchain/tests/duchain_multiplefiles.cpp @@ -1,269 +1,269 @@ /* This file is part of KDevelop Copyright 2010 Niko Sams Copyright 2011 Milian Wolff This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "duchain_multiplefiles.h" #include #include #include #include #include #include #include QTEST_MAIN(Php::TestDUChainMultipleFiles) using namespace KDevelop; using namespace Php; void TestDUChainMultipleFiles::initTestCase() { DUChainTestBase::initTestCase(); TestCore* core = dynamic_cast(ICore::self()); Q_ASSERT(core); m_projectController = new TestProjectController(core); core->setProjectController(m_projectController); } void TestDUChainMultipleFiles::testImportsGlobalFunction() { TopDUContext::Features features = TopDUContext::VisibleDeclarationsAndContexts; TestProject* project = new TestProject; m_projectController->closeAllProjects(); m_projectController->addProject(project); - TestFile f1("imports(f1.topContext(), CursorInRevision(0, 0))); } void TestDUChainMultipleFiles::testImportsBaseClassNotYetParsed() { TopDUContext::Features features = TopDUContext::VisibleDeclarationsAndContexts; TestProject* project = new TestProject; m_projectController->closeAllProjects(); m_projectController->addProject(project); - TestFile f2("imports(f1.topContext(), CursorInRevision(0, 0))); QVERIFY(ICore::self()->languageController()->backgroundParser()->queuedCount() == 0); } void TestDUChainMultipleFiles::testNonExistingBaseClass() { TopDUContext::Features features = TopDUContext::VisibleDeclarationsAndContexts; TestProject* project = new TestProject; m_projectController->closeAllProjects(); m_projectController->addProject(project); - TestFile f1("languageController()->backgroundParser()->queuedCount() == 0); } void TestDUChainMultipleFiles::testImportsGlobalFunctionNotYetParsed() { TopDUContext::Features features = TopDUContext::VisibleDeclarationsAndContexts; TestProject* project = new TestProject; m_projectController->closeAllProjects(); m_projectController->addProject(project); - TestFile f2("imports(f1.topContext(), CursorInRevision(0, 0))); } void TestDUChainMultipleFiles::testNonExistingGlobalFunction() { TopDUContext::Features features = TopDUContext::VisibleDeclarationsAndContexts; TestProject* project = new TestProject; m_projectController->closeAllProjects(); m_projectController->addProject(project); - TestFile f2("languageController()->backgroundParser()->queuedCount() == 0); } void TestDUChainMultipleFiles::testImportsStaticFunctionNotYetParsed() { TopDUContext::Features features = TopDUContext::VisibleDeclarationsAndContexts; TestProject* project = new TestProject; m_projectController->closeAllProjects(); m_projectController->addProject(project); - TestFile f2("imports(f1.topContext(), CursorInRevision(0, 0))); } void TestDUChainMultipleFiles::testNonExistingStaticFunction() { TopDUContext::Features features = TopDUContext::VisibleDeclarationsAndContexts; TestProject* project = new TestProject; m_projectController->closeAllProjects(); m_projectController->addProject(project); - TestFile f2("languageController()->backgroundParser()->queuedCount() == 0); } void TestDUChainMultipleFiles::testForeachImportedIdentifier() { // see https://bugs.kde.org/show_bug.cgi?id=269369 TopDUContext::Features features = TopDUContext::VisibleDeclarationsAndContexts; TestProject* project = new TestProject; m_projectController->closeAllProjects(); m_projectController->addProject(project); // build dependency - TestFile f1("bar(); foreach($i as $a => $b) {} } \n" " public function bar() { $a = new SomeIterator(); return $a; }\n" - " }\n", "php", project); + " }\n"), QStringLiteral("php"), project); for(int i = 0; i < 2; ++i) { if (i > 0) { features = static_cast(features | TopDUContext::ForceUpdate); } f2.parse(features); QVERIFY(f2.waitForParsed()); QTest::qWait(100); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(f2.topContext()->childContexts().size(), 1); DUContext* ACtx = f2.topContext()->childContexts().first(); QVERIFY(ACtx); QCOMPARE(ACtx->childContexts().size(), 4); Declaration* iDec = ACtx->childContexts().at(1)->localDeclarations().first(); QVERIFY(iDec); Declaration* SomeIteratorDec = f1.topContext()->localDeclarations().first(); QVERIFY(SomeIteratorDec); if (i == 0) { QEXPECT_FAIL("", "needs a full two-pass (i.e. get rid of PreDeclarationBuilder)", Continue); } QVERIFY(iDec->abstractType()->equals(SomeIteratorDec->abstractType().constData())); QVERIFY(f2.topContext()->imports(f1.topContext(), CursorInRevision(0, 0))); } } void TestDUChainMultipleFiles::testUpdateForeach() { TopDUContext::Features features = TopDUContext::AllDeclarationsContextsAndUses; TestProject* project = new TestProject; m_projectController->closeAllProjects(); m_projectController->addProject(project); - TestFile f(" $k) {}\n", "php", project); + TestFile f(QStringLiteral(" $k) {}\n"), QStringLiteral("php"), project); f.parse(features); QVERIFY(f.waitForParsed()); QVERIFY(f.topContext()); { DUChainWriteLocker lock; QVERIFY(f.topContext()->problems().isEmpty()); QCOMPARE(f.topContext()->findDeclarations(Identifier("k")).count(), 1); - Declaration* kDec = f.topContext()->findDeclarations(Identifier("k")).first(); + Declaration* kDec = f.topContext()->findDeclarations(Identifier(QStringLiteral("k"))).first(); QCOMPARE(kDec->rangeInCurrentRevision().start().line(), 1); QCOMPARE(kDec->rangeInCurrentRevision().start().column(), 0); QCOMPARE(kDec->uses().count(), 1); QCOMPARE(kDec->uses().begin()->count(), 1); QCOMPARE(kDec->uses().begin()->begin()->start.line, 2); } // delete $k = null; line - f.setFileContents(" $k) {}\n"); + f.setFileContents(QStringLiteral(" $k) {}\n")); f.parse(static_cast(features | TopDUContext::ForceUpdate)); QVERIFY(f.waitForParsed()); QVERIFY(f.topContext()); { DUChainWriteLocker lock; QVERIFY(f.topContext()->problems().isEmpty()); QCOMPARE(f.topContext()->findDeclarations(Identifier("k")).count(), 1); - Declaration* kDec = f.topContext()->findDeclarations(Identifier("k")).first(); + Declaration* kDec = f.topContext()->findDeclarations(Identifier(QStringLiteral("k"))).first(); QCOMPARE(kDec->rangeInCurrentRevision().start().line(), 1); QCOMPARE(kDec->rangeInCurrentRevision().start().column(), 25); QCOMPARE(kDec->uses().count(), 0); } } diff --git a/duchain/tests/duchaintestbase.cpp b/duchain/tests/duchaintestbase.cpp index 08fa2ea..ade90c8 100644 --- a/duchain/tests/duchaintestbase.cpp +++ b/duchain/tests/duchaintestbase.cpp @@ -1,213 +1,213 @@ /* This file is part of KDevelop Copyright 2006 Hamish Rodda 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 "duchaintestbase.h" #include #include #include #include #include #include #include #include #include #include "../completion/codemodelitem.h" #include "../builders/declarationbuilder.h" #include "../builders/usebuilder.h" #include "../dumptypes.h" #include "../../parser/parsesession.h" #include "../../parser/phpdebugvisitor.h" #include "../helper.h" using namespace KDevelop; namespace Php { void DUChainTestBase::initTestCase() { AutoTestShell::init(); TestCore::initialize(Core::NoUi); DUChain::self()->disablePersistentStorage(); CodeRepresentation::setDiskChangesForbidden(true); //yeah... adding a testcase here is kinda strange, but anyways - we have to check for special //handling of the internal functions file //see e.g. testDeclarationReturnTypeDocBlock QByteArray content("problems().isEmpty()); // set features and modification revision, to prevent test cases that use // the full language plugin from re-parsing the big internal function file ctx->setFeatures(TopDUContext::AllDeclarationsAndContexts); ParsingEnvironmentFilePointer file = ctx->parsingEnvironmentFile(); QVERIFY(file); file->setModificationRevision(ModificationRevision::revisionForFile(internalFunctionFile())); DUChain::self()->updateContextEnvironment(ctx, file.data()); } void DUChainTestBase::cleanupTestCase() { TestCore::shutdown(); } CompletionTreeItemPointer DUChainTestBase::searchDeclaration(QList items, Declaration* declaration) { foreach(const CompletionTreeItemPointer &item, items) { if (item->declaration().data() == declaration) { return item; } } return CompletionTreeItemPointer(); } bool DUChainTestBase::hasImportedParentContext(TopDUContext* top, DUContext* lookingFor) { qDebug() << "this topcontext has " << top->importedParentContexts().count() << " imported parent contexts" << "\n we are looking for: " << lookingFor->url().byteArray(); foreach(const DUContext::Import &import, top->importedParentContexts()) { if (import.context(top)) { qDebug() << import.context(top)->url().byteArray(); } if (import.context(top) == lookingFor) { return true; } } return false; } TopDUContext* DUChainTestBase::parseAdditionalFile(const IndexedString& fileName, const QByteArray& contents) { ParseSession session; session.setContents(contents); StartAst* ast = 0; if (!session.parse(&ast)) qFatal("can't parse"); EditorIntegrator editor(&session); session.setCurrentDocument(fileName); DeclarationBuilder declarationBuilder(&editor); TopDUContext* top = declarationBuilder.build(fileName, ast); if ( fileName != internalFunctionFile() ) { UseBuilder useBuilder(&editor); useBuilder.buildUses(ast); } if (!session.problems().isEmpty()) { DUChainWriteLocker lock; foreach( const ProblemPointer& p, session.problems() ) { top->addProblem(p); } } return top; } TopDUContext* DUChainTestBase::parse(const QByteArray& unit, DumpAreas dump, QUrl url, TopDUContext* update) { if (dump) qDebug() << "==== Beginning new test case...:" << endl << unit; ParseSession session; session.setContents(unit); StartAst* ast = 0; if (!session.parse(&ast)) { qDebug() << "Parse failed"; return 0; } if (dump & DumpAST) { qDebug() << "===== AST:"; DebugVisitor debugVisitor(session.tokenStream(), session.contents()); debugVisitor.visitNode(ast); } static int testNumber = 0; if (url.isEmpty()) { - url = QUrl(QString("file:///internal/%1").arg(testNumber++)); + url = QUrl(QStringLiteral("file:///internal/%1").arg(testNumber++)); } EditorIntegrator editor(&session); session.setCurrentDocument(IndexedString(url)); DeclarationBuilder declarationBuilder(&editor); TopDUContext* top = declarationBuilder.build(session.currentDocument(), ast, ReferencedTopDUContext(update)); if (!session.problems().isEmpty()) { DUChainWriteLocker lock; foreach( const ProblemPointer& p, session.problems() ) { top->addProblem(p); } } if ( IndexedString(url) != internalFunctionFile() ) { UseBuilder useBuilder(&editor); useBuilder.buildUses(ast); } if (dump & DumpDUChain) { qDebug() << "===== DUChain:"; DUChainWriteLocker lock(DUChain::lock()); DUChainDumper d; d.dump(top); } if (dump & DumpType) { qDebug() << "===== Types:"; DUChainWriteLocker lock(DUChain::lock()); DumpTypes dt; foreach(const AbstractType::Ptr& type, declarationBuilder.topTypes()) { dt.dump(type.data()); } } if (dump) qDebug() << "===== Finished test case."; return top; } } diff --git a/duchain/tests/uses.cpp b/duchain/tests/uses.cpp index 1d624bb..5735476 100644 --- a/duchain/tests/uses.cpp +++ b/duchain/tests/uses.cpp @@ -1,1225 +1,1225 @@ /* 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 "../declarations/classdeclaration.h" #include "../declarations/variabledeclaration.h" #include "../declarations/traitmethodaliasdeclaration.h" #include "../declarations/traitmemberaliasdeclaration.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("file:///internal/usestest/memberFunctionCall.php")); + 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::memberVariable() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("foo; "); - TopDUContext* top = parse(method, DumpNone, QUrl("file:///internal/usestest/memberVariable.php")); + 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::variable() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("foo; foo($a); "); TopDUContext* top = parse(method, DumpAll); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); QList ranges; ranges << RangeInRevision(1, 42 - 3, 1, 44 - 3) << RangeInRevision(1, 46 - 3, 1, 48 - 3) << RangeInRevision(1, 59 - 3, 1, 61 - 3); compareUses(top->localDeclarations().at(1), ranges); } void TestUses::varInString() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method(" ranges; ranges << RangeInRevision(0, 13, 0, 15) << RangeInRevision(0, 17, 0, 19); compareUses(top->localDeclarations().at(0), ranges); } void TestUses::variableInNamespace() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("foo; foo($a); };"); TopDUContext* top = parse(method, DumpAll); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); QList ranges; ranges << RangeInRevision(1, 55, 1, 57) << RangeInRevision(1, 59, 1, 61) << RangeInRevision(1, 72, 1, 74); compareUses(top->localDeclarations().at(2), ranges); } void TestUses::globalVariableInNamespace() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("foo; foo($a); };"); TopDUContext* top = parse(method, DumpAll); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); QList ranges; ranges << RangeInRevision(1, 55, 1, 57) << RangeInRevision(1, 59, 1, 61) << RangeInRevision(1, 72, 1, 74); compareUses(top->localDeclarations().at(1), ranges); } void TestUses::variableInOtherNamespace() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("foo; foo($a); };"); TopDUContext* top = parse(method, DumpAll); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); QList ranges; ranges << RangeInRevision(1, 73, 1, 75) << RangeInRevision(1, 77, 1, 79) << RangeInRevision(1, 90, 1, 92); compareUses(top->localDeclarations().at(2), ranges); } void TestUses::memberVarInString() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789 01234567890123 4567890123456789 QByteArray method("v; \"$a->v {$a->v}\"; "); TopDUContext* top = parse(method, DumpAll); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); QList ranges; ranges << RangeInRevision(0, 43, 0, 45) << RangeInRevision(0, 51, 0, 53) << RangeInRevision(0, 58, 0, 60); compareUses(top->localDeclarations().at(1), ranges); ranges.clear(); ranges << RangeInRevision(0, 47, 0, 48) << RangeInRevision(0, 55, 0, 56) << RangeInRevision(0, 62, 0, 63); compareUses(top->childContexts().first()->localDeclarations().first(), ranges); } void TestUses::memberFunctionInString() { // 0 1 2 3 4 5 6 7 // 012345678901234567890123456789012345678901234567 890123456789 01234567890123456789 QByteArray method("foo()}\"; "); TopDUContext* top = parse(method, DumpAll); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); //$a compareUses(top->localDeclarations().at(1), RangeInRevision(0, 50, 0, 52)); //foo compareUses(top->childContexts().first()->localDeclarations().first(), RangeInRevision(0, 54, 0, 57)); } void TestUses::variableTypeChange() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method(" ranges; ranges << RangeInRevision(0, 25, 0, 27); ranges << RangeInRevision(0, 29, 0, 31); ranges << RangeInRevision(0, 37, 0, 39); ranges << RangeInRevision(0, 41, 0, 43); ranges << RangeInRevision(0, 51, 0, 53); compareUses(top->localDeclarations().at(1), ranges); } void TestUses::variableTypeChangeInFunction() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method(" ranges; ranges << RangeInRevision(0, 28, 0, 30); ranges << RangeInRevision(0, 32, 0, 34); ranges << RangeInRevision(0, 38, 0, 40); ranges << RangeInRevision(0, 42, 0, 44); compareUses(top->childContexts().at(1)->localDeclarations().at(0), ranges); } void TestUses::classExtends() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().at(0), RangeInRevision(0, 31, 0, 32)); } void TestUses::classImplements() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().at(0), RangeInRevision(0, 38, 0, 39)); } void TestUses::classImplementsMultiple() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().at(0), RangeInRevision(0, 54, 0, 55)); compareUses(top->localDeclarations().at(1), RangeInRevision(0, 57, 0, 58)); } void TestUses::interfaceExtends() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().at(0), RangeInRevision(0, 39, 0, 40)); } void TestUses::interfaceExtendsMultiple() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().at(0), RangeInRevision(0, 55, 0, 56)); compareUses(top->localDeclarations().at(1), RangeInRevision(0, 58, 0, 59)); } void TestUses::staticMemberFunctionCall() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().first(), RangeInRevision(0, 47, 0, 48)); compareUses(top->childContexts().first()->localDeclarations().first(), RangeInRevision(0, 50, 0, 53)); } void TestUses::staticMemberVariable() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().first(), RangeInRevision(0, 43, 0, 44)); compareUses(top->childContexts().first()->localDeclarations().first(), RangeInRevision(0, 46, 0, 50)); compareUses(top->localDeclarations().at(1), RangeInRevision(0, 52, 0, 56)); } void TestUses::constant() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().first(), RangeInRevision(0, 28, 0, 29)); } void TestUses::classConstant() { { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().first(), RangeInRevision(0, 39, 0, 40)); compareUses(top->childContexts().first()->localDeclarations().first(), RangeInRevision(0, 42, 0, 45)); } { // bug: https://bugs.kde.org/show_bug.cgi?id=241597 // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("childContexts().first()->localDeclarations().first(); QVERIFY(dec->abstractType()->modifiers() & AbstractType::ConstModifier); QCOMPARE(dec->qualifiedIdentifier().toString(), QString("a::FOO")); compareUses(dec, QList() << RangeInRevision(1, 43, 1, 46) << RangeInRevision(2, 3, 2, 6) << RangeInRevision(3, 3, 3, 6)); } } void TestUses::classParent() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method(" range; range << RangeInRevision(0, 47, 0, 48); range << RangeInRevision(0, 66, 0, 72); compareUses(top->localDeclarations().first(), range); compareUses(top->childContexts().first()->localDeclarations().first(), RangeInRevision(0, 74, 0, 75)); } void TestUses::classSelf() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().first(), RangeInRevision(0, 28, 0, 32)); compareUses(top->childContexts().first()->localDeclarations().first(), RangeInRevision(0, 34, 0, 35)); } void TestUses::classThis() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("x(); } } "); TopDUContext* top = parse(method, DumpAll); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); compareUses(top->localDeclarations().first(), RangeInRevision(0, 28, 0, 33)); compareUses(top->childContexts().first()->localDeclarations().first(), RangeInRevision(0, 35, 0, 36)); } void TestUses::objectWithClassName() { // 0 1 2 3 4 5 6 7 8 // 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("k; Aa::j; Aa::$i;"); TopDUContext* top = parse(method, DumpAll); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); QList ranges; ranges << RangeInRevision(0, 66, 0, 66 + 2); ranges << RangeInRevision(0, 78, 0, 78 + 2); ranges << RangeInRevision(0, 85, 0, 85 + 2); compareUses(top->localDeclarations().first(), ranges); compareUses(top->localDeclarations().at(1), RangeInRevision(0, 70, 0, 70 + 3)); } void TestUses::classAndConstWithSameName() { // 0 1 2 3 4 5 6 7 8 // 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().first(), RangeInRevision(0, 38, 0, 39)); compareUses(top->localDeclarations().at(1), RangeInRevision(0, 31, 0, 32)); compareUses(top->localDeclarations().at(2), RangeInRevision(0, 76, 0, 77)); compareUses(top->localDeclarations().at(3), RangeInRevision(0, 73, 0, 74)); } void TestUses::classAndFunctionWithSameName() { // 0 1 2 3 4 5 6 7 8 // 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().first(), RangeInRevision(0, 35, 0, 36)); compareUses(top->localDeclarations().at(1), RangeInRevision(0, 38, 0, 39)); } void TestUses::constAndVariableWithSameName() { // 0 1 2 3 4 5 6 7 8 // 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().first(), RangeInRevision(0, 30, 0, 32)); compareUses(top->localDeclarations().at(1), RangeInRevision(0, 27, 0, 28)); } void TestUses::functionAndClassWithSameName() { // 0 1 2 3 4 5 6 7 8 // 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("childContexts().first()->localDeclarations().first(); QCOMPARE(fnAsdf->uses().keys().count(), 0); compareUses(top->localDeclarations().at(1), RangeInRevision(0, 70, 0, 74)); } void TestUses::constantInClassMember() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("findDeclarations(Identifier("TEST")).first(); + Declaration* constant = top->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("y")).first(); + 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("z")).first(); + Declaration *z = x->logicalInternalContext(top)->findDeclarations(Identifier(QStringLiteral("z"))).first(); QVERIFY(z); // $a - Declaration *a = z->logicalInternalContext(top)->findDeclarations(Identifier("a")).first(); + 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("exception")).first(); + Declaration *d = top->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("s")); + QList< Declaration* > 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("foobar")); + QList 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("foobar")); + 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("barfoo")); + 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("asdf")); + QList 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("foo")).last(); + dec = top->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("foo::bar")).first(); + 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("foo::bar::MyConst")).first(); + 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("foo::bar::myclass")).first(); + 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("foo::bar::myinterface")).first(); + dec = top->findDeclarations(QualifiedIdentifier(QStringLiteral("foo::bar::myinterface"))).first(); QVERIFY(dynamic_cast(dec)); compareUses(dec, RangeInRevision(14, 53, 14, 64) ); - dec = top->findDeclarations(QualifiedIdentifier("foo::bar::myclass::ClassConst")).first(); + dec = top->findDeclarations(QualifiedIdentifier(QStringLiteral("foo::bar::myclass::ClassConst"))).first(); compareUses(dec, RangeInRevision(10, 18, 10, 28)); - dec = top->findDeclarations(QualifiedIdentifier("foo::bar::myfunc")).first(); + 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("foo")).first(); + dec = top->findDeclarations(QualifiedIdentifier(QStringLiteral("foo"))).first(); QCOMPARE(dec->kind(), Declaration::Namespace); compareUses(dec, RangeInRevision(5, 4, 5, 7)); - dec = top->findDeclarations(QualifiedIdentifier("foo::bar")).first(); + dec = top->findDeclarations(QualifiedIdentifier(QStringLiteral("foo::bar"))).first(); QCOMPARE(dec->kind(), Declaration::Namespace); compareUses(dec, RangeInRevision(5, 8, 5, 11)); - dec = top->findDeclarations(QualifiedIdentifier("verylong")).first(); + dec = top->findDeclarations(QualifiedIdentifier(QStringLiteral("verylong"))).first(); QCOMPARE(dec->kind(), Declaration::Namespace); compareUses(dec, RangeInRevision(5, 13, 5, 21)); - dec = top->findDeclarations(QualifiedIdentifier("bar")).first(); + 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("short")).first(); + 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("baz::a")).first(); + 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("foo::bar::a")).first(); + dec = top->findDeclarations(QualifiedIdentifier(QStringLiteral("foo::bar::a"))).first(); compareUses(dec, RangeInRevision(7, 8, 7, 9)); - dec = top->findDeclarations(QualifiedIdentifier("foo::bar::b")).first(); + dec = top->findDeclarations(QualifiedIdentifier(QStringLiteral("foo::bar::b"))).first(); compareUses(dec, RangeInRevision(7, 15, 7, 16)); - dec = top->findDeclarations(QualifiedIdentifier("foo::bar::C")).first(); + dec = top->findDeclarations(QualifiedIdentifier(QStringLiteral("foo::bar::C"))).first(); compareUses(dec, RangeInRevision(7, 24, 7, 25)); - dec = top->findDeclarations(QualifiedIdentifier("verylong::a")).first(); + dec = top->findDeclarations(QualifiedIdentifier(QStringLiteral("verylong::a"))).first(); compareUses(dec, RangeInRevision(8, 10, 8, 11)); - dec = top->findDeclarations(QualifiedIdentifier("verylong::b")).first(); + dec = top->findDeclarations(QualifiedIdentifier(QStringLiteral("verylong::b"))).first(); compareUses(dec, RangeInRevision(8, 19, 8, 20)); - dec = top->findDeclarations(QualifiedIdentifier("verylong::C")).first(); + 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::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::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 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 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", DumpAll); QVERIFY(top); DUChainReleaser releaseTop(top); DUChainWriteLocker lock; Declaration* dec; QVector topDecs = top->localDeclarations(); TraitMethodAliasDeclaration* method; TraitMemberAliasDeclaration* member; QCOMPARE(topDecs.size(), 9); - dec = top->findDeclarations(QualifiedIdentifier("a")).first(); + 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) ); - dec = top->findDeclarations(QualifiedIdentifier("b")).first(); + 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) ); - dec = top->findDeclarations(QualifiedIdentifier("c")).first(); + 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) ); - dec = topDecs[3]->internalContext()->findLocalDeclarations(Identifier("one")).first(); + 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(7, 20, 7, 23) ); - dec = topDecs[3]->internalContext()->findLocalDeclarations(Identifier("two")).first(); + 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(7, 31, 7, 34) ); - dec = topDecs[4]->internalContext()->findLocalDeclarations(Identifier("one")).first(); + 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(8, 20, 8, 23) ); - dec = topDecs[4]->internalContext()->findLocalDeclarations(Identifier("two")).first(); + 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(8, 31, 8, 34) ); - dec = topDecs[4]->internalContext()->findLocalDeclarations(Identifier("four")).first(); + 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(8, 42, 8, 46) ); - dec = topDecs[5]->internalContext()->findLocalDeclarations(Identifier("one")).first(); + 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(9, 20, 9, 23) ); - dec = topDecs[5]->internalContext()->findLocalDeclarations(Identifier("two")).first(); + 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(9, 31, 9, 34) ); - dec = topDecs[5]->internalContext()->findLocalDeclarations(Identifier("baz")).first(); + 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(9, 42, 9, 45) ); - dec = topDecs[5]->internalContext()->findLocalDeclarations(Identifier("six")).first(); + 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(9, 51, 9, 54) ); } 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)); } } diff --git a/duchain/types/integraltypeextended.cpp b/duchain/types/integraltypeextended.cpp index 596ec5e..f4c4be2 100644 --- a/duchain/types/integraltypeextended.cpp +++ b/duchain/types/integraltypeextended.cpp @@ -1,85 +1,85 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2009 Milian Wolff * * * * 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 "integraltypeextended.h" #include using namespace KDevelop; namespace Php { REGISTER_TYPE(IntegralTypeExtended); IntegralTypeExtended::IntegralTypeExtended(const IntegralTypeExtended& rhs) : IntegralType(copyData(*rhs.d_func())) { } IntegralTypeExtended::IntegralTypeExtended(IntegralTypeExtendedData& data) : IntegralType(data) { } IntegralTypeExtended::IntegralTypeExtended(uint type) : IntegralType(createData()) { setDataType(type); setModifiers(ConstModifier); } QString IntegralTypeExtended::toString() const { if ( d_func()->m_dataType == TypeResource ) { - return "resource"; + return QStringLiteral("resource"); } return KDevelop::IntegralType::toString(); } KDevelop::AbstractType* IntegralTypeExtended::clone() const { return new IntegralTypeExtended(*this); } uint IntegralTypeExtended::hash() const { return 4 * KDevelop::IntegralType::hash(); } bool IntegralTypeExtended::equals(const KDevelop::AbstractType* rhs) const { if( this == rhs ) { return true; } if ( !IntegralType::equals(rhs) ) { return false; } Q_ASSERT( fastCast(rhs) ); const IntegralTypeExtended* type = static_cast(rhs); return d_func()->m_dataType == type->d_func()->m_dataType; } } diff --git a/kdevphpsupport.json b/kdevphpsupport.json index b3262c7..5f3097f 100644 --- a/kdevphpsupport.json +++ b/kdevphpsupport.json @@ -1,61 +1,63 @@ { "KPlugin": { "Category": "Language Support", "Description": "PHP Language Support", "Description[ast]": "Sofitu del llinguax PHP", "Description[ca@valencia]": "Implementació del llenguatge PHP", "Description[ca]": "Implementació del llenguatge PHP", "Description[cs]": "Podpora jazyka PHP", "Description[de]": "Sprachunterstützung für PHP", "Description[es]": "Implementación del lenguaje PHP", + "Description[et]": "PHP keele toetus", "Description[fi]": "PHP-kielituki", "Description[gl]": "Soporte da linguaxe PHP", "Description[ko]": "PHP 언어 지원", "Description[nl]": "Ondersteuning van de PHP-taal", "Description[pl]": "Obsługa języka PHP", "Description[pt]": "Suporte da Linguagem PHP", "Description[pt_BR]": "Suporte à linguagem PHP", "Description[sk]": "Jazyková podpora PHP", "Description[sl]": "Podpora jeziku PHP", "Description[sv]": "PHP-språkstöd", "Description[uk]": "Підтримка мови PHP", "Description[x-test]": "xxPHP Language Supportxx", "Description[zh_CN]": "PHP 语言支持", "Icon": "application-x-php", "Id": "KDevPhpSupport", "Name": "PHP Language Support", "Name[ast]": "Sofitu del llinguax PHP", "Name[ca@valencia]": "Implementació del llenguatge PHP", "Name[ca]": "Implementació del llenguatge PHP", "Name[cs]": "Podpora jazyka PHP", "Name[de]": "Sprachunterstützung für PHP", "Name[es]": "Implementación del lenguaje PHP", + "Name[et]": "PHP keele toetus", "Name[fi]": "PHP-kielituki", "Name[gl]": "Soporte da linguaxe PHP", "Name[ko]": "PHP 언어 지원", "Name[nl]": "Ondersteuning van de PHP-taal", "Name[pl]": "Obsługa języka PHP", "Name[pt]": "Suporte da Linguagem PHP", "Name[pt_BR]": "Suporte à linguagem PHP", "Name[sk]": "Jazyková podpora PHP", "Name[sl]": "Podpora jeziku PHP", "Name[sv]": "PHP-språkstöd", "Name[uk]": "Підтримка мови PHP", "Name[x-test]": "xxPHP Language Supportxx", "Name[zh_CN]": "PHP 语言支持", "ServiceTypes": [ "KDevelop/Plugin" ] }, "X-KDevelop-Args": "PHP", "X-KDevelop-Interfaces": [ "ILanguageSupport", "org.kdevelop.PHPSupport" ], "X-KDevelop-Language": "Php", "X-KDevelop-LoadMode": "AlwaysOn", "X-KDevelop-Mode": "NoGUI", "X-KDevelop-SupportedMimeTypes": [ "application/x-php" ] } \ No newline at end of file diff --git a/parser/main.cpp b/parser/main.cpp index 63bb602..d172838 100644 --- a/parser/main.cpp +++ b/parser/main.cpp @@ -1,43 +1,43 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2008 Niko Sams * * Copyright 2009 Milian Wolff * * * * 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 "parsesession.h" #include "phplexer.h" #include "phpparser.h" #include "phpdebugvisitor.h" #include "phpast.h" #include "tokenstream.h" #include "phptokentext.h" #include using namespace Php; typedef KDevelopUtils::DebugLanguageParserHelper PhpParser; int main(int argc, char* argv[]) { - KAboutData aboutData( "php-parser", i18n( "php-parser" ), - "1", i18n("KDevelop PHP parser debugging utility"), KAboutLicense::GPL, - i18n( "(c) 2008 Niko Sams, 2009 Milian Wolff" ), {}, "http://www.kdevelop.org" ); + KAboutData aboutData( QStringLiteral("php-parser"), i18n( "php-parser" ), + QStringLiteral("1"), i18n("KDevelop PHP parser debugging utility"), KAboutLicense::GPL, + i18n( "(c) 2008 Niko Sams, 2009 Milian Wolff" ), {}, QStringLiteral("http://www.kdevelop.org") ); return KDevelopUtils::initAndRunParser(aboutData, argc, argv); } diff --git a/parser/parsesession.cpp b/parser/parsesession.cpp index 3c2911e..212aeff 100644 --- a/parser/parsesession.cpp +++ b/parser/parsesession.cpp @@ -1,178 +1,178 @@ /***************************************************************************** * Copyright (c) 2007 Piyush verma * * Copyright (c) 2008 Niko Sams * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * *****************************************************************************/ #include "parsesession.h" #include "phpast.h" #include "kdev-pg-memory-pool.h" #include "kdev-pg-token-stream.h" #include "parserdebug.h" #include #include #include #include #include #include namespace Php { ParseSession::ParseSession() : m_debug(false), m_pool(new KDevPG::MemoryPool()), m_tokenStream(new TokenStream()) { } ParseSession::~ParseSession() { delete m_pool; delete m_tokenStream; } QString ParseSession::contents() const { return m_contents; } void ParseSession::setContents(const QString& contents) { m_contents = contents; } void ParseSession::setCurrentDocument(const KDevelop::IndexedString& filename) { m_currentDocument = filename; } KDevelop::IndexedString ParseSession::currentDocument() const { return m_currentDocument; } bool ParseSession::readFile(const QString& filename, const char* codec) { m_currentDocument = KDevelop::IndexedString(filename); QFile f(filename); if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) { KDevelop::ProblemPointer p(new KDevelop::Problem()); p->setSource(KDevelop::IProblem::Disk); p->setDescription(i18n("Could not open file '%1'", filename)); switch (f.error()) { case QFile::ReadError: p->setExplanation(i18n("File could not be read from.")); break; case QFile::OpenError: p->setExplanation(i18n("File could not be opened.")); break; case QFile::PermissionsError: p->setExplanation(i18n("File permissions prevent opening for read.")); break; default: break; } p->setFinalLocation(KDevelop::DocumentRange(m_currentDocument, KTextEditor::Range())); m_problems << p; qCWarning(PARSER) << "Could not open file" << filename; return false; } QTextStream s(&f); if (codec) s.setCodec(QTextCodec::codecForName(codec)); m_contents = s.readAll(); return true; } void ParseSession::setDebug(bool debug) { m_debug = debug; } TokenStream* ParseSession::tokenStream() const { return m_tokenStream; } Parser* ParseSession::createParser(int initialState) { Parser* parser = new Parser; parser->setTokenStream(m_tokenStream); parser->setMemoryPool(m_pool); parser->setDebug(m_debug); parser->setCurrentDocument(m_currentDocument); parser->setTodoMarkers(KDevelop::ICore::self()->languageController()->completionSettings()->todoMarkerWords()); parser->tokenize(m_contents, initialState); return parser; } bool ParseSession::parse(Php::StartAst** ast) { Parser* parser = createParser(); StartAst* phpAst; bool matched = parser->parseStart(&phpAst); if (matched) { qCDebug(PARSER) << "Successfully parsed"; *ast = phpAst; } else { *ast = 0; - parser->expectedSymbol(AstNode::StartKind, "start"); + parser->expectedSymbol(AstNode::StartKind, QStringLiteral("start")); qCDebug(PARSER) << "Couldn't parse content"; } m_problems << parser->problems(); delete parser; return matched; } KDevelop::CursorInRevision ParseSession::positionAt(qint64 offset) const { qint64 line, column; m_tokenStream->locationTable()->positionAt(offset, &line, &column); return KDevelop::CursorInRevision(line, column); } QString ParseSession::symbol(qint64 token) const { const TokenStream::Token& tok = m_tokenStream->at(token); return m_contents.mid(tok.begin, tok.end - tok.begin + 1); } QString ParseSession::symbol(AstNode* node) const { const TokenStream::Token& startTok = m_tokenStream->at(node->startToken); const TokenStream::Token& endTok = m_tokenStream->at(node->endToken); return m_contents.mid(startTok.begin, endTok.end - startTok.begin + 1); } QString ParseSession::docComment(qint64 token) const { const TokenStream::Token& tok = m_tokenStream->at(token); if (!tok.docCommentEnd) return QString(); return m_contents.mid(tok.docCommentBegin, tok.docCommentEnd - tok.docCommentBegin + 1); } QList ParseSession::problems() { return m_problems; } } diff --git a/parser/php.g b/parser/php.g index d482cd4..bbc475a 100644 --- a/parser/php.g +++ b/parser/php.g @@ -1,1192 +1,1192 @@ ------------------------------------------------------------------------------- -- Copyright (c) 2008 Niko Sams -- -- This grammar is free software; you can redistribute it and/or -- modify it under the terms of the GNU Library General Public -- License as published by the Free Software Foundation; either -- version 2 of the License, or (at your option) any later version. -- -- This grammar is distributed in the hope that it will be useful, -- but WITHOUT ANY WARRANTY; without even the implied warranty of -- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -- Lesser General Public License for more details. -- -- You should have received a copy of the GNU Library General Public License -- along with this library; see the file COPYING.LIB. If not, write to -- the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, -- Boston, MA 02110-1301, USA. ----------------------------------------------------------- ----------------------------------------------------------- -- Grammar for PHP 5.2 -- Modelled after the Zend Grammar shipped with PHP5.2 -- source, the PHP Language Reference documentation, -- and parts taken from KDevelop Java Grammar ----------------------------------------------------------- -- 4 first/first conflicts: -- - var_expression: variable vs. varExpressionNormal -- no problem because of ifs that allow always just one rule -- - classNameReference: STRING vs. staticMember (foo vs. foo::$bar) -- resolved by LA() -- - encapsVar: STRING_VARNAME LBRACKET vs. expr (expr allows STRING_VARNAME too - but not LBRACKET) -- resolved by LA() -- - constantOrClassConst: constant vs. class constant (FOO v.s Cls::FOO) -- resolved by LA() (could be avoided, but the Ast is much cleaner that way) -- 1 first/follow conflicts: -- - elseifList: dangling-else conflict - should be ok -- TODO: (post 1.0.0 release) -- 1) decrease memory consumption -- 1.1) use quint32 instead of qint64 for end/start tokens -- 1.2) investigate whether using a map/hash for the ducontext member of all -- ast nodes gives a significant memory decrease while not hampering performance -- 1.3) investigate how unions could be used for exclusive AST node members -- 1.4) see whether we can always use the expression lists instead of both -- single member pointer and list of members, esp. in expressions -- 2) better cope with invalid code, have at least a partial AST -- 3) investigate whether expanding the visitor lookup to a -- (albeit huge) switch() in KDev-PG-Qt gives a significant performance gain -- I have the gut feeling that the current lookup takes unnecessary much time -- ------------------------------------------------------------ -- Forward declaration in phpast.h ------------------------------------------------------------ [: #include namespace KDevelop { class DUContext; } :] ------------------------------------------------------------ -- Additional includes for the parser ------------------------------------------------------------ %parser_declaration_header "tokenstream.h" %parser_declaration_header "QtCore/QString" %parser_declaration_header "language/duchain/problem.h" %parser_declaration_header "phplexer.h" %parser_bits_header "parserdebug.h" ------------------------------------------------------------ -- Export macro to use the parser in a shared lib ------------------------------------------------------------ %export_macro "KDEVPHPPARSER_EXPORT" %export_macro_header "parserexport.h" ------------------------------------------------------------ -- Enumeration types for additional AST members, -- in the global "Php" namespace ------------------------------------------------------------ %namespace [: class Lexer; enum ModifierFlags { ModifierPrivate = 1, ModifierPublic = 1 << 1, ModifierProtected = 1 << 2, ModifierStatic = 1 << 3, ModifierFinal = 1 << 4, ModifierAbstract = 1 << 5 }; enum ClassModifier { NormalClass, AbstractClass, FinalClass }; enum ScalarTypes { ScalarTypeInt, ScalarTypeFloat, ScalarTypeString }; enum CastType { CastInt, CastDouble, CastString, CastArray, CastObject, CastBool, CastUnset }; enum OperationType { OperationPlus = 1, OperationMinus, OperationConcat, OperationMul, OperationDiv, OperationExp, OperationMod, OperationAnd, OperationOr, OperationXor, OperationSl, OperationSr }; :] ------------------------------------------------------------ -- Ast Node class members ------------------------------------------------------------ %ast_extra_members [: KDevelop::DUContext* ducontext; :] ------------------------------------------------------------ -- Parser class members ------------------------------------------------------------ %parserclass (public declaration) [: /** * Transform the raw input into tokens. * When this method returns, the parser's token stream has been filled * and any parse*() method can be called. */ void tokenize(const QString& contents, int initialState = Lexer::HtmlState); enum ProblemType { Error, Warning, Info, Todo }; KDevelop::ProblemPointer reportProblem( Parser::ProblemType type, const QString& message, int tokenOffset = -1 ); QList problems() { return m_problems; } QString tokenText(qint64 begin, qint64 end); void setDebug(bool debug); void setCurrentDocument(KDevelop::IndexedString url); void setTodoMarkers(const QStringList& markers); void extractTodosFromComment(const QString& comment, qint64 offset); enum InitialLexerState { HtmlState = 0, DefaultState = 1 }; :] %parserclass (private declaration) [: enum VarExpressionState { Normal, OnlyVariable, OnlyNewObject }; QString m_contents; bool m_debug; KDevelop::IndexedString m_currentDocument; QList m_problems; struct ParserState { VarExpressionState varExpressionState; bool varExpressionIsVariable; }; ParserState m_state; QRegularExpression m_todoMarkers; :] %parserclass (constructor) [: m_state.varExpressionState = Normal; m_state.varExpressionIsVariable = false; :] %token_stream TokenStream ;; ----------------------------------------------------------- -- List of defined tokens ----------------------------------------------------------- -- keywords: %token ABSTRACT ("abstract"), BREAK ("break"), CASE ("case"), CATCH ("catch"), CLASS ("class"), CONST ("const"), CONTINUE ("continue"), DEFAULT ("default"), DO ("do"), ELSE ("else"), EXTENDS ("extends"), FINAL ("final"), FOR ("for"), IF ("if"), IMPLEMENTS ("implements"), INSTANCEOF ("instanceof"), INTERFACE ("interface"), NEW ("new"), PRIVATE ("private"), PROTECTED ("protected"), PUBLIC ("public"), RETURN ("return"), STATIC ("static"), SWITCH ("switch"), THROW ("throw"), TRY ("try"), WHILE ("while"), ECHO ("echo"), PRINT ("print"), FINALLY ("finally"), CLONE ("clone"), EXIT ("exit"), ELSEIF ("elseif"), ENDIF ("endif"), ENDWHILE ("endwhile"), ENDFOR ("endfor"), FOREACH ("foreach"), ENDFOREACH ("endforeach"), DECLARE ("declare"), ENDDECLARE ("enddeclare"), AS ("as"), ENDSWITCH ("endswitch"), FUNCTION ("function"), USE ("use"), GLOBAL ("global"), VAR ("var "), UNSET ("unset"), ISSET ("isset"), EMPTY ("empty"), HALT_COMPILER ("halt compiler"), DOUBLE_ARROW ("=>"), LIST ("list"), ARRAY ("array"), CLASS_C ("__CLASS__"), METHOD_C ("__METHOD__"), FUNC_C ("__FUNCTION__"), LINE ("__LINE__"), FILE ("__FILE__"), COMMENT ("comment"), DOC_COMMENT ("doc comment"), PAAMAYIM_NEKUDOTAYIM ("::"), INCLUDE ("include"), INCLUDE_ONCE ("include_once"), EVAL ("eval"), REQUIRE ("require"), REQUIRE_ONCE ("require_once"), NAMESPACE ("namespace"), NAMESPACE_C("__NAMESPACE__"), USE("use"), GOTO ("goto"), TRAIT ("trait"), INSTEADOF ("insteadof"), CALLABLE ("callable") ;; -- casts: %token INT_CAST ("int cast"), DOUBLE_CAST ("double cast"), STRING_CAST ("string cast"), ARRAY_CAST ("array cast"), OBJECT_CAST ("object cast"), BOOL_CAST ("bool cast"), UNSET_CAST ("unset cast") ;; -- seperators: %token SEMICOLON (";"), DOUBLE_QUOTE ("\""), LBRACKET ("["), RBRACKET ("]"), LPAREN ("("), RPAREN (")"), LBRACE ("{"), RBRACE ("}"), COMMA (","), AT ("@"), CURLY_OPEN ("curly open"), -- { in "{$foo}"; not the same as LBRACE DOLLAR_OPEN_CURLY_BRACES ("${"), START_HEREDOC ("start heredoc"), END_HEREDOC ("end heredoc"), BACKTICK ("`"), BACKSLASH ("\\"), START_NOWDOC("start nowdoc"), END_NOWDOC("end nowdoc") ;; -- operators: %token IS_EQUAL ("=="), IS_NOT_EQUAL ("!="), IS_IDENTICAL ("==="), IS_NOT_IDENTICAL ("!=="), IS_SMALLER ("<"), IS_GREATER (">"), IS_SMALLER_OR_EQUAL ("<="), IS_GREATER_OR_EQUAL (">="), BOOLEAN_OR ("||"), BOOLEAN_AND ("&&"), ASSIGN ("="), EXP_ASSIGN("**="), PLUS_ASSIGN ("+="), MINUS_ASSIGN ("-="), MUL_ASSIGN ("*="), DIV_ASSIGN ("/="), CONCAT_ASSIGN (".="), MOD_ASSIGN ("%="), AND_ASSIGN ("&="), OR_ASSIGN ("|="), XOR_ASSIGN ("^="), SL_ASSIGN ("<<="), SR_ASSIGN (">>="), OBJECT_OPERATOR ("->"), PLUS ("+"), MINUS("-"), CONCAT("."), INC ("++"), DEC ("--"), BANG ("!"), QUESTION ("?"), COLON (":"), BIT_AND ("&"), BIT_OR("|"), BIT_XOR ("^"), SL ("<<"), SR (">>"), MUL("*"), DIV("/"), MOD ("%"), TILDE ("~"), DOLLAR ("$"), EXP ("**"), LOGICAL_OR ("logical or"), LOGICAL_AND ("logical and"), LOGICAL_XOR ("logical xor") ;; -- literals and identifiers: %token INLINE_HTML ("inline html"), WHITESPACE ("whitespace"), CONSTANT_ENCAPSED_STRING ("constant encapsed string"), VARIABLE ("variable"), ENCAPSED_AND_WHITESPACE ("encapsed and whitespace"), DNUMBER ("double number"), LNUMBER ("long number"), NUM_STRING ("num string"), STRING ("string"), STRING_VARNAME ("string varname") ;; -- when in "${varname}" -- open/close tags %token OPEN_TAG (""), OPEN_TAG_WITH_ECHO (" start ;; namespaceDeclaration=namespaceDeclarationStatement | statement=topStatement -> outerTopStatement ;; -- first/first conflict for FUNCTION (?[: (LA(1).kind == Token_FUNCTION && ((LA(2).kind == Token_BIT_AND && LA(3).kind == Token_LPAREN) || LA(2).kind == Token_LPAREN)) || LA(1).kind != Token_FUNCTION :] statement=statement ) | functionDeclaration=functionDeclarationStatement | classDeclaration=classDeclarationStatement | traitDeclaration=traitDeclarationStatement | interfaceDeclaration=interfaceDeclarationStatement | HALT_COMPILER LPAREN RPAREN SEMICOLON -- Lexer stops allready -> topStatement ;; [: bool reported = false; while ( true ) { :] try/recover(#statements=topStatement)* [: if (yytoken != Token_RBRACE && yytoken != Token_EOF && yytoken != Token_CLOSE_TAG && yytoken != Token_ELSEIF && yytoken != Token_ELSE && yytoken != Token_ENDIF && yytoken != Token_ENDFOREACH && yytoken != Token_ENDFOR && yytoken != Token_ENDWHILE && yytoken != Token_ENDSWITCH && yytoken != Token_ENDDECLARE && yytoken != Token_CASE && yytoken != Token_DEFAULT) { if (!reported) { qint64 index = tokenStream->index() - 1; Token &token = tokenStream->at(index); - QString tokenValue = token.kind != 0 ? tokenText(token.begin, token.end) : "EOF"; - reportProblem(Error, QString("Unexpected token \"%1\".").arg(tokenValue)); + QString tokenValue = token.kind != 0 ? tokenText(token.begin, token.end) : QStringLiteral("EOF"); + reportProblem(Error, QStringLiteral("Unexpected token \"%1\".").arg(tokenValue)); reported = true; } yylex(); } else { break; } } :] -> innerStatementList ;; --Operator Precedence, from PHP Manual --left or --left xor --left and --right print --right = += -= *= /= .= %= &= |= ^= <<= >>= assignment --left ? : ternary --left || logical --left && logical --left | bitwise --left ^ bitwise --left & bitwise and references --non-associative == != === !== comparison --non-associative < <= > >= comparison --left << >> bitwise --left + - . arithmetic and string --left * / % arithmetic --non-associative ! ~ - (int) (float) (string) (array) (object) @ types --non-associative ++ -- increment/decrement --left [ array() --non-associative new new expression=logicalOrExpression -> expr ;; #expression=logicalXorExpression @ LOGICAL_OR -> logicalOrExpression ;; #expression=logicalAndExpression @ LOGICAL_XOR -> logicalXorExpression ;; #expression=printExpression @ LOGICAL_AND -> logicalAndExpression ;; (print=PRINT*) expression=assignmentExpression -> printExpression ;; -- leftside must me a variable, we check afterwards if it was a variable and -- if not we report an error 0 --needed for line below [: m_state.varExpressionIsVariable = false; :] --reset flag expression=conditionalExpression ( assignmentExpressionEqual=assignmentExpressionEqual | ( ( PLUS_ASSIGN [: (*yynode)->operation = OperationPlus; :] | MINUS_ASSIGN [: (*yynode)->operation = OperationMinus; :] | MUL_ASSIGN [: (*yynode)->operation = OperationMul; :] | EXP_ASSIGN [: (*yynode)->operation = OperationExp; :] | DIV_ASSIGN [: (*yynode)->operation = OperationDiv; :] | CONCAT_ASSIGN [: (*yynode)->operation = OperationConcat; :] | MOD_ASSIGN [: (*yynode)->operation = OperationMod; :] | AND_ASSIGN [: (*yynode)->operation = OperationAnd; :] | OR_ASSIGN [: (*yynode)->operation = OperationOr; :] | XOR_ASSIGN [: (*yynode)->operation = OperationXor; :] | SL_ASSIGN [: (*yynode)->operation = OperationSl; :] | SR_ASSIGN [: (*yynode)->operation = OperationSr; :] ) assignmentExpressionCheckIfVariable assignmentExpression=assignmentExpression) | 0) -> assignmentExpression [ member variable operation: OperationType; ];; --=& is special: -- $foo =& $var; is allowed but not $foo =& 'static'; -- $foo =& new bar(); is allowed too but deprecated and reports a warning --we set a flag (varExpressionState) with that var_expression accepts only valid parts --this is done in such a strage way because we need the full expression to allow --things like $foo =& $bar || e(); ASSIGN assignmentExpressionCheckIfVariable --as in assignmentExpression (BIT_AND [: if (yytoken == Token_NEW) { - reportProblem(Warning, "=& new foo() is deprecated", -2); + reportProblem(Warning, QStringLiteral("=& new foo() is deprecated"), -2); m_state.varExpressionState = OnlyNewObject; } else { m_state.varExpressionState = OnlyVariable; }:] | 0) assignmentExpression=assignmentExpression [: m_state.varExpressionState = Normal; :] -> assignmentExpressionEqual ;; -- check if var_expression was a variable, if not report an error -- varExpressionIsVariable is set in var_expression 0 --to allow cpp-code [: if (!m_state.varExpressionIsVariable) { - reportProblem(Error, "Left side is not a variable"); + reportProblem(Error, QStringLiteral("Left side is not a variable")); return false; } :] -> assignmentExpressionCheckIfVariable ;; expression=booleanOrExpression ( QUESTION (ifExpression=expr|0) COLON elseExpression=conditionalExpression | 0 ) -> conditionalExpression ;; #expression=booleanAndExpression @ BOOLEAN_OR -> booleanOrExpression ;; #expression=bitOrExpression @ BOOLEAN_AND -> booleanAndExpression ;; #expression=bitXorExpression @ BIT_OR -> bitOrExpression ;; #expression=bitAndExpression @ BIT_XOR -> bitXorExpression ;; #expression=equalityExpression @ BIT_AND -> bitAndExpression ;; expression=relationalExpression (#additionalExpression=equalityExpressionRest)* -> equalityExpression ;; ( IS_EQUAL | IS_NOT_EQUAL | IS_IDENTICAL | IS_NOT_IDENTICAL ) expression=relationalExpression -> equalityExpressionRest ;; expression=shiftExpression ( (#additionalExpression=relationalExpressionRest)+ --instanceof as in java.g (correct??) | INSTANCEOF instanceofType=classNameReference | 0 ) -> relationalExpression ;; ( IS_SMALLER | IS_GREATER | IS_SMALLER_OR_EQUAL | IS_GREATER_OR_EQUAL ) expression=shiftExpression -> relationalExpressionRest ;; expression=additiveExpression (#additionalExpression=shiftExpressionRest)* -> shiftExpression ;; ( SL | SR ) expression=additiveExpression -> shiftExpressionRest ;; expression=multiplicativeExpression (#additionalExpression=additiveExpressionRest)* -> additiveExpression ;; ( PLUS [: (*yynode)->operation = OperationPlus; :] | MINUS [: (*yynode)->operation = OperationMinus; :] | CONCAT [: (*yynode)->operation = OperationConcat; :] ) expression=multiplicativeExpression -> additiveExpressionRest [ member variable operation: OperationType; ];; expression=unaryExpression (#additionalExpression=multiplicativeExpressionRest)* -> multiplicativeExpression ;; ( MUL [: (*yynode)->operation = OperationMul; :] | DIV [: (*yynode)->operation = OperationDiv; :] | EXP [: (*yynode)->operation = OperationExp; :] | MOD [: (*yynode)->operation = OperationMod; :] ) expression=unaryExpression -> multiplicativeExpressionRest [ member variable operation: OperationType; ];; ( MINUS unaryExpression=unaryExpression | PLUS unaryExpression=unaryExpression | BANG unaryExpression=unaryExpression | TILDE unaryExpression=unaryExpression | INT_CAST unaryExpression=unaryExpression [: (*yynode)->castType = CastInt; :] | DOUBLE_CAST unaryExpression=unaryExpression [: (*yynode)->castType = CastDouble; :] | STRING_CAST unaryExpression=unaryExpression [: (*yynode)->castType = CastString; :] | ARRAY_CAST unaryExpression=unaryExpression [: (*yynode)->castType = CastArray; :] | OBJECT_CAST unaryExpression=unaryExpression [: (*yynode)->castType = CastObject; :] | BOOL_CAST unaryExpression=unaryExpression [: (*yynode)->castType = CastBool; :] | UNSET_CAST unaryExpression=unaryExpression [: (*yynode)->castType = CastUnset; :] | AT unaryExpression=unaryExpression | LIST LPAREN assignmentList=assignmentList RPAREN ASSIGN unaryExpression=unaryExpression | EXIT (LPAREN (expression=expr | 0) RPAREN | 0) | EVAL LPAREN expression=expr RPAREN | INCLUDE includeExpression=unaryExpression | INCLUDE_ONCE includeExpression=unaryExpression | REQUIRE includeExpression=unaryExpression | REQUIRE_ONCE includeExpression=unaryExpression | unaryExpressionNotPlusminus=unaryExpressionNotPlusminus ) -> unaryExpression [ member variable castType: CastType; ];; (#prefixOperator=postprefixOperator)* varExpression=varExpression (#postfixOperator=postprefixOperator)* -> unaryExpressionNotPlusminus ;; op=INC | op=DEC -> postprefixOperator ;; --first/first conflict - no problem because of ifs ?[: m_state.varExpressionState == OnlyVariable :] 0 [: m_state.varExpressionState = Normal; :] variable=variable | ?[: m_state.varExpressionState == OnlyNewObject :] 0 [: m_state.varExpressionState = Normal; :] newObject=varExpressionNewObject | varExpressionNormal=varExpressionNormal | varExpressionArray=varExpressionArray arrayIndex=arrayIndexSpecifier* -> varExpression ;; LPAREN try/rollback (newObject=varExpressionNewObject RPAREN (#variableProperties=instantiationAccess*)) catch (expression=expr RPAREN) | BACKTICK encapsList=encapsList BACKTICK --try/rollback resolves conflict scalar vs. staticMember (foo::bar vs. foo::$bar) --varExpressionIsVariable flag is needed for assignmentExpression | try/rollback (variable=variable [: m_state.varExpressionIsVariable = true; :]) catch (scalar=scalar) | ISSET LPAREN (#issetVariable=variable @ COMMA) RPAREN | EMPTY LPAREN emptyVarialbe=variable RPAREN | newObject=varExpressionNewObject | CLONE cloneCar=varExpressionNormal | closure=closure -> varExpressionNormal ;; ARRAY LPAREN (#arrayValues=arrayPairValue -- break because array(1,) is allowed (solves FIRST/FOLLOW conflict) @ (COMMA [: if (yytoken == Token_RPAREN) { break; } :] ) | 0) RPAREN | LBRACKET (#arrayValues=arrayPairValue -- break because [1,] is allowed (solves FIRST/FOLLOW conflict) @ (COMMA [: if (yytoken == Token_RBRACKET) { break; } :] ) | 0) RBRACKET -> varExpressionArray ;; -- http://wiki.php.net/rfc/closures FUNCTION (isRef=BIT_AND|0) LPAREN parameters=parameterList RPAREN ( USE LPAREN lexicalVars=lexicalVarList RPAREN | 0) LBRACE try/recover(functionBody=innerStatementList) RBRACE -> closure ;; - (#lexicalVars=lexicalVar @ COMMA) | 0 [: reportProblem(Error, "Use list of closure must not be empty."); :] + (#lexicalVars=lexicalVar @ COMMA) | 0 [: reportProblem(Error, QStringLiteral("Use list of closure must not be empty.")); :] -> lexicalVarList ;; (isRef=BIT_AND | 0) variable=variableIdentifier -> lexicalVar ;; NEW className=classNameReference ctor=ctorArguments -> varExpressionNewObject ;; LPAREN parameterList=functionCallParameterList RPAREN | 0 -> ctorArguments ;; #parameters=functionCallParameterListElement @ COMMA | 0 -> functionCallParameterList ;; (BIT_AND variable=variable) | expr=expr -> functionCallParameterListElement ;; #element=assignmentListElement @COMMA -> assignmentList ;; variable=variable | LIST LPAREN assignmentList=assignmentList RPAREN | 0 -> assignmentListElement ;; expr=expr (DOUBLE_ARROW (exprValue=expr | BIT_AND varValue=variable) | 0) | BIT_AND variable=variable -> arrayPairValue ;; var=baseVariableWithFunctionCalls (#variableProperties=variableObjectProperty*) -> variable ;; OBJECT_OPERATOR | PAAMAYIM_NEKUDOTAYIM -> objectOperator ;; ( ?[: LA(1).kind == Token_DOLLAR:] LBRACE variable=variable RBRACE | objectProperty=objectProperty ) (isFunctionCall=LPAREN parameterList=functionCallParameterList RPAREN arrayIndex=arrayIndexSpecifier* | 0) -> variableProperty ;; objectOperator variableProperty=variableProperty -> variableObjectProperty ;; OBJECT_OPERATOR variableProperty=variableProperty -> instantiationAccess ;; --Conflict -- foo::$bar[0] (=baseVariable-staticMember) --vs.foo::$bar[0](); (=static function call) try/rollback (functionCall=functionCall arrayIndex=arrayIndexSpecifier*) catch (baseVariable=baseVariable) -> baseVariableWithFunctionCalls ;; LBRACKET (expr=expr | 0) RBRACKET -> arrayIndexSpecifier ;; LBRACKET (expr=expr) RBRACKET -> stringIndexSpecifier ;; stringFunctionNameOrClass=namespacedIdentifier ( LPAREN stringParameterList=functionCallParameterList RPAREN | PAAMAYIM_NEKUDOTAYIM ( stringFunctionName=identifier LPAREN stringParameterList=functionCallParameterList RPAREN | varFunctionName=variableWithoutObjects LPAREN stringParameterList=functionCallParameterList RPAREN ) ) | varFunctionName=variableWithoutObjects LPAREN varParameterList=functionCallParameterList RPAREN -> functionCall ;; var=compoundVariableWithSimpleIndirectReference #offsetItems=dimListItem* | staticMember=staticMember -> baseVariable ;; variable=variableIdentifier | DOLLAR LBRACE expr=expr RBRACE -> compoundVariable ;; ( DOLLAR ( DOLLAR+ | 0 ) ( indirectVariable=variableIdentifier | LBRACE expr=expr RBRACE ) | variable=variableIdentifier ) -> compoundVariableWithSimpleIndirectReference ;; className=namespacedIdentifier PAAMAYIM_NEKUDOTAYIM variable=variableWithoutObjects -> staticMember ;; LBRACE try/recover(statements=innerStatementList) RBRACE | IF LPAREN ifExpr=expr RPAREN ( COLON statements=innerStatementList newElseifList newElseSingle ENDIF semicolonOrCloseTag | ifStatement=statement elseifList=elseifList elseSingle=elseSingle ) | WHILE LPAREN whileExpr=expr RPAREN whileStatement=whileStatement | FOR LPAREN forExpr1=forExpr SEMICOLON forExpr2=forExpr SEMICOLON forExpr3=forExpr RPAREN forStatement=forStatement | SWITCH LPAREN swtichExpr=expr RPAREN switchCaseList=switchCaseList | FOREACH LPAREN ( -- allow $var as &$i and not expr() as &$i try/rollback(foreachVar=variable AS foreachVarAsVar=foreachVariable) catch(foreachExpr=expr AS foreachExprAsVar=variable)) (DOUBLE_ARROW foreachVariable=foreachVariable | 0) RPAREN foreachStatement=foreachStatement | DECLARE LPAREN declareItem=declareItem @ COMMA RPAREN declareStatement | SEMICOLON -- empty statement | TRY LBRACE try/recover(statements=innerStatementList) RBRACE #catches=catchItem* (FINALLY LBRACE finallyBody=innerStatementList RBRACE | 0) | UNSET LPAREN #unsetVariables=variable @ COMMA RPAREN semicolonOrCloseTag -- fix first/follow with goto target | ( ?[: LA(1).kind != Token_STRING || LA(2).kind != Token_COLON :] expr=expr semicolonOrCloseTag ) | DO doStatement=statement WHILE LPAREN whileExpr=expr RPAREN semicolonOrCloseTag | BREAK (breakExpr=expr | 0) semicolonOrCloseTag | CONTINUE (continueExpr=expr | 0) semicolonOrCloseTag | RETURN (returnExpr=expr | 0) semicolonOrCloseTag | GLOBAL #globalVars=globalVar @ COMMA semicolonOrCloseTag | STATIC #staticVars=staticVar @ COMMA semicolonOrCloseTag | ECHO #echoExprs=expr @ COMMA semicolonOrCloseTag | THROW throwExpr=expr semicolonOrCloseTag -- throws error in zend parser, so ignored | USE use_filename semicolonOrCloseTag | CLOSE_TAG | OPEN_TAG | OPEN_TAG_WITH_ECHO expr=expr semicolonOrCloseTag | INLINE_HTML | CONST #consts=constantDeclaration @ COMMA SEMICOLON | USE #useNamespace=useNamespace @ COMMA SEMICOLON | GOTO gotoLabel=STRING SEMICOLON | gotoTarget=STRING COLON -> statement ;; identifier=namespacedIdentifier (AS aliasIdentifier=identifier | 0) -> useNamespace ;; identifier=identifier ASSIGN scalar=staticScalar -> constantDeclaration ;; SEMICOLON | CLOSE_TAG -> semicolonOrCloseTag ;; LBRACE (SEMICOLON | 0) try/recover(caseList=caseList) RBRACE | COLON (SEMICOLON | 0) caseList=caseList ENDSWITCH semicolonOrCloseTag -> switchCaseList ;; #caseItems=case_item* -> caseList ;; CASE expr=expr (COLON | SEMICOLON) statements=innerStatementList | def=DEFAULT (COLON | SEMICOLON) statements=innerStatementList -> case_item ;; CATCH LPAREN catchClass=namespacedIdentifier var=variableIdentifier RPAREN LBRACE try/recover(statements=innerStatementList) RBRACE -> catchItem ;; statement=statement | COLON statements=innerStatementList ENDDECLARE semicolonOrCloseTag -> declareStatement ;; STRING ASSIGN scalar=staticScalar -> declareItem ;; (BIT_AND | 0) variable=variable -> foreachVariable ;; statement=statement | COLON statements=innerStatementList ENDFOREACH semicolonOrCloseTag -> foreachStatement ;; var=variableIdentifier (ASSIGN value=staticScalar | 0) -> staticVar ;; var=variableIdentifier | DOLLAR (dollarVar=variable | LBRACE expr=expr RBRACE) -> globalVar ;; #exprs=expr @ COMMA | 0 -> forExpr ;; statement=statement | COLON statements=innerStatementList ENDFOR semicolonOrCloseTag -> forStatement ;; statement=statement | COLON statements=innerStatementList ENDWHILE semicolonOrCloseTag -> whileStatement ;; --first/follow conflict; todo check if this is a problem #elseifListItem=elseifListItem* -> elseifList ;; ELSEIF LPAREN expr=expr RPAREN statement=statement -> elseifListItem ;; ELSE statement=statement | 0 -> elseSingle ;; #newElseifListItem=newelseifListItem* -> newElseifList ;; ELSEIF LPAREN expr=expr RPAREN COLON statements=innerStatementList -> newelseifListItem ;; ELSE COLON statements=innerStatementList | 0 -> newElseSingle ;; --TODO --resolve STRING vs. staticMember conflict -- ?[: LA(2).kind != Token_PAAMAYIM_NEKUDOTAYIM :] identifier=namespacedIdentifier | staticIdentifier = STATIC | dynamicClassNameReference=dynamicClassNameReference -> classNameReference ;; baseVariable=baseVariable (OBJECT_OPERATOR objectProperty=objectProperty properties=dynamicClassNameVariableProperties | 0) -> dynamicClassNameReference ;; #properties=dynamicClassNameVariableProperty* -> dynamicClassNameVariableProperties ;; OBJECT_OPERATOR property=objectProperty -> dynamicClassNameVariableProperty ;; objectDimList=objectDimList | variableWithoutObjects=variableWithoutObjects -> objectProperty ;; variableName=variableName #offsetItems=dimListItem* -> objectDimList ;; variable=compoundVariableWithSimpleIndirectReference #offsetItems=dimListItem* -> variableWithoutObjects ;; arrayIndex=arrayIndexSpecifier | LBRACE expr=expr RBRACE -> dimListItem ;; name=identifier | LBRACE expr=expr RBRACE -> variableName ;; commonScalar=commonScalar | constantOrClassConst=constantOrClassConst | varname=STRING_VARNAME | DOUBLE_QUOTE encapsList=encapsList DOUBLE_QUOTE stringIndex=stringIndexSpecifier* | START_HEREDOC encapsList=encapsList END_HEREDOC -> scalar ;; constant=namespacedIdentifier ( PAAMAYIM_NEKUDOTAYIM classConstant=identifier | 0 ) -> constantOrClassConst ;; #encaps=encaps* -> encapsList ;; var=encapsVar | value=ENCAPSED_AND_WHITESPACE -> encaps ;; -- first/first conflict resolved by LA(2) --(expr allows STRING_VARNAME too - but without [expr]) DOLLAR_OPEN_CURLY_BRACES ( ?[: LA(2).kind == Token_LBRACKET:] STRING_VARNAME arrayIndex=arrayIndexSpecifier RBRACE | expr=expr RBRACE ) | variable=variableIdentifier (OBJECT_OPERATOR propertyIdentifier=identifier | LBRACKET offset=encapsVarOffset RBRACKET | 0) | CURLY_OPEN expr=expr RBRACE -> encapsVar ;; STRING | NUM_STRING | variableIdentifier -> encapsVarOffset ;; LNUMBER [: (*yynode)->scalarType = ScalarTypeInt; :] | DNUMBER [: (*yynode)->scalarType = ScalarTypeFloat; :] | string=CONSTANT_ENCAPSED_STRING [: (*yynode)->scalarType = ScalarTypeString; :] stringIndex=stringIndexSpecifier* | LINE [: (*yynode)->scalarType = ScalarTypeInt; :] | FILE [: (*yynode)->scalarType = ScalarTypeString; :] | CLASS_C [: (*yynode)->scalarType = ScalarTypeString; :] | METHOD_C [: (*yynode)->scalarType = ScalarTypeString; :] | FUNC_C [: (*yynode)->scalarType = ScalarTypeString; :] | NAMESPACE_C [: (*yynode)->scalarType = ScalarTypeString; :] | START_NOWDOC STRING END_NOWDOC [: (*yynode)->scalarType = ScalarTypeString; :] -> commonScalar [ member variable scalarType: ScalarTypes; ] ;; FUNCTION (BIT_AND | 0) functionName=identifier LPAREN parameters=parameterList RPAREN LBRACE try/recover(functionBody=innerStatementList) RBRACE -> functionDeclarationStatement ;; (#parameters=parameter @ COMMA) | 0 -> parameterList ;; (parameterType=namespacedIdentifier | arrayType=ARRAY | callableType=CALLABLE | 0) (isRef=BIT_AND | 0) variable=variableIdentifier (ASSIGN defaultValue=staticScalar | 0) -> parameter ;; value=commonScalar | constantOrClassConst=constantOrClassConst | PLUS plusValue=staticScalar | MINUS minusValue=staticScalar | array=ARRAY LPAREN (#arrayValues=staticArrayPairValue -- break because array(1,) is allowed @ (COMMA [: if (yytoken == Token_RPAREN) { break; } :] ) | 0) RPAREN | array=LBRACKET (#arrayValues=staticArrayPairValue -- break because [1,] is allowed @ (COMMA [: if (yytoken == Token_RBRACKET) { break; } :] ) | 0) RBRACKET -> staticScalar ;; #val1=staticScalar (DOUBLE_ARROW #val2=staticScalar | 0) -> staticArrayPairValue ;; (isGlobal=BACKSLASH | 0) #namespaceName=identifier+ @ BACKSLASH -> namespacedIdentifier ;; string=STRING -> identifier ;; variable=VARIABLE -> variableIdentifier ;; NAMESPACE #namespaceName=identifier* @ BACKSLASH ( -- the semicolon case needs at least one namespace identifier, the {...} case not... - SEMICOLON [: if (!(*yynode)->namespaceNameSequence) { reportProblem(Error, "Missing namespace identifier.", -2); } :] + SEMICOLON [: if (!(*yynode)->namespaceNameSequence) { reportProblem(Error, QStringLiteral("Missing namespace identifier."), -2); } :] | LBRACE try/recover(body=innerStatementList) RBRACE ) -> namespaceDeclarationStatement ;; INTERFACE interfaceName=identifier (EXTENDS extends=classImplements | 0) LBRACE try/recover(body=classBody) RBRACE -> interfaceDeclarationStatement ;; TRAIT traitName=identifier LBRACE body=classBody RBRACE -> traitDeclarationStatement ;; modifier=optionalClassModifier CLASS className=identifier (EXTENDS extends=classExtends | 0) (IMPLEMENTS implements=classImplements | 0) LBRACE body=classBody RBRACE -> classDeclarationStatement ;; identifier=namespacedIdentifier -> classExtends ;; #implements=namespacedIdentifier @ COMMA -> classImplements ;; -- error recovery, to understand it you probably have to look at the generated code ;-) [: bool reported = false; while ( true ) { :] try/recover(#classStatements=classStatement)* [: if (yytoken != Token_RBRACE && yytoken != Token_EOF && yytoken != Token_CLOSE_TAG) { if (!reported) { - reportProblem(Error, "Unexpected token in class context."); + reportProblem(Error, QStringLiteral("Unexpected token in class context.")); reported = true; } yylex(); } else { break; } } :] RBRACE [: rewind(tokenStream->index() - 2); :] -> classBody ;; CONST #consts=constantDeclaration @ COMMA SEMICOLON | VAR variable=classVariableDeclaration SEMICOLON | modifiers=optionalModifiers ( variable=classVariableDeclaration SEMICOLON | FUNCTION (BIT_AND | 0) methodName=identifier LPAREN parameters=parameterList RPAREN methodBody=methodBody ) | USE #traits=namespacedIdentifier @ COMMA (imports=traitAliasDeclaration|SEMICOLON) -> classStatement ;; LBRACE #statements=traitAliasStatement @ (SEMICOLON [: if (yytoken == Token_RBRACE) { break; } :]) RBRACE -> traitAliasDeclaration ;; importIdentifier=traitAliasIdentifier (AS (modifiers=optionalModifiers | 0) aliasIdentifier=identifier|INSTEADOF #conflictIdentifier=namespacedIdentifier @ COMMA) -> traitAliasStatement ;; identifier=namespacedIdentifier PAAMAYIM_NEKUDOTAYIM methodIdentifier=identifier -> traitAliasIdentifier ;; SEMICOLON -- abstract method | LBRACE try/recover(statements=innerStatementList) RBRACE -> methodBody ;; #vars=classVariable @ COMMA -> classVariableDeclaration ;; variable=variableIdentifier (ASSIGN value=staticScalar | 0) -> classVariable ;; ( PUBLIC [: (*yynode)->modifiers |= ModifierPublic; :] | PROTECTED [: (*yynode)->modifiers |= ModifierProtected; :] | PRIVATE [: (*yynode)->modifiers |= ModifierPrivate; :] | STATIC [: (*yynode)->modifiers |= ModifierStatic; :] | ABSTRACT [: (*yynode)->modifiers |= ModifierAbstract; :] | FINAL [: (*yynode)->modifiers |= ModifierFinal; :] | 0 )* -> optionalModifiers[ member variable modifiers: unsigned int; ] ;; ( ABSTRACT [: (*yynode)->modifier = AbstractClass; :] | FINAL [: (*yynode)->modifier = FinalClass; :] | 0 ) -> optionalClassModifier[ member variable modifier: ClassModifier; ] ;; ----------------------------------------------------------------- -- Code segments copied to the implementation (.cpp) file. -- If existent, kdevelop-pg's current syntax requires this block -- to occur at the end of the file. ----------------------------------------------------------------- [: #include #include namespace Php { void Parser::tokenize(const QString& contents, int initialState) { m_contents = contents; Lexer lexer(tokenStream, contents, initialState); int kind = Parser::Token_EOF; int lastDocCommentBegin; int lastDocCommentEnd; do { lastDocCommentBegin = 0; lastDocCommentEnd = 0; kind = lexer.nextTokenKind(); while (kind == Parser::Token_WHITESPACE || kind == Parser::Token_COMMENT || kind == Parser::Token_DOC_COMMENT) { if (kind == Parser::Token_COMMENT || kind == Parser::Token_DOC_COMMENT) { extractTodosFromComment(tokenText(lexer.tokenBegin(), lexer.tokenEnd()), lexer.tokenBegin()); } if (kind == Parser::Token_DOC_COMMENT) { lastDocCommentBegin = lexer.tokenBegin(); lastDocCommentEnd = lexer.tokenEnd(); } kind = lexer.nextTokenKind(); } if ( !kind ) // when the lexer returns 0, the end of file is reached { kind = Parser::Token_EOF; } Parser::Token &t = tokenStream->push(); t.begin = lexer.tokenBegin(); t.end = lexer.tokenEnd(); t.kind = kind; t.docCommentBegin = lastDocCommentBegin; t.docCommentEnd = lastDocCommentEnd; //if ( m_debug ) qDebug() << kind << tokenText(t.begin,t.end) << t.begin << t.end; } while ( kind != Parser::Token_EOF ); yylex(); // produce the look ahead token } void Parser::extractTodosFromComment(const QString& comment, qint64 startPosition) { auto i = m_todoMarkers.globalMatch(comment); while (i.hasNext()) { auto match = i.next(); auto p = reportProblem(Todo, match.captured(1), 0); qint64 line = 0; qint64 column = 0; tokenStream->locationTable()->positionAt(startPosition, &line, &column); auto location = p->finalLocation(); location.setStart(KTextEditor::Cursor(line, column + match.capturedStart(1))); location.setEnd(KTextEditor::Cursor(line, column + match.capturedEnd(1))); p->setFinalLocation(location); }; } void Parser::setTodoMarkers(const QStringList& markers) { - QString pattern = "^(?:[/\\*\\s]*)(.*(?:"; + QString pattern = QStringLiteral("^(?:[/\\*\\s]*)(.*(?:"); bool first = true; foreach(const QString& marker, markers) { if (!first) { pattern += '|'; } pattern += QRegularExpression::escape(marker); first = false; } - pattern += ").*?)(?:[/\\*\\s]*)$"; + pattern += QStringLiteral(").*?)(?:[/\\*\\s]*)$"); m_todoMarkers.setPatternOptions(QRegularExpression::MultilineOption); m_todoMarkers.setPattern(pattern); } QString Parser::tokenText(qint64 begin, qint64 end) { return m_contents.mid(begin,end-begin+1); } KDevelop::ProblemPointer Parser::reportProblem( Parser::ProblemType type, const QString& message, int offset ) { qint64 sLine; qint64 sCol; qint64 index = tokenStream->index() + offset; tokenStream->startPosition(index, &sLine, &sCol); qint64 eLine; qint64 eCol; tokenStream->endPosition(index, &eLine, &eCol); auto p = KDevelop::ProblemPointer(new KDevelop::Problem()); p->setSource(KDevelop::IProblem::Parser); switch ( type ) { case Error: p->setSeverity(KDevelop::IProblem::Error); break; case Warning: p->setSeverity(KDevelop::IProblem::Warning); break; case Info: p->setSeverity(KDevelop::IProblem::Hint); break; case Todo: p->setSeverity(KDevelop::IProblem::Hint); p->setSource(KDevelop::IProblem::ToDo); break; } p->setDescription(message); KTextEditor::Range range(sLine, sCol, eLine, eCol + 1); p->setFinalLocation(KDevelop::DocumentRange(m_currentDocument, range)); m_problems << p; return p; } // custom error recovery void Parser::expectedToken(int /*expected*/, qint64 /*where*/, const QString& name) { - reportProblem( Parser::Error, QString("Expected token \"%1\"").arg(name)); + reportProblem( Parser::Error, QStringLiteral("Expected token \"%1\"").arg(name)); } void Parser::expectedSymbol(int /*expectedSymbol*/, const QString& name) { qint64 line; qint64 col; qint64 index = tokenStream->index()-1; Token &token = tokenStream->at(index); qCDebug(PARSER) << "token starts at:" << token.begin; qCDebug(PARSER) << "index is:" << index; tokenStream->startPosition(index, &line, &col); QString tokenValue = tokenText(token.begin, token.end); qint64 eLine; qint64 eCol; tokenStream->endPosition(index, &eLine, &eCol); reportProblem( Parser::Error, - QString("Expected symbol \"%1\" (current token: \"%2\" [%3] at %4:%5 - %6:%7)") - .arg(name) - .arg(token.kind != 0 ? tokenValue : "EOF") + QStringLiteral("Expected symbol \"%1\" (current token: \"%2\" [%3] at %4:%5 - %6:%7)") + .arg(name, + token.kind != 0 ? tokenValue : QStringLiteral("EOF")) .arg(token.kind) .arg(line) .arg(col) .arg(eLine) .arg(eCol)); } void Parser::setDebug( bool debug ) { m_debug = debug; } void Parser::setCurrentDocument(KDevelop::IndexedString url) { m_currentDocument = url; } Parser::ParserState *Parser::copyCurrentState() { ParserState *state = new ParserState(); state->varExpressionState = m_state.varExpressionState; state->varExpressionIsVariable = m_state.varExpressionIsVariable; return state; } void Parser::restoreState( Parser::ParserState* state) { m_state.varExpressionState = state->varExpressionState; m_state.varExpressionIsVariable = state->varExpressionIsVariable; } } // end of namespace Php :] -- kate: space-indent on; indent-width 4; tab-width 4; replace-tabs on; auto-insert-doxygen on; mode KDevelop-PG[-Qt] diff --git a/parser/test/lexertest.cpp b/parser/test/lexertest.cpp index 5e80263..c21dc13 100644 --- a/parser/test/lexertest.cpp +++ b/parser/test/lexertest.cpp @@ -1,544 +1,544 @@ /* This file is part of KDevelop Copyright 2006 Hamish Rodda 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 "lexertest.h" #include #include "parsesession.h" #include "phplexer.h" #include "phptokentext.h" QTEST_MAIN(Php::LexerTest) namespace Php { #define COMPARE_TOKEN(tokenStream, index, tokenKind, startLine, startColumn, endLine, endColumn) \ { \ QVERIFY(tokenStream->at(index).kind == tokenKind); \ qint64 line; qint64 column; \ tokenStream->startPosition(index, &line, &column); \ QCOMPARE(line, (qint64) startLine); \ QCOMPARE(column, (qint64) startColumn); \ tokenStream->endPosition(index, &line, &column); \ QCOMPARE(line, (qint64) endLine); \ QCOMPARE(column, (qint64) endColumn); \ } LexerTest::LexerTest() { } void LexerTest::testOpenTagWithNewline() { - TokenStream* ts = tokenize("size() == 3); COMPARE_TOKEN(ts, 0, Parser::Token_OPEN_TAG, 0, 0, 0, 5); COMPARE_TOKEN(ts, 1, Parser::Token_STRING, 1, 0, 1, 2); COMPARE_TOKEN(ts, 2, Parser::Token_SEMICOLON, 1, 3, 1, 3); delete ts; } void LexerTest::testOpenTagWithSpace() { - TokenStream* ts = tokenize("size() == 3); COMPARE_TOKEN(ts, 0, Parser::Token_OPEN_TAG, 0, 0, 0, 5); COMPARE_TOKEN(ts, 1, Parser::Token_STRING, 0, 6, 0, 8); COMPARE_TOKEN(ts, 2, Parser::Token_SEMICOLON, 0, 9, 0, 9); delete ts; } void LexerTest::testCommentOneLine() { - TokenStream* ts = tokenize("size() == 4); COMPARE_TOKEN(ts, 0, Parser::Token_OPEN_TAG, 0, 0, 0, 5); COMPARE_TOKEN(ts, 1, Parser::Token_COMMENT, 1, 0, 1, 9); COMPARE_TOKEN(ts, 2, Parser::Token_STRING, 2, 0, 2, 2); COMPARE_TOKEN(ts, 3, Parser::Token_SEMICOLON, 2, 3, 2, 3); delete ts; } void LexerTest::testCommentOneLine2() { - TokenStream* ts = tokenize("size() == 4); COMPARE_TOKEN(ts, 0, Parser::Token_OPEN_TAG, 0, 0, 0, 5); COMPARE_TOKEN(ts, 1, Parser::Token_COMMENT, 1, 0, 1, 8); COMPARE_TOKEN(ts, 2, Parser::Token_STRING, 2, 0, 2, 2); COMPARE_TOKEN(ts, 3, Parser::Token_SEMICOLON, 2, 3, 2, 3); delete ts; } void LexerTest::testCommentMultiLine() { - TokenStream* ts = tokenize("size() == 5); COMPARE_TOKEN(ts, 0, Parser::Token_OPEN_TAG, 0, 0, 0, 5); COMPARE_TOKEN(ts, 1, Parser::Token_COMMENT, 1, 0, 2, 5); COMPARE_TOKEN(ts, 2, Parser::Token_WHITESPACE, 2, 6, 2, 6); COMPARE_TOKEN(ts, 3, Parser::Token_STRING, 3, 0, 3, 2); COMPARE_TOKEN(ts, 4, Parser::Token_SEMICOLON, 3, 3, 3, 3); delete ts; } void LexerTest::testCommentMultiLine2() { - TokenStream* ts = tokenize("size() == 5); COMPARE_TOKEN(ts, 0, Parser::Token_OPEN_TAG, 0, 0, 0, 5); COMPARE_TOKEN(ts, 1, Parser::Token_COMMENT, 1, 0, 2, 5); COMPARE_TOKEN(ts, 2, Parser::Token_WHITESPACE, 2, 6, 2, 6); COMPARE_TOKEN(ts, 3, Parser::Token_STRING, 3, 0, 3, 2); COMPARE_TOKEN(ts, 4, Parser::Token_SEMICOLON, 3, 3, 3, 3); delete ts; } void LexerTest::testEndTag() { - TokenStream* ts = tokenize("\n>", true, Lexer::DefaultState); + TokenStream* ts = tokenize(QStringLiteral("\n>"), true, Lexer::DefaultState); //don't crash and we are fine delete ts; } void LexerTest::testNewlineInString() { //0 1 //012345 6 7 890123456789 - TokenStream* ts = tokenize("size() == 3); COMPARE_TOKEN(ts, 1, Parser::Token_CONSTANT_ENCAPSED_STRING, 0, 6, 1, 0); COMPARE_TOKEN(ts, 2, Parser::Token_SEMICOLON, 1, 1, 1, 1); delete ts; } void LexerTest::testNewlineInString2() { //0 //0123 4567 - TokenStream* ts = tokenize("size(), 3); COMPARE_TOKEN(ts, 1, Parser::Token_CONSTANT_ENCAPSED_STRING, 0, 6, 1, 0); COMPARE_TOKEN(ts, 2, Parser::Token_SEMICOLON, 1, 1, 1, 1); delete ts; } void LexerTest::testNewlineInStringWithVar() { - TokenStream* ts = tokenize("size(), 6); COMPARE_TOKEN(ts, 1, Parser::Token_DOUBLE_QUOTE, 0, 6, 0, 6); COMPARE_TOKEN(ts, 2, Parser::Token_VARIABLE, 0, 7, 0, 8); COMPARE_TOKEN(ts, 3, Parser::Token_ENCAPSED_AND_WHITESPACE, 0, 9, 0, 9); COMPARE_TOKEN(ts, 4, Parser::Token_DOUBLE_QUOTE, 1, 0, 1, 0); COMPARE_TOKEN(ts, 5, Parser::Token_SEMICOLON, 1, 1, 1, 1); delete ts; } void LexerTest::testNewlineInStringWithVar2() { //0 1 //012345 6 789 0123456789 - TokenStream* ts = tokenize("size(), 7); COMPARE_TOKEN(ts, 1, Parser::Token_DOUBLE_QUOTE, 0, 6, 0, 6); COMPARE_TOKEN(ts, 2, Parser::Token_ENCAPSED_AND_WHITESPACE, 0, 7, 0, 7); COMPARE_TOKEN(ts, 3, Parser::Token_VARIABLE, 1, 0, 1, 1); COMPARE_TOKEN(ts, 4, Parser::Token_ENCAPSED_AND_WHITESPACE, 1, 2, 1, 2); COMPARE_TOKEN(ts, 5, Parser::Token_DOUBLE_QUOTE, 2, 0, 2, 0); COMPARE_TOKEN(ts, 6, Parser::Token_SEMICOLON, 2, 1, 2, 1); delete ts; } void LexerTest::testNewlineInStringWithVar3() { //0 1 //012345 6 789 0123456789 - TokenStream* ts = tokenize("size(), 7); COMPARE_TOKEN(ts, 1, Parser::Token_DOUBLE_QUOTE, 0, 6, 0, 6); COMPARE_TOKEN(ts, 2, Parser::Token_ENCAPSED_AND_WHITESPACE, 0, 7, 0, 8); COMPARE_TOKEN(ts, 3, Parser::Token_VARIABLE, 0, 9, 0, 10); COMPARE_TOKEN(ts, 4, Parser::Token_ENCAPSED_AND_WHITESPACE, 0, 11, 0, 11); COMPARE_TOKEN(ts, 5, Parser::Token_DOUBLE_QUOTE, 0, 12, 0, 12); COMPARE_TOKEN(ts, 6, Parser::Token_SEMICOLON, 0, 13, 0, 13); delete ts; } void LexerTest::testMultiplePhpSections() { //0 1 //012345 6 789 0123456789 - TokenStream* ts = tokenize("\n\n", true); + TokenStream* ts = tokenize(QStringLiteral("\n\n"), true); QCOMPARE((int)ts->size(), 9); qint64 index = 0; for (qint64 line = 0; line <= 2; ++line) { if (line == 1) { // the html stuff in the middle COMPARE_TOKEN(ts, index, Parser::Token_INLINE_HTML, 0, 11, 1, 6); ++index; } else { // the php stuff (symmetric) at the start and end COMPARE_TOKEN(ts, index, Parser::Token_OPEN_TAG, line, 0, line, 5); ++index; COMPARE_TOKEN(ts, index, Parser::Token_VARIABLE, line, 6, line, 7); ++index; COMPARE_TOKEN(ts, index, Parser::Token_SEMICOLON, line, 8, line, 8); ++index; COMPARE_TOKEN(ts, index, Parser::Token_CLOSE_TAG, line, 9, line, 10); ++index; } } delete ts; } void LexerTest::testHereDoc() { - TokenStream* ts = tokenize("size(), 12); COMPARE_TOKEN(ts, 0, Parser::Token_OPEN_TAG, 0, 0, 0, 5); COMPARE_TOKEN(ts, 1, Parser::Token_ECHO, 1, 0, 1, 3); COMPARE_TOKEN(ts, 3, Parser::Token_START_HEREDOC, 1, 5, 1, 12); COMPARE_TOKEN(ts, 4, Parser::Token_ENCAPSED_AND_WHITESPACE, 2, 0, 2, 5); COMPARE_TOKEN(ts, 5, Parser::Token_VARIABLE, 2, 6, 2, 10); COMPARE_TOKEN(ts, 6, Parser::Token_ENCAPSED_AND_WHITESPACE, 2, 11, 3, 3); COMPARE_TOKEN(ts, 7, Parser::Token_END_HEREDOC, 4, 0, 4, 3); COMPARE_TOKEN(ts, 8, Parser::Token_SEMICOLON, 4, 4, 4, 4); COMPARE_TOKEN(ts, 10, Parser::Token_VARIABLE, 5, 0, 5, 6); COMPARE_TOKEN(ts, 11, Parser::Token_SEMICOLON, 5, 7, 5, 7); delete ts; } void LexerTest::testHereDocQuoted() { - TokenStream* ts = tokenize("size(), 12); COMPARE_TOKEN(ts, 0, Parser::Token_OPEN_TAG, 0, 0, 0, 5); COMPARE_TOKEN(ts, 1, Parser::Token_ECHO, 1, 0, 1, 3); COMPARE_TOKEN(ts, 3, Parser::Token_START_HEREDOC, 1, 5, 1, 14); COMPARE_TOKEN(ts, 4, Parser::Token_ENCAPSED_AND_WHITESPACE, 2, 0, 2, 5); COMPARE_TOKEN(ts, 5, Parser::Token_VARIABLE, 2, 6, 2, 10); COMPARE_TOKEN(ts, 6, Parser::Token_ENCAPSED_AND_WHITESPACE, 2, 11, 3, 3); COMPARE_TOKEN(ts, 7, Parser::Token_END_HEREDOC, 4, 0, 4, 3); COMPARE_TOKEN(ts, 8, Parser::Token_SEMICOLON, 4, 4, 4, 4); COMPARE_TOKEN(ts, 10, Parser::Token_VARIABLE, 5, 0, 5, 6); COMPARE_TOKEN(ts, 11, Parser::Token_SEMICOLON, 5, 7, 5, 7); delete ts; } void LexerTest::testNowdoc() { - TokenStream* ts = tokenize("size(), 10); COMPARE_TOKEN(ts, 0, Parser::Token_OPEN_TAG, 0, 0, 0, 5); COMPARE_TOKEN(ts, 1, Parser::Token_ECHO, 1, 0, 1, 3); COMPARE_TOKEN(ts, 3, Parser::Token_START_NOWDOC, 1, 5, 1, 14); COMPARE_TOKEN(ts, 4, Parser::Token_STRING, 2, 0, 3, 3); COMPARE_TOKEN(ts, 5, Parser::Token_END_NOWDOC, 4, 0, 4, 3); COMPARE_TOKEN(ts, 6, Parser::Token_SEMICOLON, 4, 4, 4, 4); COMPARE_TOKEN(ts, 8, Parser::Token_VARIABLE, 5, 0, 5, 6); COMPARE_TOKEN(ts, 9, Parser::Token_SEMICOLON, 5, 7, 5, 7); delete ts; } void LexerTest::testCommonStringTokens() { // all these should have open_tag followed by constant encapsed string foreach ( const QString& code, QStringList() << "size(), 2); COMPARE_TOKEN(ts, 0, Parser::Token_OPEN_TAG, 0, 0, 0, 5); COMPARE_TOKEN(ts, 1, Parser::Token_CONSTANT_ENCAPSED_STRING, 0, 6, 0, code.size() - 1); delete ts; } } void LexerTest::testNonTerminatedStringWithVar() { - TokenStream* ts = tokenize("size(), 3); COMPARE_TOKEN(ts, 0, Parser::Token_OPEN_TAG, 0, 0, 0, 5); COMPARE_TOKEN(ts, 1, Parser::Token_DOUBLE_QUOTE, 0, 6, 0, 6); COMPARE_TOKEN(ts, 2, Parser::Token_VARIABLE, 0, 7, 0, 8); delete ts; } void LexerTest::testPhpBlockWithComment() { TokenStream* ts = tokenize( - "\n" - "size(), 5); COMPARE_TOKEN(ts, 0, Parser::Token_OPEN_TAG, 0, 0, 0, 5); COMPARE_TOKEN(ts, 1, Parser::Token_COMMENT, 1, 0, 1, 6); COMPARE_TOKEN(ts, 2, Parser::Token_CLOSE_TAG, 2, 0, 2, 1); COMPARE_TOKEN(ts, 3, Parser::Token_INLINE_HTML, 2, 2, 2, 2); COMPARE_TOKEN(ts, 4, Parser::Token_OPEN_TAG, 3, 0, 3, 5); delete ts; } void LexerTest::testNamespaces() { TokenStream* ts = tokenize( - "size(), 25); COMPARE_TOKEN(ts, 0, Parser::Token_OPEN_TAG, 0, 0, 0, 5); COMPARE_TOKEN(ts, 1, Parser::Token_NAMESPACE, 1, 0, 1, 8); COMPARE_TOKEN(ts, 2, Parser::Token_WHITESPACE, 1, 9, 1, 9); COMPARE_TOKEN(ts, 3, Parser::Token_STRING, 1, 10, 1, 12); COMPARE_TOKEN(ts, 4, Parser::Token_SEMICOLON, 1, 13, 1, 13); COMPARE_TOKEN(ts, 6, Parser::Token_NAMESPACE, 2, 0, 2, 8); COMPARE_TOKEN(ts, 7, Parser::Token_WHITESPACE, 2, 9, 2, 9); COMPARE_TOKEN(ts, 8, Parser::Token_STRING, 2, 10, 2, 12); COMPARE_TOKEN(ts, 9, Parser::Token_BACKSLASH, 2, 13, 2, 13); COMPARE_TOKEN(ts, 10, Parser::Token_STRING, 2, 14, 2, 16); COMPARE_TOKEN(ts, 11, Parser::Token_SEMICOLON, 2, 17, 2, 17); COMPARE_TOKEN(ts, 13, Parser::Token_NAMESPACE, 3, 0, 3, 8); COMPARE_TOKEN(ts, 14, Parser::Token_WHITESPACE, 3, 9, 3, 9); COMPARE_TOKEN(ts, 15, Parser::Token_STRING, 3, 10, 3, 12); COMPARE_TOKEN(ts, 16, Parser::Token_BACKSLASH, 3, 13, 3, 13); COMPARE_TOKEN(ts, 17, Parser::Token_STRING, 3, 14, 3, 16); COMPARE_TOKEN(ts, 18, Parser::Token_BACKSLASH, 3, 17, 3, 17); COMPARE_TOKEN(ts, 19, Parser::Token_STRING, 3, 18, 3, 20); COMPARE_TOKEN(ts, 20, Parser::Token_WHITESPACE, 3, 21, 3, 21); COMPARE_TOKEN(ts, 21, Parser::Token_LBRACE, 3, 22, 3, 22); COMPARE_TOKEN(ts, 23, Parser::Token_RBRACE, 4, 0, 4, 0); delete ts; } void LexerTest::testCloseTagInComment() { { TokenStream* ts = tokenize( - "" + QStringLiteral("") , true); QCOMPARE((int)ts->size(), 3); COMPARE_TOKEN(ts, 0, Parser::Token_OPEN_TAG, 0, 0, 0, 5); COMPARE_TOKEN(ts, 1, Parser::Token_COMMENT, 0, 6, 0, 13); COMPARE_TOKEN(ts, 2, Parser::Token_CLOSE_TAG, 0, 14, 0, 15); delete ts; } { TokenStream* ts = tokenize( - "" + QStringLiteral("") , true); QCOMPARE((int)ts->size(), 3); COMPARE_TOKEN(ts, 0, Parser::Token_OPEN_TAG, 0, 0, 0, 5); COMPARE_TOKEN(ts, 1, Parser::Token_COMMENT, 0, 6, 0, 13); COMPARE_TOKEN(ts, 2, Parser::Token_CLOSE_TAG, 0, 14, 0, 15); delete ts; } } void LexerTest::testBinaryNumber() { - TokenStream* ts = tokenize("size(), 6); COMPARE_TOKEN(ts, 0, Parser::Token_OPEN_TAG, 0, 0, 0, 5); COMPARE_TOKEN(ts, 1, Parser::Token_LNUMBER, 1, 0, 1, 3); COMPARE_TOKEN(ts, 2, Parser::Token_SEMICOLON, 1, 4, 1, 4); COMPARE_TOKEN(ts, 3, Parser::Token_WHITESPACE, 1, 5, 1, 5); COMPARE_TOKEN(ts, 4, Parser::Token_LNUMBER, 2, 0, 2, 3); COMPARE_TOKEN(ts, 5, Parser::Token_SEMICOLON, 2, 4, 2, 4); delete ts; } void LexerTest::testHexadecimalNumber() { - TokenStream* ts = tokenize("size(), 12); COMPARE_TOKEN(ts, 0, Parser::Token_OPEN_TAG, 0, 0, 0, 5); COMPARE_TOKEN(ts, 1, Parser::Token_LNUMBER, 1, 0, 1, 3); COMPARE_TOKEN(ts, 2, Parser::Token_SEMICOLON, 1, 4, 1, 4); COMPARE_TOKEN(ts, 3, Parser::Token_WHITESPACE, 1, 5, 1, 5); COMPARE_TOKEN(ts, 4, Parser::Token_LNUMBER, 2, 0, 2, 3); COMPARE_TOKEN(ts, 5, Parser::Token_SEMICOLON, 2, 4, 2, 4); COMPARE_TOKEN(ts, 6, Parser::Token_WHITESPACE, 2, 5, 2, 5); COMPARE_TOKEN(ts, 7, Parser::Token_LNUMBER, 3, 0, 3, 6); COMPARE_TOKEN(ts, 8, Parser::Token_SEMICOLON, 3, 7, 3, 7); COMPARE_TOKEN(ts, 9, Parser::Token_WHITESPACE, 3, 8, 3, 8); COMPARE_TOKEN(ts, 10, Parser::Token_LNUMBER, 4, 0, 4, 6); COMPARE_TOKEN(ts, 11, Parser::Token_SEMICOLON, 4, 7, 4, 7); delete ts; } void LexerTest::testTypeHintsOnFunction() { - TokenStream *ts = tokenize(" ts(tokenize(QStringLiteral("size(), 25); COMPARE_TOKEN(ts, 0, Parser::Token_OPEN_TAG, 0, 0, 0, 5); COMPARE_TOKEN(ts, 1, Parser::Token_FUNCTION, 1, 0, 1, 7); COMPARE_TOKEN(ts, 2, Parser::Token_WHITESPACE, 1, 8, 1, 8); COMPARE_TOKEN(ts, 3, Parser::Token_STRING, 1, 9, 1, 9); COMPARE_TOKEN(ts, 4, Parser::Token_LPAREN, 1, 10, 1, 10); COMPARE_TOKEN(ts, 5, Parser::Token_VARIABLE, 1, 11, 1, 12); COMPARE_TOKEN(ts, 6, Parser::Token_COMMA, 1, 13, 1, 13); COMPARE_TOKEN(ts, 7, Parser::Token_WHITESPACE, 1, 14, 1, 14); COMPARE_TOKEN(ts, 8, Parser::Token_ARRAY, 1, 15, 1, 19); COMPARE_TOKEN(ts, 9, Parser::Token_WHITESPACE, 1, 20, 1, 20); COMPARE_TOKEN(ts, 10, Parser::Token_VARIABLE, 1, 21, 1, 22); COMPARE_TOKEN(ts, 11, Parser::Token_WHITESPACE, 1, 23, 1, 23); COMPARE_TOKEN(ts, 12, Parser::Token_ASSIGN, 1, 24, 1, 24); COMPARE_TOKEN(ts, 13, Parser::Token_WHITESPACE, 1, 25, 1, 25); COMPARE_TOKEN(ts, 14, Parser::Token_LBRACKET, 1, 26, 1, 26); COMPARE_TOKEN(ts, 15, Parser::Token_RBRACKET, 1, 27, 1, 27); COMPARE_TOKEN(ts, 16, Parser::Token_COMMA, 1, 28, 1, 28); COMPARE_TOKEN(ts, 17, Parser::Token_WHITESPACE, 1, 29, 1, 29); COMPARE_TOKEN(ts, 18, Parser::Token_CALLABLE, 1, 30, 1, 37); COMPARE_TOKEN(ts, 19, Parser::Token_WHITESPACE, 1, 38, 1, 38); COMPARE_TOKEN(ts, 20, Parser::Token_VARIABLE, 1, 39, 1, 40); COMPARE_TOKEN(ts, 21, Parser::Token_RPAREN, 1, 41, 1, 41); COMPARE_TOKEN(ts, 22, Parser::Token_WHITESPACE, 1, 42, 1, 42); COMPARE_TOKEN(ts, 23, Parser::Token_LBRACE, 1, 43, 1, 43); COMPARE_TOKEN(ts, 24, Parser::Token_RBRACE, 1, 44, 1, 44); } void LexerTest::testExponentiation() { - TokenStream *ts = tokenize(" ts(tokenize(QStringLiteral("size(), 18); COMPARE_TOKEN(ts, 0, Parser::Token_OPEN_TAG, 0, 0, 0, 5); COMPARE_TOKEN(ts, 1, Parser::Token_VARIABLE, 1, 0, 1, 1); COMPARE_TOKEN(ts, 2, Parser::Token_WHITESPACE, 1, 2, 1, 2); COMPARE_TOKEN(ts, 3, Parser::Token_ASSIGN, 1, 3, 1, 3); COMPARE_TOKEN(ts, 4, Parser::Token_WHITESPACE, 1, 4, 1, 4); COMPARE_TOKEN(ts, 5, Parser::Token_LNUMBER, 1, 5, 1, 5); COMPARE_TOKEN(ts, 6, Parser::Token_WHITESPACE, 1, 6, 1, 6); COMPARE_TOKEN(ts, 7, Parser::Token_EXP, 1, 7, 1, 8); COMPARE_TOKEN(ts, 8, Parser::Token_WHITESPACE, 1, 9, 1, 9); COMPARE_TOKEN(ts, 9, Parser::Token_LNUMBER, 1, 10, 1, 10); COMPARE_TOKEN(ts, 10, Parser::Token_SEMICOLON, 1, 11, 1, 11); COMPARE_TOKEN(ts, 11, Parser::Token_WHITESPACE, 1, 12, 1, 12); COMPARE_TOKEN(ts, 12, Parser::Token_VARIABLE, 1, 13, 1, 14); COMPARE_TOKEN(ts, 13, Parser::Token_WHITESPACE, 1, 15, 1, 15); COMPARE_TOKEN(ts, 14, Parser::Token_EXP_ASSIGN, 1, 16, 1, 18); COMPARE_TOKEN(ts, 15, Parser::Token_WHITESPACE, 1, 19, 1, 19); COMPARE_TOKEN(ts, 16, Parser::Token_LNUMBER, 1, 20, 1, 20); COMPARE_TOKEN(ts, 17, Parser::Token_SEMICOLON, 1, 21, 1, 21); } void LexerTest::testExceptionFinally() { - TokenStream *ts = tokenize(" ts(tokenize(QStringLiteral("size(), 19); COMPARE_TOKEN(ts, 0, Parser::Token_OPEN_TAG, 0, 0, 0, 5); COMPARE_TOKEN(ts, 1, Parser::Token_TRY, 1, 0, 1, 2); COMPARE_TOKEN(ts, 2, Parser::Token_WHITESPACE, 1, 3, 1, 3); COMPARE_TOKEN(ts, 3, Parser::Token_LBRACE, 1, 4, 1, 4); COMPARE_TOKEN(ts, 4, Parser::Token_WHITESPACE, 1, 5, 1, 5); COMPARE_TOKEN(ts, 5, Parser::Token_VARIABLE, 1, 6, 1, 7); COMPARE_TOKEN(ts, 6, Parser::Token_WHITESPACE, 1, 8, 1, 8); COMPARE_TOKEN(ts, 7, Parser::Token_ASSIGN, 1, 9, 1, 9); COMPARE_TOKEN(ts, 8, Parser::Token_WHITESPACE, 1, 10, 1, 10); COMPARE_TOKEN(ts, 9, Parser::Token_LNUMBER, 1, 11, 1, 11); COMPARE_TOKEN(ts, 10, Parser::Token_SEMICOLON, 1, 12, 1, 12); COMPARE_TOKEN(ts, 11, Parser::Token_WHITESPACE, 1, 13, 1, 13); COMPARE_TOKEN(ts, 12, Parser::Token_RBRACE, 1, 14, 1, 14); COMPARE_TOKEN(ts, 13, Parser::Token_WHITESPACE, 1, 15, 1, 15); COMPARE_TOKEN(ts, 14, Parser::Token_FINALLY, 1, 16, 1, 22); COMPARE_TOKEN(ts, 15, Parser::Token_WHITESPACE, 1, 23, 1, 23); COMPARE_TOKEN(ts, 16, Parser::Token_LBRACE, 1, 24, 1, 24); COMPARE_TOKEN(ts, 17, Parser::Token_WHITESPACE, 1, 25, 1, 25); COMPARE_TOKEN(ts, 18, Parser::Token_RBRACE, 1, 26, 1, 26); } TokenStream* LexerTest::tokenize(const QString& unit, bool debug, int initialState) { TokenStream* tokenStream = new TokenStream; Lexer lexer(tokenStream, unit, initialState); int token; int i = 0; QList tokens; while ((token = lexer.nextTokenKind())) { Parser::Token &t = tokenStream->push(); t.begin = lexer.tokenBegin(); t.end = lexer.tokenEnd(); t.kind = token; tokens << t; } if (debug) { foreach(const Parser::Token &t, tokens) { qint64 beginLine; qint64 beginColumn; tokenStream->startPosition(i, &beginLine, &beginColumn); qint64 endLine; qint64 endColumn; tokenStream->endPosition(i, &endLine, &endColumn); qDebug() << tokenText(t.kind) - << unit.mid(t.begin, t.end - t.begin + 1).replace('\n', "\\n") - << QString("[%0-%1] - [%2-%3]").arg(beginLine).arg(beginColumn).arg(endLine).arg(endColumn); + << unit.mid(t.begin, t.end - t.begin + 1).replace('\n', QLatin1String("\\n")) + << QStringLiteral("[%0-%1] - [%2-%3]").arg(beginLine).arg(beginColumn).arg(endLine).arg(endColumn); ++i; } } return tokenStream; } } diff --git a/phplanguagesupport.cpp b/phplanguagesupport.cpp index e9116bb..b3ae279 100644 --- a/phplanguagesupport.cpp +++ b/phplanguagesupport.cpp @@ -1,190 +1,190 @@ /***************************************************************************** * Copyright (c) 2007 Piyush verma * * Copyright (c) 2009 Niko Sams * * Copyright (c) 2010 Milian Wolff * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * *****************************************************************************/ #include "phplanguagesupport.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "phpparsejob.h" #include "phphighlighting.h" #include "kdevphpversion.h" #include "phpdebug.h" #include "codegen/refactoring.h" #include #include #include "completion/model.h" #include "completion/worker.h" #include "navigation/navigationwidget.h" #include #include "duchain/helper.h" #include using namespace KTextEditor; using namespace KDevelop; K_PLUGIN_FACTORY_WITH_JSON(KDevPhpSupportFactory, "kdevphpsupport.json", registerPlugin(); ) namespace Php { LanguageSupport::LanguageSupport(QObject* parent, const QVariantList& /*args*/) : KDevelop::IPlugin(QStringLiteral("kdevphpsupport"), parent), KDevelop::ILanguageSupport() { Q_ASSERT(internalFunctionFile().toUrl().isValid()); KDEV_USE_EXTENSION_INTERFACE(KDevelop::ILanguageSupport) m_highlighting = new Php::Highlighting(this); m_refactoring = new Php::Refactoring(this); CodeCompletionModel* ccModel = new CodeCompletionModel(this); new KDevelop::CodeCompletion(this, ccModel, name()); } LanguageSupport::~LanguageSupport() { parseLock()->lockForWrite(); //By locking the parse-mutexes, we make sure that parse- and preprocess-jobs //get a chance to finish in a good state parseLock()->unlock(); } KDevelop::ParseJob *LanguageSupport::createParseJob(const IndexedString &url) { return new ParseJob(url, this); } QString LanguageSupport::name() const { - return "Php"; + return QStringLiteral("Php"); } KDevelop::ICodeHighlighting* LanguageSupport::codeHighlighting() const { return m_highlighting; } KDevelop::ContextMenuExtension LanguageSupport::contextMenuExtension(Context* context) { ContextMenuExtension cm; EditorContext *ed = dynamic_cast(context); if (ed && ICore::self()->languageController()->languagesForUrl(ed->url()).contains(this)) { // It's safe to add our own ContextMenuExtension. m_refactoring->fillContextMenu(cm, context); } return cm; } QPair LanguageSupport::wordUnderCursor(const QUrl& url, const Cursor& position) { KDevelop::IDocument* doc = core()->documentController()->documentForUrl(url); if(!doc || !doc->textDocument()) return {}; int lineNumber = position.line(); int lineLength = doc->textDocument()->lineLength(lineNumber); QString line = doc->textDocument()->text(Range(lineNumber, 0, lineNumber, lineLength)); int startCol = position.column(); for ( ; startCol >= 0; --startCol ) { if ( !line[startCol].isLetter() && line[startCol] != '_' ) { // don't include the wrong char if ( startCol != position.column() ) { ++startCol; } break; } } int endCol = position.column(); for ( ; endCol <= lineLength; ++endCol ) { if ( !line[endCol].isLetter() && line[endCol] != '_' ) { break; } } QString word = line.mid(startCol, endCol - startCol); Range range(lineNumber, startCol, lineNumber, endCol); return qMakePair(word, range); } bool isMagicConstant(QPair word) { if ( word.second.isValid() && !word.second.isEmpty() ) { - if ( word.first == "__FILE__" || word.first == "__LINE__" || - word.first == "__METHOD__" || word.first == "__CLASS__" || - word.first == "__FUNCTION__" || word.first == "__NAMESPACE__" + if ( word.first == QLatin1String("__FILE__") || word.first == QLatin1String("__LINE__") || + word.first == QLatin1String("__METHOD__") || word.first == QLatin1String("__CLASS__") || + word.first == QLatin1String("__FUNCTION__") || word.first == QLatin1String("__NAMESPACE__") ///TODO: php 5.3: __DIR__ ) { ///TODO: maybe we should use the tokenizer to really make sure this is such a token /// and we are not inside a string, comment or similar /// otoh, it doesn't hurt imo return true; } } return false; } QWidget* LanguageSupport::specialLanguageObjectNavigationWidget(const QUrl& url, const Cursor& position) { QPair word = wordUnderCursor(url, position); if ( isMagicConstant(word) ) { DUChainReadLocker lock; if (TopDUContext* top = standardContext(url)) { return new NavigationWidget(TopDUContextPointer(top), position, word.first); } else { return 0; } } return ILanguageSupport::specialLanguageObjectNavigationWidget(url, position); } Range LanguageSupport::specialLanguageObjectRange(const QUrl& url, const Cursor& position) { QPair word = wordUnderCursor(url, position); if ( isMagicConstant(word) ) { return word.second; } return ILanguageSupport::specialLanguageObjectRange(url, position); } } #include "phplanguagesupport.moc" diff --git a/testprovider/phpunitprovider.cpp b/testprovider/phpunitprovider.cpp index 56b53e7..6f891b2 100644 --- a/testprovider/phpunitprovider.cpp +++ b/testprovider/phpunitprovider.cpp @@ -1,180 +1,180 @@ /*************************************************************************** * 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(PhpUnitProviderFactory, 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) << "Recieved 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->isAbstract() || !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() : "No project"); + 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->isAbstract()) { foreach (Declaration* member, classDeclaration->internalContext()->localDeclarations()) { qCDebug(TESTPROVIDER) << "Trying test case declaration" << member; - if (member->isFunctionDeclaration() && member->identifier().toString().startsWith("test")) + 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::getInheriters(d, steps)) { processTestCaseDeclaration(inheriter); } } #include "phpunitprovider.moc" diff --git a/testprovider/phpunitrunjob.cpp b/testprovider/phpunitrunjob.cpp index 09587ff..72b8fa4 100644 --- a/testprovider/phpunitrunjob.cpp +++ b/testprovider/phpunitrunjob.cpp @@ -1,200 +1,200 @@ /*************************************************************************** * 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 "phpunitrunjob.h" #include "phpunittestsuite.h" #include "testdoxdelegate.h" #include "testproviderdebug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include PhpUnitRunJob::PhpUnitRunJob(PhpUnitTestSuite* suite, const QStringList& cases, KDevelop::OutputJob::OutputJobVerbosity verbosity, QObject* parent) : KJob(parent) , m_process(0) , m_suite(suite) , m_cases(cases) , m_job(0) , m_outputJob(0) , m_verbosity(verbosity) { } KJob* createTestJob(QString launchModeId, QStringList arguments ) { - KDevelop::LaunchConfigurationType* type = KDevelop::ICore::self()->runController()->launchConfigurationTypeForId( "Script Application" ); + KDevelop::LaunchConfigurationType* type = KDevelop::ICore::self()->runController()->launchConfigurationTypeForId( QStringLiteral("Script Application") ); KDevelop::ILaunchMode* mode = KDevelop::ICore::self()->runController()->launchModeForId( launchModeId ); qCDebug(TESTPROVIDER) << "got mode and type:" << type << type->id() << mode << mode->id(); Q_ASSERT(type && mode); KDevelop::ILauncher* launcher = 0; foreach (KDevelop::ILauncher *l, type->launchers()) { //qCDebug(TESTPROVIDER) << "available launcher" << l << l->id() << l->supportedModes(); if (l->supportedModes().contains(mode->id())) { launcher = l; break; } } Q_ASSERT(launcher); KDevelop::ILaunchConfiguration* ilaunch = 0; QList launchConfigurations = KDevelop::ICore::self()->runController()->launchConfigurations(); foreach (KDevelop::ILaunchConfiguration *l, launchConfigurations) { if (l->type() == type && l->config().readEntry("ConfiguredByPhpUnit", false)) { ilaunch = l; break; } } if (!ilaunch) { ilaunch = KDevelop::ICore::self()->runController()->createLaunchConfiguration( type, qMakePair( mode->id(), launcher->id() ), 0, //TODO add project i18n("PHPUnit") ); ilaunch->config().writeEntry("ConfiguredByPhpUnit", true); //qCDebug(TESTPROVIDER) << "created config, launching"; } else { //qCDebug(TESTPROVIDER) << "reusing generated config, launching"; } type->configureLaunchFromCmdLineArguments( ilaunch->config(), arguments ); return KDevelop::ICore::self()->runController()->execute(launchModeId, ilaunch); } void PhpUnitRunJob::start() { m_process = new KProcess(this); // TODO: Arguments from test cases QStringList args; if (m_cases != m_suite->cases()) { - args << "--filter"; - args << '"' + m_cases.join("|") + '"'; + args << QStringLiteral("--filter"); + args << '"' + m_cases.join(QStringLiteral("|")) + '"'; } - args << "--testdox" << m_suite->name() << m_suite->url().toLocalFile(); + args << QStringLiteral("--testdox") << m_suite->name() << m_suite->url().toLocalFile(); const QString exe = QStandardPaths::findExecutable(QStringLiteral("phpunit")); if (exe.isEmpty()) { KDevelop::ITestController* tc = KDevelop::ICore::self()->testController(); tc->notifyTestRunFinished(m_suite, m_result); emitResult(); return; } args.prepend(exe); - args.prepend("php"); + args.prepend(QStringLiteral("php")); - m_job = createTestJob("execute", args); + m_job = createTestJob(QStringLiteral("execute"), args); m_outputJob = qobject_cast(m_job); if (!m_outputJob) { if (UnprotectedExecuteCompositeJob* cjob = qobject_cast(m_job)) { m_outputJob = qobject_cast(cjob->subjobs().last()); } } Q_ASSERT(m_outputJob); if (m_outputJob) { m_outputJob->setVerbosity(m_verbosity); - connect(m_outputJob->model(), SIGNAL(rowsInserted(QModelIndex,int,int)), SLOT(rowsInserted(QModelIndex,int,int))); + connect(m_outputJob->model(), &QAbstractItemModel::rowsInserted, this, &PhpUnitRunJob::rowsInserted); } - connect(m_job, SIGNAL(finished(KJob*)), SLOT(processFinished(KJob*))); + connect(m_job, &KJob::finished, this, &PhpUnitRunJob::processFinished); } bool PhpUnitRunJob::doKill() { if (m_job) { m_job->kill(); } return true; } void PhpUnitRunJob::processFinished(KJob* job) { if (job->error() == 1) { m_result.suiteResult = KDevelop::TestResult::Failed; } else if (job->error() == 0) { m_result.suiteResult = KDevelop::TestResult::Passed; foreach (KDevelop::TestResult::TestCaseResult result, m_result.testCaseResults) { if (result == KDevelop::TestResult::Failed) { m_result.suiteResult = KDevelop::TestResult::Failed; break; } } } else { m_result.suiteResult = KDevelop::TestResult::Error; } qCDebug(TESTPROVIDER) << m_result.suiteResult << m_result.testCaseResults; KDevelop::ICore::self()->testController()->notifyTestRunFinished(m_suite, m_result); emitResult(); } void PhpUnitRunJob::rowsInserted(const QModelIndex &parent, int startRow, int endRow) { Q_ASSERT(m_outputJob); static QRegExp testResultLineExp = QRegExp("\\[([x\\s])\\]"); for (int row = startRow; row <= endRow; ++row) { QString line = m_outputJob->model()->data(m_outputJob->model()->index(row, 0, parent), Qt::DisplayRole).toString(); int i = testResultLineExp.indexIn(line); if (i > -1) { - bool passed = testResultLineExp.cap(1) == "x"; + bool passed = testResultLineExp.cap(1) == QLatin1String("x"); QString testCase = "test" + line.mid(i+4).toLower().remove(' '); qCDebug(TESTPROVIDER) << "Got result in " << line << " for " << testCase; if (m_cases.contains(testCase, Qt::CaseInsensitive)) { foreach (const QString& realCaseName, m_cases) { if (QString::compare(testCase, realCaseName, Qt::CaseInsensitive) == 0) { m_result.testCaseResults[testCase] = (passed ? KDevelop::TestResult::Passed : KDevelop::TestResult::Failed); break; } } } } else { qCDebug(TESTPROVIDER) << line << testResultLineExp.pattern() << i; } } } diff --git a/testprovider/testdoxdelegate.cpp b/testprovider/testdoxdelegate.cpp index 448853e..6ac55c9 100644 --- a/testprovider/testdoxdelegate.cpp +++ b/testprovider/testdoxdelegate.cpp @@ -1,55 +1,55 @@ /*************************************************************************** * 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 "testdoxdelegate.h" TestDoxDelegate::TestDoxDelegate(QObject* parent): QItemDelegate(parent), failBrush(KColorScheme::View, KColorScheme::NegativeText), passBrush(KColorScheme::View, KColorScheme::PositiveText) { } TestDoxDelegate::~TestDoxDelegate() { } void TestDoxDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { const QString line = index.data().toString(); QStyleOptionViewItem opt = option; - if (line.startsWith(" [x]")) + if (line.startsWith(QLatin1String(" [x]"))) { highlight(opt, passBrush); } - else if (line.startsWith(" [ ]")) + else if (line.startsWith(QLatin1String(" [ ]"))) { highlight(opt, failBrush); } QItemDelegate::paint(painter, opt, index); } void TestDoxDelegate::highlight(QStyleOptionViewItem& option, const KStatefulBrush& brush, bool bold) const { option.font.setBold(bold); option.palette.setBrush(QPalette::Text, brush.brush(option.palette)); }