diff --git a/completion/context.cpp b/completion/context.cpp index 10a29f3..24b8f19 100644 --- a/completion/context.cpp +++ b/completion/context.cpp @@ -1,1795 +1,1795 @@ /* 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 ( 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( 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 ) == 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 ( 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_DIR: 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_TRAIT_C: case Parser::Token_TRY: case Parser::Token_UNSET: case Parser::Token_USE: case Parser::Token_VARIABLE: case Parser::Token_VOID: case Parser::Token_WHILE: case Parser::Token_WHITESPACE: case Parser::Token_YIELD: case Parser::Token_YIELD_FROM: /// 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 == 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 == 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 = nullptr; 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 = nullptr; if (idType) { declaration = idType->declaration(m_duContext->topContext()); } DUContext* ctx = nullptr; 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 != 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 = 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(QStringLiteral("function")); if (currentClass->classModifier() == ClassDeclarationData::Abstract) { // abstract is only allowed in abstract classes 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(QStringLiteral("final"))) { ADD_KEYWORD("final"); } } if (modifiers.contains(QStringLiteral("private"))) { // overloadable functions must not be declared private showOverloadable = false; } else if (modifiers.contains(QStringLiteral("protected"))) { // only show protected overloadable methods filterPublic = true; } else if (addKeywords && !modifiers.contains(QStringLiteral("public"))) { ADD_KEYWORD("public"); ADD_KEYWORD("protected"); ADD_KEYWORD("private"); } 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(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().index(); continue; } // skip already implemented functions if (alreadyImplemented.contains(decl.first->indexedIdentifier().index())) { 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().index(); continue; } // skip final members if (classFunc && classFunc->isFinal()) { // make sure no non-final base members are added alreadyImplemented << decl.first->indexedIdentifier().index(); 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 (classFunc && classFunc->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().index(); } } } } else { qCDebug(COMPLETION) << "invalid owner declaration for overloadable completion"; } } } else if (m_memberAccessOperation == BackslashAccess || m_memberAccessOperation == NamespaceChoose) { DUContext* ctx = nullptr; 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 = nullptr; 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(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; const auto decls = m_duContext->allDeclarations( CursorInRevision::invalid(), m_duContext->topContext() ); qCDebug(COMPLETION) << "setContext: using all declarations visible:" << decls.size(); QVectorIterator 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().index())) continue; existingIdentifiers.insert(dec->indexedIdentifier().index()); } 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 = nullptr; 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 = nullptr; 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; } 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() .declaration(m_duContext->topContext()); if (!isValidCompletionItem(decl)) { continue; } if ( !decl->isFunctionDeclaration() ) { if ( ClassDeclaration* classDec = dynamic_cast(decl) ) { // search for ctor decl = nullptr; 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. + ///TODO: use user's style for indentation 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(QStringLiteral("exception")); QList decs = dec->context()->findDeclarations(exceptionQId); Q_ASSERT(decs.count()); 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()) { 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 609d6ef..fdf1378 100644 --- a/completion/helpers.cpp +++ b/completion/helpers.cpp @@ -1,216 +1,216 @@ /* * 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::argumentContext(dec)) parameters = DUChainUtils::argumentContext(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 += 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) { IndexedString defaultStr = decl->defaultParameters()[defaultParamNum]; if (!defaultStr.isEmpty()) { ret += " = " + defaultStr.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(QStringLiteral("function"), Qt::CaseInsensitive)) { tokens << QStringLiteral("function"); text = text.left(text.length() - 8); } QStringList possibleTokens; 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 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 QStringLiteral(" "); } return QStringLiteral("\t"); } -QString getIndendation( const QString &line ) { +QString getIndentation( const QString &line ) { return line.left(line.indexOf(QRegExp("\\S"), 0)); } } diff --git a/completion/helpers.h b/completion/helpers.h index bd1878d..5abd4cc 100644 --- a/completion/helpers.h +++ b/completion/helpers.h @@ -1,75 +1,75 @@ /* * 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. */ #ifndef COMPLETIONHELPERS_H #define COMPLETIONHELPERS_H #include #include #include "phpcompletionexport.h" class QString; class QVariant; template class QList; namespace KTextEditor { class Document; } namespace Php { class NormalDeclarationCompletionItem; /** * @param phpTypeHinting set to true if you only want PHP-valid typehinting (i.e. only array and classes) */ void KDEVPHPCOMPLETION_EXPORT createArgumentList(const NormalDeclarationCompletionItem& item, QString& ret, QList* highlighting, bool phpTypeHinting = false); /// get a list of tokens which define a method at the end of the given text /// NOTE: result will contain "function" if it's the last token at the end of the text QStringList getMethodTokens(QString text); /** * Get a string representing an indentation. * * @param document The current document. * @returns a QString containing the indentation to be used. */ const QString KDEVPHPCOMPLETION_EXPORT indentString(KTextEditor::Document *document); /** - * Get the indendation of a given line. + * Get the indentation of a given line. * * You usually want to use it with something like the following: * * \code - * const QString indendation = getIndendation( document->line(replaceRange.start().line()) ); + * const QString indentation = getIndentation( document->line(replaceRange.start().line()) ); * \endcode */ -QString KDEVPHPCOMPLETION_EXPORT getIndendation( const QString &line ); +QString KDEVPHPCOMPLETION_EXPORT getIndentation( const QString &line ); } #endif diff --git a/completion/implementationitem.cpp b/completion/implementationitem.cpp index f776551..ab8d6ee 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; + // get indentation + QString indentation; { QString currentLine = document->line(replaceRange.start().line()); - indendation = getIndendation(currentLine); + indentation = getIndentation(currentLine); - if ( !currentLine.isEmpty() && currentLine != indendation ) { + if ( !currentLine.isEmpty() && currentLine != indentation ) { // since theres some non-whitespace in this line, skip to the enxt one - replText += '\n' + indendation; + replText += '\n' + indentation; } - if (indendation.isEmpty()) { - // use a minimal indendation + if (indentation.isEmpty()) { + // use a minimal indentation // TODO: respect code style - indendation = QStringLiteral(" "); - replText += indendation; + indentation = QStringLiteral(" "); + replText += indentation; } } #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 + " * "; + replText += "/**\n" + indentation + " * "; // insert old comment: - const QString indentationWithExtra = "\n" + indendation + " *"; + const QString indentationWithExtra = "\n" + indentation + " *"; replText += m_declaration->comment().replace('\n', indentationWithExtra.toAscii().constData()); - replText += "\n" + indendation + " * @overload " + m_declaration->internalContext()->scopeIdentifier(true).toString(); - replText += "\n" + indendation + " **/\n" + indendation; + replText += "\n" + indentation + " * @overload " + m_declaration->internalContext()->scopeIdentifier(true).toString(); + replText += "\n" + indentation + " **/\n" + indentation; } #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(QStringLiteral("public")) && !modifiers.contains(QStringLiteral("protected"))) { if (member->accessPolicy() == Declaration::Protected) { replText += QLatin1String("protected "); } else { replText += QLatin1String("public "); } } 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(QStringLiteral("function"))) { replText += QLatin1String("function "); } replText += functionName; { // get argument list QString arguments; createArgumentList(*this, arguments, nullptr, true); replText += arguments; } QString arguments; QVector parameters; if (DUChainUtils::argumentContext(m_declaration.data())) parameters = DUChainUtils::argumentContext(m_declaration.data())->localDeclarations(); arguments = '('; bool first = true; foreach(Declaration* dec, parameters) { if (first) first = false; else 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 += QStringLiteral("\n%1{\n%1 ").arg(indendation); + replText += QStringLiteral("\n%1{\n%1 ").arg(indentation); if (isInterface || m_type == ImplementationItem::Implement) { } else if (!isConstructorOrDestructor && !voidReturnType) { - replText += QStringLiteral("$ret = parent::%2%3;\n%1 return $ret;").arg(indendation, functionName, arguments); + replText += QStringLiteral("$ret = parent::%2%3;\n%1 return $ret;").arg(indentation, functionName, arguments); } else { replText += QStringLiteral("parent::%1%2;").arg(functionName, arguments); } replText += QStringLiteral("\n%1}\n%1") - .arg(indendation); + .arg(indentation); } //TODO: properly place the cursor inside the {} part document->replaceText(replaceRange, replText); } else { qCDebug(COMPLETION) << "Declaration disappeared"; } } } diff --git a/completion/keyworditem.cpp b/completion/keyworditem.cpp index 1eb5581..98f3835 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('\n', '\n' + getIndentation(document->line(word.start().line()))); replacement = replacement.replace(QLatin1String("%INDENT%"), indentString(document)); int cursorPos = replacement.indexOf(QStringLiteral("%CURSOR%")); int selectionEnd = -1; if ( cursorPos != -1 ) { replacement.remove(QStringLiteral("%CURSOR%")); } else { cursorPos = replacement.indexOf(QStringLiteral("%SELECT%")); if ( cursorPos != -1 ) { replacement.remove(QStringLiteral("%SELECT%")); selectionEnd = replacement.indexOf(QStringLiteral("%ENDSELECT%"), cursorPos + 1); if ( selectionEnd == -1 ) { selectionEnd = replacement.length(); } 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/docs/phpdocsconfig.ui b/docs/phpdocsconfig.ui index e1a72b1..a63adb9 100644 --- a/docs/phpdocsconfig.ui +++ b/docs/phpdocsconfig.ui @@ -1,93 +1,93 @@ PhpDocsConfigUI 0 0 560 404 PHP Manual Integration Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'DejaVu Sans'; font-size:8pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">By default the online <a href="http://php.net"><span style=" text-decoration: underline; color:#0000ff;">PHP documentation</span></a> is used remotely. Alternatively you can set the location to a local copy of the documentation in the language of your choice. Make sure you downloaded the <span style=" font-style:italic;">"Many HTML files"</span> package.</p></body></html> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">By default the online <a href="https://php.net"><span style=" text-decoration: underline; color:#0000ff;">PHP documentation</span></a> is used remotely. Alternatively you can set the location to a local copy of the documentation in the language of your choice. Make sure you downloaded the <span style=" font-style:italic;">"Many HTML files"</span> package.</p></body></html> false true true QFormLayout::ExpandingFieldsGrow Location: Qt::Vertical 20 40 KUrlRequester QWidget
kurlrequester.h
diff --git a/duchain/builders/declarationbuilder.cpp b/duchain/builders/declarationbuilder.cpp index 6ac024e..6286c41 100644 --- a/duchain/builders/declarationbuilder.cpp +++ b/duchain/builders/declarationbuilder.cpp @@ -1,1754 +1,1754 @@ /*************************************************************************** * 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 "../expressionvisitor.h" #include "predeclarationbuilder.h" #include #define ifDebug(x) using namespace KDevelop; namespace Php { DeclarationBuilder::FindVariableResults::FindVariableResults() : find(true) , isArray(false) , node(nullptr) { } 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 + // at least one "->" in the assignment 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, const ReferencedTopDUContext& updateContext_) { ReferencedTopDUContext updateContext(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(); QString className = classDec->prettyName().str(); if (isReservedClassName(className)) { reportError(i18n("Cannot use '%1' as class name as it is reserved", className), node->className); } } void DeclarationBuilder::visitInterfaceDeclarationStatement(InterfaceDeclarationStatementAst *node) { ClassDeclaration* interfaceDec = openTypeDeclaration(node->interfaceName, ClassDeclarationData::Interface); openType(interfaceDec->abstractType()); DeclarationBuilderBase::visitInterfaceDeclarationStatement(node); closeType(); closeDeclaration(); QString interfaceName = interfaceDec->prettyName().str(); if (isReservedClassName(interfaceName)) { reportError(i18n("Cannot use '%1' as class name as it is reserved", interfaceName), node->interfaceName); } } void DeclarationBuilder::visitTraitDeclarationStatement(TraitDeclarationStatementAst * node) { ClassDeclaration* traitDec = openTypeDeclaration(node->traitName, ClassDeclarationData::Trait); openType(traitDec->abstractType()); DeclarationBuilderBase::visitTraitDeclarationStatement(node); closeType(); closeDeclaration(); m_upcomingClassVariables.clear(); QString traitName = traitDec->prettyName().str(); if (isReservedClassName(traitName)) { reportError(i18n("Cannot use '%1' as class name as it is reserved", traitName), node->traitName); } } ClassDeclaration* DeclarationBuilder::openTypeDeclaration(IdentifierAst* name, ClassDeclarationData::ClassType type) { ClassDeclaration* classDec = m_types.value(name->string, nullptr); 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->constsSequence) { 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("Cannot use 'final' as constant modifier"), node->modifiers); } if (m_currentModifers & ModifierStatic) { reportError(i18n("Cannot use 'static' as constant modifier"), node->modifiers); } if (m_currentModifers & ModifierAbstract) { reportError(i18n("Cannot use 'abstract' as constant modifier"), node->modifiers); } } } else { m_currentModifers = 0; } DeclarationBuilderBase::visitClassStatement(node); m_currentModifers = 0; } 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(nullptr); QVector localDeclarations = currentContext()->localDeclarations(nullptr); ifDebug(qCDebug(DUCHAIN) << "Importing from" << dec.data()->identifier().toString() << "to" << currentContext()->localScopeIdentifier().toString();) foreach (Declaration* import, declarations) { Declaration* found = nullptr; foreach (Declaration* local, localDeclarations) { ifDebug(qCDebug(DUCHAIN) << "Comparing" << import->identifier().toString() << "with" << local->identifier().toString();) if (auto trait = dynamic_cast(local)) { if (trait->aliasedDeclaration().data() == import) { ifDebug(qCDebug(DUCHAIN) << "Already imported";) found = local; break; } if (local->identifier() == import->identifier()) { ClassMethodDeclaration* importMethod = dynamic_cast(import); if (trait->isOverriding(import->context()->indexedLocalScopeIdentifier())) { ifDebug(qCDebug(DUCHAIN) << "Is overridden";) found = local; break; } else if (importMethod) { reportError( i18n("Trait method %1 has not been applied, because there are collisions with other trait methods on %2") .arg(importMethod->prettyName().str(), dynamic_cast(currentDeclaration())->prettyName().str()) , it->element, IProblem::Error ); found = local; break; } } } if (auto trait = dynamic_cast(local)) { if (trait->aliasedDeclaration().data() == import) { ifDebug(qCDebug(DUCHAIN) << "Already imported";) found = local; break; } } if (local->identifier() == import->identifier()) { if (dynamic_cast(local) && dynamic_cast(import)) { found = local; break; } } } if (found) { setEncountered(found); continue; } ifDebug(qCDebug(DUCHAIN) << "Importing new declaration";) CursorInRevision cursor = m_editor->findRange(it->element).start; if (auto olddec = dynamic_cast(import)) { TraitMethodAliasDeclaration* newdec = openDefinition(olddec->qualifiedIdentifier(), RangeInRevision(cursor, cursor)); openAbstractType(olddec->abstractType()); newdec->setPrettyName(olddec->prettyName()); newdec->setAccessPolicy(olddec->accessPolicy()); newdec->setKind(Declaration::Type); newdec->setAliasedDeclaration(IndexedDeclaration(olddec)); newdec->setStatic(olddec->isStatic()); closeType(); closeDeclaration(); } else if (auto olddec = dynamic_cast(import)) { TraitMemberAliasDeclaration* newdec = openDefinition(olddec->qualifiedIdentifier(), RangeInRevision(cursor, cursor)); openAbstractType(olddec->abstractType()); newdec->setAccessPolicy(olddec->accessPolicy()); newdec->setKind(Declaration::Instance); newdec->setAliasedDeclaration(IndexedDeclaration(olddec)); newdec->setStatic(olddec->isStatic()); closeType(); closeDeclaration(); } } if ( it->hasNext() ) { it = it->next; } else { break; } } } void DeclarationBuilder::visitClassExtends(ClassExtendsAst *node) { addBaseType(node->identifier); } void DeclarationBuilder::visitClassImplements(ClassImplementsAst *node) { const KDevPG::ListNode *__it = node->implementsSequence->front(), *__end = __it; do { addBaseType(__it->element); __it = __it->next; } while (__it != __end); DeclarationBuilderBase::visitClassImplements(node); } void DeclarationBuilder::visitClassVariable(ClassVariableAst *node) { QualifiedIdentifier name = identifierForNode(node->variable); if (m_reportErrors) { // check for redeclarations DUChainWriteLocker lock(DUChain::lock()); Q_ASSERT(currentContext()->type() == DUContext::Class); foreach(Declaration * dec, currentContext()->findLocalDeclarations(name.first(), startPos(node))) { if (wasEncountered(dec) && !dec->isFunctionDeclaration() && !(dec->abstractType()->modifiers() & AbstractType::ConstModifier)) { reportRedeclarationError(dec, node); break; } } } openClassMemberDeclaration(node->variable, name); DeclarationBuilderBase::visitClassVariable(node); closeDeclaration(); } void DeclarationBuilder::openClassMemberDeclaration(AstNode* node, const QualifiedIdentifier &name) { DUChainWriteLocker lock(DUChain::lock()); // dirty hack: declarations of class members outside the class context would // make the class context encompass the newRange. This is not what we want. RangeInRevision oldRange = currentContext()->range(); RangeInRevision newRange = editorFindRange(node, node); openDefinition(name, newRange); ClassMemberDeclaration* dec = dynamic_cast(currentDeclaration()); Q_ASSERT(dec); if (m_currentModifers & ModifierPublic) { dec->setAccessPolicy(Declaration::Public); } else if (m_currentModifers & ModifierProtected) { dec->setAccessPolicy(Declaration::Protected); } else if (m_currentModifers & ModifierPrivate) { dec->setAccessPolicy(Declaration::Private); } if (m_currentModifers & ModifierStatic) { dec->setStatic(true); } dec->setKind(Declaration::Instance); currentContext()->setRange(oldRange); } void DeclarationBuilder::declareClassMember(DUContext *parentCtx, AbstractType::Ptr type, const QualifiedIdentifier& identifier, AstNode* node ) { if ( m_upcomingClassVariables.contains(identifier) ) { if (m_actuallyRecompiling) { DUChainWriteLocker lock; if (Declaration* dec = currentContext()->findDeclarationAt(startPos(node))) { if (dynamic_cast(dec)) { // invalidate declaration, it got added // see also bug https://bugs.kde.org/show_bug.cgi?id=241750 delete dec; } } } return; } DUChainWriteLocker lock(DUChain::lock()); // 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) { DUChainWriteLocker lock(DUChain::lock()); if (m_reportErrors) { // check for redeclarations 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(identifierForNode(node->identifier), m_editor->findRange(node->identifier)); { DUChainWriteLocker lock(DUChain::lock()); dec->setAccessPolicy(Declaration::Public); dec->setStatic(true); dec->setKind(Declaration::Instance); } DeclarationBuilderBase::visitConstantDeclaration(node); closeDeclaration(); } void DeclarationBuilder::visitClassConstantDeclaration(ClassConstantDeclarationAst *node) { DUChainWriteLocker lock; 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 'class' constant if (identifierForNode(node->identifier).toString().toLower() == QLatin1String("class")) { reportError(i18n("A class constant must not be called 'class'; it is reserved for class name fetching"), node); } // check for redeclarations 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(identifierForNode(node->identifier), m_editor->findRange(node->identifier)); if (m_currentModifers & ModifierProtected) { dec->setAccessPolicy(Declaration::Protected); } else if (m_currentModifers & ModifierPrivate) { dec->setAccessPolicy(Declaration::Private); } else { dec->setAccessPolicy(Declaration::Public); } dec->setStatic(true); dec->setKind(Declaration::Instance); lock.unlock(); DeclarationBuilderBase::visitClassConstantDeclaration(node); closeDeclaration(); } 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 if (node->aliasNonModifierIdentifier) { alias = identifierPairForNode(node->aliasNonModifierIdentifier).second; } else { alias = original; } if (!list.isEmpty()) { ClassMethodDeclaration* olddec = dynamic_cast(list.first()); TraitMethodAliasDeclaration* newdec; // no existing declaration found, create one if (node->aliasIdentifier || node->aliasNonModifierIdentifier) { if (node->aliasIdentifier) { newdec = openDefinition(alias, m_editor->findRange(node->aliasIdentifier)); newdec->setPrettyName(identifierPairForNode(node->aliasIdentifier).first); } else { newdec = openDefinition(alias, m_editor->findRange(node->aliasNonModifierIdentifier)); newdec->setPrettyName(identifierPairForNode(node->aliasNonModifierIdentifier).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 & ModifierAbstract) { reportError(i18n("Cannot use 'abstract' as method modifier"), node->modifiers, IProblem::Error); } 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()); 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 & ModifierAbstract) { reportError(i18n("Cannot use 'abstract' as method modifier"), node->modifiers, IProblem::Error); } 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); } } } 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::visitParameterList(ParameterListAst* node) { PushValue push(m_functionDeclarationPreviousArgument, nullptr); DeclarationBuilderBase::visitParameterList(node); } 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->isVariadic != -1) { reportError(i18n("Variadic parameter cannot have a default value"), node->defaultValue); } else if (node->parameterType && node->parameterType->typehint && isClassTypehint(node->parameterType->typehint, m_editor) && 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->parameterType && node->parameterType->typehint && node->parameterType->typehint->genericType && symbol.compare(QLatin1String("null"), Qt::CaseInsensitive) != 0) { NamespacedIdentifierAst* typehintNode = node->parameterType->typehint->genericType; const KDevPG::ListNode< IdentifierAst* >* it = typehintNode->namespaceNameSequence->back(); QString typehintName = m_editor->parseSession()->symbol(it->element); if (typehintName.compare(QLatin1String("object"), Qt::CaseInsensitive) == 0) { reportError(i18n("Default value for parameters with an object type can only be NULL."), node->defaultValue); } } } else { funDec->addDefaultParameter(IndexedString{}); } { // create variable declaration for argument DUChainWriteLocker lock(DUChain::lock()); RangeInRevision newRange = editorFindRange(node->variable, node->variable); VariableDeclaration *dec = openDefinition(identifierForNode(node->variable), newRange); dec->setKind(Declaration::Instance); dec->setVariadic(node->isVariadic != -1); } DeclarationBuilderBase::visitParameter(node); if (node->parameterType && node->parameterType->typehint && isClassTypehint(node->parameterType->typehint, m_editor)) { NamespacedIdentifierAst* typehintNode = node->parameterType->typehint->genericType; const KDevPG::ListNode< IdentifierAst* >* it = typehintNode->namespaceNameSequence->back(); QString className = m_editor->parseSession()->symbol(it->element); if (isReservedClassName(className)) { reportError(i18n("Cannot use '%1' as class name as it is reserved", className), typehintNode); } } if (m_functionDeclarationPreviousArgument && m_functionDeclarationPreviousArgument->isVariadic != -1) { reportError(i18n("Only the last parameter can be variadic."), m_functionDeclarationPreviousArgument); } closeDeclaration(); m_functionDeclarationPreviousArgument = node; } void DeclarationBuilder::visitFunctionDeclarationStatement(FunctionDeclarationStatementAst* node) { isGlobalRedeclaration(identifierForNode(node->functionName), node->functionName, FunctionDeclarationType); FunctionDeclaration* dec = m_functions.value(node->functionName->string, nullptr); 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::visitReturnType(ReturnTypeAst* node) { if (node->typehint && isClassTypehint(node->typehint, m_editor)) { NamespacedIdentifierAst* typehintNode = node->typehint->genericType; const KDevPG::ListNode< IdentifierAst* >* it = typehintNode->namespaceNameSequence->back(); QString className = m_editor->parseSession()->symbol(it->element); if (isReservedClassName(className)) { reportError(i18n("Cannot use '%1' as class name as it is reserved", className), typehintNode); } } } void DeclarationBuilder::visitClosure(ClosureAst* node) { setComment(formatComment(node, editor())); { DUChainWriteLocker lock; FunctionDeclaration *dec = openDefinition(QualifiedIdentifier(), editor()->findRange(node->startToken)); dec->setKind(Declaration::Type); dec->clearDefaultParameters(); } DeclarationBuilderBase::visitClosure(node); closeDeclaration(); } void DeclarationBuilder::visitLexicalVar(LexicalVarAst* node) { DeclarationBuilderBase::visitLexicalVar(node); QualifiedIdentifier id = identifierForNode(node->variable); DUChainWriteLocker lock; if ( recompiling() ) { // sadly we can't use findLocalDeclarations() here, since it un-aliases declarations foreach ( Declaration* dec, currentContext()->localDeclarations() ) { if ( dynamic_cast(dec) && dec->identifier() == id.first() ) { // don't redeclare but reuse the existing declaration encounter(dec); return; } } } // no existing declaration found, create one foreach(Declaration* aliasedDeclaration, currentContext()->findDeclarations(id)) { if (aliasedDeclaration->kind() == Declaration::Instance) { AliasDeclaration* dec = openDefinition(id, editor()->findRange(node->variable)); dec->setAliasedDeclaration(aliasedDeclaration); closeDeclaration(); break; } } } bool DeclarationBuilder::isGlobalRedeclaration(const QualifiedIdentifier &identifier, AstNode* node, DeclarationType type) { if (!m_reportErrors) { return false; } ///TODO: method redeclaration etc. if (type != ClassDeclarationType && type != FunctionDeclarationType && type != ConstantDeclarationType) { // the other types can be redeclared return false; } DUChainWriteLocker lock(DUChain::lock()); QList declarations = currentContext()->topContext()->findDeclarations( identifier, startPos(node) ); foreach(Declaration* dec, declarations) { if (wasEncountered(dec) && isMatch(dec, type)) { reportRedeclarationError(dec, node); return true; } } return false; } void DeclarationBuilder::reportRedeclarationError(Declaration* declaration, AstNode* node) { if (declaration->range().contains(startPos(node))) { // make sure this is not a wrongly reported redeclaration error return; } if (declaration->context()->topContext()->url() == internalFunctionFile()) { reportError(i18n("Cannot redeclare PHP internal %1.", declaration->toString()), node); } else if (auto trait = dynamic_cast(declaration)) { reportError( i18n("%1 and %2 define the same property (%3) in the composition of %1. This might be incompatible, to improve maintainability consider using accessor methods in traits instead.") .arg(dynamic_cast(currentDeclaration())->prettyName().str(), dynamic_cast(trait->aliasedDeclaration().data()->context()->owner())->prettyName().str(), dynamic_cast(trait)->identifier().toString()), node, IProblem::Warning ); } else { ///TODO: try to shorten the filename by removing the leading path to the current project reportError( i18n("Cannot redeclare %1, already declared in %2 on line %3.", declaration->toString(), declaration->context()->topContext()->url().str(), declaration->range().start.line + 1 ), node ); } } void DeclarationBuilder::visitOuterTopStatement(OuterTopStatementAst* node) { //docblock of an AssignmentExpression setComment(formatComment(node, m_editor)); m_lastTopStatementComment = m_editor->parseSession()->docComment(node->startToken); DeclarationBuilderBase::visitOuterTopStatement(node); } void DeclarationBuilder::visitAssignmentExpression(AssignmentExpressionAst* node) { if ( node->assignmentExpressionEqual ) { PushValue restore(m_findVariable); DeclarationBuilderBase::visitAssignmentExpression(node); } else { DeclarationBuilderBase::visitAssignmentExpression(node); } } void DeclarationBuilder::visitVariable(VariableAst* node) { if ( m_findVariable.find ) { getVariableIdentifier(node, m_findVariable.identifier, m_findVariable.parentIdentifier, m_findVariable.node, m_findVariable.isArray); m_findVariable.find = false; } DeclarationBuilderBase::visitVariable(node); } void DeclarationBuilder::declareVariable(DUContext* parentCtx, AbstractType::Ptr type, const QualifiedIdentifier& identifier, AstNode* node) { DUChainWriteLocker lock(DUChain::lock()); // we must not re-assign $this in a class context /// Qualified identifier for 'this' static const QualifiedIdentifier thisQId(QStringLiteral("this")); if ( identifier == thisQId && currentContext()->parentContext() && currentContext()->parentContext()->type() == DUContext::Class ) { // checks if imports \ArrayAccess ClassDeclaration* currentClass = dynamic_cast(currentContext()->parentContext()->owner()); ClassDeclaration* arrayAccess = nullptr; auto imports = currentContext()->parentContext()->importedParentContexts(); for( const DUContext::Import& ctx : imports ) { DUContext* import = ctx.context(topContext()); if(import->type() == DUContext::Class) { ClassDeclaration* importedClass = dynamic_cast(import->owner()); if(importedClass) { if(importedClass->prettyName().str() == "ArrayAccess" && importedClass->classType() == ClassDeclarationData::ClassType::Interface && !import->parentContext()->owner()) { arrayAccess = importedClass; } } } } IntegralType* thisVar = static_cast(type.data()); // check if this is used as array if(arrayAccess && currentClass && thisVar && thisVar->dataType() == AbstractType::TypeArray) { uint noOfFunc = 0; auto declarations = currentContext()->parentContext()->localDeclarations(); // check if class implements all 4 functions for(auto &dec : declarations) { if(dec->isFunctionDeclaration()) { QualifiedIdentifier func = dec->qualifiedIdentifier(); QString funcname = func.last().identifier().str(); if(funcname == "offsetexists" || funcname == "offsetget" || funcname == "offsetset" || funcname == "offsetunset") { noOfFunc++; } } } if(noOfFunc < 4) { // check if class is not abstract if(currentClass->classModifier() != ClassDeclarationData::ClassModifier::Abstract) { reportError(i18n("Class %1 contains %2 abstract methods and must therefore be declared abstract or implement the remaining methods.",currentClass->prettyName().str(),4-noOfFunc), QList() << node); } } return; } reportError(i18n("Cannot re-assign $this."), QList() << node); return; } const RangeInRevision newRange = editorFindRange(node, node); // check if this variable is already declared { QList< Declaration* > decs = parentCtx->findDeclarations(identifier.first(), startPos(node), nullptr, DUContext::DontSearchInParent); if ( !decs.isEmpty() ) { QList< Declaration* >::const_iterator it = decs.constEnd() - 1; while ( true ) { // we expect that the list of declarations has the newest declaration at back if ( dynamic_cast( *it ) ) { if (!wasEncountered(*it)) { encounter(*it); // force new range https://bugs.kde.org/show_bug.cgi?id=262189, // might be wrong when we had syntax errors in there before (*it)->setRange(newRange); } if ( (*it)->abstractType() && !(*it)->abstractType()->equals(type.data()) ) { // if it's currently mixed and we now get something more definite, use that instead if ( ReferenceType::Ptr rType = ReferenceType::Ptr::dynamicCast((*it)->abstractType()) ) { if ( IntegralType::Ptr integral = IntegralType::Ptr::dynamicCast(rType->baseType()) ) { if ( integral->dataType() == IntegralType::TypeMixed ) { // referenced mixed to referenced @p type ReferenceType::Ptr newType(new ReferenceType()); newType->setBaseType(type); (*it)->setType(newType); return; } } } if ( IntegralType::Ptr integral = IntegralType::Ptr::dynamicCast((*it)->abstractType()) ) { if ( integral->dataType() == IntegralType::TypeMixed ) { // mixed to @p type (*it)->setType(type); return; } } // else make it unsure UnsureType::Ptr unsure = UnsureType::Ptr::dynamicCast((*it)->abstractType()); // maybe it's referenced? ReferenceType::Ptr rType = ReferenceType::Ptr::dynamicCast((*it)->abstractType()); if ( !unsure && rType ) { unsure = UnsureType::Ptr::dynamicCast(rType->baseType()); } if ( !unsure ) { unsure = UnsureType::Ptr(new UnsureType()); if ( rType ) { unsure->addType(rType->baseType()->indexed()); } else { unsure->addType((*it)->indexedType()); } } unsure->addType(type->indexed()); if ( rType ) { rType->setBaseType(AbstractType::Ptr(unsure.data())); (*it)->setType(rType); } else { (*it)->setType(unsure); } } return; } if ( it == decs.constBegin() ) { break; } --it; } } } VariableDeclaration *dec = openDefinition(identifier, newRange); dec->setKind(Declaration::Instance); if (!m_lastTopStatementComment.isEmpty()) { QRegExp rx("(\\*|///)\\s*@superglobal"); if (rx.indexIn(m_lastTopStatementComment) != -1) { dec->setSuperglobal(true); } } //own closeDeclaration() that doesn't use lastType() dec->setType(type); // variable declarations are not namespaced in PHP if (currentContext()->type() == DUContext::Namespace) { dec->setContext(currentContext()->topContext()); } eventuallyAssignInternalContext(); DeclarationBuilderBase::closeDeclaration(); } DUContext* getClassContext(const QualifiedIdentifier &identifier, DUContext* currentCtx) { /// Qualified identifier for 'this' static const QualifiedIdentifier thisQId(QStringLiteral("this")); if ( identifier == thisQId ) { if ( currentCtx->parentContext() && currentCtx->parentContext()->type() == DUContext::Class ) { return currentCtx->parentContext(); } } else { DUChainReadLocker lock(DUChain::lock()); foreach( Declaration* parent, currentCtx->topContext()->findDeclarations(identifier) ) { if ( StructureType::Ptr ctype = parent->type() ) { return ctype->internalContext(currentCtx->topContext()); } } ///TODO: if we can't find anything here we might have to use the findDeclarationImport helper } return nullptr; } ///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); bool alreadyDeclared = false; // check for already declared class members if (!m_findVariable.identifier.isEmpty() && !m_findVariable.parentIdentifier.isEmpty()) { DUContext* ctx = getClassContext(m_findVariable.parentIdentifier, currentContext()); if (ctx) { DUChainReadLocker lock(DUChain::lock()); ifDebug(qCDebug(DUCHAIN) << "checking if already declared: " << m_findVariable.identifier.toString();) QList decs; foreach ( Declaration* dec, currentContext()->findDeclarations(m_findVariable.identifier) ) { if ( dec->kind() == Declaration::Kind::Instance) { if (dec->range() == editorFindRange(m_findVariable.node)) { // Don't reuse previous declarations as the type might have changed. continue; } ClassMemberDeclaration *classDec = dynamic_cast(dec); if (classDec && classDec->accessPolicy() == Declaration::Private) { if (currentContext()->parentContext() == dec->context()) { decs << dec; ifDebug(qCDebug(DUCHAIN) << "found class property:" << dec->toString();) } } else { decs << dec; ifDebug(qCDebug(DUCHAIN) << "found class property:" << dec->toString();) } } } lock.unlock(); if (!decs.isEmpty()) { Declaration * dec = decs.last(); if (dec) { encounter(dec); alreadyDeclared = true; IntegralType::Ptr type = dec->type(); if (type && type->dataType() == IntegralType::TypeNull) { DUChainWriteLocker wlock; dec->setAbstractType(currentAbstractType()); } } } lock.lock(); if (decs.isEmpty() && (!currentContext()->parentContext() || !currentContext()->parentContext()->imports(ctx))) { ifDebug(qCDebug(DUCHAIN) << "nothing found in current context, look in class context of parent";) QList decs; foreach ( Declaration* dec, ctx->findDeclarations(m_findVariable.identifier) ) { if ( dec->kind() == Declaration::Kind::Instance) { if (dec->range() == editorFindRange(m_findVariable.node)) { // Don't reuse previous declarations as the type might have changed. continue; } ifDebug(qCDebug(DUCHAIN) << "found class property:" << dec->toString();) decs << dec; } } lock.unlock(); if (!decs.isEmpty()) { Declaration * dec = decs.last(); if (dec) { alreadyDeclared = true; IntegralType::Ptr type = dec->type(); // check for redeclaration of private or protected stuff DUContext *parentCtx = currentContext()->parentContext(); ClassMemberDeclaration *classDec = dynamic_cast(dec); if (classDec && classDec->accessPolicy() == Declaration::Private && classDec->context() != parentCtx) { reportError(i18n("Cannot access private property %1", classDec->toString()), m_findVariable.node); } else if (classDec && classDec->accessPolicy() == Declaration::Protected && classDec->context() != parentCtx) { reportError(i18n("Cannot access protected property %1", classDec->toString()), m_findVariable.node); } else if (type && type->dataType() == IntegralType::TypeNull) { DUChainWriteLocker wlock; dec->setAbstractType(currentAbstractType()); } } } } } } if ( !alreadyDeclared && !m_findVariable.identifier.isEmpty() && currentAbstractType()) { ifDebug(qCDebug(DUCHAIN) << "not yet declared: " << m_findVariable.identifier.toString();) //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 + // assignment 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); if (!dec) { dec = findDeclarationImport(FunctionDeclarationType, node->stringFunctionName, GlobalScope); } } else if ( node->stringFunctionNameOrClass ) { id = identifierForNamespace(node->stringFunctionNameOrClass, m_editor); dec = findDeclarationImport(FunctionDeclarationType, id); if (!dec) { id.setExplicitlyGlobal(true); dec = findDeclarationImport(FunctionDeclarationType, id); } } else { ///TODO: node->varFunctionName } if ( dec ) { m_currentFunctionType = dec->type(); } else { m_currentFunctionType = nullptr; } DeclarationBuilderBase::visitFunctionCall(node); m_currentFunctionType = oldFunction; } else { // optimize for internal function file DeclarationBuilderBase::visitFunctionCall(node); } if (node->stringFunctionNameOrClass && !node->stringFunctionName && !node->varFunctionName) { if (id.toString(RemoveExplicitlyGlobalPrefix) == QLatin1String("define") && node->stringParameterList && node->stringParameterList->parametersSequence && node->stringParameterList->parametersSequence->count() > 0) { //constant, defined through define-function //find name of the constant (first argument of the function call) CommonScalarAst* scalar = findCommonScalar(node->stringParameterList->parametersSequence->at(0)->element); if (scalar && scalar->string != -1) { QString constant = m_editor->parseSession()->symbol(scalar->string); constant = constant.mid(1, constant.length() - 2); RangeInRevision newRange = editorFindRange(scalar, scalar); AbstractType::Ptr type; if (node->stringParameterList->parametersSequence->count() > 1) { type = getTypeForNode(node->stringParameterList->parametersSequence->at(1)->element); Q_ASSERT(type); type->setModifiers(type->modifiers() | AbstractType::ConstModifier); } // TODO: else report error? DUChainWriteLocker lock; // find fitting context to put define in, // pick first namespace or global context otherwise DUContext* ctx = currentContext(); while (ctx->type() != DUContext::Namespace && ctx->parentContext()) { ctx = ctx->parentContext(); } injectContext(ctx); //constants are always global QualifiedIdentifier identifier(constant); isGlobalRedeclaration(identifier, scalar, ConstantDeclarationType); Declaration* dec = openDefinition(identifier, newRange); dec->setKind(Declaration::Instance); if (type) { dec->setType(type); injectType(type); } closeDeclaration(); closeInjectedContext(); } } } } void DeclarationBuilder::visitFunctionCallParameterList(FunctionCallParameterListAst* node) { PushValue push(m_functionCallPreviousArgument, nullptr); PushValue pos(m_functionCallParameterPos, 0); DeclarationBuilderBase::visitFunctionCallParameterList(node); } 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 + // https://de.php.net/manual/en/language.references.whatdo.php // declare with NULL type, just like PHP does declareFoundVariable(AbstractType::Ptr(new IntegralType(IntegralType::TypeNull))); } } if (m_functionCallPreviousArgument && m_functionCallPreviousArgument->isVariadic != -1 && node->isVariadic == -1) { reportError(i18n("Cannot use positional argument after argument unpacking"), node); } m_functionCallPreviousArgument = node; ++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 = nullptr; 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, nullptr); 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); DeclarationPointer dec = findDeclarationImport(ClassDeclarationType, qid); if (!dec && !qid.explicitlyGlobal()) { QualifiedIdentifier globalQid = qid; globalQid.setExplicitlyGlobal(true); dec = findDeclarationImport(ClassDeclarationType, globalQid); } 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 { // NamespaceAliasDeclarations can't use a global import identifier qid.setExplicitlyGlobal(false); NamespaceAliasDeclaration* decl = openDefinition(id.second, m_editor->findRange(idNode)); decl->setImportIdentifier( qid ); decl->setPrettyName( id.first ); decl->setKind(Declaration::NamespaceAlias); } closeDeclaration(); if (node->aliasIdentifier) { QString aliasName = m_editor->parseSession()->symbol(node->aliasIdentifier); if (isReservedClassName(aliasName)) { reportError(i18n("Cannot use %1 as %2 because '%2' is a special class name", qid.toString(), aliasName), node->aliasIdentifier); } } } void DeclarationBuilder::visitVarExpression(VarExpressionAst* node) { DeclarationBuilderBase::visitVarExpression(node); if (node->isGenerator != -1 && currentContext()->type() != DUContext::Other) { reportError(i18n("The 'yield' expression can only be used inside a function"), node); } } 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); } } bool DeclarationBuilder::isReservedClassName(QString className) { return className.compare(QLatin1String("string"), Qt::CaseInsensitive) == 0 || className.compare(QLatin1String("bool"), Qt::CaseInsensitive) == 0 || className.compare(QLatin1String("int"), Qt::CaseInsensitive) == 0 || className.compare(QLatin1String("float"), Qt::CaseInsensitive) == 0 || className.compare(QLatin1String("iterable"), Qt::CaseInsensitive) == 0 || className.compare(QLatin1String("object"), Qt::CaseInsensitive) == 0 || className.compare(QLatin1String("null"), Qt::CaseInsensitive) == 0 || className.compare(QLatin1String("true"), Qt::CaseInsensitive) == 0 || className.compare(QLatin1String("false"), Qt::CaseInsensitive) == 0; } } diff --git a/duchain/builders/declarationbuilder.h b/duchain/builders/declarationbuilder.h index 7b24bdf..2d1a53c 100644 --- a/duchain/builders/declarationbuilder.h +++ b/duchain/builders/declarationbuilder.h @@ -1,249 +1,249 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2008 Niko Sams * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef DECLARATIONBUILDER_H #define DECLARATIONBUILDER_H #include "typebuilder.h" #include "helper.h" #include #include namespace KDvelop { class Declaration; } namespace Php { class ParseSession; class EditorIntegrator; class ClassDeclaration; class FunctionDeclaration; class NamespaceDeclaration; typedef KDevelop::AbstractDeclarationBuilder DeclarationBuilderBase; /** * The DeclarationBuilder builds declarations, types and contexts for everything in a AST. * * \note Since PHP allows the usage of functions, classes and interfaces before definition, * a \see PreDeclarationBuilder is used to get the declarations _and_ types for those. * Thus type- and declaratoinbuilding for these is skipped in this class. */ class KDEVPHPDUCHAIN_EXPORT DeclarationBuilder : public DeclarationBuilderBase { public: DeclarationBuilder(EditorIntegrator* editor) : m_currentModifers(0) { m_editor = editor; m_findVariable.find = false; } KDevelop::ReferencedTopDUContext build(const KDevelop::IndexedString& url, AstNode* node, const KDevelop::ReferencedTopDUContext& updateContext = KDevelop::ReferencedTopDUContext()) override; void startVisiting(AstNode* node) override; protected: void visitClassDeclarationStatement(ClassDeclarationStatementAst *node) override; void visitInterfaceDeclarationStatement(InterfaceDeclarationStatementAst *node) override; void visitTraitDeclarationStatement(TraitDeclarationStatementAst *node) override; void visitClassStatement(ClassStatementAst *node) override; virtual void importTraitMethods(ClassStatementAst *node); void visitClassExtends(ClassExtendsAst *node) override; void visitClassImplements(ClassImplementsAst *node) override; void visitParameterList(ParameterListAst *node) override; void visitParameter(ParameterAst *node) override; void visitFunctionDeclarationStatement(FunctionDeclarationStatementAst *node) override; void visitReturnType(ReturnTypeAst *node) override; void visitClassVariable(ClassVariableAst *node) override; void visitConstantDeclaration(ConstantDeclarationAst *node) override; void visitClassConstantDeclaration(ClassConstantDeclarationAst *node) override; void visitTraitAliasStatement(TraitAliasStatementAst *node) override; virtual void createTraitAliasDeclarations(TraitAliasStatementAst *node, KDevelop::DeclarationPointer dec); void visitOuterTopStatement(OuterTopStatementAst* node) override; void visitAssignmentExpression(AssignmentExpressionAst* node) override; void visitAssignmentExpressionEqual(AssignmentExpressionEqualAst *node) override; void visitVariable(VariableAst* node) override; void visitFunctionCall(FunctionCallAst* node) override; void visitFunctionCallParameterList(FunctionCallParameterListAst* node) override; void visitFunctionCallParameterListElement(FunctionCallParameterListElementAst* node) override; void visitStatement(StatementAst* node) override; void visitStaticVar(StaticVarAst* node) override; void visitGlobalVar(GlobalVarAst* node) override; void visitCatchItem(CatchItemAst *node) override; void visitUnaryExpression( UnaryExpressionAst* node ) override; void visitAssignmentListElement(AssignmentListElementAst* node) override; void openNamespace(NamespaceDeclarationStatementAst* parent, IdentifierAst* node, const IdentifierPair& identifier, const KDevelop::RangeInRevision& range) override; void closeNamespace(NamespaceDeclarationStatementAst* parent, IdentifierAst* node, const IdentifierPair& identifier) override; void visitUseNamespace(UseNamespaceAst* node) override; void visitClosure(ClosureAst* node) override; void visitLexicalVar(LexicalVarAst* node) override; void visitVarExpression(VarExpressionAst* node) override; /// checks whether the body is empty (i.e. equals ";" instead of "{...}") bool isEmptyMethodBody(const MethodBodyAst* body) const { return !body || !body->statements; } void closeDeclaration() override; void classContextOpened(KDevelop::DUContext* context) override; void supportBuild(AstNode* node, KDevelop::DUContext* context = nullptr) override; void closeContext() override; /// don't forget to closeDeclaration() afterwards /// set m_currentModifers to your likings and reset it afterwards void openClassMemberDeclaration(AstNode* node, const KDevelop::QualifiedIdentifier& name); void updateCurrentType() override; private: /// because the predeclarationbuilder runs before us, /// we always "think" that we are recompiling, while this is not necessarily true bool m_actuallyRecompiling; struct FindVariableResults { /// Set this to true if you want to catch any variable in the lower AST tree bool find; /// If the found variable is accessed as an array ($var[...]) this is set to true. /// @see m_findVariable bool isArray; /// The identifier for the found variable. /// @see m_findVariable KDevelop::QualifiedIdentifier identifier; /// The identifier for the parent of the found variable. Empty if /// the found variable is not a class member. /// @see m_findVariable KDevelop::QualifiedIdentifier parentIdentifier; /// The AstNode of the found variable. Use this for declarations. /// @see m_findVariable AstNode* node; FindVariableResults(); }; FindVariableResults m_findVariable; /// The position of the current argument, will only be set inside function calls. int m_functionCallParameterPos; /// Type of the current function, will only be set inside function calls. KDevelop::FunctionType::Ptr m_currentFunctionType; /// The AstNode of the previous function declaration argument ParameterAst *m_functionDeclarationPreviousArgument = nullptr; /// The AstNode of the previous function call argument FunctionCallParameterListElementAst *m_functionCallPreviousArgument = nullptr; unsigned int m_currentModifers; QString m_lastTopStatementComment; QHash m_types; QHash m_functions; QHash m_namespaces; QVector m_upcomingClassVariables; /// handles common stuff for both interfaces and classes ClassDeclaration* openTypeDeclaration(IdentifierAst *name, KDevelop::ClassDeclarationData::ClassType type); /// check if this declaration is already declared bool isGlobalRedeclaration(const KDevelop::QualifiedIdentifier &identifier, AstNode *node, DeclarationType type); /// check if a non-abstract method declaration tries to overwrite a final base method /// or whether a abstract method is redeclared /// @param ids The identifier for the current method /// @param curClass the current class we are in /// @param node the node we are processing, used to access modifiers and for error reporting bool isBaseMethodRedeclaration(const IdentifierPair &ids, ClassDeclaration *curClass, ClassStatementAst *node); /// reports a redeclaration error for the given node /// @param declaration the old declaration /// @param node the AstNode which resembles the redeclaration void reportRedeclarationError(KDevelop::Declaration* declaration, AstNode *node); /** * Get the interesting identifiers out of a VariableAst node: * $var yields @p id = 'var', @p parent = '' * $var->asdf yields @p id = 'asdf', @p parent = 'asdf' * $var->...->foo->bar yields @p id = 'bar', @p parent => 'foo' * * @note If the parent or the identifier itself end on an array access, e.g. $var[0] or * $var->...->parent[0]->bar, @p arrayAccess will be set to true. * * @param id the last identifier * @param parent the parent of the last identifier * @param targetNode the node of the last identifier * @param arrayAccess the node actually ends on an array access, like $node->var->..->asdf[0] */ void getVariableIdentifier(VariableAst *node, KDevelop::QualifiedIdentifier &id, KDevelop::QualifiedIdentifier &parent, AstNode* &targetNode, bool &arrayAccess); /** - * Declare a class member in @p parentCtx. Validates whether the current context allowes + * Declare a class member in @p parentCtx. Validates whether the current context allows * redeclaration of private/protected members. * * @param parentCtx The class context you want to add the member to. * @param type The type of the member. * @param identifier The identifier of the member. * @param node The node of the member. */ void declareClassMember(KDevelop::DUContext *parentCtx, KDevelop::AbstractType::Ptr type, const KDevelop::QualifiedIdentifier& identifier, AstNode* node ); /** * Declare a variable in @p parentCtx. If the variable is already defined in the * context and it's last type equals @p type, don't do anything. * * @param parentCtx The context you want to declare the variable in. * @param type The type of the variable * @param identifier The identifier for the variable. * @param node The node for the variable. */ void declareVariable(KDevelop::DUContext *parentCtx, KDevelop::AbstractType::Ptr type, const KDevelop::QualifiedIdentifier& identifier, AstNode* node ); /** * Wrapper that operates declares the found variable. It will declare it * either as a class member or as a variable, depending whether a parent was found. * * It will also check whether that var also exists and if so, won't do anything. * * @param type When the var gets declared, this will be it's type. * * @see m_findVariable * @see declareClassMeember * @see declareVariable */ void declareFoundVariable(KDevelop::AbstractType::Ptr type); /** * Sets encountered and updates the comment when we are recompiling. */ void encounter(KDevelop::Declaration* dec); bool isReservedClassName(QString className); }; } #endif // DECLARATIONBUILDER_H diff --git a/duchain/builders/typebuilder.cpp b/duchain/builders/typebuilder.cpp index b4dd2d1..d51bfe3 100644 --- a/duchain/builders/typebuilder.cpp +++ b/duchain/builders/typebuilder.cpp @@ -1,622 +1,622 @@ /*************************************************************************** * 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/indexedcontainer.h" #include "../types/integraltypeextended.h" #include "../types/structuretype.h" #include #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)) { return AbstractType::Ptr(new IntegralTypeExtended(IntegralTypeExtended::TypeObject)); } QualifiedIdentifier typehint = QualifiedIdentifier(type.toLower().replace(QLatin1Literal("\\"), QLatin1Literal("::"))); if (typehint.toString().startsWith(QLatin1Literal("::"))) { typehint.setExplicitlyGlobal(true); } //don't use openTypeFromName as it uses cursor for findDeclarations DeclarationPointer decl = findDeclarationImport(ClassDeclarationType, typehint); 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() == 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, 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, 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; } 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); FunctionType::Ptr functionType = FunctionType::Ptr(new FunctionType()); openType(functionType); openContextType(functionType); AbstractType::Ptr phpdocReturnType = parseDocComment(node, QStringLiteral("return")); functionType->setReturnType(returnType(node->returnType, phpdocReturnType, editor(), currentContext())); m_gotReturnTypeFromDocComment = functionType->returnType(); updateCurrentType(); TypeBuilderBase::visitClassStatement(node); if (currentType() && !currentType()->returnType()) { currentType()->setReturnType(AbstractType::Ptr(new IntegralType(IntegralType::TypeVoid))); } closeContextType(); closeType(); } else if (node->constsSequence) { //class constant TypeBuilderBase::visitClassStatement(node); } else { //member-variable parseDocComment(node, QStringLiteral("var")); TypeBuilderBase::visitClassStatement(node); if (m_gotTypeFromDocComment) { clearLastType(); m_gotTypeFromDocComment = false; } } } void TypeBuilder::visitClassVariable(ClassVariableAst *node) { if (!m_gotTypeFromDocComment) { if (node->value) { openAbstractType(getTypeForNode(node->value)); } else { openAbstractType(AbstractType::Ptr(new IntegralType(IntegralType::TypeNull))); } 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::visitClassConstantDeclaration(ClassConstantDeclarationAst* node) { if (!m_gotTypeFromDocComment || !currentAbstractType()) { AbstractType::Ptr type = getTypeForNode(node->scalar); type->setModifiers(type->modifiers() | AbstractType::ConstModifier); openAbstractType(type); TypeBuilderBase::visitClassConstantDeclaration(node); closeType(); } else { currentAbstractType()->setModifiers(currentAbstractType()->modifiers() & AbstractType::ConstModifier); TypeBuilderBase::visitClassConstantDeclaration(node); } } void TypeBuilder::visitParameter(ParameterAst *node) { AbstractType::Ptr phpDocTypehint; if (m_currentFunctionParams.count() > currentType()->arguments().count()) { phpDocTypehint = m_currentFunctionParams.at(currentType()->arguments().count()); } AbstractType::Ptr type = parameterType(node, phpDocTypehint, editor(), currentContext()); 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); openContextType(type); AbstractType::Ptr phpdocReturnType = parseDocComment(node, QStringLiteral("return")); type->setReturnType(returnType(node->returnType, phpdocReturnType, editor(), currentContext())); m_gotReturnTypeFromDocComment = type->returnType(); updateCurrentType(); TypeBuilderBase::visitFunctionDeclarationStatement(node); if (!type->returnType()) { type->setReturnType(AbstractType::Ptr(new IntegralType(IntegralType::TypeVoid))); } closeContextType(); } void TypeBuilder::visitClosure(ClosureAst* node) { m_currentFunctionParams = parseDocCommentParams(node); FunctionType::Ptr type = FunctionType::Ptr(new FunctionType()); openType(type); openContextType(type); AbstractType::Ptr phpdocReturnType = parseDocComment(node, QStringLiteral("return")); type->setReturnType(returnType(node->returnType, phpdocReturnType, editor(), currentContext())); m_gotReturnTypeFromDocComment = type->returnType(); updateCurrentType(); TypeBuilderBase::visitClosure(node); if (!type->returnType()) { type->setReturnType(AbstractType::Ptr(new IntegralType(IntegralType::TypeVoid))); } closeContextType(); 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())) { bool existingTypeIsCallable = ft->returnType().cast() && ft->returnType().cast()->dataType() == IntegralTypeExtended::TypeCallable; bool newTypeIsCallable = type.cast() && type.cast()->dataType() == IntegralTypeExtended::TypeCallable; if (ft->returnType().cast() && ft->returnType().cast()->dataType() == IntegralType::TypeMixed) { //don't add TypeMixed to the list, just ignore ft->setReturnType(type); } else if ((existingTypeIsCallable && type.cast()) || (newTypeIsCallable && ft->returnType().cast())) { //If one type is "callable" and the other a real function, the result is just a "callable". ft->setReturnType(AbstractType::Ptr(new IntegralTypeExtended(IntegralTypeExtended::TypeCallable))); } 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 = nullptr; 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... + // 0 == global lookup and the declaration is found again... classDec = dynamic_cast(type->declaration(nullptr)); } if (classDec) { /// Qualified identifier for 'iterator' static QualifiedIdentifier iteratorQId(QStringLiteral("iterator")); iteratorQId.setExplicitlyGlobal(true); 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(QStringLiteral("current")); auto classContext = classDec->internalContext(); if (classContext) { foreach (Declaration *d, classContext->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::visitVarExpression(Php::VarExpressionAst *node) { if (hasCurrentContextType() && node->isGenerator != -1 && !m_gotReturnTypeFromDocComment) { FunctionType::Ptr ft = FunctionType::Ptr::dynamicCast(currentContextType()); static QualifiedIdentifier generatorQId(QStringLiteral("generator")); generatorQId.setExplicitlyGlobal(true); DeclarationPointer generatorDecl = findDeclarationImport(ClassDeclarationType, generatorQId); if (ft && generatorDecl) { AbstractType::Ptr generatorType = generatorDecl->abstractType(); if (generatorType) { ft->setReturnType(generatorType); } } updateCurrentType(); } TypeBuilderBase::visitVarExpression(node); } void TypeBuilder::updateCurrentType() { // do nothing } } diff --git a/duchain/tests/duchain.cpp b/duchain/tests/duchain.cpp index 8621721..a8deed1 100644 --- a/duchain/tests/duchain.cpp +++ b/duchain/tests/duchain.cpp @@ -1,4187 +1,4187 @@ /* This file is part of KDevelop Copyright 2008 Niko Sams This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "duchain.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "helper.h" #include "../declarations/classdeclaration.h" #include "../declarations/classmethoddeclaration.h" #include "../declarations/functiondeclaration.h" #include "../declarations/variabledeclaration.h" #include "../types/structuretype.h" #include "../types/integraltypeextended.h" #include "../types/indexedcontainer.h" #include using namespace KDevelop; using namespace Php; QTEST_MAIN(Php::TestDUChain) TestDUChain::TestDUChain() { } void TestDUChain::declareFunction() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("childContexts().count(), 2); QCOMPARE(top->localDeclarations().count(), 1); Declaration* dec = top->localDeclarations().at(0); QVERIFY(dec); QCOMPARE(dec->context(), top); QCOMPARE(dec->internalContext(), top->childContexts().at(1)); // no return means void as return type FunctionType::Ptr ftype = FunctionType::Ptr::dynamicCast(dec->abstractType()); QVERIFY(ftype); IntegralType::Ptr itype = IntegralType::Ptr::dynamicCast(ftype->returnType()); QVERIFY(itype->dataType() == IntegralType::TypeVoid); QCOMPARE(top->childContexts().at(0)->type(), DUContext::Function); QCOMPARE(top->childContexts().at(1)->type(), DUContext::Other); } void TestDUChain::declareBaseTypeFunction() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("childContexts().count(), 2); QCOMPARE(top->localDeclarations().count(), 1); Declaration* dec = top->localDeclarations().at(0); QVERIFY(dec); QCOMPARE(dec->context(), top); QCOMPARE(dec->internalContext(), top->childContexts().at(1)); // no return means void as return type FunctionType::Ptr ftype = FunctionType::Ptr::dynamicCast(dec->abstractType()); QVERIFY(ftype); IntegralType::Ptr itype = IntegralType::Ptr::dynamicCast(ftype->returnType()); QVERIFY(itype->dataType() == IntegralType::TypeVoid); QCOMPARE(top->childContexts().at(0)->type(), DUContext::Function); QCOMPARE(top->childContexts().at(1)->type(), DUContext::Other); } void TestDUChain::declareSemiReservedFunction() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("parentContext()); QCOMPARE(top->childContexts().count(), 2); QCOMPARE(top->localDeclarations().count(), 5); //class A Declaration* dec = top->localDeclarations().at(0); QCOMPARE(dec->uses().count(), 1); QCOMPARE(dec->uses().begin()->count(), 1); //$i Declaration* decVar = top->localDeclarations().at(2); QCOMPARE(decVar->identifier(), Identifier("i")); qDebug() << decVar->abstractType()->toString(); UnsureType::Ptr unsureType = decVar->type(); QVERIFY(unsureType); QCOMPARE(unsureType->typesSize(), 3u); // = new A(); QCOMPARE(unsureType->types()[0].abstractType().cast()->qualifiedIdentifier(), QualifiedIdentifier("a")); QVERIFY(unsureType->types()[0].abstractType()->equals(dec->abstractType().data())); // = new B(); //class B dec = top->localDeclarations().at(1); QCOMPARE(dec->uses().count(), 1); QCOMPARE(dec->uses().begin()->count(), 2); QCOMPARE(unsureType->types()[1].abstractType().cast()->qualifiedIdentifier(), QualifiedIdentifier("b")); QVERIFY(unsureType->types()[1].abstractType()->equals(dec->abstractType().data())); // = 'foo'; QVERIFY(unsureType->types()[2].abstractType().cast()); QVERIFY(unsureType->types()[2].abstractType().cast()->dataType() == IntegralType::TypeString); //$j decVar = top->localDeclarations().at(3); QCOMPARE(decVar->identifier(), Identifier("j")); StructureType::Ptr classType = decVar->type(); QVERIFY(classType); QCOMPARE(classType->qualifiedIdentifier(), QualifiedIdentifier("b")); QVERIFY(classType->equals(dec->abstractType().data())); // $a decVar = top->localDeclarations().at(4); QCOMPARE(decVar->identifier(), Identifier("a")); QVERIFY(decVar->type()); } void TestDUChain::varTypehint() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().at(0); //$i Declaration* decVar = top->localDeclarations().at(1); QCOMPARE(decVar->identifier(), Identifier("i")); StructureType::Ptr classType = decVar->type(); QVERIFY(classType); QCOMPARE(classType->qualifiedIdentifier(), QualifiedIdentifier("a")); QVERIFY(classType->equals(dec->abstractType().data())); } void TestDUChain::declareClass() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("parentContext()); QCOMPARE(top->childContexts().count(), 1); DUContext* contextClassA = top->childContexts().first(); QCOMPARE(top->localDeclarations().count(), 1); Declaration* dec = top->localDeclarations().first(); QCOMPARE(dec->kind(), Declaration::Type); QCOMPARE(dec->toString(), QString("class A")); QCOMPARE(dec->qualifiedIdentifier(), QualifiedIdentifier("a")); QCOMPARE(dec->isDefinition(), true); QCOMPARE(dec->logicalInternalContext(top), contextClassA); qDebug() << contextClassA->localScopeIdentifier().toString(); QCOMPARE(contextClassA->localScopeIdentifier(), QualifiedIdentifier("a")); QCOMPARE(contextClassA->childContexts().count(), 8); QCOMPARE(contextClassA->childContexts().first()->localScopeIdentifier(), QualifiedIdentifier("foo")); DUContext* contextMethodBodyFoo = contextClassA->childContexts().at(1); QCOMPARE(contextMethodBodyFoo->localScopeIdentifier(), QualifiedIdentifier("foo")); QCOMPARE(contextMethodBodyFoo->importedParentContexts().count(), 1); QCOMPARE(contextMethodBodyFoo->childContexts().count(), 0); QVERIFY(contextMethodBodyFoo->importedParentContexts().first().context(top) == contextClassA->childContexts().first()); //foo() dec = contextClassA->localDeclarations().at(0); ClassFunctionDeclaration* funDec = dynamic_cast(dec); QVERIFY(funDec); QCOMPARE(funDec->kind(), Declaration::Type); QCOMPARE(funDec->identifier(), Identifier("foo")); QCOMPARE(funDec->accessPolicy(), Declaration::Public); QCOMPARE(funDec->isStatic(), false); { // no return means void as return type FunctionType::Ptr ftype = FunctionType::Ptr::dynamicCast(dec->abstractType()); QVERIFY(ftype); IntegralType::Ptr itype = IntegralType::Ptr::dynamicCast(ftype->returnType()); QVERIFY(itype->dataType() == IntegralType::TypeVoid); } //bar() dec = contextClassA->localDeclarations().at(1); funDec = dynamic_cast(dec); QVERIFY(funDec); QCOMPARE(funDec->identifier(), Identifier("bar")); QCOMPARE(funDec->accessPolicy(), Declaration::Protected); QCOMPARE(funDec->isStatic(), true); //baz() dec = contextClassA->localDeclarations().at(2); funDec = dynamic_cast(dec); QVERIFY(funDec); QCOMPARE(funDec->identifier(), Identifier("baz")); QCOMPARE(funDec->accessPolicy(), Declaration::Private); QCOMPARE(funDec->isStatic(), false); //boo() dec = contextClassA->localDeclarations().at(3); funDec = dynamic_cast(dec); QVERIFY(funDec); QCOMPARE(funDec->identifier(), Identifier("boo")); QCOMPARE(funDec->accessPolicy(), Declaration::Public); } void TestDUChain::declareBaseTypeClass() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("problems().count(), 1); } void TestDUChain::declareClassWithSemiReservedMethod() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("parentContext()); QCOMPARE(top->childContexts().count(), 1); DUContext* contextClassA = top->childContexts().first(); QCOMPARE(top->localDeclarations().count(), 1); Declaration* dec = top->localDeclarations().first(); QCOMPARE(dec->kind(), Declaration::Type); QCOMPARE(dec->toString(), QString("class A")); QCOMPARE(dec->qualifiedIdentifier(), QualifiedIdentifier("a")); QCOMPARE(dec->isDefinition(), true); QCOMPARE(dec->logicalInternalContext(top), contextClassA); qDebug() << contextClassA->localScopeIdentifier().toString(); QCOMPARE(contextClassA->localScopeIdentifier(), QualifiedIdentifier("a")); QCOMPARE(contextClassA->childContexts().count(), 4); QCOMPARE(contextClassA->childContexts().first()->localScopeIdentifier(), QualifiedIdentifier("switch")); DUContext* contextMethodBodyFoo = contextClassA->childContexts().at(1); QCOMPARE(contextMethodBodyFoo->localScopeIdentifier(), QualifiedIdentifier("switch")); QCOMPARE(contextMethodBodyFoo->importedParentContexts().count(), 1); QCOMPARE(contextMethodBodyFoo->childContexts().count(), 0); QVERIFY(contextMethodBodyFoo->importedParentContexts().first().context(top) == contextClassA->childContexts().first()); //switch() dec = contextClassA->localDeclarations().at(0); ClassFunctionDeclaration* funDec = dynamic_cast(dec); QVERIFY(funDec); QCOMPARE(funDec->kind(), Declaration::Type); QCOMPARE(funDec->identifier(), Identifier("switch")); QCOMPARE(funDec->accessPolicy(), Declaration::Public); QCOMPARE(funDec->isStatic(), false); { // no return means void as return type FunctionType::Ptr ftype = FunctionType::Ptr::dynamicCast(dec->abstractType()); QVERIFY(ftype); IntegralType::Ptr itype = IntegralType::Ptr::dynamicCast(ftype->returnType()); QVERIFY(itype->dataType() == IntegralType::TypeVoid); } //public() dec = contextClassA->localDeclarations().at(1); funDec = dynamic_cast(dec); QVERIFY(funDec); QCOMPARE(funDec->identifier(), Identifier("public")); QCOMPARE(funDec->accessPolicy(), Declaration::Protected); QCOMPARE(funDec->isStatic(), true); } void TestDUChain::declareClassWithBaseTypeMethod() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("parentContext()); QCOMPARE(top->childContexts().count(), 1); DUContext* contextClassA = top->childContexts().first(); QCOMPARE(top->localDeclarations().count(), 1); Declaration* dec = top->localDeclarations().first(); QCOMPARE(dec->kind(), Declaration::Type); QCOMPARE(dec->toString(), QString("class A")); QCOMPARE(dec->qualifiedIdentifier(), QualifiedIdentifier("a")); QCOMPARE(dec->isDefinition(), true); QCOMPARE(dec->logicalInternalContext(top), contextClassA); qDebug() << contextClassA->localScopeIdentifier().toString(); QCOMPARE(contextClassA->localScopeIdentifier(), QualifiedIdentifier("a")); QCOMPARE(contextClassA->childContexts().count(), 4); QCOMPARE(contextClassA->childContexts().first()->localScopeIdentifier(), QualifiedIdentifier("string")); DUContext* contextMethodBodyFoo = contextClassA->childContexts().at(1); QCOMPARE(contextMethodBodyFoo->localScopeIdentifier(), QualifiedIdentifier("string")); QCOMPARE(contextMethodBodyFoo->importedParentContexts().count(), 1); QCOMPARE(contextMethodBodyFoo->childContexts().count(), 0); QVERIFY(contextMethodBodyFoo->importedParentContexts().first().context(top) == contextClassA->childContexts().first()); //string() dec = contextClassA->localDeclarations().at(0); ClassFunctionDeclaration* funDec = dynamic_cast(dec); QVERIFY(funDec); QCOMPARE(funDec->kind(), Declaration::Type); QCOMPARE(funDec->identifier(), Identifier("string")); QCOMPARE(funDec->accessPolicy(), Declaration::Public); QCOMPARE(funDec->isStatic(), false); { // no return means void as return type FunctionType::Ptr ftype = FunctionType::Ptr::dynamicCast(dec->abstractType()); QVERIFY(ftype); IntegralType::Ptr itype = IntegralType::Ptr::dynamicCast(ftype->returnType()); QVERIFY(itype->dataType() == IntegralType::TypeVoid); } //iterable() dec = contextClassA->localDeclarations().at(1); funDec = dynamic_cast(dec); QVERIFY(funDec); QCOMPARE(funDec->identifier(), Identifier("iterable")); QCOMPARE(funDec->accessPolicy(), Declaration::Protected); QCOMPARE(funDec->isStatic(), true); } void TestDUChain::classMemberVar() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("parentContext()); QCOMPARE(top->childContexts().count(), 1); DUContext* contextClassA = top->childContexts().first(); QCOMPARE(top->localDeclarations().count(), 1); Declaration* dec = top->localDeclarations().first(); QCOMPARE(dec->qualifiedIdentifier(), QualifiedIdentifier("a")); QCOMPARE(dec->isDefinition(), true); QCOMPARE(dec->logicalInternalContext(top), contextClassA); QCOMPARE(contextClassA->localScopeIdentifier(), QualifiedIdentifier("a")); QCOMPARE(contextClassA->childContexts().count(), 0); QCOMPARE(contextClassA->localDeclarations().count(), 4); //$foo ClassMemberDeclaration* var = dynamic_cast(contextClassA->localDeclarations().first()); QVERIFY(var); QCOMPARE(var->identifier(), Identifier("foo")); QCOMPARE(var->accessPolicy(), Declaration::Public); QCOMPARE(var->isStatic(), false); QVERIFY(var->type()); QVERIFY(var->type()->dataType() == IntegralType::TypeNull); //$bar var = dynamic_cast(contextClassA->localDeclarations().at(1)); QVERIFY(var); QCOMPARE(var->identifier(), Identifier("bar")); QCOMPARE(var->accessPolicy(), Declaration::Protected); QCOMPARE(var->isStatic(), false); StructureType::Ptr type = var->type(); QVERIFY(type); QCOMPARE(type->qualifiedIdentifier(), QualifiedIdentifier("a")); //$baz var = dynamic_cast(contextClassA->localDeclarations().at(2)); QVERIFY(var); QCOMPARE(var->identifier(), Identifier("baz")); QCOMPARE(var->accessPolicy(), Declaration::Private); QCOMPARE(var->isStatic(), true); QVERIFY(var->type()); QVERIFY(var->type()->dataType() == IntegralType::TypeString); //$boo var = dynamic_cast(contextClassA->localDeclarations().at(3)); QVERIFY(var); QCOMPARE(var->identifier(), Identifier("boo")); QCOMPARE(var->accessPolicy(), Declaration::Public); QCOMPARE(var->isStatic(), false); QVERIFY(var->type()); QVERIFY(var->type()->dataType() == IntegralType::TypeInt); } void TestDUChain::classMemberVarAfterUse() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("a = 1; } public $a = 1; }"); TopDUContext* top = parse(method, DumpNone); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); QVERIFY(!top->parentContext()); QCOMPARE(top->childContexts().count(), 1); QVERIFY(top->problems().isEmpty()); DUContext* contextClassB = top->childContexts().first(); QCOMPARE(top->localDeclarations().count(), 1); Declaration* dec = top->localDeclarations().first(); QCOMPARE(dec->qualifiedIdentifier(), QualifiedIdentifier("b")); QCOMPARE(dec->isDefinition(), true); QCOMPARE(dec->logicalInternalContext(top), contextClassB); QCOMPARE(contextClassB->localScopeIdentifier(), QualifiedIdentifier("b")); QCOMPARE(contextClassB->childContexts().count(), 2); QCOMPARE(contextClassB->localDeclarations().count(), 2); //$foo ClassMemberDeclaration* var = dynamic_cast(contextClassB->localDeclarations().at(1)); QVERIFY(var); QCOMPARE(var->identifier(), Identifier("a")); QCOMPARE(var->accessPolicy(), Declaration::Public); QCOMPARE(var->isStatic(), false); QVERIFY(var->type()); QVERIFY(var->type()->dataType() == IntegralType::TypeInt); QVERIFY(var->range() == RangeInRevision(0, 54, 0, 56)); } void TestDUChain::classMemberVarDocBlockType() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("parentContext()); QCOMPARE(top->childContexts().count(), 2); DUContext* contextClassA = top->childContexts().at(0)->childContexts().first(); DUContext* contextClassB = top->childContexts().at(1)->childContexts().first(); QCOMPARE(top->localDeclarations().count(), 2); Declaration* dec = top->childContexts().first()->localDeclarations().first(); QCOMPARE(dec->qualifiedIdentifier(), QualifiedIdentifier("test::a")); QCOMPARE(dec->isDefinition(), true); QCOMPARE(contextClassA->localScopeIdentifier(), QualifiedIdentifier("a")); QCOMPARE(contextClassA->childContexts().count(), 0); QCOMPARE(contextClassA->localDeclarations().count(), 0); QCOMPARE(contextClassB->localScopeIdentifier(), QualifiedIdentifier("b")); QCOMPARE(contextClassB->childContexts().count(), 0); QCOMPARE(contextClassB->localDeclarations().count(), 1); //$foo ClassMemberDeclaration* var = dynamic_cast(contextClassB->localDeclarations().first()); QVERIFY(var); QCOMPARE(var->identifier(), Identifier("foo")); QCOMPARE(var->accessPolicy(), Declaration::Public); QCOMPARE(var->isStatic(), false); StructureType::Ptr type = var->type(); QVERIFY(type); QCOMPARE(type->qualifiedIdentifier(), QualifiedIdentifier("test::a")); } void TestDUChain::returnTypeGenerator_data() { QTest::addColumn("code"); //Note: in practice, Generator is defined by php, but this class is not loaded in this test, so define it ourselves QTest::newRow("simple yield expression") << QStringLiteral(" 1; }\n"); QTest::newRow("yield equality expression") << QStringLiteral("> 1; }\n"); QTest::newRow("yield bit expression") << QStringLiteral(" 'value'; }\n"); } void TestDUChain::returnTypeGenerator() { QFETCH(QString, code); TopDUContext* top = parse(code.toUtf8(), DumpNone); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); QVERIFY(!top->parentContext()); QCOMPARE(top->childContexts().count(), 3); QCOMPARE(top->localDeclarations().count(), 2); Declaration* dec = top->localDeclarations().at(1); QCOMPARE(dec->qualifiedIdentifier(), QualifiedIdentifier("foo")); FunctionType::Ptr functionType = dec->type(); QVERIFY(functionType); StructureType::Ptr retType = StructureType::Ptr::dynamicCast(functionType->returnType()); QVERIFY(retType); QCOMPARE(retType->qualifiedIdentifier(), QualifiedIdentifier("generator")); } void TestDUChain::returnTypeGeneratorDelegation() { //Note: in practice, Generator is defined by php, but this class is not loaded in this test, so define it ourselves // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("parentContext()); QCOMPARE(top->childContexts().count(), 5); QCOMPARE(top->localDeclarations().count(), 3); Declaration* dec = top->localDeclarations().at(1); QCOMPARE(dec->qualifiedIdentifier(), QualifiedIdentifier("foo")); FunctionType::Ptr functionType = dec->type(); QVERIFY(functionType); StructureType::Ptr retType = StructureType::Ptr::dynamicCast(functionType->returnType()); QVERIFY(retType); QCOMPARE(retType->qualifiedIdentifier(), QualifiedIdentifier("generator")); dec = top->localDeclarations().at(2); QCOMPARE(dec->qualifiedIdentifier(), QualifiedIdentifier("bar")); functionType = dec->type(); QVERIFY(functionType); retType = StructureType::Ptr::dynamicCast(functionType->returnType()); QVERIFY(retType); QCOMPARE(retType->qualifiedIdentifier(), QualifiedIdentifier("generator")); } void TestDUChain::returnTypeClass() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("parentContext()); QCOMPARE(top->childContexts().count(), 5); QCOMPARE(top->localDeclarations().count(), 3); Declaration* dec = top->localDeclarations().at(1); QCOMPARE(dec->qualifiedIdentifier(), QualifiedIdentifier("foo")); FunctionType::Ptr functionType = dec->type(); QVERIFY(functionType); StructureType::Ptr retType = StructureType::Ptr::dynamicCast(functionType->returnType()); QVERIFY(retType); QCOMPARE(retType->qualifiedIdentifier(), QualifiedIdentifier("a")); dec = top->localDeclarations().at(2); QCOMPARE(dec->qualifiedIdentifier(), QualifiedIdentifier("bar")); functionType = dec->type(); QVERIFY(functionType); retType = StructureType::Ptr::dynamicCast(functionType->returnType()); QVERIFY(retType); QCOMPARE(retType->qualifiedIdentifier(), QualifiedIdentifier("a")); } void TestDUChain::declarationReturnType() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("parentContext()); QCOMPARE(top->childContexts().count(), 3); QCOMPARE(top->localDeclarations().count(), 3); Declaration* dec = top->localDeclarations().at(1); FunctionType::Ptr fType = dec->type(); QVERIFY(fType); QVERIFY(StructureType::Ptr::dynamicCast(fType->returnType())); QCOMPARE(StructureType::Ptr::dynamicCast(fType->returnType())->qualifiedIdentifier(), QualifiedIdentifier("a")); dec = top->localDeclarations().at(2); QCOMPARE(dec->identifier(), Identifier("i")); StructureType::Ptr type = dec->type(); QVERIFY(type); QCOMPARE(type->qualifiedIdentifier(), QualifiedIdentifier("a")); } void TestDUChain::declarationReturnTypeInRecursingFunction() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method(" decs = top->childContexts().last()->findDeclarations(Identifier(QStringLiteral("i"))); QCOMPARE(decs.size(), 1); Declaration* dec = decs.first(); StructureType::Ptr type = dec->type(); QVERIFY(type); QCOMPARE(type->qualifiedIdentifier(), QualifiedIdentifier("a")); } void TestDUChain::declarationMultipleReturnTypes() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().at(1)->type(); QVERIFY(fType); qDebug() << fType->toString(); TypePtr ut = UnsureType::Ptr::dynamicCast(fType->returnType()); QVERIFY(ut); QCOMPARE(2u, ut->typesSize()); ///TODO: why are the types not in the correct order, i.e. null, A QVERIFY(ut->types()[0].type()); QVERIFY(ut->types()[0].type()->declaration(top)); QCOMPARE(ut->types()[0].type()->declaration(top)->qualifiedIdentifier(), QualifiedIdentifier("a")); QVERIFY(ut->types()[1].type()); QVERIFY(ut->types()[1].type()->dataType() == IntegralType::TypeNull); fType = top->localDeclarations().at(2)->type(); QVERIFY(fType); qDebug() << fType->toString(); QVERIFY(IntegralType::Ptr::dynamicCast(fType->returnType())); QVERIFY(IntegralType::Ptr::dynamicCast(fType->returnType())->dataType() == IntegralType::TypeInt); } void TestDUChain::returnTypeViaMember() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("fa($param); }\n" " function fb2($param) { $i = $this->anormal->fa($param); } }"); TopDUContext* top = parse(method, DumpNone); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); QVector decs = top->localDeclarations(); QCOMPARE(decs.size(), 2); ClassDeclaration* aDec = dynamic_cast(decs.first()); QVERIFY(aDec); ClassDeclaration* bDec = dynamic_cast(decs.last()); QVERIFY(bDec); QCOMPARE(bDec->logicalInternalContext(top)->localDeclarations().size(), 4); typedef QPair idPair; foreach ( const idPair & pair, QList< idPair >() << qMakePair(QString("fb1"), QString("astatic")) << qMakePair(QString("fb2"), QString("anormal")) ) { qDebug() << pair.first << pair.second; ClassMethodDeclaration* fDec = dynamic_cast( bDec->logicalInternalContext(top)->findDeclarations(Identifier(pair.first)).first() ); QVERIFY(fDec); ClassMemberDeclaration* mDec = dynamic_cast( bDec->logicalInternalContext(top)->findDeclarations(Identifier(pair.second)).first() ); QVERIFY(mDec); QVERIFY(mDec->type()); QCOMPARE(mDec->type()->declaration(top), aDec); QCOMPARE(fDec->logicalInternalContext(top)->localDeclarations().size(), 1); Declaration* iDec = fDec->logicalInternalContext(top)->localDeclarations().first(); QCOMPARE(iDec->identifier().toString(), QString("i")); QVERIFY(iDec->type()); QCOMPARE(iDec->type()->declaration(top), aDec); } } void TestDUChain::declarationReturnTypeDocBlock() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("childContexts().at(0)->localDeclarations().at(0); FunctionType::Ptr fType = dec->type(); QVERIFY(fType); QVERIFY(StructureType::Ptr::dynamicCast(fType->returnType())); QCOMPARE(StructureType::Ptr::dynamicCast(fType->returnType())->qualifiedIdentifier(), QualifiedIdentifier("a")); //function foo dec = top->localDeclarations().at(2); fType = dec->type(); QVERIFY(fType); QVERIFY(StructureType::Ptr::dynamicCast(fType->returnType())); QCOMPARE(StructureType::Ptr::dynamicCast(fType->returnType())->qualifiedIdentifier(), QualifiedIdentifier("a")); //function bar dec = top->localDeclarations().at(3); fType = dec->type(); QVERIFY(fType); QVERIFY(IntegralTypeExtended::Ptr::dynamicCast(fType->returnType())); QVERIFY(IntegralTypeExtended::Ptr::dynamicCast(fType->returnType())->dataType() == IntegralTypeExtended::TypeObject); //test hint in internal functions file of a type that is added later on // function QList decs = top->findDeclarations(Identifier(QStringLiteral("should_return_exception"))); QCOMPARE(decs.size(), 1); dec = decs.first(); fType = dec->type(); QVERIFY(fType); QVERIFY(StructureType::Ptr::dynamicCast(fType->returnType())); QCOMPARE(StructureType::Ptr::dynamicCast(fType->returnType())->qualifiedIdentifier(), QualifiedIdentifier("exception")); // method decs = top->findDeclarations(Identifier(QStringLiteral("internal_test_class"))); QCOMPARE(decs.size(), 1); ClassDeclaration* cdec = dynamic_cast(decs.first()); QVERIFY(cdec); decs = cdec->logicalInternalContext(top)->findDeclarations(Identifier(QStringLiteral("should_return_exception"))); QCOMPARE(decs.size(), 1); dec = decs.first(); fType = dec->type(); QVERIFY(fType); QVERIFY(StructureType::Ptr::dynamicCast(fType->returnType())); QCOMPARE(StructureType::Ptr::dynamicCast(fType->returnType())->qualifiedIdentifier(), QualifiedIdentifier("exception")); } void TestDUChain::declarationReturnTypeDocBlockIntegral() { QByteArray method("localDeclarations().at(0)->type(); QVERIFY(fType); QVERIFY(IntegralType::Ptr::dynamicCast(fType->returnType())); QVERIFY(IntegralType::Ptr::dynamicCast(fType->returnType())->dataType() == IntegralType::TypeString); //function bar fType = top->localDeclarations().at(1)->type(); QVERIFY(fType); QVERIFY(IntegralType::Ptr::dynamicCast(fType->returnType())); QVERIFY(IntegralType::Ptr::dynamicCast(fType->returnType())->dataType() == IntegralType::TypeMixed); //function aaa fType = top->childContexts().at(4)->localDeclarations().first()->type(); QVERIFY(fType); QVERIFY(IntegralType::Ptr::dynamicCast(fType->returnType())); QVERIFY(IntegralType::Ptr::dynamicCast(fType->returnType())->dataType() == IntegralType::TypeInt); } void TestDUChain::declarationReturnTypeClassChain() { QByteArray method("childContexts().first(); QCOMPARE(ctx->type(), DUContext::Class); QVERIFY(ctx->owner()); QVERIFY(StructureType::Ptr::dynamicCast(ctx->owner()->abstractType())); //function a // FIXME QEXPECT_FAIL("", "This test fails after porting the plugin to KF5.", Abort); QVERIFY(/* func a (this) */ ctx->localDeclarations().at(0)->type().data() == ctx->owner()->abstractType().data()); QVERIFY(/* func b (self) */ ctx->localDeclarations().at(1)->type().data() == ctx->owner()->abstractType().data()); } void TestDUChain::declarationReturnTypeTypehint() { //Typehint preferred over phpdoc preferred over inferred type QByteArray method("parentContext()); QCOMPARE(top->childContexts().count(), 2); QCOMPARE(top->localDeclarations().count(), 1); FunctionType::Ptr fun = top->localDeclarations().first()->type(); QVERIFY(fun); IntegralType::Ptr returnType = IntegralType::Ptr::dynamicCast(fun->returnType()); QVERIFY(returnType); QVERIFY(returnType->dataType() == IntegralType::TypeBoolean); } void TestDUChain::declarationReturnTypeTypehintVoid() { //Typehint preferred over phpdoc preferred over inferred type QByteArray method("parentContext()); QCOMPARE(top->childContexts().count(), 2); QCOMPARE(top->localDeclarations().count(), 1); FunctionType::Ptr fun = top->localDeclarations().first()->type(); QVERIFY(fun); IntegralType::Ptr returnType = IntegralType::Ptr::dynamicCast(fun->returnType()); QVERIFY(returnType); QVERIFY(returnType->dataType() == IntegralType::TypeVoid); } void TestDUChain::declarationReturnTypeTypehintObject() { //Typehint preferred over phpdoc preferred over inferred type QByteArray method("parentContext()); QCOMPARE(top->childContexts().count(), 2); QCOMPARE(top->localDeclarations().count(), 1); FunctionType::Ptr fun = top->localDeclarations().first()->type(); QVERIFY(fun); IntegralTypeExtended::Ptr returnType = IntegralTypeExtended::Ptr::dynamicCast(fun->returnType()); QVERIFY(returnType); QVERIFY(returnType->dataType() == IntegralTypeExtended::TypeObject); } void TestDUChain::declareTypehintFunction() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("parentContext()); QCOMPARE(top->childContexts().count(), 3); QCOMPARE(top->localDeclarations().count(), 2); Declaration* dec = top->localDeclarations().at(0); QCOMPARE(dec->internalContext(), top->childContexts().at(0)); QCOMPARE(dec->uses().count(), 1); QCOMPARE(dec->uses().begin()->count(), 1); QCOMPARE(top->childContexts().at(0)->localScopeIdentifier(), QualifiedIdentifier("a")); QCOMPARE(top->childContexts().at(0)->childContexts().count(), 0); DUContext* contextFunctionFoo = top->childContexts().at(1); QCOMPARE(contextFunctionFoo->localScopeIdentifier(), QualifiedIdentifier("foo")); DUContext* contextFunctionBodyFoo = top->childContexts().at(2); QCOMPARE(contextFunctionBodyFoo->localScopeIdentifier(), QualifiedIdentifier("foo")); QCOMPARE(contextFunctionBodyFoo->importedParentContexts().count(), 1); QCOMPARE(contextFunctionBodyFoo->childContexts().count(), 0); QVERIFY(contextFunctionBodyFoo->importedParentContexts().first().context(top) == contextFunctionFoo); QVERIFY(top->childContexts().at(1)->localDeclarations().first()->type()); QCOMPARE(top->childContexts().at(1)->localDeclarations().first()->type()->qualifiedIdentifier(), QualifiedIdentifier("a")); FunctionType::Ptr fType = top->localDeclarations().at(1)->type(); QVERIFY(fType); QVERIFY(StructureType::Ptr::dynamicCast(fType->returnType())); QCOMPARE(StructureType::Ptr::dynamicCast(fType->returnType())->qualifiedIdentifier(), QualifiedIdentifier("a")); } void TestDUChain::declareVariadicFunction() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().first()->type(); QVERIFY(fun); QCOMPARE(fun->arguments().count(), 1); AbstractType::Ptr arg = fun->arguments().first(); QVERIFY(arg); QVERIFY(arg.cast()); QCOMPARE(arg.cast()->typesCount(), 1); QCOMPARE(arg.cast()->prettyName().str(), QStringLiteral("array")); AbstractType::Ptr typehint = arg.cast()->typeAt(0).abstractType(); QVERIFY(typehint); QVERIFY(IntegralType::Ptr::dynamicCast(typehint)); QVERIFY(IntegralType::Ptr::dynamicCast(typehint)->dataType() == IntegralType::TypeMixed); } void TestDUChain::declareTypehintVariadicFunction() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().at(1)->type(); QVERIFY(fun); QCOMPARE(fun->arguments().count(), 1); AbstractType::Ptr arg = fun->arguments().first(); QVERIFY(arg); QVERIFY(arg.cast()); QCOMPARE(arg.cast()->typesCount(), 1); QCOMPARE(arg.cast()->prettyName().str(), QStringLiteral("array")); AbstractType::Ptr typehint = arg.cast()->typeAt(0).abstractType(); QVERIFY(typehint); QCOMPARE(typehint->toString(), QStringLiteral("A")); } void TestDUChain::declareTypehintObjectFunction() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().first()->type(); QVERIFY(fun); QCOMPARE(fun->arguments().count(), 1); QVERIFY(IntegralType::Ptr::dynamicCast(fun->arguments().first())); QVERIFY(IntegralType::Ptr::dynamicCast(fun->arguments().first())->dataType() == IntegralTypeExtended::TypeObject); IntegralTypeExtended::Ptr type = top->childContexts().first()->localDeclarations().first()->type(); QVERIFY(type); QVERIFY(type->dataType() == IntegralTypeExtended::TypeObject); } void TestDUChain::declareTypehintArrayFunction() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().first()->type(); QVERIFY(fun); QCOMPARE(fun->arguments().count(), 1); QVERIFY(IntegralType::Ptr::dynamicCast(fun->arguments().first())); QVERIFY(IntegralType::Ptr::dynamicCast(fun->arguments().first())->dataType() == IntegralType::TypeArray); IntegralType::Ptr type = top->childContexts().first()->localDeclarations().first()->type(); QVERIFY(type); QVERIFY(type->dataType() == IntegralType::TypeArray); } void TestDUChain::declareTypehintCallableFunction() { // 0 1 2 3 // 0123456789012345678901234567890123 QByteArray method("localDeclarations().first()->type(); QVERIFY(fun); QCOMPARE(fun->arguments().count(), 1); QVERIFY(IntegralTypeExtended::Ptr::dynamicCast(fun->arguments().first())); QVERIFY(IntegralTypeExtended::Ptr::dynamicCast(fun->arguments().first())->dataType() == IntegralTypeExtended::TypeCallable); IntegralTypeExtended::Ptr type = top->childContexts().first()->localDeclarations().first()->type(); QVERIFY(type); QVERIFY(type->dataType() == IntegralTypeExtended::TypeCallable); } void Php::TestDUChain::functionWithCallableAndFunctionReturn() { QByteArray method("localDeclarations().first()->type(); QVERIFY(fun); QCOMPARE(fun->arguments().count(), 1); QVERIFY(IntegralTypeExtended::Ptr::dynamicCast(fun->arguments().first())); QVERIFY(IntegralTypeExtended::Ptr::dynamicCast(fun->arguments().first())->dataType() == IntegralTypeExtended::TypeCallable); IntegralTypeExtended::Ptr retType = IntegralTypeExtended::Ptr::dynamicCast(fun->returnType()); QVERIFY(retType); QVERIFY(retType->dataType() == IntegralTypeExtended::TypeCallable); } void TestDUChain::declareTypehintIterableFunction() { //Note: in practice, Traversable is defined by php, but this interface is not loaded in this test, so define it ourselves // 0 1 2 3 // 0123456789012345678901234567890123 QByteArray method("localDeclarations().count(), 2); FunctionType::Ptr fun = top->localDeclarations().at(1)->type(); QVERIFY(fun); QCOMPARE(fun->arguments().count(), 1); UnsureType::Ptr argType = UnsureType::Ptr::dynamicCast(fun->arguments().first()); QVERIFY(argType); QCOMPARE(argType->typesSize(), 2u); QVERIFY(argType->types()[0].abstractType().cast()); QVERIFY(argType->types()[0].abstractType().cast()->dataType() == IntegralType::TypeArray); QVERIFY(argType->types()[1].abstractType().cast()); QCOMPARE(argType->types()[1].abstractType().cast()->qualifiedIdentifier(), QualifiedIdentifier("traversable")); } void TestDUChain::declareTypehintBoolFunction() { // 0 1 2 3 // 0123456789012345678901234567890123 QByteArray method("localDeclarations().first()->type(); QVERIFY(fun); QCOMPARE(fun->arguments().count(), 1); QVERIFY(IntegralType::Ptr::dynamicCast(fun->arguments().first())); QVERIFY(IntegralType::Ptr::dynamicCast(fun->arguments().first())->dataType() == IntegralType::TypeBoolean); IntegralType::Ptr type = top->childContexts().first()->localDeclarations().first()->type(); QVERIFY(type); QVERIFY(type->dataType() == IntegralType::TypeBoolean); } void TestDUChain::declareTypehintFloatFunction() { // 0 1 2 3 // 0123456789012345678901234567890123 QByteArray method("localDeclarations().first()->type(); QVERIFY(fun); QCOMPARE(fun->arguments().count(), 1); QVERIFY(IntegralType::Ptr::dynamicCast(fun->arguments().first())); QVERIFY(IntegralType::Ptr::dynamicCast(fun->arguments().first())->dataType() == IntegralType::TypeFloat); IntegralType::Ptr type = top->childContexts().first()->localDeclarations().first()->type(); QVERIFY(type); QVERIFY(type->dataType() == IntegralType::TypeFloat); } void TestDUChain::declareTypehintIntFunction() { // 0 1 2 3 // 0123456789012345678901234567890123 QByteArray method("localDeclarations().first()->type(); QVERIFY(fun); QCOMPARE(fun->arguments().count(), 1); QVERIFY(IntegralType::Ptr::dynamicCast(fun->arguments().first())); QVERIFY(IntegralType::Ptr::dynamicCast(fun->arguments().first())->dataType() == IntegralType::TypeInt); IntegralType::Ptr type = top->childContexts().first()->localDeclarations().first()->type(); QVERIFY(type); QVERIFY(type->dataType() == IntegralType::TypeInt); } void TestDUChain::declareTypehintStringFunction() { // 0 1 2 3 // 0123456789012345678901234567890123 QByteArray method("localDeclarations().first()->type(); QVERIFY(fun); QCOMPARE(fun->arguments().count(), 1); QVERIFY(IntegralType::Ptr::dynamicCast(fun->arguments().first())); QVERIFY(IntegralType::Ptr::dynamicCast(fun->arguments().first())->dataType() == IntegralType::TypeString); IntegralType::Ptr type = top->childContexts().first()->localDeclarations().first()->type(); QVERIFY(type); QVERIFY(type->dataType() == IntegralType::TypeString); } void TestDUChain::declareNullableTypehintArrayFunction() { // 0 1 2 3 // 0123456789012345678901234567890123 QByteArray method("localDeclarations().first()->type(); QVERIFY(fun); QCOMPARE(fun->arguments().count(), 1); UnsureType::Ptr argType = UnsureType::Ptr::dynamicCast(fun->arguments().first()); QVERIFY(argType); QCOMPARE(argType->typesSize(), 2u); QVERIFY(argType->types()[0].abstractType().cast()); QVERIFY(argType->types()[0].abstractType().cast()->dataType() == IntegralType::TypeArray); QVERIFY(argType->types()[1].abstractType().cast()); QVERIFY(argType->types()[1].abstractType().cast()->dataType() == IntegralType::TypeNull); UnsureType::Ptr type = top->childContexts().first()->localDeclarations().first()->type(); QVERIFY(type); QCOMPARE(type->typesSize(), 2u); QVERIFY(type->types()[0].abstractType().cast()); QVERIFY(type->types()[0].abstractType().cast()->dataType() == IntegralType::TypeArray); QVERIFY(type->types()[1].abstractType().cast()); QVERIFY(type->types()[1].abstractType().cast()->dataType() == IntegralType::TypeNull); } void TestDUChain::declareTypehintWithPhpdocFunction() { // 0 1 2 3 // 0123456789012345678901234567890123 QByteArray method("localDeclarations().first()->type(); QVERIFY(fun); QCOMPARE(fun->arguments().count(), 1); QVERIFY(IntegralType::Ptr::dynamicCast(fun->arguments().first())); QVERIFY(IntegralType::Ptr::dynamicCast(fun->arguments().first())->dataType() == IntegralType::TypeInt); IntegralType::Ptr type = top->childContexts().first()->localDeclarations().first()->type(); QVERIFY(type); QVERIFY(type->dataType() == IntegralType::TypeInt); } void TestDUChain::declareNullableTypehintMixedFunction() { // 0 1 2 3 // 0123456789012345678901234567890123 QByteArray method("localDeclarations().first()->type(); QVERIFY(fun); QCOMPARE(fun->arguments().count(), 1); QVERIFY(IntegralType::Ptr::dynamicCast(fun->arguments().first())); QVERIFY(IntegralType::Ptr::dynamicCast(fun->arguments().first())->dataType() == IntegralType::TypeMixed); IntegralType::Ptr type = top->childContexts().first()->localDeclarations().first()->type(); QVERIFY(type); QVERIFY(type->dataType() == IntegralType::TypeMixed); } void TestDUChain::declareTypehintNullableIterableFunction() { //Note: in practice, Traversable is defined by php, but this interface is not loaded in this test, so define it ourselves // 0 1 2 3 // 0123456789012345678901234567890123 QByteArray method("localDeclarations().count(), 2); FunctionType::Ptr fun = top->localDeclarations().at(1)->type(); QVERIFY(fun); QCOMPARE(fun->arguments().count(), 1); UnsureType::Ptr argType = UnsureType::Ptr::dynamicCast(fun->arguments().first()); QVERIFY(argType); QCOMPARE(argType->typesSize(), 3u); QVERIFY(argType->types()[0].abstractType().cast()); QVERIFY(argType->types()[0].abstractType().cast()->dataType() == IntegralType::TypeArray); QVERIFY(argType->types()[1].abstractType().cast()); QCOMPARE(argType->types()[1].abstractType().cast()->qualifiedIdentifier(), QualifiedIdentifier("traversable")); QVERIFY(argType->types()[2].abstractType().cast()); QVERIFY(argType->types()[2].abstractType().cast()->dataType() == IntegralType::TypeNull); } void TestDUChain::classImplementsInterface() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("childContexts().count(), 2); QCOMPARE(top->localDeclarations().count(), 2); //interface I Declaration* dec = top->localDeclarations().at(0); QVERIFY(dec->isDefinition()); QCOMPARE(dec->identifier(), Identifier("i")); QCOMPARE(dec->toString(), QString("interface I")); StructureType::Ptr typeI = dec->type(); QCOMPARE(typeI->qualifiedIdentifier(), QualifiedIdentifier("i")); QVERIFY(typeI->declaration(top) == dec); ClassDeclaration* classDec = dynamic_cast(dec); QVERIFY(classDec); QCOMPARE(classDec->classType(), ClassDeclarationData::Interface); QCOMPARE(dec->internalContext(), top->childContexts().at(0)); QCOMPARE(dec->internalContext()->childContexts().count(), 0); QCOMPARE(dec->internalContext()->importedParentContexts().count(), 0); QCOMPARE(dec->internalContext()->localScopeIdentifier(), QualifiedIdentifier("i")); QCOMPARE(dec->uses().count(), 1); QCOMPARE(dec->uses().begin()->count(), 1); IndexedType indexedTypeI = classDec->indexedType(); //class A dec = top->localDeclarations().at(1); QVERIFY(dec->isDefinition()); QCOMPARE(dec->identifier(), Identifier("a")); StructureType::Ptr typeA = dec->type(); QCOMPARE(typeA->qualifiedIdentifier(), QualifiedIdentifier("a")); QVERIFY(typeA->declaration(top) == dec); classDec = dynamic_cast(dec); QVERIFY(classDec); QCOMPARE(classDec->classType(), ClassDeclarationData::Class); QCOMPARE(dec->internalContext(), top->childContexts().at(1)); QCOMPARE(dec->internalContext()->childContexts().count(), 0); QCOMPARE(dec->internalContext()->localScopeIdentifier(), QualifiedIdentifier("a")); //class A imports interface I context QCOMPARE(dec->internalContext()->importedParentContexts().count(), 1); QVERIFY(dec->internalContext()->importedParentContexts().at(0).context(top) == top->childContexts().at(0)); QCOMPARE(classDec->baseClassesSize(), 1u); QCOMPARE(classDec->baseClasses()[0].baseClass, indexedTypeI); QCOMPARE(dec->uses().count(), 0); } void TestDUChain::classExtends() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("childContexts().count(), 2); QCOMPARE(top->localDeclarations().count(), 2); //class A Declaration* dec = top->localDeclarations().at(0); QVERIFY(dec->isDefinition()); QCOMPARE(dec->identifier(), Identifier("a")); StructureType::Ptr typeA = dec->type(); QCOMPARE(typeA->qualifiedIdentifier(), QualifiedIdentifier("a")); QVERIFY(typeA->declaration(top) == dec); ClassDeclaration* classDec = dynamic_cast(dec); QVERIFY(classDec); QCOMPARE(classDec->classType(), ClassDeclarationData::Class); QCOMPARE(dec->internalContext(), top->childContexts().at(0)); QCOMPARE(dec->internalContext()->childContexts().count(), 0); QCOMPARE(dec->internalContext()->importedParentContexts().count(), 0); QCOMPARE(dec->internalContext()->localScopeIdentifier(), QualifiedIdentifier("a")); QCOMPARE(dec->uses().count(), 1); QCOMPARE(dec->uses().begin()->count(), 1); IndexedType indexedTypeA = classDec->indexedType(); //class B dec = top->localDeclarations().at(1); QVERIFY(dec->isDefinition()); QCOMPARE(dec->identifier(), Identifier("b")); StructureType::Ptr typeB = dec->type(); QCOMPARE(typeB->qualifiedIdentifier(), QualifiedIdentifier("b")); QVERIFY(typeB->declaration(top) == dec); classDec = dynamic_cast(dec); QVERIFY(classDec); QCOMPARE(classDec->classType(), ClassDeclarationData::Class); QCOMPARE(dec->internalContext(), top->childContexts().at(1)); QCOMPARE(dec->internalContext()->childContexts().count(), 0); QCOMPARE(dec->internalContext()->localScopeIdentifier(), QualifiedIdentifier("b")); //class B imports class A context QCOMPARE(dec->internalContext()->importedParentContexts().count(), 1); QVERIFY(dec->internalContext()->importedParentContexts().at(0).context(top) == top->childContexts().at(0)); QCOMPARE(classDec->baseClassesSize(), 1u); QCOMPARE(classDec->baseClasses()[0].baseClass, indexedTypeA); QCOMPARE(dec->uses().count(), 0); } void TestDUChain::staticMethod() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().at(2)->type(); QVERIFY(type); QCOMPARE(type->qualifiedIdentifier(), QualifiedIdentifier("b")); } void TestDUChain::ownStaticMethod() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("childContexts().at(1)); QVERIFY(top->childContexts().at(1)->localDeclarations().at(0)); QVERIFY(top->childContexts().at(1)->localDeclarations().at(0)->type()); AbstractType::Ptr ret = top->childContexts().at(1)->localDeclarations().at(0) ->type()->returnType(); QVERIFY(StructureType::Ptr::dynamicCast(ret)); QCOMPARE(StructureType::Ptr::dynamicCast(ret)->declaration(top), top->localDeclarations().at(0)); QVERIFY(top->childContexts().at(1)->childContexts().at(1 + 2)); QVERIFY(top->childContexts().at(1)->childContexts().at(1 + 2)->localDeclarations().at(0)); QVERIFY(top->childContexts().at(1)->childContexts().at(1 + 2)->localDeclarations().at(0)->type()); QCOMPARE(top->childContexts().at(1)->childContexts().at(1 + 2)->localDeclarations().at(0) ->type()->qualifiedIdentifier(), QualifiedIdentifier("b")); QCOMPARE(top->childContexts().at(1)->childContexts().at(1 + 2)->localDeclarations().at(1) ->type()->qualifiedIdentifier(), QualifiedIdentifier("b")); } void TestDUChain::thisVar() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("x(); } } "); TopDUContext* top = parse(method, DumpNone); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); FunctionType::Ptr fn = top->childContexts().at(0)->localDeclarations().at(0)->type(); QVERIFY(fn); StructureType::Ptr cls = StructureType::Ptr::dynamicCast(fn->returnType()); QVERIFY(cls); QCOMPARE(cls->qualifiedIdentifier(), QualifiedIdentifier("a")); fn = top->childContexts().at(0)->localDeclarations().at(1)->type(); QVERIFY(fn); cls = StructureType::Ptr::dynamicCast(fn->returnType()); QVERIFY(cls); QCOMPARE(cls->qualifiedIdentifier(), QualifiedIdentifier("a")); } void TestDUChain::objectFunctionCall() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("x(); } } "); TopDUContext* top = parse(method, DumpNone); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); FunctionType::Ptr fn = top->childContexts().at(1)->localDeclarations().at(0)->type(); QVERIFY(fn); StructureType::Ptr cls = StructureType::Ptr::dynamicCast(fn->returnType()); QVERIFY(cls); QCOMPARE(cls->qualifiedIdentifier(), QualifiedIdentifier("b")); fn = top->childContexts().at(1)->localDeclarations().at(1)->type(); QVERIFY(fn); cls = StructureType::Ptr::dynamicCast(fn->returnType()); QVERIFY(cls); QCOMPARE(cls->qualifiedIdentifier(), QualifiedIdentifier("b")); } void TestDUChain::objectFunctionCall2() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("x()->c(); } } "); TopDUContext* top = parse(method, DumpNone); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); FunctionType::Ptr fn = top->childContexts().at(2)->localDeclarations().at(1)->type(); QVERIFY(fn); StructureType::Ptr cls = StructureType::Ptr::dynamicCast(fn->returnType()); QVERIFY(cls); QCOMPARE(cls->qualifiedIdentifier(), QualifiedIdentifier("c")); } void TestDUChain::objectFunctionCall3() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("b();"); TopDUContext* top = parse(method, DumpNone); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(top->localDeclarations().at(2)->qualifiedIdentifier(), QualifiedIdentifier("i")); QCOMPARE(top->localDeclarations().at(2)->type()->qualifiedIdentifier(), QualifiedIdentifier("a"));; QCOMPARE(top->localDeclarations().at(3)->qualifiedIdentifier(), QualifiedIdentifier("j")); QCOMPARE(top->localDeclarations().at(3)->type()->qualifiedIdentifier(), QualifiedIdentifier("b"));; } void TestDUChain::objectVariable() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("foo;"); TopDUContext* top = parse(method, DumpNone); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(top->localDeclarations().at(3)->qualifiedIdentifier(), QualifiedIdentifier("i")); QCOMPARE(top->localDeclarations().at(3)->type()->qualifiedIdentifier(), QualifiedIdentifier("b"));; } void TestDUChain::staticMemberVariable() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().at(2)->qualifiedIdentifier(), QualifiedIdentifier("i")); QCOMPARE(top->localDeclarations().at(2)->type()->qualifiedIdentifier(), QualifiedIdentifier("b"));; } void TestDUChain::ownStaticMemberVariable() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("childContexts().at(1)->childContexts().at(1); QCOMPARE(barContext->localDeclarations().at(0)->type()->qualifiedIdentifier(), QualifiedIdentifier("b")); QCOMPARE(barContext->localDeclarations().at(1)->type()->qualifiedIdentifier(), QualifiedIdentifier("b")); } void TestDUChain::classConst_data() { QTest::addColumn("classBody"); QTest::addColumn("problems"); QTest::newRow("int") << "const C = 1;" << 0; QTest::newRow("string") << "const C = 'asdf';" << 0; QTest::newRow("float") << "const C = 0.5;" << 0; QTest::newRow("bool") << "const C = true;" << 0; QTest::newRow("selfConst") << "const C2 = 1; const C = self::C2;" << 0; QTest::newRow("parentConst") << "const C = parent::P;" << 0; QTest::newRow("null") << "const C = null;" << 0; QTest::newRow("array") << "const C = array();" << 0; QTest::newRow("expression") << "const C = 'foo' . 'foo';" << 0; } void TestDUChain::classConst() { QFETCH(QString, classBody); QFETCH(int, problems); QString fullClass("childContexts().count(), 2); QCOMPARE(top->problems().count(), problems); QCOMPARE(top->findDeclarations(QualifiedIdentifier("a::C")).count(), 1); QCOMPARE(top->findDeclarations(QualifiedIdentifier("a::C")).first()->context(), top->childContexts().last()); } void TestDUChain::classConstWithTypeHint() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("childContexts().count(), 1); QCOMPARE(top->problems().count(), 0); QList< Declaration* > decs = top->findDeclarations(QualifiedIdentifier("a::C")); QCOMPARE(decs.count(), 1); QCOMPARE(decs.first()->context(), top->childContexts().last()); IntegralType::Ptr type = decs.first()->abstractType().cast(); QVERIFY(type); QCOMPARE(type->dataType(), IntegralType::TypeInt); QVERIFY(type->modifiers() & AbstractType::ConstModifier); } void TestDUChain::classConstVisibility() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("childContexts().count(), 1); QCOMPARE(top->problems().count(), 0); QList< Declaration* > decs = top->findDeclarations(QualifiedIdentifier("a::B")); QCOMPARE(decs.count(), 1); QCOMPARE(decs.first()->context(), top->childContexts().last()); IntegralType::Ptr type = decs.first()->abstractType().cast(); QVERIFY(type); QCOMPARE(type->dataType(), IntegralType::TypeInt); QVERIFY(type->modifiers() & AbstractType::ConstModifier); ClassMemberDeclaration* cmdec = dynamic_cast(decs.first()); QVERIFY(cmdec->accessPolicy() == Declaration::Public); decs = top->findDeclarations(QualifiedIdentifier("a::C")); QCOMPARE(decs.count(), 1); QCOMPARE(decs.first()->context(), top->childContexts().last()); type = decs.first()->abstractType().cast(); QVERIFY(type); QCOMPARE(type->dataType(), IntegralType::TypeInt); QVERIFY(type->modifiers() & AbstractType::ConstModifier); cmdec = dynamic_cast(decs.first()); QVERIFY(cmdec->accessPolicy() == Declaration::Protected); decs = top->findDeclarations(QualifiedIdentifier("a::D")); QCOMPARE(decs.count(), 1); QCOMPARE(decs.first()->context(), top->childContexts().last()); type = decs.first()->abstractType().cast(); QVERIFY(type); QCOMPARE(type->dataType(), IntegralType::TypeInt); QVERIFY(type->modifiers() & AbstractType::ConstModifier); cmdec = dynamic_cast(decs.first()); QVERIFY(cmdec->accessPolicy() == Declaration::Private); } void TestDUChain::semiReservedClassConst() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("childContexts().count(), 1); QCOMPARE(top->problems().count(), 0); QCOMPARE(top->findDeclarations(QualifiedIdentifier("a::SWITCH")).count(), 1); QCOMPARE(top->findDeclarations(QualifiedIdentifier("a::SWITCH")).first()->context(), top->childContexts().last()); QCOMPARE(top->findDeclarations(QualifiedIdentifier("a::PUBLIC")).count(), 1); QCOMPARE(top->findDeclarations(QualifiedIdentifier("a::PUBLIC")).first()->context(), top->childContexts().last()); QCOMPARE(top->findDeclarations(QualifiedIdentifier("a::STRING")).count(), 1); QCOMPARE(top->findDeclarations(QualifiedIdentifier("a::STRING")).first()->context(), top->childContexts().last()); } void TestDUChain::illegalClassConst_data() { QTest::addColumn("code"); QTest::newRow("final const") << QStringLiteral("problems().count(), 1); } void TestDUChain::fileConst_data() { QTest::addColumn("code"); QTest::addColumn("problems"); QTest::addColumn("dataType"); QTest::newRow("int") << "const C = 1;" << 0 << (uint) IntegralType::TypeInt; QTest::newRow("string") << "const C = 'asdf';" << 0 << (uint) IntegralType::TypeString; QTest::newRow("float") << "const C = 0.5;" << 0 << (uint) IntegralType::TypeFloat; QTest::newRow("bool") << "const C = true;" << 0 << (uint) IntegralType::TypeBoolean; QTest::newRow("array") << "const C = array();" << 0 << (uint) IntegralType::TypeArray; QTest::newRow("expression") << "const C = 'foo' . 'foo';" << 0 << (uint) IntegralType::TypeString; } void TestDUChain::fileConst() { QFETCH(QString, code); QFETCH(int, problems); QFETCH(uint, dataType); code.prepend("problems().count(), problems); QList< Declaration* > decs = top->findDeclarations(QualifiedIdentifier(QStringLiteral("C"))); QCOMPARE(decs.count(), 1); IntegralType::Ptr type = decs.first()->abstractType().cast(); QVERIFY(type); QCOMPARE(type->dataType(), dataType); QVERIFY(type->modifiers() & AbstractType::ConstModifier); } void TestDUChain::semiReservedFileConst() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("findDeclarations(QualifiedIdentifier("FOO")).count(), 1); QCOMPARE(top->findDeclarations(QualifiedIdentifier("BAR")).count(), 1); QCOMPARE(top->findDeclarations(QualifiedIdentifier("FOO")).first()->context(), top); QCOMPARE(top->findDeclarations(QualifiedIdentifier("BAR")).first()->context(), top); QVERIFY(top->findDeclarations(QualifiedIdentifier("FOO")).first()->abstractType()->modifiers() & AbstractType::ConstModifier); QVERIFY(top->findDeclarations(QualifiedIdentifier("BAR")).first()->abstractType()->modifiers() & AbstractType::ConstModifier); } void TestDUChain::defaultFunctionParam() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("(top->localDeclarations().first()); QVERIFY(fun); QCOMPARE(fun->defaultParametersSize(), 3u); QVERIFY(fun->defaultParameters()[0].isEmpty()); QCOMPARE(fun->defaultParameters()[1].str(), QString("false")); QCOMPARE(fun->defaultParameters()[2].str(), QString("null")); } void TestDUChain::defaultFunctionParamWithTypehint() { QByteArray method("localDeclarations().first()->type(); QVERIFY(fun); QCOMPARE(fun->arguments().count(), 1); IntegralType::Ptr argType = IntegralType::Ptr::dynamicCast(fun->arguments().first()); QVERIFY(argType); QVERIFY(argType->dataType() == IntegralType::TypeArray); } void TestDUChain::nullDefaultFunctionParamWithTypehint() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().first()->type(); QVERIFY(fun); QCOMPARE(fun->arguments().count(), 1); UnsureType::Ptr argType = UnsureType::Ptr::dynamicCast(fun->arguments().first()); QVERIFY(argType); QCOMPARE(argType->typesSize(), 2u); QVERIFY(argType->types()[0].abstractType().cast()); QVERIFY(argType->types()[0].abstractType().cast()->dataType() == IntegralType::TypeArray); QVERIFY(argType->types()[1].abstractType().cast()); QVERIFY(argType->types()[1].abstractType().cast()->dataType() == IntegralType::TypeNull); } void TestDUChain::globalFunction() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("importedParentContexts().count(), 1); QVERIFY(DUChain::self()->chainForDocument(internalFunctionFile())); QCOMPARE(DUChain::self()->chainForDocument(internalFunctionFile()), top->importedParentContexts().first().context(top)); QCOMPARE(top->findDeclarations(QualifiedIdentifier("substr")).count(), 1); } void TestDUChain::globalVariableFromInternalFunctions() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("importedParentContexts().count(), 1); QVERIFY(DUChain::self()->chainForDocument(internalFunctionFile())); QCOMPARE(DUChain::self()->chainForDocument(internalFunctionFile()), top->importedParentContexts().first().context(top)); QCOMPARE(top->findDeclarations(QualifiedIdentifier("_GET")).count(), 1); } void TestDUChain::newObjectFromOtherFile() { TopDUContext* addTop = parseAdditionalFile(IndexedString("/duchaintest/foo.php"), "localDeclarations().first()->type()->declaration(top), addTop->localDeclarations().first()); } void TestDUChain::unknownReturnType() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().at(0); FunctionType::Ptr fType = dec->type(); QVERIFY(fType); QVERIFY(IntegralType::Ptr::dynamicCast(fType->returnType())); QVERIFY(IntegralType::Ptr::staticCast(fType->returnType())->dataType() == IntegralType::TypeVoid); } void TestDUChain::staticFunctionCallFromOtherFile() { TopDUContext* addTop = parseAdditionalFile(IndexedString("/duchaintest/foo2.php"), "childContexts().first()->localDeclarations().first()->type(); QVERIFY(fun); StructureType::Ptr ret = StructureType::Ptr::dynamicCast(fun->returnType()); qDebug() << fun->returnType()->toString(); QVERIFY(ret); QCOMPARE(ret->declaration(top), top->localDeclarations().first()); } void TestDUChain::internalFunctions() { return; //disabled because it is too slow QString fileName = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kdevphpsupport/phpfunctions.php")); QFile file(fileName); file.open(QIODevice::ReadOnly | QIODevice::Text); TopDUContext* top = parse(file.readAll(), DumpNone); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); } void TestDUChain::trueFalse() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().at(0)->type()->dataType() == IntegralType::TypeBoolean); QVERIFY(top->localDeclarations().at(1)->type()->dataType() == IntegralType::TypeBoolean); } void TestDUChain::null() { QByteArray method("localDeclarations().at(0)->type()->dataType() == IntegralType::TypeNull); } void TestDUChain::array() { QByteArray method("localDeclarations().at(0)->type()->dataType() == IntegralType::TypeArray); QVERIFY(top->localDeclarations().at(1)->type()->dataType() == IntegralType::TypeArray); // $b[] = 'test'; is not a redeclaration of b! Esp. it's type should not change. QCOMPARE(top->findDeclarations(Identifier("b")).count(), 1); } void TestDUChain::functionDocBlock() { { TopDUContext* top = parse("localDeclarations().first()->comment(), QByteArray("Foo")); } { TopDUContext* top = parse("localDeclarations().first()->comment(), QByteArray("Bar")); QCOMPARE(top->childContexts().first()->localDeclarations().first()->comment(), QByteArray("Foo")); } { TopDUContext* top = parse("localDeclarations().first()->comment(), QByteArray("Foo")); } { TopDUContext* top = parse("childContexts().first()->localDeclarations().first()->comment(), QByteArray("Foo")); } { TopDUContext* top = parse("childContexts().first()->localDeclarations().first()->comment(), QByteArray("Foo")); } { TopDUContext* top = parse("localDeclarations().first()->comment(), QByteArray("Foo\n Bar")); } { - // same as above but with indendation + // same as above but with indentation TopDUContext* top = parse("localDeclarations().first()->comment(), QByteArray("Foo\n Bar")); } } void TestDUChain::variableDocBlock() { { TopDUContext* top = parse("localDeclarations().first()->comment(), QByteArray("Foo")); QCOMPARE(top->localDeclarations().at(1)->comment(), QByteArray("Foo")); } { TopDUContext* top = parse("localDeclarations().first()->comment(), QByteArray("Foo")); QCOMPARE(top->localDeclarations().at(1)->comment(), QByteArray("Foo")); } } void TestDUChain::functionDocBlockParams() { TopDUContext* top = parse("localDeclarations().at(1)->type()->arguments().count(), 4); AbstractType::Ptr arg = top->localDeclarations().at(1)->type()->arguments().at(0); QVERIFY(IntegralType::Ptr::dynamicCast(arg)); QVERIFY(IntegralType::Ptr::dynamicCast(arg)->dataType() == IntegralType::TypeInt); QVERIFY(top->childContexts().at(1)->localDeclarations().at(0)->type()); QVERIFY(top->childContexts().at(1)->localDeclarations().at(0)->type()->dataType() == IntegralType::TypeInt); arg = top->localDeclarations().at(1)->type()->arguments().at(1); QVERIFY(StructureType::Ptr::dynamicCast(arg)); QCOMPARE(StructureType::Ptr::dynamicCast(arg)->declaration(top), top->localDeclarations().at(0)); QCOMPARE(top->childContexts().at(1)->localDeclarations().at(1)->type()->declaration(top), top->localDeclarations().at(0)); arg = top->localDeclarations().at(1)->type()->arguments().at(2); QVERIFY(IntegralType::Ptr::dynamicCast(arg)); QVERIFY(IntegralType::Ptr::dynamicCast(arg)->dataType() == IntegralType::TypeMixed); arg = top->localDeclarations().at(1)->type()->arguments().at(3); QVERIFY(IntegralType::Ptr::dynamicCast(arg)); QVERIFY(IntegralType::Ptr::dynamicCast(arg)->dataType() == IntegralType::TypeMixed); } } void TestDUChain::memberFunctionDocBlockParams() { TopDUContext* top = parse("childContexts().first()->localDeclarations().first()->type()->arguments().count(), 3); AbstractType::Ptr arg = top->childContexts().first()->localDeclarations().first()->type()->arguments().at(0); QVERIFY(IntegralType::Ptr::dynamicCast(arg)); QVERIFY(IntegralType::Ptr::dynamicCast(arg)->dataType() == IntegralType::TypeBoolean); arg = top->childContexts().first()->localDeclarations().first()->type()->arguments().at(1); QVERIFY(StructureType::Ptr::dynamicCast(arg)); QCOMPARE(StructureType::Ptr::dynamicCast(arg)->declaration(top), top->localDeclarations().at(0)); arg = top->childContexts().first()->localDeclarations().first()->type()->arguments().at(2); QVERIFY(IntegralType::Ptr::dynamicCast(arg)); QVERIFY(IntegralType::Ptr::dynamicCast(arg)->dataType() == IntegralType::TypeArray); } } void TestDUChain::foreachLoop() { { TopDUContext* top = parse("$i) { $i; }", DumpAll); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(top->localDeclarations().count(), 3); QCOMPARE(top->localDeclarations().at(1)->qualifiedIdentifier(), QualifiedIdentifier("k")); QVERIFY(top->localDeclarations().at(1)->abstractType().cast()); QCOMPARE(top->localDeclarations().at(1)->abstractType().cast()->dataType(), static_cast(IntegralType::TypeMixed)); QCOMPARE(top->localDeclarations().at(2)->qualifiedIdentifier(), QualifiedIdentifier("i")); QVERIFY(top->localDeclarations().at(2)->abstractType().cast()); QCOMPARE(top->localDeclarations().at(2)->abstractType().cast()->dataType(), static_cast(IntegralType::TypeMixed)); } { // bug: https://bugs.kde.org/show_bug.cgi?id=237110 TopDUContext* top = parse("localDeclarations().count(), 3); QCOMPARE(top->localDeclarations().at(1)->qualifiedIdentifier(), QualifiedIdentifier("b")); qDebug() << top->localDeclarations().at(1)->toString(); QVERIFY(top->localDeclarations().at(1)->abstractType().cast()); QCOMPARE(top->localDeclarations().at(1)->abstractType().cast()->dataType(), static_cast(IntegralType::TypeMixed)); QCOMPARE(top->localDeclarations().at(2)->qualifiedIdentifier(), QualifiedIdentifier("c")); QVERIFY(top->localDeclarations().at(2)->abstractType().cast()); QCOMPARE(top->localDeclarations().at(2)->abstractType().cast()->qualifiedIdentifier().toString(), QString("stdclass")); } } void TestDUChain::php4StyleConstructor() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("bb(); } } "); TopDUContext* top = parse(method, DumpAll); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); Declaration* dec = top->childContexts().first()->localDeclarations().at(0); QVERIFY(dec); QCOMPARE(dec->qualifiedIdentifier(), QualifiedIdentifier("aa::aa")); ClassFunctionDeclaration* classFuncDec = dynamic_cast(dec); QVERIFY(classFuncDec); QVERIFY(classFuncDec->isConstructor()); } void TestDUChain::constructor() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 { QByteArray method("childContexts().first()->localDeclarations().at(0); QVERIFY(dec); ClassFunctionDeclaration* classFuncDec = dynamic_cast(dec); QVERIFY(classFuncDec); QVERIFY(!classFuncDec->isDestructor()); QVERIFY(classFuncDec->isConstructor()); } { QByteArray method("childContexts().first()->localDeclarations().at(0); QVERIFY(dec); ClassFunctionDeclaration* classFuncDec = dynamic_cast(dec); QVERIFY(classFuncDec); QVERIFY(!classFuncDec->isDestructor()); QVERIFY(classFuncDec->isConstructor()); } } void TestDUChain::destructor() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("childContexts().first()->localDeclarations().at(0); QVERIFY(dec); ClassFunctionDeclaration* classFuncDec = dynamic_cast(dec); QVERIFY(classFuncDec); QVERIFY(classFuncDec->isDestructor()); QVERIFY(!classFuncDec->isConstructor()); } void TestDUChain::functionInFunction() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().at(0)->qualifiedIdentifier(), QualifiedIdentifier("aaa")); } void TestDUChain::objectWithClassName() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("foo();"); TopDUContext* top = parse(method, DumpNone, QUrl(QStringLiteral("file:///internal/testObjectWithClassName.php"))); DUChainReleaser releaseTop(top); // update top (the pointer will be the same) QByteArray method2("foo();"); TopDUContext* top2 = parse(method2, DumpNone, QUrl(QStringLiteral("file:///internal/testObjectWithClassName.php"))); QVERIFY(top2 == top); } void TestDUChain::largeNumberOfDeclarations() { TopDUContext* top = new TopDUContext(IndexedString(QUrl(QStringLiteral("file:///internal/testurl"))), RangeInRevision(0, 0, 6000, 0), nullptr); DUChain::self()->addDocumentChain(top); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); for (int i = 0; i < 6000; ++i) { RangeInRevision newRange(i, 0, i, 1); auto* dec = new Declaration(newRange, top); dec->setIdentifier(Identifier(QStringLiteral("dec%0").arg(i))); dec->setAbstractType(AbstractType::Ptr(nullptr)); } } void TestDUChain::staticVariable() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("childContexts().at(1)->localDeclarations().count(), 6); QCOMPARE(top->childContexts().at(1)->localDeclarations().first()->qualifiedIdentifier(), QualifiedIdentifier("aaa::foo")); QVERIFY(top->childContexts().at(1)->localDeclarations().first()->type()); QCOMPARE(top->childContexts().at(1)->localDeclarations().first()->type()->dataType(), (uint)IntegralType::TypeMixed); QCOMPARE(top->childContexts().at(1)->localDeclarations().at(1)->qualifiedIdentifier(), QualifiedIdentifier("aaa::bar")); QVERIFY(top->childContexts().at(1)->localDeclarations().at(1)->type()); QCOMPARE(top->childContexts().at(1)->localDeclarations().at(1)->type()->dataType(), (uint)IntegralType::TypeInt); QCOMPARE(top->childContexts().at(1)->localDeclarations().at(2)->qualifiedIdentifier(), QualifiedIdentifier("aaa::baz")); QVERIFY(top->childContexts().at(1)->localDeclarations().at(2)->type()); QCOMPARE(top->childContexts().at(1)->localDeclarations().at(2)->type()->dataType(), (uint)IntegralType::TypeString); QVERIFY(top->childContexts().at(1)->localDeclarations().at(3)->type()); QCOMPARE(top->childContexts().at(1)->localDeclarations().at(3)->type()->dataType(), (uint)IntegralType::TypeArray); QVERIFY(top->childContexts().at(1)->localDeclarations().at(4)->type()); QCOMPARE(top->childContexts().at(1)->localDeclarations().at(4)->type()->dataType(), (uint)IntegralType::TypeInt); QVERIFY(top->childContexts().at(1)->localDeclarations().at(5)->type()); QCOMPARE(top->childContexts().at(1)->localDeclarations().at(5)->type()->dataType(), (uint)IntegralType::TypeInt); } void TestDUChain::returnTypeTwoDeclarations() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().at(0); FunctionType::Ptr functionType = dec->type(); QVERIFY(functionType); UnsureType::Ptr retType = UnsureType::Ptr::dynamicCast(functionType->returnType()); QVERIFY(retType); QCOMPARE(retType->typesSize(), 2u); QVERIFY(retType->types()[0].abstractType().cast()); QCOMPARE(retType->types()[0].abstractType().cast()->dataType(), (uint)IntegralType::TypeString); QVERIFY(retType->types()[1].abstractType().cast()); QCOMPARE(retType->types()[1].abstractType().cast()->dataType(), (uint)IntegralType::TypeInt); } void TestDUChain::globalVariableNotVisibleInFunction() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("findDeclarations(QualifiedIdentifier("a")).first()->uses().count(), 0); } void TestDUChain::globalVariableInFunction() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("findDeclarations(QualifiedIdentifier("a")).first()->uses().count(), 1); } void TestDUChain::nonGlobalVariableInFunction() { // bug: https://bugs.kde.org/show_bug.cgi?id=240920 // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("findLocalDeclarations(Identifier("a")).count(), 1); QCOMPARE(top->findLocalDeclarations(Identifier("a")).first()->uses().count(), 0); QCOMPARE(top->childContexts().count(), 2); QCOMPARE(top->childContexts().last()->findLocalDeclarations(Identifier("a")).count(), 1); QCOMPARE(top->childContexts().last()->findLocalDeclarations(Identifier("a")).first()->uses().count(), 0); } void TestDUChain::superglobalInFunction() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("findDeclarations(QualifiedIdentifier("_GET")).count(), 1); Declaration* dec = top->findDeclarations(QualifiedIdentifier(QStringLiteral("_GET"))).first(); QVERIFY(dynamic_cast(dec)); QVERIFY(static_cast(dec)->isSuperglobal()); QCOMPARE(dec->uses().keys().count(), 1); QCOMPARE(dec->uses().values().count(), 1); QCOMPARE(dec->uses().values().first().count(), 2); QCOMPARE(dec->uses().values().first().first(), RangeInRevision(0, 3, 0, 8)); QCOMPARE(dec->uses().values().first().at(1), RangeInRevision(0, 27, 0, 32)); } void TestDUChain::returnWithoutFunction() { //yes, this is possible in php, you then have $a as return value of an include call QByteArray method("localDeclarations().at(2)->internalContext()->importedParentContexts().empty()); QCOMPARE(top->localDeclarations().at(1)->internalContext()->importedParentContexts().count(), 1); QCOMPARE(top->localDeclarations().at(1)->internalContext()->importedParentContexts().first().context(top), top->localDeclarations().at(2)->internalContext()); QCOMPARE(top->localDeclarations().at(0)->internalContext()->importedParentContexts().count(), 1); QCOMPARE(top->localDeclarations().at(0)->internalContext()->importedParentContexts().first().context(top), top->localDeclarations().at(1)->internalContext()); } void TestDUChain::circularInterface() { QByteArray method("problems().count(), 0); QVERIFY(top->localDeclarations().at(0)->internalContext()->importedParentContexts().empty()); QCOMPARE(top->localDeclarations().at(1)->internalContext()->importedParentContexts().count(), 1); QCOMPARE(top->localDeclarations().at(1)->internalContext()->importedParentContexts().first().context(top), top->localDeclarations().at(0)->internalContext()); QCOMPARE(top->localDeclarations().at(2)->internalContext()->importedParentContexts().count(), 1); QCOMPARE(top->localDeclarations().at(2)->internalContext()->importedParentContexts().first().context(top), top->localDeclarations().at(1)->internalContext()); } void TestDUChain::findDeclarations() { DUChainWriteLocker lock(DUChain::lock()); TopDUContext* top1 = new TopDUContext(IndexedString(QUrl(QStringLiteral("file:///internal/testfile1"))), RangeInRevision(0, 0, 0, 10), nullptr); DUChainReleaser releaseTop1(top1); DUChain::self()->addDocumentChain(top1); TopDUContext* top2 = new TopDUContext(IndexedString(QUrl(QStringLiteral("file:///internal/testfile2"))), RangeInRevision(0, 0, 0, 10), nullptr); DUChainReleaser releaseTop2(top2); DUChain::self()->addDocumentChain(top2); Declaration* declaration = new Declaration(RangeInRevision(0, 0, 0, 3), top1); declaration->setIdentifier(Identifier(QStringLiteral("foo"))); QVERIFY(!top1->usingImportsCache()); QVERIFY(!top2->usingImportsCache()); QCOMPARE(1, top1->findDeclarations(Identifier("foo")).count()); QCOMPARE(0, top2->findDeclarations(Identifier("foo")).count()); top2->addImportedParentContext(top1); QVERIFY(!top1->usingImportsCache()); QVERIFY(!top2->usingImportsCache()); QCOMPARE(1, top2->findDeclarations(Identifier("foo")).count()); top2->clearImportedParentContexts(); QCOMPARE(top2->importedParentContexts().size(), 0); QVERIFY(!top1->usingImportsCache()); QVERIFY(!top2->usingImportsCache()); QCOMPARE(0, top2->findDeclarations(Identifier("foo")).count()); top2->addImportedParentContext(top1); QCOMPARE(1, top2->findDeclarations(Identifier("foo")).count()); } void TestDUChain::memberTypeAfterMethod() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("childContexts().first(); // function foo { ClassMemberDeclaration* var = dynamic_cast(contextClassA->localDeclarations().first()); QVERIFY(var); QCOMPARE(var->identifier(), Identifier("foo")); QCOMPARE(var->accessPolicy(), Declaration::Public); QCOMPARE(var->isStatic(), false); QVERIFY(var->type()); IntegralType::Ptr ret = var->type()->returnType().cast(); QVERIFY(ret); QVERIFY(ret->dataType() == IntegralType::TypeVoid); } // public $bar { ClassMemberDeclaration* var = dynamic_cast(contextClassA->localDeclarations().at(1)); QVERIFY(var); QCOMPARE(var->identifier(), Identifier("bar")); QCOMPARE(var->accessPolicy(), Declaration::Public); QCOMPARE(var->isStatic(), false); QVERIFY(var->type()); QVERIFY(var->type()->dataType() == IntegralType::TypeNull); } } void TestDUChain::catchDeclaration() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("(top->localDeclarations().first()); QVERIFY(ex); QCOMPARE(ex->identifier(), Identifier("e")); QVERIFY(ex->type()); QCOMPARE(QualifiedIdentifier("exception"), ex->type()->declaration(top)->qualifiedIdentifier()); } void TestDUChain::resourceType() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("(top->localDeclarations().first()); QVERIFY(fun); FunctionType::Ptr ftype = FunctionType::Ptr::dynamicCast(fun->abstractType()); QVERIFY(ftype); IntegralType::Ptr rtype = IntegralType::Ptr::dynamicCast(ftype->returnType()); QVERIFY(rtype); QCOMPARE(rtype->toString(), QString("resource")); QVERIFY(rtype->dataType() == IntegralTypeExtended::TypeResource); } void TestDUChain::foreachIterator() { QByteArray code; code.append("localDeclarations().at(3); QCOMPARE(iDec->qualifiedIdentifier(), QualifiedIdentifier("i")); QVERIFY(iDec->type()); QCOMPARE(iDec->type()->qualifiedIdentifier(), QualifiedIdentifier("b")); QVERIFY(top->localDeclarations().first() == iDec->type()->declaration(top)); } void TestDUChain::foreachIterator2() { QByteArray code; code.append("localDeclarations().size(), 3); Declaration* iDec = top->localDeclarations().at(2); QCOMPARE(iDec->qualifiedIdentifier(), QualifiedIdentifier("i")); qDebug() << iDec->abstractType()->toString(); QVERIFY(iDec->type()); QCOMPARE(iDec->type()->qualifiedIdentifier(), QualifiedIdentifier("b")); QVERIFY(top->localDeclarations().first() == iDec->type()->declaration(top)); } void TestDUChain::foreachIterator3() { QByteArray code; code.append("localDeclarations().at(3); QCOMPARE(iDec->qualifiedIdentifier(), QualifiedIdentifier("i")); QVERIFY(iDec->type()); QCOMPARE(iDec->type()->qualifiedIdentifier(), QualifiedIdentifier("b")); QVERIFY(top->localDeclarations().first() == iDec->type()->declaration(top)); } void TestDUChain::foreachIterator4() { // see also: https://bugs.kde.org/show_bug.cgi?id=276603 QByteArray code = "i){}\n" " foreach(array(1,2) as $this->k => $this->v){}\n" " foreach(array(1,2) as A::$s){}\n" " }\n" "}\n"; TopDUContext* top = parse(code, DumpNone); DUChainReleaser releaseTop(top); DUChainWriteLocker lock; QVERIFY(top->problems().isEmpty()); Declaration* aDec = top->localDeclarations().first(); DUContext* fooCtx = top->childContexts().first()->childContexts().last(); QVERIFY(fooCtx->owner()); QCOMPARE(aDec->uses().size(), 1); QCOMPARE(aDec->uses().begin()->size(), 4); } void TestDUChain::returnThis() { QByteArray code("childContexts().first()->localDeclarations().first(); QVERIFY(dec->type()); AbstractType::Ptr t = dec->type()->returnType(); qDebug() << t->toString(); QVERIFY(StructureType::Ptr::dynamicCast(t)); QVERIFY(StructureType::Ptr::dynamicCast(t)->declaration(top) == top->localDeclarations().first()); } void TestDUChain::unsureReturnType() { QByteArray code("localDeclarations().first(); QVERIFY(dec->type()); TypePtr ut = dec->type()->returnType().cast(); QVERIFY(ut); QCOMPARE((uint)2, ut->typesSize()); QVERIFY(ut->types()[0].type()); QVERIFY(ut->types()[0].type()->dataType() == IntegralType::TypeBoolean); QVERIFY(ut->types()[1].type()); QVERIFY(ut->types()[1].type()->dataType() == IntegralType::TypeInt); } void TestDUChain::unsureReturnType2() { QByteArray code("localDeclarations().at(2); QVERIFY(dec->type()); TypePtr ut = dec->type()->returnType().cast(); QVERIFY(ut); QCOMPARE((uint)2, ut->typesSize()); QVERIFY(ut->types()[0].type()); QCOMPARE(ut->types()[0].type()->toString(), QString("A")); QVERIFY(ut->types()[1].type()); QCOMPARE(ut->types()[1].type()->toString(), QString("B")); } void TestDUChain::unsureReturnType3() { QByteArray code("localDeclarations().at(0); QVERIFY(dec->type()); qDebug() << dec->type()->returnType()->toString(); TypePtr ut = dec->type()->returnType().cast(); QVERIFY(ut); QCOMPARE((uint)3, ut->typesSize()); QVERIFY(ut->types()[0].type()); QVERIFY(ut->types()[0].type()->dataType() == IntegralType::TypeInt); QVERIFY(ut->types()[1].type()); QVERIFY(ut->types()[1].type()->dataType() == IntegralType::TypeBoolean); QVERIFY(ut->types()[2].type()); QVERIFY(ut->types()[2].type()->dataType() == IntegralType::TypeString); } void TestDUChain::unsureReturnType4() { QByteArray code("localDeclarations().first(); QVERIFY(dec->type()); TypePtr ut = dec->type()->returnType().cast(); QVERIFY(ut); QCOMPARE((uint)2, ut->typesSize()); QVERIFY(ut->types()[0].type()); QVERIFY(ut->types()[0].type()->dataType() == IntegralType::TypeBoolean); QVERIFY(ut->types()[1].type()); QVERIFY(ut->types()[1].type()->dataType() == IntegralType::TypeInt); } void TestDUChain::referencedArgument() { // php does not return references QByteArray code("localDeclarations().first(); QVERIFY(dec->type()); qDebug() << dec->abstractType()->toString(); IntegralType::Ptr aType = dec->type()->returnType().cast(); QVERIFY(aType); QCOMPARE(aType->dataType(), (uint)IntegralType::TypeInt); QCOMPARE(top->childContexts().first()->type(), DUContext::Function); ReferenceType::Ptr rType = top->childContexts().first()->localDeclarations().first()->abstractType().cast(); QVERIFY(rType); QVERIFY(rType->baseType()->equals(aType.data())); } void TestDUChain::unsureReferencedArgument() { // php does not return references QByteArray code("localDeclarations().first(); QVERIFY(dec->type()); qDebug() << dec->abstractType()->toString(); UnsureType::Ptr aType = dec->type()->returnType().cast(); QVERIFY(aType); QCOMPARE(aType->typesSize(), 2u); QCOMPARE(aType->types()[0].abstractType().cast()->dataType(), (uint)IntegralType::TypeInt); QCOMPARE(aType->types()[1].abstractType().cast()->dataType(), (uint)IntegralType::TypeString); QCOMPARE(top->childContexts().first()->type(), DUContext::Function); ReferenceType::Ptr rType = top->childContexts().first()->localDeclarations().first()->abstractType().cast(); QVERIFY(rType); QVERIFY(rType->baseType()->equals(aType.data())); } void TestDUChain::defaultArgument() { // php does not return references QByteArray code("childContexts().first()->localDeclarations().first(); QVERIFY(dec->type()); QCOMPARE(dec->type()->dataType(), (uint)IntegralType::TypeInt); } void TestDUChain::declareMemberOutOfClass() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray code("asdf = true; $bar->asdf = false;\n" // not allowed: "$bar->prot = 1;\n" // not allowed: "$bar->priv = 1;"); TopDUContext* top = parse(code, DumpAST); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); { // $bar is only declared once QList decs = top->findLocalDeclarations(Identifier(QStringLiteral("bar"))); QCOMPARE(decs.size(), 1); Declaration *dec = decs.first(); QVERIFY(dec->type()); QVERIFY(dec->type()->declaration(top)->identifier().nameEquals(Identifier("foo"))); // while we are at it, compare uses QCOMPARE(dec->uses().keys().count(), 1); QCOMPARE(dec->uses().values().count(), 1); QCOMPARE(dec->uses().values().first().count(), 4); qDebug() << dec->uses().values().first().at(0).castToSimpleRange(); QCOMPARE(dec->uses().values().first().at(0), RangeInRevision(1, 16, 1, 20)); qDebug() << dec->uses().values().first().at(1).castToSimpleRange(); QCOMPARE(dec->uses().values().first().at(1), RangeInRevision(1, 35, 1, 39)); qDebug() << dec->uses().values().first().at(2).castToSimpleRange(); QCOMPARE(dec->uses().values().first().at(2), RangeInRevision(2, 0, 2, 4)); qDebug() << dec->uses().values().first().at(3).castToSimpleRange(); QCOMPARE(dec->uses().values().first().at(3), RangeInRevision(3, 0, 3, 4)); } { // check if asdf got declared QList decs = top->childContexts().first()->findDeclarations(Identifier(QStringLiteral("asdf"))); // the type of both assignments to $bar->asdf are the same, hence it should only be declared once QCOMPARE(decs.size(), 1); ClassMemberDeclaration* cmdec = dynamic_cast(decs.first()); QVERIFY(cmdec); QVERIFY(cmdec->accessPolicy() == Declaration::Public); QVERIFY(!cmdec->isStatic()); QVERIFY(cmdec->type()); QVERIFY(cmdec->type()->dataType() == IntegralType::TypeBoolean); } // check that prot and priv don't get redeclared QCOMPARE(top->problems().count(), 2); QCOMPARE(top->problems().at(0)->finalLocation().start().line(), 2); QCOMPARE(top->problems().at(1)->finalLocation().start().line(), 3); } void TestDUChain::declareMemberOutOfClass2() { // see also: https://bugs.kde.org/show_bug.cgi?id=283356 // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray code("x = 1;\n" "class A { var $x = 1; }"); TopDUContext* top = parse(code, DumpAST); QVERIFY(top); // update top = parse(code, DumpNone, top->url().toUrl(), ReferencedTopDUContext(top)); DUChainReleaser releaseTop(top); DUChainWriteLocker lock; QVERIFY(top->problems().isEmpty()); QList decs = top->findLocalDeclarations(Identifier(QStringLiteral("a"))); QCOMPARE(decs.size(), 2); { Declaration *dec = decs.first(); QVERIFY(dynamic_cast(dec)); QVERIFY(dec->type()); QVERIFY(dec->type()->declaration(top)->identifier().nameEquals(Identifier("a"))); } { Declaration *dec = decs.last(); QVERIFY(dynamic_cast(dec)); QVERIFY(dec->type()); QVERIFY(dec->type()->declaration(top)->identifier().nameEquals(Identifier("a"))); } { // check if x got declared QList decs = top->childContexts().first()->findDeclarations(Identifier(QStringLiteral("x"))); // the type of both assignments to $a->x are the same, hence it should only be declared once QCOMPARE(decs.size(), 1); ClassMemberDeclaration* cmdec = dynamic_cast(decs.first()); QVERIFY(cmdec); QVERIFY(cmdec->accessPolicy() == Declaration::Public); QVERIFY(!cmdec->isStatic()); QVERIFY(cmdec->type()); QCOMPARE(cmdec->type()->dataType(), (uint) IntegralType::TypeInt); } } void TestDUChain::declareMemberInClassMethod() { QByteArray code("asdf = true; $this->asdf = false; }\n" // should only declare bar once as private " private $xyz = 0; function test2() { $this->xyz = 42; }\n" // should create a local declaration for the private attribute " function test3() { $this->prot = 42;\n$this->priv = 42; }\n" " }"); TopDUContext* top = parse(code, DumpAST); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); { // asdf QList decs = top->childContexts().last()->findLocalDeclarations(Identifier(QStringLiteral("asdf"))); QCOMPARE(decs.size(), 1); ClassMemberDeclaration *dec = dynamic_cast(decs.first()); QVERIFY(dec); QVERIFY(dec->accessPolicy() == Declaration::Public); QVERIFY(!dec->isStatic()); QVERIFY(dec->type()); QVERIFY(dec->type()->dataType() == IntegralType::TypeBoolean); } { // xyz QList decs = top->childContexts().last()->findLocalDeclarations(Identifier(QStringLiteral("xyz"))); QCOMPARE(decs.size(), 1); ClassMemberDeclaration *dec = dynamic_cast(decs.first()); QVERIFY(dec); QVERIFY(dec->accessPolicy() == Declaration::Private); QVERIFY(!dec->isStatic()); QVERIFY(dec->type()); QVERIFY(dec->type()->dataType() == IntegralType::TypeInt); } { // priv QList decs = top->childContexts().last()->findLocalDeclarations(Identifier(QStringLiteral("priv"))); QCOMPARE(decs.size(), 1); ClassMemberDeclaration *dec = dynamic_cast(decs.first()); QVERIFY(dec); QVERIFY(dec->accessPolicy() == Declaration::Public); QVERIFY(!dec->isStatic()); QVERIFY(dec->type()); QVERIFY(dec->type()->dataType() == IntegralType::TypeInt); } { // prot QVERIFY(top->childContexts().last()->findLocalDeclarations(Identifier("prot")).isEmpty()); } QCOMPARE(top->problems().count(), 0); } void TestDUChain::thisRedeclaration() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray code("test = true; $this = false;} }"); TopDUContext* top = parse(code, DumpAST); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); // only $this = false is a problem, $this->test = true is perfectly valid QCOMPARE(top->problems().count(), 1); qDebug() << top->problems().first()->finalLocation(); QVERIFY(top->problems().first()->finalLocation() == KDevelop::DocumentRange(top->url(), KTextEditor::Range(0, 50, 0, 55))); } void TestDUChain::implicitArrayDeclaration() { ///TODO: adapt to unsure type once it's supported { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray code(" decs = top->findDeclarations(Identifier(QStringLiteral("a"))); QCOMPARE(decs.size(), 1); VariableDeclaration* vdec = dynamic_cast(decs.first()); QVERIFY(vdec); QVERIFY(vdec->type()); QVERIFY(vdec->type()->dataType() == IntegralType::TypeArray); } { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray code(" decs = top->findDeclarations(Identifier(QStringLiteral("a"))); QCOMPARE(decs.size(), 1); VariableDeclaration* vdec = dynamic_cast(decs.first()); QVERIFY(vdec); QVERIFY(vdec->type()); QVERIFY(vdec->type()->dataType() == IntegralType::TypeArray); } { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray code("a[1] = true;"); TopDUContext* top = parse(code, DumpAST); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); QList decs = top->childContexts().first()->findDeclarations(Identifier(QStringLiteral("a"))); QCOMPARE(decs.size(), 1); ClassMemberDeclaration* cmdec = dynamic_cast(decs.first()); QVERIFY(cmdec); QVERIFY(cmdec->type()); QVERIFY(cmdec->type()->dataType() == IntegralType::TypeArray); } { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray code("a[$b] = true;"); TopDUContext* top = parse(code, DumpAST); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); QList decs = top->childContexts().first()->findDeclarations(Identifier(QStringLiteral("a"))); QCOMPARE(decs.size(), 1); ClassMemberDeclaration* cmdec = dynamic_cast(decs.first()); QVERIFY(cmdec); QVERIFY(cmdec->type()); QVERIFY(cmdec->type()->dataType() == IntegralType::TypeArray); } } void TestDUChain::implicitReferenceDeclaration() { { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray code(" decs = top->findDeclarations(Identifier(QStringLiteral("bar"))); QCOMPARE(decs.size(), 1); QVERIFY(dynamic_cast(decs.first())); QVERIFY(decs.first()->type()); qDebug() << decs.first()->type()->dataType() << decs.first()->toString(); QVERIFY(decs.first()->type()->dataType() == IntegralType::TypeNull); } { // a user reported a crash with the code example below // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray code("a);} }"); TopDUContext* top = parse(code, DumpAST); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); QVERIFY( top->childContexts().last()->localScopeIdentifier() == QualifiedIdentifier("foo")); // a is already declared QList decs = top->childContexts().last()->findDeclarations(Identifier(QStringLiteral("a"))); QCOMPARE(decs.size(), 1); ClassMemberDeclaration* cmdec = dynamic_cast(decs.first()); QVERIFY(cmdec); QVERIFY(cmdec->type()); qDebug() << cmdec->type()->dataType() << cmdec->toString(); QVERIFY(cmdec->type()->dataType() == IntegralType::TypeNull); } } void TestDUChain::classContextRange() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray code(" foobar = 1; $a->barFoo= 0;"); TopDUContext* top = parse(code, DumpAST); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(top->childContexts().first()->range(), KDevelop::RangeInRevision(0, 6, 0, 17)); QCOMPARE(top->childContexts().first()->localDeclarations().count(), 2); } void TestDUChain::lateClassMembers() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray code("val = 'b'; } private $val = 'a'; } "); TopDUContext* top = parse(code, DumpAST); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); ClassDeclaration* cdec = dynamic_cast(top->localDeclarations().first()); QVERIFY(cdec); QList decs = cdec->logicalInternalContext(top)->findDeclarations(Identifier(QStringLiteral("val"))); QCOMPARE(decs.count(), 1); ClassMemberDeclaration* cmdec = dynamic_cast(decs.first()); QVERIFY(cmdec); QCOMPARE(cmdec->accessPolicy(), Declaration::Private); } void TestDUChain::list() { foreach ( const QString& code, QStringList() << " decs = top->findDeclarations(Identifier(identifier)); QCOMPARE(decs.size(), 1); Declaration *dec = decs.first(); QVERIFY(dec->type()); QCOMPARE(dec->type()->dataType(), (uint) IntegralType::TypeMixed); ///TODO: support arrays better and compare to actual type } } } void TestDUChain::alternateDocCommentTypeHints() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("(top->localDeclarations().first()); QVERIFY(cdec); QVERIFY(cdec->type()); QVector decs = cdec->logicalInternalContext(top)->localDeclarations(); QCOMPARE(decs.size(), 1); Declaration *dec = decs.first(); QVERIFY(dec->type()); QCOMPARE(dec->type()->declaration(top), cdec); } void TestDUChain::findFunctionArgs() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("(top->localDeclarations().first()); QVERIFY(funcDec); QVERIFY(funcDec->internalContext()); QVERIFY(funcDec->internalFunctionContext()); QVERIFY(funcDec->internalContext()->imports(funcDec->internalFunctionContext())); QList decs; foreach ( Declaration* arg, funcDec->internalFunctionContext()->localDeclarations() ) { decs = funcDec->internalContext()->findDeclarations(arg->identifier()); QCOMPARE(decs.size(), 1); decs = funcDec->internalContext()->findDeclarations(arg->qualifiedIdentifier()); qDebug() << arg->qualifiedIdentifier().toString(); QEXPECT_FAIL("", "strangely the arg dec is only found with its identifier, not by its qualifiedidentifier...", Continue); QCOMPARE(decs.size(), 1); } } void TestDUChain::undeclaredPropertyInString() { // testcase for bug 209814 // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("baz\"; } }", DumpNone); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(top->childContexts().size(), 1); DUContext* classCtx = top->childContexts().first(); QVERIFY(classCtx->type() == DUContext::Class); QCOMPARE(classCtx->localDeclarations().size(), 2); QCOMPARE(classCtx->findDeclarations(Identifier("foo")).size(), 1); QCOMPARE(classCtx->findDeclarations(Identifier("bar")).size(), 1); } void TestDUChain::undeclaredVarPropertyInString() { // testcase for bug 210043 // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("baz\";", DumpNone); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); // just don't crash } void TestDUChain::upcommingClassInString() { // testcase for bug 232687 // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("blah\";\n" " }\n" "}\n" "class B {\n" " var $blah;\n" "}\n", DumpNone); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); // just don't crash } void TestDUChain::namespaces() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("problems().count(), 0); QCOMPARE(top->childContexts().size(), 4); QCOMPARE(top->childContexts().at(0)->localScopeIdentifier().toString(), QString("asdf")); QCOMPARE(top->childContexts().at(1)->localScopeIdentifier().toString(), QString("ns1")); QCOMPARE(top->childContexts().at(2)->type(), DUContext::Function); QCOMPARE(top->childContexts().at(3)->localScopeIdentifier().toString(), QString("a")); QCOMPARE(top->localDeclarations().size(), 3); QCOMPARE(top->localDeclarations().at(0)->kind(), Declaration::Namespace); QCOMPARE(top->localDeclarations().at(1)->kind(), Declaration::Namespace); QCOMPARE(top->localDeclarations().at(2)->kind(), Declaration::Type); QCOMPARE(top->findDeclarations(QualifiedIdentifier("asdf")).size(), 1); QCOMPARE(top->childContexts().at(0)->localDeclarations().size(), 3); QCOMPARE(top->findDeclarations(QualifiedIdentifier("asdf::a")).size(), 1); QCOMPARE(top->findDeclarations(QualifiedIdentifier("asdf::b")).size(), 1); QCOMPARE(top->findDeclarations(QualifiedIdentifier("asdf::c")).size(), 1); QCOMPARE(top->findDeclarations(QualifiedIdentifier("ns1")).size(), 1); QCOMPARE(top->childContexts().at(1)->localDeclarations().size(), 1); QCOMPARE(top->childContexts().at(1)->localDeclarations().first()->kind(), Declaration::Namespace); ///TODO: support \ as separator QCOMPARE(top->childContexts().at(1)->localDeclarations().first()->qualifiedIdentifier().toString(), QString("ns1::ns2")); QCOMPARE(top->findDeclarations(QualifiedIdentifier("ns1::ns2")).size(), 1); QCOMPARE(top->findDeclarations(QualifiedIdentifier("ns1::ns2")).first()->logicalInternalContext(top)->localDeclarations().size(), 3); QCOMPARE(top->childContexts().at(1)->childContexts().size(), 1); QCOMPARE(top->childContexts().at(1)->childContexts().first()->localDeclarations().size(), 3); QCOMPARE(top->findDeclarations(QualifiedIdentifier("ns1::ns2")).first()->logicalInternalContext(top)->localDeclarations().first()->qualifiedIdentifier().toString(), QString("ns1::ns2::a")); QCOMPARE(top->findDeclarations(QualifiedIdentifier("ns1::ns2::a")).size(), 1); QCOMPARE(top->findDeclarations(QualifiedIdentifier("ns1::ns2::b")).size(), 1); QCOMPARE(top->findDeclarations(QualifiedIdentifier("ns1::ns2::c")).size(), 1); QCOMPARE(top->findDeclarations(QualifiedIdentifier("a")).size(), 1); QCOMPARE(top->findDeclarations(QualifiedIdentifier("b")).size(), 0); QCOMPARE(top->findDeclarations(QualifiedIdentifier("c")).size(), 0); ///TODO: prevent redeclarations of namespaces } void TestDUChain::namespacesNoCurly() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("problems().count(), 0); foreach(ProblemPointer p, top->problems()) { qDebug() << p->description() << p->explanation() << p->finalLocation(); } QCOMPARE(top->childContexts().size(), 2); QCOMPARE(top->childContexts().at(0)->localScopeIdentifier().toString(), QString("asdf")); QCOMPARE(top->childContexts().at(1)->localScopeIdentifier().toString(), QString("ns1")); QCOMPARE(top->localDeclarations().size(), 2); QCOMPARE(top->localDeclarations().at(0)->kind(), Declaration::Namespace); QCOMPARE(top->localDeclarations().at(1)->kind(), Declaration::Namespace); } void TestDUChain::namespacesBaseType() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("problems().count(), 0); foreach(ProblemPointer p, top->problems()) { qDebug() << p->description() << p->explanation() << p->finalLocation(); } QCOMPARE(top->childContexts().size(), 1); QCOMPARE(top->childContexts().at(0)->localScopeIdentifier().toString(), QString("string")); QCOMPARE(top->localDeclarations().size(), 1); QCOMPARE(top->localDeclarations().at(0)->kind(), Declaration::Namespace); } void TestDUChain::useNamespace() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("localDeclarations().count(), 5); Declaration* dec = top->localDeclarations().at(2); QCOMPARE(dec->qualifiedIdentifier().toString(), QString("ns2")); QVERIFY(dynamic_cast(dec)); dec = top->localDeclarations().at(3); QCOMPARE(dec->qualifiedIdentifier().toString(), QString("ns5")); QVERIFY(dynamic_cast(dec)); dec = top->localDeclarations().at(4); QCOMPARE(dec->qualifiedIdentifier().toString(), QString("ns6")); QVERIFY(dynamic_cast(dec)); - ///TODO: find out why this is explictly required + ///TODO: find out why this is explicitly required QVERIFY(!dynamic_cast(dec)->importIdentifier().explicitlyGlobal()); } void TestDUChain::useBaseTypeNamespace() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("localDeclarations().count(), 4); Declaration* dec = top->localDeclarations().at(2); QCOMPARE(dec->qualifiedIdentifier().toString(), QString("string")); QVERIFY(dynamic_cast(dec)); dec = top->localDeclarations().at(3); QCOMPARE(dec->qualifiedIdentifier().toString(), QString("iterable")); QVERIFY(dynamic_cast(dec)); } void TestDUChain::useNamespaceBaseTypeAlias() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("problems().count(), 2); } void TestDUChain::namespaceStaticVar() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("problems().isEmpty()); Declaration* fooDec = top->findDeclarations(QualifiedIdentifier(QStringLiteral("ns::c::foo"))).first(); QVERIFY(fooDec); QVERIFY(!fooDec->uses().isEmpty()); QVERIFY(!fooDec->uses().begin()->isEmpty()); QCOMPARE(fooDec->uses().begin()->begin()->start.line, 5); } void TestDUChain::namespacedCatch() { // see also: https://bugs.kde.org/show_bug.cgi?id=281451 // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("problems().isEmpty()); Declaration* eDec = top->findDeclarations(QualifiedIdentifier(QStringLiteral("ns::e"))).first(); QVERIFY(eDec); QVERIFY(!eDec->uses().isEmpty()); QVERIFY(!eDec->uses().begin()->isEmpty()); QCOMPARE(eDec->uses().begin()->begin()->start.line, 6); } struct TestUse { TestUse(const QString& _id, Declaration::Kind _kind, int _uses) : id(_id), kind(_kind), uses(_uses) {} TestUse() {} QualifiedIdentifier id; Declaration::Kind kind; int uses; }; Q_DECLARE_METATYPE ( TestUse ) Q_DECLARE_METATYPE ( QList ) void TestDUChain::errorRecovery_data() { QTest::addColumn("code"); QTest::addColumn< QList >("usesMap"); QTest::newRow("conditional") << QStringLiteral("() << TestUse(QStringLiteral("a"), Declaration::Instance, 1)); QTest::newRow("namespace") << QStringLiteral("() << TestUse(QStringLiteral("foo"), Declaration::Namespace, 1) << TestUse(QStringLiteral("y"), Declaration::Namespace, 0) << TestUse(QStringLiteral("foo::a"), Declaration::Instance, 1)); QTest::newRow("class") << QStringLiteral("() << TestUse(QStringLiteral("foo"), Declaration::Type, 0) << TestUse(QStringLiteral("foo::bar"), Declaration::Instance, 1) << TestUse(QStringLiteral("foo::func"), Declaration::Type, 1) ); } void TestDUChain::errorRecovery() { QFETCH(QString, code); QFETCH(QList, usesMap); TopDUContext* top = parse(code.toLocal8Bit(), DumpAll); QVERIFY(top); DUChainReleaser releaseTop(top); DUChainWriteLocker lock; foreach ( const TestUse& use, usesMap ) { QList< Declaration* > decs = top->findDeclarations(use.id); QCOMPARE(decs.count(), 1); Declaration* dec = decs.first(); QCOMPARE(dec->kind(), use.kind); if (use.uses) { QCOMPARE(dec->uses().count(), 1); QCOMPARE(dec->uses().begin()->count(), use.uses); } } } void TestDUChain::varStatic() { //bug: https://bugs.kde.org/244076 // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("problems().empty()); // we cannot support anything though :( } void TestDUChain::staticNowdoc() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("problems().empty()); QCOMPARE(top->childContexts().first()->localDeclarations().count(), 2); QCOMPARE(top->childContexts().first()->localDeclarations().first()->type()->dataType(), static_cast(IntegralType::TypeString)); QCOMPARE(top->childContexts().first()->localDeclarations().last()->type()->dataType(), static_cast(IntegralType::TypeString)); } void TestDUChain::curlyVarAfterObj() { // bug: https://bugs.kde.org/show_bug.cgi?id=241645 // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("{$a->bar}();\n" "$a->{$a->asdf};\n" , DumpNone); QVERIFY(top); DUChainReleaser releaseTop(top); DUChainWriteLocker lock; QVERIFY(top->problems().empty()); } void TestDUChain::embeddedHTML_data() { QTest::addColumn("code"); QTest::newRow("if") << QStringLiteral("\n"); QTest::newRow("elseif") << QStringLiteral("\n\n"); QTest::newRow("foreach") << QStringLiteral("\n\n"); QTest::newRow("switch") << QStringLiteral("\n\n"); QTest::newRow("for") << QStringLiteral("\n\n"); QTest::newRow("while") << QStringLiteral("\n\n"); QTest::newRow("else") << QStringLiteral(""); } void TestDUChain::embeddedHTML() { QFETCH(QString, code); TopDUContext* top = parse(code.toLocal8Bit(), DumpNone); QVERIFY(top); DUChainReleaser releaseTop(top); DUChainWriteLocker lock; QVERIFY(top->problems().empty()); } void TestDUChain::cases() { // testcase for bug https://bugs.kde.org/show_bug.cgi?id=245832 TopDUContext* top = parse("problems().empty()); } void TestDUChain::closureParser() { // testcase for the parser after closures where introduced, // to make sure nothing brakes and all parser conflicts are resolved TopDUContext* top = parse("problems().empty()); } void TestDUChain::closures() { TopDUContext* top = parse("problems().isEmpty()); QCOMPARE(top->localDeclarations().count(), 2); Declaration* l = top->localDeclarations().first(); QCOMPARE(l->identifier().toString(), QString("l")); Declaration* closure = top->localDeclarations().last(); QVERIFY(closure->identifier().isEmpty()); FunctionType::Ptr funcType = closure->type(); QVERIFY(funcType); QCOMPARE(funcType->arguments().count(), 2); QVERIFY(funcType->arguments().at(0).cast()); QCOMPARE(funcType->arguments().at(0).cast()->dataType(), static_cast(IntegralType::TypeMixed)); QVERIFY(funcType->arguments().at(1).cast()); QCOMPARE(funcType->arguments().at(1).cast()->qualifiedIdentifier().toString(), QString("stdclass")); QVERIFY(funcType->returnType().cast()); QCOMPARE(funcType->returnType().cast()->dataType(), static_cast(IntegralType::TypeInt)); QVERIFY(l->abstractType()->equals(closure->abstractType().constData())); } void TestDUChain::closureEmptyUse() { // test case for: https://bugs.kde.org/show_bug.cgi?id=267105 // don't crash but report parse error TopDUContext* top = parse(" 2; };\n", DumpNone); QVERIFY(top); DUChainReleaser releaseTop(top); DUChainWriteLocker lock; QCOMPARE(top->problems().size(), 1); } void TestDUChain::iifeParser() { // testcase for bug https://bugs.kde.org/show_bug.cgi?id=370515 TopDUContext* top = parse("problems().empty()); } void TestDUChain::iife() { TopDUContext* top = parse("problems().isEmpty()); QCOMPARE(top->localDeclarations().count(), 2); Declaration* l = top->localDeclarations().first(); QCOMPARE(l->identifier().toString(), QString("l")); Declaration* iife = top->localDeclarations().last(); QVERIFY(iife->identifier().isEmpty()); } void TestDUChain::gotoTest() { TopDUContext* top = parse("problems().isEmpty()); ///TODO: create declaration for destination label ///TODO: create use for goto label ///TODO: report error when trying to jump into loop or switch statement } void TestDUChain::ternary() { TopDUContext* top = parse("problems().isEmpty()); } void TestDUChain::bug296709() { // see also: https://bugs.kde.org/show_bug.cgi?id=296709 // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse( "problems().isEmpty()); QList< Declaration* > decs = top->findLocalDeclarations(Identifier(QStringLiteral("a"))); QCOMPARE(decs.size(), 1); QCOMPARE(decs.at(0)->range(), RangeInRevision(1, 19, 1, 21)); QCOMPARE(decs.at(0)->uses().count(), 1); QCOMPARE(decs.at(0)->uses().begin()->count(), 1); QCOMPARE(decs.at(0)->uses().begin()->first(), RangeInRevision(2, 2, 2, 4)); } void TestDUChain::declareFinalMethod() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("parentContext()); QCOMPARE(top->childContexts().count(), 1); DUContext* contextClassA = top->childContexts().first(); Declaration* dec = contextClassA->localDeclarations().at(0); ClassFunctionDeclaration* funDec = dynamic_cast(dec); QVERIFY(funDec); QCOMPARE(funDec->qualifiedIdentifier(), QualifiedIdentifier("a::foo")); QVERIFY(funDec->isFinal()); } void Php::TestDUChain::testTodoExtractor() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("languageController()->completionSettings()->todoMarkerWords().contains("TODO")); QVERIFY(KDevelop::ICore::self()->languageController()->completionSettings()->todoMarkerWords().contains("FIXME")); TopDUContext* top = parse(method, DumpAll); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); QVERIFY(top); QCOMPARE(top->problems().size(), 2); QCOMPARE(top->problems().at(0)->description(), QString("TODO: bla")); QCOMPARE(top->problems().at(0)->range(), RangeInRevision(1, 3, 1, 12)); QCOMPARE(top->problems().at(1)->description(), QString("FIXME blub")); QCOMPARE(top->problems().at(1)->range(), RangeInRevision(2, 4, 2, 14)); } void TestDUChain::useThisAsArray() { QByteArray method("values[$offset]; }\n" " function offsetSet($offset, $value) { $this->values[$offset] = $value; }\n" " function offsetExists($offset) { return array_key_exists($offset, $this->values); }\n" " function offsetUnset($offset) { unset($this->values[$offset]); }\n" " function setTest() { $this['test'] = 'test'; } \n" " }\n"); TopDUContext* top = parse(method); QVERIFY(top); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(top->importedParentContexts().count(), 1); QVERIFY(DUChain::self()->chainForDocument(internalFunctionFile())); QCOMPARE(DUChain::self()->chainForDocument(internalFunctionFile()), top->importedParentContexts().first().context(top)); QVERIFY(top->problems().isEmpty()); } void TestDUChain::wrongUseOfThisAsArray() { // missing functions from \ArrayAccess and not declared abstract QByteArray method("problems().size(),1); } void TestDUChain::staticFunctionClassPhp54() { QByteArray method("problems().isEmpty()); QCOMPARE(top->localDeclarations().count(),1); Declaration* dec = top->localDeclarations().at(0); ClassDeclaration* classDec = dynamic_cast(dec); QCOMPARE(classDec->uses().count(),1); } void TestDUChain::functionArgumentUnpacking_data() { QTest::addColumn("code"); QTest::newRow("unpackVariable") << QStringLiteral("problems().isEmpty()); } void TestDUChain::illegalExpression_data() { QTest::addColumn("code"); QTest::newRow("equality expression") << QStringLiteral(" 2 > 1;\n"); QTest::newRow("double print expression") << QStringLiteral("foo;\n"); QTest::newRow("instanceof with class constant") << QStringLiteral("foo();\n"); QTest::newRow("instanceof with static method") << QStringLiteral("("code"); QTest::newRow("simple print expression") << QStringLiteral("problems().isEmpty()); } void TestDUChain::simpleExpression_data() { QTest::addColumn("code"); QTest::newRow("string concat") << QStringLiteral("problems().isEmpty()); } void TestDUChain::generatorAssignment() { //Note: in practice, Generator is defined by php, but this class is not loaded in this test, so define it ourselves // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("parentContext()); QCOMPARE(top->childContexts().count(), 3); QCOMPARE(top->localDeclarations().count(), 2); Declaration* dec = top->localDeclarations().at(1); QCOMPARE(dec->qualifiedIdentifier(), QualifiedIdentifier("foo")); FunctionType::Ptr functionType = dec->type(); QVERIFY(functionType); StructureType::Ptr retType = StructureType::Ptr::dynamicCast(functionType->returnType()); QVERIFY(retType); QCOMPARE(retType->qualifiedIdentifier(), QualifiedIdentifier("generator")); dec = top->childContexts().at(2)->localDeclarations().at(0); QCOMPARE(dec->identifier(), Identifier("a")); IntegralType::Ptr type = dec->type(); QVERIFY(type); QVERIFY(type->dataType() == IntegralType::TypeMixed); } void TestDUChain::generatorClosure() { //Note: in practice, Generator is defined by php, but this class is not loaded in this test, so define it ourselves // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("parentContext()); QCOMPARE(top->childContexts().count(), 3); QCOMPARE(top->localDeclarations().count(), 2); Declaration* dec = top->localDeclarations().at(1); QCOMPARE(dec->qualifiedIdentifier(), QualifiedIdentifier("foo")); FunctionType::Ptr functionType = dec->type(); QVERIFY(functionType); IntegralType::Ptr retType = IntegralType::Ptr::dynamicCast(functionType->returnType()); QVERIFY(retType); QVERIFY(retType->dataType() == IntegralType::TypeVoid); dec = top->childContexts().at(2)->localDeclarations().at(0); QCOMPARE(dec->identifier(), Identifier("a")); Declaration* closure = top->childContexts().at(2)->localDeclarations().at(1); QVERIFY(closure->identifier().isEmpty()); FunctionType::Ptr funcType = closure->type(); QVERIFY(funcType); QVERIFY(funcType->returnType().cast()); QCOMPARE(funcType->returnType().cast()->dataType(), static_cast(IntegralType::TypeVoid)); QVERIFY(dec->abstractType()->equals(closure->abstractType().constData())); dec = top->childContexts().at(2)->childContexts().at(1)->localDeclarations().at(0); QCOMPARE(dec->identifier(), Identifier("b")); closure = top->childContexts().at(2)->childContexts().at(1)->localDeclarations().at(1); QVERIFY(closure->identifier().isEmpty()); funcType = closure->type(); QVERIFY(funcType); StructureType::Ptr generatorType = StructureType::Ptr::dynamicCast(funcType->returnType()); QVERIFY(generatorType); QCOMPARE(generatorType->qualifiedIdentifier(), QualifiedIdentifier("generator")); QVERIFY(dec->abstractType()->equals(closure->abstractType().constData())); }