File indexing completed on 2024-04-21 15:24:06
0001 /* 0002 SPDX-FileCopyrightText: 2007 David Nolden <david.nolden.kdevelop@art-master.de> 0003 SPDX-FileCopyrightText: 2008 Hamish Rodda <rodda@kde.org> 0004 SPDX-FileCopyrightText: 2008 Niko Sams <niko.sams@gmail.com> 0005 SPDX-FileCopyrightText: 2009 Milian Wolff <mail@milianw.de> 0006 0007 SPDX-License-Identifier: LGPL-2.0-only 0008 */ 0009 0010 #include "context.h" 0011 0012 #include <ktexteditor/view.h> 0013 #include <ktexteditor/document.h> 0014 #include <KLocalizedString> 0015 0016 #include <language/duchain/ducontext.h> 0017 #include <language/duchain/duchain.h> 0018 #include <language/duchain/duchainlock.h> 0019 #include <language/duchain/types/identifiedtype.h> 0020 #include <language/duchain/types/functiontype.h> 0021 #include <language/duchain/codemodel.h> 0022 #include <language/duchain/classdeclaration.h> 0023 #include <language/duchain/types/unsuretype.h> 0024 #include <language/duchain/parsingenvironment.h> 0025 #include <language/util/includeitem.h> 0026 #include <language/codecompletion/codecompletion.h> 0027 0028 #include <util/pushvalue.h> 0029 #include <util/path.h> 0030 0031 #include <interfaces/icore.h> 0032 #include <interfaces/iprojectcontroller.h> 0033 #include <interfaces/iproject.h> 0034 0035 #include <project/projectmodel.h> 0036 0037 #include "../duchain/completioncodemodel.h" 0038 #include "../duchain/expressionparser.h" 0039 #include "../duchain/helper.h" 0040 #include "../duchain/declarations/variabledeclaration.h" 0041 #include "../duchain/declarations/classmethoddeclaration.h" 0042 #include "../duchain/types/structuretype.h" 0043 0044 #include "../parser/phpparser.h" 0045 #include "../parser/phptokentext.h" 0046 0047 #include "includefileitem.h" 0048 #include "codemodelitem.h" 0049 #include "completiondebug.h" 0050 #include "helpers.h" 0051 #include "implementationitem.h" 0052 #include "keyworditem.h" 0053 0054 #include <KIO/Global> 0055 0056 #define LOCKDUCHAIN DUChainReadLocker lock(DUChain::lock()) 0057 0058 #define ifDebug(x) 0059 0060 using namespace KDevelop; 0061 0062 namespace Php 0063 { 0064 0065 typedef QList<Parser::TokenType> TokenList; 0066 0067 /** 0068 * Utility class which makes it easier to access the relevant parts 0069 * of the token stream for code completion. 0070 * 0071 * TODO: This class should be reviewed imo - I just hacked it together, quick'n'dirty 0072 */ 0073 class TokenAccess { 0074 public: 0075 /// Setup the token stream from the input code 0076 TokenAccess(const QString &code) 0077 : m_code(code) 0078 { 0079 0080 Lexer lexer(&m_stream, code); 0081 int token; 0082 while ((token = lexer.nextTokenKind())) { 0083 Parser::Token &t = m_stream.push(); 0084 t.begin = lexer.tokenBegin(); 0085 t.end = lexer.tokenEnd(); 0086 t.kind = token; 0087 } 0088 // move to last token 0089 m_pos = m_stream.size() - 1; 0090 } 0091 0092 /// returns Token_INVALID if the position is invalid 0093 /// else returns the type of the current token 0094 Parser::TokenType type() const { 0095 if ( m_pos == -1 ) { 0096 return Parser::Token_INVALID; 0097 } else { 0098 return (Parser::TokenType) m_stream.at(m_pos).kind; 0099 } 0100 } 0101 0102 /// convenience comparison to a tokentype 0103 bool operator==(const Parser::TokenType& other) const { 0104 return other == type(); 0105 } 0106 0107 /// move to previous token 0108 void pop() { 0109 if ( m_pos >= 0 ) { 0110 --m_pos; 0111 } 0112 } 0113 0114 /// move relative to current token 0115 /// NOTE: make sure you honor the boundaries. 0116 void moveTo(const qint64 &relPos) { 0117 m_pos += relPos; 0118 Q_ASSERT(m_pos > 0); 0119 Q_ASSERT(m_pos < m_stream.size()); 0120 } 0121 0122 /// get type of token relative to current position 0123 /// returns Token_INVALID if the position goes out of the boundaries 0124 int typeAt(const qint64 &relPos) const { 0125 const qint64 pos = m_pos + relPos; 0126 if ( pos >= 0 && pos < m_stream.size() ) { 0127 return m_stream.at(pos).kind; 0128 } else { 0129 return Parser::Token_INVALID; 0130 } 0131 } 0132 0133 /// Get string for token at a given position relative to the current one. 0134 /// NOTE: Make sure you honor the boundaries. 0135 QString stringAt(const qint64 &relPos) const { 0136 Parser::Token token = at(relPos); 0137 return m_code.mid(token.begin, token.end - token.begin + 1); 0138 } 0139 0140 /// check whether the current token is prepended by the list of tokens 0141 /// @return -1 when not prepended by the list, else the relative index-position 0142 qint64 prependedBy(const TokenList &list, bool skipWhitespace = false ) const { 0143 // this would be useless, hence forbid it 0144 Q_ASSERT ( !list.isEmpty() ); 0145 0146 if ( m_pos < list.count() - 1 ) { 0147 // not enough tokens 0148 return -1; 0149 } else { 0150 uint pos = 1; 0151 foreach ( Parser::TokenType type, list ) { 0152 if ( skipWhitespace && m_stream.at( m_pos - pos).kind == Parser::Token_WHITESPACE ) { 0153 ++pos; 0154 } 0155 if ( m_stream.at( m_pos - pos).kind == type ) { 0156 ++pos; 0157 continue; 0158 } else { 0159 return -1; 0160 } 0161 } 0162 return pos; 0163 } 0164 } 0165 0166 /// Get the token relative to the current one. 0167 /// NOTE: Make sure you honor the boundaries. 0168 Parser::Token at(const qint64 &relPos) const { 0169 const qint64 pos = m_pos + relPos; 0170 Q_ASSERT(pos >= 0); 0171 Q_ASSERT(pos < m_stream.size()); 0172 return m_stream.at(pos); 0173 } 0174 0175 private: 0176 const QString m_code; 0177 TokenStream m_stream; 0178 qint64 m_pos; 0179 }; 0180 0181 /** 0182 * Pops all tokens from the @p lastToken and stops at the LPAREN. 0183 */ 0184 void removeOtherArguments(TokenAccess &lastToken) 0185 { 0186 Q_ASSERT(lastToken.type() == Parser::Token_COMMA); 0187 0188 // remove all other arguments 0189 int openLParen = 0; 0190 do { 0191 lastToken.pop(); 0192 if ( lastToken.type() == Parser::Token_RPAREN ) { 0193 ++openLParen; 0194 } else if ( lastToken.type() == Parser::Token_LPAREN ) { 0195 if ( openLParen == 0 ) { 0196 return; 0197 } else { 0198 --openLParen; 0199 } 0200 } 0201 } while ( lastToken.type() != Parser::Token_INVALID ); 0202 } 0203 0204 /** 0205 * if token at @p pos is whitespace, decrease pos by one. 0206 */ 0207 inline void skipWhiteSpace(const TokenAccess &lastToken, qint64 &pos) 0208 { 0209 if ( lastToken.typeAt(pos) == Parser::Token_WHITESPACE ) { 0210 --pos; 0211 } 0212 } 0213 0214 /// add keyword to list of completion items 0215 #define ADD_KEYWORD(x) items << CompletionTreeItemPointer( new KeywordItem( QStringLiteral(x), Php::CodeCompletionContext::Ptr(this) ) ) 0216 #define ADD_KEYWORD2(x, y) items << CompletionTreeItemPointer( new KeywordItem( QStringLiteral(x), Php::CodeCompletionContext::Ptr(this), QStringLiteral(y) ) ) 0217 0218 int completionRecursionDepth = 0; 0219 0220 CodeCompletionContext::CodeCompletionContext(KDevelop::DUContextPointer context, const QString& text, const QString& followingText, const KDevelop::CursorInRevision& position, int depth) 0221 : KDevelop::CodeCompletionContext(context, text, position, depth) 0222 , m_memberAccessOperation(NoMemberAccess), m_parentAccess(false), m_isFileCompletionAfterDirname(false) 0223 { 0224 // use other ctor for parents 0225 Q_ASSERT(depth == 0); 0226 0227 ifDebug(qCDebug(COMPLETION) << "non-processed text: " + text;) 0228 0229 if ( context->type() == DUContext::Class || context->type() == DUContext::Function || context->type() == DUContext::Other 0230 || context->type() == DUContext::Namespace ) 0231 { 0232 if ( !m_parentContext && !m_text.startsWith(QLatin1String("<?php ")) ) { 0233 ifDebug(qCDebug(COMPLETION) << "added start tag: " + m_text;) 0234 m_text.prepend("<?php "); 0235 } 0236 } 0237 0238 m_valid = !m_text.isEmpty(); 0239 0240 if (!m_valid) { 0241 qCDebug(COMPLETION) << "empty completion text"; 0242 return; 0243 } 0244 0245 TokenAccess lastToken(m_text); 0246 // ifDebug(qCDebug(COMPLETION) << "clearing completion text");) 0247 // m_text.clear(); 0248 0249 /// even when we skip to some more meaning ful token, this will 0250 /// always be the end position of the last token 0251 const qint64 lastTokenEnd = lastToken.at(0).end + 1; 0252 0253 bool lastWasWhitespace = lastToken == Parser::Token_WHITESPACE; 0254 if ( lastWasWhitespace ) { 0255 ifDebug(qCDebug(COMPLETION) << "skipping whitespace token";) 0256 lastToken.pop(); 0257 } 0258 0259 // when the text after the current token starts with /* we are inside 0260 // a multi line comment => don't offer completion 0261 if ( m_text.mid( lastTokenEnd, 2 ) == QLatin1String("/*") ) { 0262 ifDebug(qCDebug(COMPLETION) << "no completion in comments"); 0263 m_valid = false; 0264 return; 0265 } 0266 0267 ifDebug(qCDebug(COMPLETION) << tokenText(lastToken.type());) 0268 0269 ///TODO: REFACTOR: push some stuff into its own methods 0270 /// and call them from inside the big switch. 0271 /// Then we can forget about having additional checks 0272 /// beforehand and can handle it all in one place. 0273 0274 // The following tokens require a whitespace after them for code-completion: 0275 if ( !lastWasWhitespace ) { 0276 switch ( lastToken.type() ) { 0277 case Parser::Token_EXTENDS: 0278 case Parser::Token_IMPLEMENTS: 0279 case Parser::Token_NEW: 0280 case Parser::Token_THROW: 0281 ifDebug(qCDebug(COMPLETION) << "need whitespace after token for completion";) 0282 m_valid = false; 0283 return; 0284 default: 0285 break; 0286 } 0287 } 0288 0289 ifDebug(qCDebug(COMPLETION) << tokenText(lastToken.type());) 0290 0291 switch ( lastToken.type() ) { 0292 case Parser::Token_COMMENT: 0293 // don't offer code completion in comments, i.e. single line comments that don't end on \n 0294 // multi-line comments are handled above 0295 if ( !lastWasWhitespace && !lastToken.stringAt(0).endsWith('\n') 0296 && !lastToken.stringAt(0).startsWith(QLatin1String("/*")) ) { 0297 ifDebug(qCDebug(COMPLETION) << "no completion in comments";) 0298 m_valid = false; 0299 } 0300 break; 0301 case Parser::Token_EXTENDS: 0302 if ( lastToken.prependedBy(TokenList() << Parser::Token_WHITESPACE << Parser::Token_STRING 0303 << Parser::Token_WHITESPACE << Parser::Token_CLASS) != -1 ) { 0304 m_memberAccessOperation = ClassExtendsChoose; 0305 forbidIdentifier(lastToken.stringAt(-2)); 0306 } else if ( lastToken.prependedBy(TokenList() << Parser::Token_WHITESPACE << Parser::Token_STRING 0307 << Parser::Token_WHITESPACE << Parser::Token_INTERFACE) != -1 ) { 0308 m_memberAccessOperation = InterfaceChoose; 0309 forbidIdentifier(lastToken.stringAt(-2)); 0310 } else { 0311 ifDebug(qCDebug(COMPLETION) << "token prepended by bad tokens, don't do completion";) 0312 m_valid = false; 0313 } 0314 break; 0315 case Parser::Token_IMPLEMENTS: 0316 if ( lastToken.prependedBy(TokenList() << Parser::Token_WHITESPACE << Parser::Token_STRING 0317 << Parser::Token_WHITESPACE << Parser::Token_CLASS) != -1 ) { 0318 m_memberAccessOperation = InterfaceChoose; 0319 forbidIdentifier(lastToken.stringAt(-2)); 0320 } else { 0321 ifDebug(qCDebug(COMPLETION) << "token prepended by bad tokens, don't do completion";) 0322 m_valid = false; 0323 } 0324 break; 0325 case Parser::Token_COMMA: 0326 { 0327 // check if we are in the list after Token_IMPLEMENTS: 0328 qint64 relPos = -1; 0329 QList<qint64> identifierPositions; 0330 while ( true ) { 0331 skipWhiteSpace(lastToken, relPos); 0332 if ( lastToken.typeAt(relPos) == Parser::Token_STRING ) { 0333 identifierPositions << relPos; 0334 --relPos; 0335 skipWhiteSpace(lastToken, relPos); 0336 // interfaces may extend more than one interface 0337 if ( ( lastToken.typeAt(relPos) == Parser::Token_EXTENDS && 0338 lastToken.typeAt(relPos - 1) == Parser::Token_WHITESPACE && 0339 lastToken.typeAt(relPos - 2) == Parser::Token_STRING && 0340 lastToken.typeAt(relPos - 3) == Parser::Token_WHITESPACE && 0341 lastToken.typeAt(relPos - 4) == Parser::Token_INTERFACE ) 0342 || // classes may implement more than one interface 0343 ( lastToken.typeAt(relPos) == Parser::Token_IMPLEMENTS && 0344 lastToken.typeAt(relPos - 1) == Parser::Token_WHITESPACE && 0345 lastToken.typeAt(relPos - 2) == Parser::Token_STRING && 0346 lastToken.typeAt(relPos - 3) == Parser::Token_WHITESPACE && 0347 lastToken.typeAt(relPos - 4) == Parser::Token_CLASS ) ) 0348 { 0349 identifierPositions << (relPos - 2); 0350 m_memberAccessOperation = InterfaceChoose; 0351 break; 0352 } else if ( lastToken.typeAt(relPos) == Parser::Token_COMMA ) { 0353 // skip to next entry 0354 --relPos; 0355 continue; 0356 } 0357 } else { 0358 break; 0359 } 0360 } 0361 if ( m_memberAccessOperation == InterfaceChoose ) { 0362 ifDebug(qCDebug(COMPLETION) << "in implementation list";) 0363 m_memberAccessOperation = InterfaceChoose; 0364 foreach ( qint64 pos, identifierPositions ) { 0365 forbidIdentifier(lastToken.stringAt(pos)); 0366 } 0367 } else { 0368 // else do function call completion 0369 m_memberAccessOperation = FunctionCallAccess; 0370 0371 ///TODO: global, static etc. enumerations. 0372 removeOtherArguments(lastToken); 0373 0374 if ( lastToken.type() == Parser::Token_INVALID ) { 0375 m_valid = false; 0376 } 0377 } 0378 } 0379 break; 0380 case Parser::Token_OPEN_TAG: 0381 // don't do completion if no whitespace is given and there is some text following, 0382 // esp. for stuff like <?php <?ph <?p 0383 if ( !lastWasWhitespace && !followingText.isEmpty() ) { 0384 ifDebug(qCDebug(COMPLETION) << "no completion because <? is followed by" + followingText;) 0385 m_valid = false; 0386 } else { 0387 // else just do normal completion 0388 m_memberAccessOperation = NoMemberAccess; 0389 } 0390 break; 0391 case Parser::Token_OBJECT_OPERATOR: 0392 m_memberAccessOperation = MemberAccess; 0393 lastToken.pop(); 0394 break; 0395 case Parser::Token_PAAMAYIM_NEKUDOTAYIM: 0396 m_memberAccessOperation = StaticMemberAccess; 0397 lastToken.pop(); 0398 break; 0399 case Parser::Token_LPAREN: 0400 { 0401 qint64 pos = -1; 0402 skipWhiteSpace(lastToken, pos); 0403 if ( lastToken.typeAt(pos) == Parser::Token_CATCH ) { 0404 m_memberAccessOperation = ExceptionChoose; 0405 } else if ( lastToken.typeAt(pos) == Parser::Token_ARRAY ) { 0406 m_memberAccessOperation = NoMemberAccess; 0407 ifDebug(qCDebug(COMPLETION) << "NoMemberAccess";) 0408 ifDebug(qCDebug(COMPLETION) << "returning early";) 0409 return; 0410 } else { 0411 m_memberAccessOperation = FunctionCallAccess; 0412 } 0413 } 0414 break; 0415 case Parser::Token_NEW: 0416 if ( lastToken.prependedBy(TokenList() << Parser::Token_WHITESPACE << Parser::Token_THROW) != -1 ) { 0417 m_memberAccessOperation = ExceptionChoose; 0418 } else { 0419 m_memberAccessOperation = NewClassChoose; 0420 } 0421 break; 0422 case Parser::Token_THROW: 0423 m_memberAccessOperation = ExceptionInstanceChoose; 0424 break; 0425 case Parser::Token_CONSTANT_ENCAPSED_STRING: 0426 { 0427 // support something like `include dirname(__FILE__) . "/...` 0428 bool isAfterDirname = false; 0429 //NOTE: prependedBy will return -1 on failure, this is what we need in these cases 0430 // on success it will return a positive number, we'll need to switch it's sign in that case 0431 qint64 relPos = lastToken.prependedBy(TokenList() << Parser::Token_CONCAT << Parser::Token_RPAREN << Parser::Token_FILE 0432 << Parser::Token_LPAREN << Parser::Token_STRING, true); 0433 if ( relPos != -1 ) { 0434 // switch sign 0435 relPos = -relPos; 0436 if ( lastToken.stringAt(relPos + 1).compare(QLatin1String("dirname"), Qt::CaseInsensitive) == 0 ) { 0437 isAfterDirname = true; 0438 } 0439 } else { 0440 relPos = lastToken.prependedBy(TokenList() << Parser::Token_CONCAT << Parser::Token_DIR, true); 0441 0442 if ( relPos != -1 ) { 0443 // switch sign 0444 relPos = -relPos; 0445 isAfterDirname = true; 0446 } 0447 } 0448 0449 skipWhiteSpace(lastToken, relPos); 0450 if ( lastToken.typeAt(relPos) == Parser::Token_LPAREN ) { 0451 --relPos; 0452 } 0453 skipWhiteSpace(lastToken, relPos); 0454 switch ( lastToken.typeAt(relPos) ) { 0455 case Parser::Token_REQUIRE: 0456 case Parser::Token_REQUIRE_ONCE: 0457 case Parser::Token_INCLUDE: 0458 case Parser::Token_INCLUDE_ONCE: 0459 m_memberAccessOperation = FileChoose; 0460 m_expression = m_text.mid( lastToken.at(0).begin + 1 ).append(followingText).trimmed(); 0461 m_isFileCompletionAfterDirname = isAfterDirname; 0462 break; 0463 default: 0464 if ( m_text.at( lastToken.at(0).begin ).unicode() == '"' ) { 0465 ///TODO: only offer variable completion 0466 m_valid = false; 0467 } else { 0468 // in or after constant strings ('...') don't offer completion at all 0469 m_valid = false; 0470 } 0471 break; 0472 } 0473 break; 0474 } 0475 break; 0476 case Parser::Token_INSTANCEOF: 0477 m_memberAccessOperation = InstanceOfChoose; 0478 break; 0479 case Parser::Token_AND_ASSIGN: 0480 case Parser::Token_ARRAY_CAST: 0481 case Parser::Token_ASSIGN: 0482 case Parser::Token_AT: 0483 case Parser::Token_BANG: 0484 case Parser::Token_BIT_AND: 0485 case Parser::Token_BIT_OR: 0486 case Parser::Token_BIT_XOR: 0487 case Parser::Token_BOOLEAN_AND: 0488 case Parser::Token_BOOLEAN_OR: 0489 case Parser::Token_BOOL_CAST: 0490 case Parser::Token_COLON: 0491 case Parser::Token_CONCAT: 0492 case Parser::Token_CONCAT_ASSIGN: 0493 case Parser::Token_CURLY_OPEN: 0494 case Parser::Token_DEC: 0495 case Parser::Token_DIV: 0496 case Parser::Token_DIV_ASSIGN: 0497 case Parser::Token_DOC_COMMENT: 0498 case Parser::Token_DOLLAR_OPEN_CURLY_BRACES: 0499 case Parser::Token_DOUBLE_ARROW: 0500 case Parser::Token_DOUBLE_CAST: 0501 case Parser::Token_DOUBLE_QUOTE: 0502 case Parser::Token_ECHO: 0503 case Parser::Token_ELLIPSIS: 0504 case Parser::Token_ENCAPSED_AND_WHITESPACE: 0505 case Parser::Token_EXIT: 0506 case Parser::Token_INC: 0507 case Parser::Token_INT_CAST: 0508 case Parser::Token_IS_EQUAL: 0509 case Parser::Token_IS_GREATER: 0510 case Parser::Token_IS_GREATER_OR_EQUAL: 0511 case Parser::Token_IS_IDENTICAL: 0512 case Parser::Token_IS_NOT_EQUAL: 0513 case Parser::Token_IS_NOT_IDENTICAL: 0514 case Parser::Token_IS_SMALLER: 0515 case Parser::Token_IS_SMALLER_OR_EQUAL: 0516 case Parser::Token_LBRACE: 0517 case Parser::Token_LBRACKET: 0518 case Parser::Token_LOGICAL_AND: 0519 case Parser::Token_LOGICAL_OR: 0520 case Parser::Token_LOGICAL_XOR: 0521 case Parser::Token_MINUS: 0522 case Parser::Token_MINUS_ASSIGN: 0523 case Parser::Token_MOD: 0524 case Parser::Token_MOD_ASSIGN: 0525 case Parser::Token_MUL: 0526 case Parser::Token_MUL_ASSIGN: 0527 case Parser::Token_NULL_COALESCE: 0528 case Parser::Token_OBJECT_CAST: 0529 case Parser::Token_OPEN_TAG_WITH_ECHO: 0530 case Parser::Token_OR_ASSIGN: 0531 case Parser::Token_PLUS: 0532 case Parser::Token_PLUS_ASSIGN: 0533 case Parser::Token_PRINT: 0534 case Parser::Token_QUESTION: 0535 case Parser::Token_RBRACE: 0536 case Parser::Token_RETURN: 0537 case Parser::Token_SEMICOLON: 0538 case Parser::Token_SL: 0539 case Parser::Token_SL_ASSIGN: 0540 case Parser::Token_SPACESHIP: 0541 case Parser::Token_SR: 0542 case Parser::Token_SR_ASSIGN: 0543 case Parser::Token_START_HEREDOC: 0544 case Parser::Token_START_NOWDOC: 0545 case Parser::Token_STRING: 0546 case Parser::Token_STRING_CAST: 0547 case Parser::Token_TILDE: 0548 case Parser::Token_UNSET_CAST: 0549 case Parser::Token_XOR_ASSIGN: 0550 case Parser::Token_EXP: 0551 case Parser::Token_EXP_ASSIGN: 0552 // normal completion is valid 0553 if ( duContext() && duContext()->type() == DUContext::Class ) { 0554 // when we are inside a class context, give overloadable members as completion 0555 m_memberAccessOperation = ClassMemberChoose; 0556 } else { 0557 m_memberAccessOperation = NoMemberAccess; 0558 } 0559 break; 0560 case Parser::Token_ABSTRACT: 0561 case Parser::Token_CONST: 0562 case Parser::Token_FINAL: 0563 case Parser::Token_PUBLIC: 0564 case Parser::Token_PRIVATE: 0565 case Parser::Token_PROTECTED: 0566 case Parser::Token_STATIC: 0567 case Parser::Token_VAR: 0568 if ( duContext() && duContext()->type() == DUContext::Class ) { 0569 // when we are inside a class context, give overloadable members as completion 0570 m_memberAccessOperation = ClassMemberChoose; 0571 } else { 0572 m_valid = false; 0573 } 0574 break; 0575 case Parser::Token_NAMESPACE: 0576 case Parser::Token_BACKSLASH: 0577 { 0578 QString identifier; 0579 qint64 relPos = 0; 0580 while (lastToken.typeAt(relPos) == Parser::Token_STRING || lastToken.typeAt(relPos) == Parser::Token_BACKSLASH) { 0581 if (lastToken.typeAt(relPos) == Parser::Token_BACKSLASH) { 0582 identifier.prepend("::"); 0583 } else { 0584 identifier.prepend(lastToken.stringAt(relPos)); 0585 } 0586 --relPos; 0587 } 0588 if ( lastToken.typeAt(relPos) == Parser::Token_NAMESPACE ) { 0589 m_memberAccessOperation = NamespaceChoose; 0590 } else { 0591 m_memberAccessOperation = BackslashAccess; 0592 } 0593 m_namespace = QualifiedIdentifier(identifier); 0594 break; 0595 } 0596 case Parser::Token_ARRAY: 0597 case Parser::Token_AS: 0598 case Parser::Token_BACKTICK: 0599 case Parser::Token_BREAK: 0600 case Parser::Token_CALLABLE: 0601 case Parser::Token_CASE: 0602 case Parser::Token_CATCH: 0603 case Parser::Token_CLASS: 0604 case Parser::Token_CLASS_C: 0605 case Parser::Token_CLONE: 0606 case Parser::Token_CLOSE_TAG: 0607 case Parser::Token_CONTINUE: 0608 case Parser::Token_DECLARE: 0609 case Parser::Token_DEFAULT: 0610 case Parser::Token_DIR: 0611 case Parser::Token_DNUMBER: 0612 case Parser::Token_DO: 0613 case Parser::Token_DOLLAR: 0614 case Parser::Token_ELSE: 0615 case Parser::Token_ELSEIF: 0616 case Parser::Token_EMPTY: 0617 case Parser::Token_ENDDECLARE: 0618 case Parser::Token_ENDFOR: 0619 case Parser::Token_ENDFOREACH: 0620 case Parser::Token_ENDIF: 0621 case Parser::Token_ENDSWITCH: 0622 case Parser::Token_ENDWHILE: 0623 case Parser::Token_END_HEREDOC: 0624 case Parser::Token_END_NOWDOC: 0625 case Parser::Token_EOF: 0626 case Parser::Token_EVAL: 0627 case Parser::Token_FILE: 0628 case Parser::Token_FINALLY: 0629 case Parser::Token_FOR: 0630 case Parser::Token_FOREACH: 0631 case Parser::Token_FUNCTION: 0632 case Parser::Token_FUNC_C: 0633 case Parser::Token_GLOBAL: 0634 case Parser::Token_HALT_COMPILER: 0635 case Parser::Token_IF: 0636 case Parser::Token_INCLUDE: 0637 case Parser::Token_INCLUDE_ONCE: 0638 case Parser::Token_INLINE_HTML: 0639 case Parser::Token_INSTEADOF: 0640 case Parser::Token_INTERFACE: 0641 case Parser::Token_INVALID: 0642 case Parser::Token_ISSET: 0643 case Parser::Token_LINE: 0644 case Parser::Token_LIST: 0645 case Parser::Token_LNUMBER: 0646 case Parser::Token_METHOD_C: 0647 case Parser::Token_NAMESPACE_C: 0648 case Parser::Token_NUM_STRING: 0649 case Parser::Token_REQUIRE: 0650 case Parser::Token_REQUIRE_ONCE: 0651 case Parser::Token_RBRACKET: 0652 case Parser::Token_RPAREN: 0653 case Parser::Token_STRING_VARNAME: 0654 case Parser::Token_SWITCH: 0655 case Parser::Token_TRAIT: 0656 case Parser::Token_TRAIT_C: 0657 case Parser::Token_TRY: 0658 case Parser::Token_UNSET: 0659 case Parser::Token_USE: 0660 case Parser::Token_VARIABLE: 0661 case Parser::Token_VOID: 0662 case Parser::Token_WHILE: 0663 case Parser::Token_WHITESPACE: 0664 case Parser::Token_YIELD: 0665 case Parser::Token_YIELD_FROM: 0666 /// TODO: code completion after goto 0667 case Parser::Token_GOTO: 0668 case Parser::TokenTypeSize: 0669 ifDebug(qCDebug(COMPLETION) << "no completion after this token";) 0670 m_valid = false; 0671 break; 0672 } 0673 0674 ifDebug( 0675 switch ( m_memberAccessOperation ) { 0676 case FileChoose: 0677 qCDebug(COMPLETION) << "FileChoose"; 0678 break; 0679 case ExceptionInstanceChoose: 0680 qCDebug(COMPLETION) << "ExceptionInstanceChoose"; 0681 break; 0682 case ExceptionChoose: 0683 qCDebug(COMPLETION) << "ExceptionChoose"; 0684 break; 0685 case ClassMemberChoose: 0686 qCDebug(COMPLETION) << "ClassMemberChoose"; 0687 break; 0688 case NoMemberAccess: 0689 qCDebug(COMPLETION) << "NoMemberAccess"; 0690 break; 0691 case NewClassChoose: 0692 qCDebug(COMPLETION) << "NewClassChoose"; 0693 break; 0694 case FunctionCallAccess: 0695 qCDebug(COMPLETION) << "FunctionCallAccess"; 0696 break; 0697 case InterfaceChoose: 0698 qCDebug(COMPLETION) << "InterfaceChoose"; 0699 break; 0700 case ClassExtendsChoose: 0701 qCDebug(COMPLETION) << "ClassExtendsChoose"; 0702 break; 0703 case MemberAccess: 0704 qCDebug(COMPLETION) << "MemberAccess"; 0705 break; 0706 case StaticMemberAccess: 0707 qCDebug(COMPLETION) << "StaticMemberAccess"; 0708 break; 0709 case InstanceOfChoose: 0710 qCDebug(COMPLETION) << "InstanceOfChoose"; 0711 break; 0712 case NamespaceChoose: 0713 qCDebug(COMPLETION) << "NamespaceChoose"; 0714 break; 0715 case BackslashAccess: 0716 qCDebug(COMPLETION) << "BackslashAccess"; 0717 break; 0718 } 0719 ) 0720 0721 ifDebug(qCDebug(COMPLETION) << tokenText(lastToken.type());) 0722 0723 // if it's not valid, we should return early 0724 if ( !m_valid ) { 0725 ifDebug(qCDebug(COMPLETION) << "invalid completion";) 0726 return; 0727 } 0728 0729 // trim the text to the end position of the current token 0730 m_text = m_text.left(lastToken.at(0).end + 1).trimmed(); 0731 ifDebug(qCDebug(COMPLETION) << "trimmed text: " << m_text;) 0732 0733 // check whether we need the expression or have everything we need and can return early 0734 switch ( m_memberAccessOperation ) { 0735 // these access operations don't need the previous expression evaluated 0736 case FileChoose: 0737 case ClassMemberChoose: 0738 case InterfaceChoose: 0739 case NewClassChoose: 0740 case ExceptionChoose: 0741 case ExceptionInstanceChoose: 0742 case ClassExtendsChoose: 0743 case NoMemberAccess: 0744 case InstanceOfChoose: 0745 case NamespaceChoose: 0746 case BackslashAccess: 0747 ifDebug(qCDebug(COMPLETION) << "returning early";) 0748 return; 0749 case FunctionCallAccess: 0750 m_memberAccessOperation = NoMemberAccess; 0751 Q_ASSERT(lastToken.type() == Parser::Token_LPAREN); 0752 if ( lastToken.prependedBy(TokenList() << Parser::Token_STRING, true) == -1 && 0753 lastToken.prependedBy(TokenList() << Parser::Token_VARIABLE, true) == -1 ) 0754 { 0755 // handle for, foreach, while, etc. 0756 ifDebug(qCDebug(COMPLETION) << "NoMemberAccess (no function call)";) 0757 } else { 0758 //The first context should never be a function-call context, 0759 //so make this a NoMemberAccess context and the parent a function-call context. 0760 ifDebug(qCDebug(COMPLETION) << "NoMemberAccess (creating parentContext for function call)";) 0761 m_parentContext = new CodeCompletionContext(m_duContext, m_position, lastToken, depth + 1); 0762 } 0763 return; 0764 case MemberAccess: 0765 case StaticMemberAccess: 0766 // these types need the expression evaluated 0767 break; 0768 } 0769 0770 evaluateExpression(lastToken); 0771 } 0772 0773 CodeCompletionContext::CodeCompletionContext(KDevelop::DUContextPointer context, const KDevelop::CursorInRevision& position, 0774 TokenAccess& lastToken, int depth) 0775 : KDevelop::CodeCompletionContext(context, QString(), position, depth) 0776 , m_memberAccessOperation(NoMemberAccess), m_parentAccess(false), m_isFileCompletionAfterDirname(false) 0777 { 0778 switch ( lastToken.type() ) { 0779 case Parser::Token_LPAREN: 0780 m_memberAccessOperation = FunctionCallAccess; 0781 break; 0782 default: 0783 qCDebug(COMPLETION) << "unhandled token type for parent context" << tokenText(lastToken.typeAt(0)); 0784 Q_ASSERT(false); 0785 m_valid = false; 0786 return; 0787 } 0788 0789 evaluateExpression(lastToken); 0790 } 0791 0792 void CodeCompletionContext::evaluateExpression(TokenAccess& lastToken) 0793 { 0794 /// token pos 0795 qint64 startPos = 0; 0796 int openLParen = 0; 0797 0798 if ( m_memberAccessOperation == FunctionCallAccess ) { 0799 Q_ASSERT(lastToken.type() == Parser::Token_LPAREN); 0800 // check ctor call 0801 qint64 pos = lastToken.prependedBy(TokenList() << Parser::Token_STRING << Parser::Token_NEW, true); 0802 if ( pos != -1 ) { 0803 startPos = -pos; 0804 ifDebug(qCDebug(COMPLETION) << "ctor call";) 0805 } else { 0806 // simple function call, get it's expression 0807 startPos = -1; 0808 ifDebug(qCDebug(COMPLETION) << "simple function call";) 0809 } 0810 } 0811 0812 static const QList<int> defaultStopTokens = QList<int>() 0813 << Parser::Token_SEMICOLON << Parser::Token_INVALID << Parser::Token_OPEN_TAG 0814 << Parser::Token_OPEN_TAG_WITH_ECHO << Parser::Token_LBRACE << Parser::Token_RBRACE 0815 << Parser::Token_IF << Parser::Token_WHILE << Parser::Token_FOR << Parser::Token_FOREACH 0816 << Parser::Token_SWITCH << Parser::Token_ELSEIF; 0817 0818 0819 // find expression start 0820 while ( !defaultStopTokens.contains(lastToken.typeAt(startPos)) && 0821 (m_memberAccessOperation == FunctionCallAccess || lastToken.typeAt(startPos) != Parser::Token_COMMA) ) 0822 { 0823 if ( lastToken.typeAt(startPos) == Parser::Token_LPAREN ) { 0824 ++openLParen; 0825 if ( openLParen > 0 ) { 0826 break; 0827 } 0828 } else if ( lastToken.typeAt(startPos) == Parser::Token_RPAREN ) { 0829 --openLParen; 0830 } 0831 --startPos; 0832 } 0833 0834 if ( openLParen < 0 ) { 0835 ifDebug(qCDebug(COMPLETION) << "too many closed parenthesis";) 0836 m_valid = false; 0837 return; 0838 } 0839 0840 // we actually incorporate the not-wanted token, hence move forward 0841 ++startPos; 0842 0843 if ( lastToken.typeAt(startPos) == Parser::Token_WHITESPACE ) { 0844 ++startPos; 0845 } 0846 0847 if ( lastToken.typeAt(startPos) == Parser::Token_RETURN ) { 0848 ///TODO: match against function return type 0849 ++startPos; 0850 0851 if ( lastToken.typeAt(startPos) == Parser::Token_WHITESPACE ) { 0852 ++startPos; 0853 } 0854 } 0855 0856 if ( m_memberAccessOperation == StaticMemberAccess ) { 0857 if ( lastToken.typeAt(startPos) != Parser::Token_STRING ) { 0858 ifDebug(qCDebug(COMPLETION) << "unsupported token for start member access:" << tokenText(lastToken.typeAt(startPos));) 0859 m_valid = false; 0860 return; 0861 } 0862 0863 const QString identifier(lastToken.stringAt(startPos).toLower()); 0864 0865 if ( identifier == QLatin1String("self") || identifier == QLatin1String("parent") || identifier == QLatin1String("static") ) { 0866 // self and parent are only accessible from within a member function of a class 0867 if (DUContext* parent = m_duContext->parentContext()) { 0868 LOCKDUCHAIN; 0869 ClassDeclaration* classDec = dynamic_cast<ClassDeclaration*>(parent->owner()); 0870 if (classDec) { 0871 if (identifier == QLatin1String("parent")) { 0872 FOREACH_FUNCTION(const BaseClassInstance& base, classDec->baseClasses) { 0873 if (StructureType::Ptr classType = base.baseClass.type<StructureType>()) { 0874 if (ClassDeclaration* baseClass = dynamic_cast<ClassDeclaration*>(classType->declaration(m_duContext->topContext()))) { 0875 if (baseClass->classType() == ClassDeclarationData::Class 0876 && baseClass->classModifier() != ClassDeclarationData::Abstract) { 0877 ifDebug(qCDebug(COMPLETION) << "correction: parent can do MemberAccess"); 0878 m_parentAccess = true; 0879 m_memberAccessOperation = MemberAccess; 0880 m_expressionResult.setDeclaration(baseClass); 0881 break; 0882 } 0883 } 0884 } 0885 } 0886 if (!m_parentAccess) { 0887 ifDebug(qCDebug(COMPLETION) << "class has no accessible parent class"); 0888 m_valid = false; 0889 return; 0890 } 0891 } else { 0892 m_expressionResult.setDeclaration(parent->owner()); 0893 } 0894 } 0895 } 0896 } else { 0897 QualifiedIdentifier id(identifier); 0898 0899 m_expressionResult.setDeclaration(findDeclarationImportHelper(duContext(), id, ClassDeclarationType)); 0900 } 0901 } else { 0902 // Now get the string of the expression and evaluate it 0903 Q_ASSERT(m_expression.isEmpty()); 0904 0905 for (qint64 i = startPos; i <= 0; ++i ) { 0906 m_expression += lastToken.stringAt(i); 0907 } 0908 0909 m_expression = m_expression.trimmed(); 0910 0911 // make sure the expression is valid 0912 if (m_memberAccessOperation == FunctionCallAccess) { 0913 m_expression.append(')'); 0914 } 0915 for ( int i = openLParen; i > 0; --i ) { 0916 m_expression.append(')'); 0917 } 0918 0919 ifDebug(qCDebug(COMPLETION) << "expression: " << m_expression;) 0920 0921 if ( !m_expression.isEmpty() ) { 0922 ExpressionParser expressionParser; 0923 m_expressionResult = expressionParser.evaluateType(m_expression.toUtf8(), m_duContext, m_position); 0924 } 0925 0926 if (m_expressionResult.type()) { 0927 LOCKDUCHAIN; 0928 ifDebug(qCDebug(COMPLETION) << "expression type: " << m_expressionResult.type()->toString();) 0929 } else { 0930 ifDebug(qCDebug(COMPLETION) << QString("expression could not be evaluated")); 0931 if ( m_memberAccessOperation == FunctionCallAccess ) { 0932 ifDebug(qCDebug(COMPLETION) << "function not found";) 0933 return; 0934 } 0935 m_valid = false; 0936 return; 0937 } 0938 } 0939 0940 lastToken.moveTo(startPos); 0941 0942 // Handle recursive contexts (Example: "ret = function1(param1, function2(" ) 0943 if ( lastToken.typeAt(-1) == Parser::Token_LPAREN || 0944 lastToken.typeAt(-1) == Parser::Token_COMMA ) { 0945 //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. 0946 0947 lastToken.moveTo(-1); 0948 if ( lastToken.type() == Parser::Token_COMMA ) { 0949 removeOtherArguments(lastToken); 0950 if ( lastToken.type() == Parser::Token_INVALID ) { 0951 ifDebug(qCDebug(COMPLETION) << QString("Could not find start position for parent function-call. Aborting.");) 0952 m_valid = false; 0953 return; 0954 } 0955 } 0956 0957 if ( lastToken.prependedBy(TokenList() << Parser::Token_STRING, true) == -1 ) { 0958 // handle for, foreach, while, etc. 0959 ifDebug(qCDebug(COMPLETION) << "No real function call";) 0960 return; 0961 } 0962 0963 ifDebug(qCDebug(COMPLETION) << QString("Recursive function-call: creating parent context")); 0964 m_parentContext = new CodeCompletionContext(m_duContext, m_position, lastToken, m_depth + 1); 0965 0966 if (!m_parentContext->isValid()) { 0967 m_parentContext = nullptr; 0968 m_valid = false; 0969 return; 0970 } 0971 } 0972 } 0973 0974 void CodeCompletionContext::forbidIdentifier(const QString& identifier) 0975 { 0976 QualifiedIdentifier id(identifier.toLower()); 0977 0978 ClassDeclaration *dec = dynamic_cast<ClassDeclaration*>( 0979 findDeclarationImportHelper(m_duContext.data(), id, 0980 ClassDeclarationType).data() 0981 ); 0982 if (dec) { 0983 forbidIdentifier(dec); 0984 } else { 0985 // might be a class we are currently writing, i.e. without a proper declaration 0986 m_forbiddenIdentifiers << id.index(); 0987 } 0988 } 0989 0990 void CodeCompletionContext::forbidIdentifier(ClassDeclaration* klass) 0991 { 0992 uint id; 0993 { 0994 LOCKDUCHAIN; 0995 // TODO: qualifiedIdentifier is marked as expensive - any other way 0996 // we can do what we are doing here? 0997 // TODO: maybe we should clar the m_fobiddenIdentifiers after we got 0998 // our list of items... 0999 id = klass->qualifiedIdentifier().index(); 1000 } 1001 if (m_forbiddenIdentifiers.contains(id)) { 1002 // nothing to do 1003 return; 1004 } 1005 1006 m_forbiddenIdentifiers << id; 1007 1008 // add parents so those are excluded from the completion items as well 1009 if (klass->baseClassesSize() > 0) { 1010 FOREACH_FUNCTION(const BaseClassInstance& base, klass->baseClasses) { 1011 StructureType::Ptr type = base.baseClass.type<StructureType>(); 1012 if (type.data()) { 1013 ClassDeclaration* parent; 1014 { 1015 LOCKDUCHAIN; 1016 parent = dynamic_cast<ClassDeclaration*>( 1017 type->declaration(m_duContext->topContext()) 1018 ); 1019 } 1020 if (parent) { 1021 forbidIdentifier(parent); 1022 } 1023 } 1024 } 1025 } 1026 } 1027 1028 CodeCompletionContext::~CodeCompletionContext() 1029 { 1030 } 1031 1032 CodeCompletionContext::MemberAccessOperation CodeCompletionContext::memberAccessOperation() const 1033 { 1034 return m_memberAccessOperation; 1035 } 1036 1037 ExpressionEvaluationResult CodeCompletionContext::memberAccessContainer() const 1038 { 1039 return m_expressionResult; 1040 } 1041 1042 CodeCompletionContext* CodeCompletionContext::parentContext() 1043 { 1044 return static_cast<CodeCompletionContext*>(KDevelop::CodeCompletionContext::parentContext()); 1045 } 1046 1047 QList<DUContext*> CodeCompletionContext::memberAccessContainers() const 1048 { 1049 QList<DUContext*> ret; 1050 QList<AbstractType::Ptr> types; 1051 AbstractType::Ptr expressionTarget = m_expressionResult.type(); 1052 if (auto unsureType = m_expressionResult.type().dynamicCast<UnsureType>()) { 1053 FOREACH_FUNCTION(const IndexedType& t, unsureType->types) { 1054 types << t.abstractType(); 1055 } 1056 } else if (auto referencedType = m_expressionResult.type().dynamicCast<ReferenceType>() ) { 1057 types << referencedType->baseType(); 1058 } else { 1059 types << expressionTarget; 1060 } 1061 foreach (const AbstractType::Ptr &type, types) { 1062 const IdentifiedType* idType = dynamic_cast<const IdentifiedType*>(type.data()); 1063 Declaration* declaration = nullptr; 1064 if (idType) { 1065 declaration = idType->declaration(m_duContext->topContext()); 1066 } 1067 DUContext* ctx = nullptr; 1068 if (declaration) { 1069 ctx = declaration->logicalInternalContext(m_duContext->topContext()); 1070 } 1071 if (ctx) { 1072 ret << ctx; 1073 } else if (declaration) { 1074 //Print some debug-output 1075 qCDebug(COMPLETION) << "Could not get internal context from" << declaration->toString(); 1076 } else { 1077 //Print some debug-output 1078 qCDebug(COMPLETION) << "Could not get declaration"; 1079 } 1080 } 1081 return ret; 1082 } 1083 1084 QList<CompletionTreeItemPointer> CodeCompletionContext::completionItems(bool& abort, bool fullCompletion) 1085 { 1086 //TODO: how should this be handled? 1087 Q_UNUSED(fullCompletion) 1088 1089 /// Indexed string for 'Php', identifies environment files from this language plugin 1090 static const IndexedString phpLangString("Php"); 1091 1092 LOCKDUCHAIN; 1093 1094 QList<CompletionTreeItemPointer> items; 1095 1096 if (!m_duContext) 1097 return items; 1098 1099 typedef QPair<Declaration*, int> DeclarationDepthPair; 1100 1101 if ( memberAccessOperation() == FileChoose ) { 1102 if ( !ICore::self() ) { 1103 // in unit tests we can't do anything 1104 qCDebug(COMPLETION) << "no core found"; 1105 return items; 1106 } 1107 // file completion 1108 const Path currentDocument(m_duContext->url().str()); 1109 Path path; 1110 Path base; 1111 if ( !m_isFileCompletionAfterDirname ) { 1112 path = Path(currentDocument.parent(), m_expression); 1113 base = path; 1114 if ( !m_expression.isEmpty() && !m_expression.endsWith('/') ) { 1115 base = base.parent(); 1116 } 1117 } else { 1118 if ( m_expression.startsWith('/') ) { 1119 path = Path(currentDocument.parent(), m_expression.mid(1)); 1120 } else { 1121 path = currentDocument.parent(); 1122 } 1123 base = path; 1124 if ( !m_expression.isEmpty() && !m_expression.endsWith('/') && m_expression != QLatin1String("/") ) { 1125 base = base.parent(); 1126 } 1127 } 1128 QList<Path> addedPaths; 1129 bool addedParentDir = false; 1130 const QUrl baseUrl = base.toUrl(); 1131 foreach ( ProjectBaseItem* item, ICore::self()->projectController()->projectModel()->itemsForPath(IndexedString(base.toUrl())) ) { 1132 if ( abort || !item->folder() ) { 1133 break; 1134 } 1135 auto folder = item->folder(); 1136 foreach ( ProjectFileItem* subFile, folder->fileList() ) { 1137 if ( abort ) { 1138 break; 1139 } 1140 if ( addedPaths.contains(subFile->path()) ) { 1141 continue; 1142 } else { 1143 addedPaths << subFile->path(); 1144 } 1145 IncludeItem item; 1146 item.isDirectory = false; 1147 item.basePath = baseUrl; 1148 item.name = subFile->fileName(); 1149 if ( m_isFileCompletionAfterDirname && !m_expression.startsWith('/') ) { 1150 item.name.prepend('/'); 1151 } 1152 items << CompletionTreeItemPointer(new IncludeFileItem(item)); 1153 } 1154 foreach ( ProjectFolderItem* subFolder, folder->folderList() ) { 1155 if ( abort ) { 1156 break; 1157 } 1158 if ( addedPaths.contains(subFolder->path()) ) { 1159 continue; 1160 } else { 1161 addedPaths << subFolder->path(); 1162 } 1163 IncludeItem item; 1164 item.isDirectory = true; 1165 item.basePath = baseUrl; 1166 item.name = subFolder->folderName(); 1167 if ( m_isFileCompletionAfterDirname && !m_expression.startsWith('/') ) { 1168 item.name.prepend('/'); 1169 } 1170 items << CompletionTreeItemPointer(new IncludeFileItem(item)); 1171 } 1172 if ( !folder->parent() && !addedParentDir && m_expression.isEmpty() ) { 1173 // expect a parent dir 1174 IncludeItem item; 1175 item.isDirectory = true; 1176 item.basePath = baseUrl; 1177 item.name = QStringLiteral(".."); 1178 items << CompletionTreeItemPointer(new IncludeFileItem(item)); 1179 } 1180 } 1181 1182 return items; 1183 } else if (memberAccessOperation() == ClassMemberChoose) { 1184 // get current class 1185 if (ClassDeclaration * currentClass = dynamic_cast<ClassDeclaration*>(m_duContext->owner())) { 1186 // whether we want to show a list of overloadable functions 1187 // i.e. not after we have typed one of the keywords var,const or abstract 1188 bool showOverloadable = true; 1189 // whether we want to remove static functions from the overloadable list 1190 // i.e. after we have typed "public function" 1191 bool filterStatic = false; 1192 // whether we want to remove non-static functions from the overloadable list 1193 // i.e. after we have typed "public static function" 1194 bool filterNonStatic = false; 1195 // private functions are always removed from the overloadable list 1196 // but when we type "protected function" only protected functions may be shown 1197 bool filterPublic = false; 1198 1199 { 1200 // add typical keywords for class member definitions 1201 QStringList modifiers = getMethodTokens(m_text); 1202 1203 // don't add keywords when "function" was already typed 1204 bool addKeywords = !modifiers.contains(QStringLiteral("function")); 1205 1206 if (currentClass->classModifier() == ClassDeclarationData::Abstract) { 1207 // abstract is only allowed in abstract classes 1208 if (modifiers.contains(QStringLiteral("abstract"))) { 1209 // don't show overloadable functions when we are defining an abstract function 1210 showOverloadable = false; 1211 } else if (addKeywords) { 1212 ADD_KEYWORD("abstract"); 1213 } 1214 } else { 1215 // final is only allowed in non-abstract classes 1216 if (addKeywords && !modifiers.contains(QStringLiteral("final"))) { 1217 ADD_KEYWORD("final"); 1218 } 1219 } 1220 1221 if (modifiers.contains(QStringLiteral("private"))) { 1222 // overloadable functions must not be declared private 1223 showOverloadable = false; 1224 } else if (modifiers.contains(QStringLiteral("protected"))) { 1225 // only show protected overloadable methods 1226 filterPublic = true; 1227 } else if (addKeywords && !modifiers.contains(QStringLiteral("public"))) { 1228 ADD_KEYWORD("public"); 1229 ADD_KEYWORD("protected"); 1230 ADD_KEYWORD("private"); 1231 } 1232 1233 if (modifiers.contains(QStringLiteral("static"))) { 1234 filterNonStatic = true; 1235 } else { 1236 if (addKeywords) { 1237 ADD_KEYWORD("static"); 1238 } else { 1239 filterStatic = true; 1240 } 1241 } 1242 1243 if (addKeywords) { 1244 ADD_KEYWORD("function"); 1245 } 1246 1247 if (modifiers.isEmpty()) { 1248 // var and const may not have any modifiers 1249 ADD_KEYWORD("var"); 1250 ADD_KEYWORD("const"); 1251 } 1252 } 1253 ifDebug( qCDebug(COMPLETION) << "showOverloadable" << showOverloadable; ) 1254 // complete overloadable methods from parents 1255 if (showOverloadable) { 1256 // TODO: use m_duContext instead of ctx 1257 // overloadable choose is only possible inside classes which extend/implement others 1258 if (currentClass->baseClassesSize()) { 1259 DUContext* ctx = currentClass->internalContext(); 1260 if (!ctx) { 1261 qCDebug(COMPLETION) << "invalid class context"; 1262 return items; 1263 } 1264 QList<uint> alreadyImplemented; 1265 //TODO: use the token stream here as well 1266 //TODO: always add __construct, __destruct and maby other magic functions 1267 // get all visible declarations and add inherited to the completion items 1268 foreach(DeclarationDepthPair decl, ctx->allDeclarations(ctx->range().end, m_duContext->topContext(), false)) { 1269 ClassMemberDeclaration *member = dynamic_cast<ClassMemberDeclaration*>(decl.first); 1270 ClassFunctionDeclaration *classFunc = dynamic_cast<ClassFunctionDeclaration*>(decl.first); 1271 if (member) { 1272 if (decl.second == 0) { 1273 // this function is already implemented 1274 alreadyImplemented << decl.first->indexedIdentifier().index(); 1275 continue; 1276 } 1277 // skip already implemented functions 1278 if (alreadyImplemented.contains(decl.first->indexedIdentifier().index())) { 1279 continue; 1280 } 1281 // skip non-static functions when requested 1282 if (filterNonStatic && !member->isStatic()) { 1283 continue; 1284 } 1285 // skip static functions when requested 1286 if (filterStatic && member->isStatic()) { 1287 continue; 1288 } 1289 // always skip private functions 1290 if (member->accessPolicy() == Declaration::Private) { 1291 continue; 1292 } 1293 // skip public functions when requested 1294 if (filterPublic && member->accessPolicy() == Declaration::Public) { 1295 // make sure no non-public base members are added 1296 alreadyImplemented << decl.first->indexedIdentifier().index(); 1297 continue; 1298 } 1299 // skip final members 1300 if (classFunc && classFunc->isFinal()) { 1301 // make sure no non-final base members are added 1302 alreadyImplemented << decl.first->indexedIdentifier().index(); 1303 continue; 1304 } 1305 // make sure we inherit or implement the base class of this member 1306 if (!member->context() || !member->context()->owner()) { 1307 qCDebug(COMPLETION) << "invalid parent context/owner:" << member->toString(); 1308 continue; 1309 } 1310 if (!currentClass->isPublicBaseClass(dynamic_cast<ClassDeclaration*>(member->context()->owner()), 1311 m_duContext->topContext())) { 1312 continue; 1313 } 1314 1315 ImplementationItem::HelperType itype; 1316 if (!member->isFunctionDeclaration()) { 1317 itype = ImplementationItem::OverrideVar; 1318 } else if (classFunc && classFunc->isAbstract()) { 1319 itype = ImplementationItem::Implement; 1320 } else { 1321 itype = ImplementationItem::Override; 1322 } 1323 ifDebug( qCDebug(COMPLETION) << "ImplementationItem" << itype; ) 1324 1325 items << CompletionTreeItemPointer(new ImplementationItem(itype, DeclarationPointer(decl.first), 1326 Php::CodeCompletionContext::Ptr(this), decl.second)); 1327 // don't add identical items twice to the completion choices 1328 alreadyImplemented << decl.first->indexedIdentifier().index(); 1329 } 1330 } 1331 } 1332 } else { 1333 qCDebug(COMPLETION) << "invalid owner declaration for overloadable completion"; 1334 } 1335 } 1336 } else if (m_memberAccessOperation == BackslashAccess || m_memberAccessOperation == NamespaceChoose) { 1337 DUContext* ctx = nullptr; 1338 if (m_namespace.isEmpty()) { 1339 ctx = m_duContext->topContext(); 1340 } else { 1341 foreach(Declaration* dec, m_duContext->topContext()->findDeclarations(m_namespace)) { 1342 if (dec->kind() == Declaration::Namespace) { 1343 ctx = dec->internalContext(); 1344 break; 1345 } 1346 } 1347 } 1348 if (!ctx) { 1349 qCDebug(COMPLETION) << "could not find namespace:" << m_namespace.toString(); 1350 return items; 1351 } 1352 foreach(Declaration* dec, ctx->localDeclarations()) { 1353 if (!isValidCompletionItem(dec)) { 1354 continue; 1355 } else { 1356 items << CompletionTreeItemPointer( 1357 new NormalDeclarationCompletionItem( 1358 DeclarationPointer(dec), 1359 Php::CodeCompletionContext::Ptr(this), depth() 1360 ) 1361 ); 1362 } 1363 } 1364 } else if (m_expressionResult.type()) { 1365 QList<DUContext*> containers = memberAccessContainers(); 1366 qCDebug(COMPLETION) << "containers: " << containers.count(); 1367 if (!containers.isEmpty()) { 1368 // get the parent class when we are inside a method declaration 1369 ClassDeclaration* currentClass = nullptr; 1370 if (m_duContext->owner() && m_duContext->owner()->isFunctionDeclaration() && 1371 m_duContext->parentContext() && m_duContext->parentContext()->owner()) { 1372 currentClass = dynamic_cast<ClassDeclaration*>(m_duContext->parentContext()->owner()); 1373 } 1374 1375 foreach(DUContext* ctx, containers) { 1376 ClassDeclaration* accessedClass = dynamic_cast<ClassDeclaration*>(ctx->owner()); 1377 if (abort) 1378 return items; 1379 1380 foreach(DeclarationDepthPair decl, ctx->allDeclarations( 1381 ctx->range().end, m_duContext->topContext(), false)) 1382 { 1383 //If we have StaticMemberAccess, which means A::Bla, show only static members, 1384 //except if we're within a class that derives from the container 1385 ClassMemberDeclaration* classMember = dynamic_cast<ClassMemberDeclaration*>(decl.first); 1386 if (memberAccessOperation() != StaticMemberAccess) { 1387 if (classMember && classMember->isStatic()) 1388 continue; //Skip static class members when not doing static access 1389 } else { 1390 if (!classMember || !classMember->isStatic()) 1391 continue; //Skip static class members when not doing static access 1392 } 1393 1394 // check access policy 1395 if (classMember && accessedClass) { 1396 // by default only show public declarations 1397 Declaration::AccessPolicy ap = Declaration::Public; 1398 if (currentClass) { 1399 // if we are inside a class, we might want to show protected or private members 1400 ClassDeclaration* memberClass = dynamic_cast<ClassDeclaration*>(classMember->context()->owner()); 1401 if (memberClass) { 1402 if (currentClass == accessedClass) { 1403 if (currentClass == memberClass) { 1404 // we can show all members of the current class 1405 ap = Declaration::Private; 1406 } else if (currentClass->isPublicBaseClass(memberClass, m_duContext->topContext())) { 1407 // we can show all but private members of ancestors of the current class 1408 ap = Declaration::Protected; 1409 } 1410 } else if (currentClass->isPublicBaseClass(accessedClass, m_duContext->topContext()) 1411 && (accessedClass == memberClass || 1412 accessedClass->isPublicBaseClass(memberClass, m_duContext->topContext()))) { 1413 // we can show all but private members of ancestors of the current class 1414 ap = Declaration::Protected; 1415 } 1416 } 1417 } 1418 if (ap < classMember->accessPolicy()) { 1419 continue; 1420 } 1421 } 1422 1423 if (!decl.first->identifier().isEmpty()) 1424 items << CompletionTreeItemPointer( 1425 new NormalDeclarationCompletionItem( 1426 DeclarationPointer( 1427 decl.first), 1428 Php::CodeCompletionContext::Ptr(this), 1429 decl.second 1430 ) 1431 ); 1432 } 1433 } 1434 } else { 1435 qCDebug(COMPLETION) << "setContext: no container-type"; 1436 } 1437 1438 } else { 1439 //Show all visible declarations 1440 QSet<uint> existingIdentifiers; 1441 const auto decls = m_duContext->allDeclarations( 1442 CursorInRevision::invalid(), 1443 m_duContext->topContext() 1444 ); 1445 1446 qCDebug(COMPLETION) << "setContext: using all declarations visible:" << decls.size(); 1447 1448 QVectorIterator<DeclarationDepthPair> i(decls); 1449 i.toBack(); 1450 while (i.hasPrevious()) { 1451 DeclarationDepthPair decl = i.previous(); 1452 Declaration* dec = decl.first; 1453 if (dec->kind() == Declaration::Instance) { 1454 // filter non-superglobal vars of other contexts 1455 if (dec->context() != m_duContext.data() && !m_duContext->imports(dec->context())) { 1456 VariableDeclaration* vDec = dynamic_cast<VariableDeclaration*>(dec); 1457 if ( vDec && !vDec->isSuperglobal() ) { 1458 continue; 1459 } 1460 } 1461 1462 if (existingIdentifiers.contains(dec->indexedIdentifier().index())) continue; 1463 existingIdentifiers.insert(dec->indexedIdentifier().index()); 1464 } 1465 if (abort) 1466 return items; 1467 if (!isValidCompletionItem(dec)) continue; 1468 items << CompletionTreeItemPointer( 1469 new NormalDeclarationCompletionItem( 1470 DeclarationPointer(dec), 1471 Php::CodeCompletionContext::Ptr(this), 1472 decl.second 1473 ) 1474 ); 1475 } 1476 1477 foreach(QSet<IndexedString> urlSets, completionFiles()) { 1478 foreach(const IndexedString &url, urlSets) { 1479 if (url == m_duContext->url()) { 1480 continue; 1481 } 1482 uint count = 0; 1483 const CodeModelItem* foundItems = nullptr; 1484 CodeModel::self().items(url, count, foundItems); 1485 for (uint i = 0; i < count; ++i) { 1486 CodeModelItem::Kind k = foundItems[i].kind; 1487 if (((k & CodeModelItem::Function) || (k & CodeModelItem::Variable)) && !(k & CodeModelItem::ClassMember)) { 1488 foreach(const ParsingEnvironmentFilePointer &env, DUChain::self()->allEnvironmentFiles(url)) { 1489 if (env->language() != phpLangString) continue; 1490 TopDUContext* top = env->topContext(); 1491 if(!top) continue; 1492 if (m_duContext->imports(top)) continue; 1493 QList<Declaration*> decls = top->findDeclarations(foundItems[i].id); 1494 foreach(Declaration* decl, decls) { 1495 if (abort) return items; 1496 // we don't want to have class methods/properties, just normal functions 1497 // and other global stuff 1498 if ( decl->context() && decl->context()->type() == DUContext::Class ) { 1499 continue; 1500 } 1501 if (!isValidCompletionItem(decl)) continue; 1502 if ( VariableDeclaration* vDec = dynamic_cast<VariableDeclaration*>(decl) ) { 1503 if ( !vDec->isSuperglobal() ) { 1504 continue; 1505 } 1506 } 1507 items << CompletionTreeItemPointer( 1508 new NormalDeclarationCompletionItem( 1509 DeclarationPointer(decl), 1510 Php::CodeCompletionContext::Ptr(this) 1511 ) 1512 ); 1513 } 1514 } 1515 } 1516 } 1517 } 1518 } 1519 1520 foreach(QSet<IndexedString> urlSets, completionFiles()) { 1521 foreach(const IndexedString &url, urlSets) { 1522 uint count = 0; 1523 const CompletionCodeModelItem* foundItems = nullptr; 1524 CompletionCodeModel::self().items(url, count, foundItems); 1525 for (uint i = 0; i < count; ++i) { 1526 if (abort) return items; 1527 if (m_memberAccessOperation == ExceptionChoose) { 1528 if (!(foundItems[i].kind & CompletionCodeModelItem::Exception)) continue; 1529 } 1530 auto files = DUChain::self()->allEnvironmentFiles(url); 1531 items.reserve(files.size()); 1532 foreach(const ParsingEnvironmentFilePointer &env, files) { 1533 Q_ASSERT(env->language() == phpLangString); 1534 items << CompletionTreeItemPointer ( new CodeModelCompletionItem(env, foundItems[i])); 1535 } 1536 } 1537 } 1538 } 1539 } 1540 1541 ///Find all recursive function-calls that should be shown as call-tips 1542 CodeCompletionContext::Ptr parentContext(this); 1543 do { 1544 if (abort) 1545 return items; 1546 1547 parentContext = parentContext->parentContext(); 1548 if (parentContext) { 1549 if (parentContext->memberAccessOperation() == CodeCompletionContext::FunctionCallAccess) { 1550 if (!parentContext->memberAccessContainer().allDeclarationIds().isEmpty()) { 1551 Declaration* decl = parentContext->memberAccessContainer().allDeclarationIds().first() 1552 .declaration(m_duContext->topContext()); 1553 1554 if (!isValidCompletionItem(decl)) { 1555 continue; 1556 } 1557 if ( !decl->isFunctionDeclaration() ) { 1558 if ( ClassDeclaration* classDec = dynamic_cast<ClassDeclaration*>(decl) ) { 1559 // search for ctor 1560 decl = nullptr; 1561 foreach ( Declaration* dec, classDec->internalContext()->findDeclarations(Identifier(u"__construct")) ) { 1562 if ( dec->isFunctionDeclaration() ) { 1563 decl = dec; 1564 break; 1565 } 1566 } 1567 if ( !decl ) { 1568 foreach ( Declaration* dec, classDec->internalContext()->findDeclarations(classDec->identifier()) ) { 1569 if ( dec->isFunctionDeclaration() ) { 1570 decl = dec; 1571 break; 1572 } 1573 } 1574 } 1575 if ( !decl ) { 1576 continue; 1577 } 1578 } else if ( !decl->type<FunctionType>() ) { 1579 qCDebug(COMPLETION) << "parent decl is neither function nor class nor closure, skipping" << decl->toString(); 1580 continue; 1581 } 1582 } 1583 items << CompletionTreeItemPointer( 1584 new NormalDeclarationCompletionItem( 1585 DeclarationPointer(decl), 1586 Php::CodeCompletionContext::Ptr(parentContext.data()) 1587 ) 1588 ); 1589 } 1590 } else { 1591 qCDebug(COMPLETION) << "parent-context has non function-call access type"; 1592 } 1593 } 1594 } while (parentContext); 1595 1596 if ( m_memberAccessOperation == NoMemberAccess ) { 1597 ///TODO: function-like statements should just be handled as a function with declaration etc. 1598 /// e.g.: empty, eval, die, exit, isset, unset 1599 /// but _not_ echo, print, catch, include*, require* 1600 ///TODO: use user's style for indentation etc. 1601 ADD_KEYWORD2("abstract class", "abstract class %SELECT%NAME%ENDSELECT% {\n%INDENT%\n}\n"); 1602 ADD_KEYWORD2("final class", "final class %SELECT%NAME%ENDSELECT% {\n%INDENT%\n}\n"); 1603 ADD_KEYWORD2("class", "class %SELECT%NAME%ENDSELECT% {\n%INDENT%\n}\n"); 1604 ADD_KEYWORD2("interface", "interface %SELECT%NAME%ENDSELECT% {\n%INDENT%\n}\n"); 1605 ADD_KEYWORD2("array", "array(\n%INDENT%%CURSOR%\n)"); 1606 ADD_KEYWORD2("break", "break;\n"); 1607 ADD_KEYWORD2("case", "case %SELECT%CASE%ENDSELECT%:\n%INDENT%\n%INDENT%break;\n"); 1608 ADD_KEYWORD2("throw", "throw %CURSOR%;\n"); 1609 ADD_KEYWORD2("try", "try {\n%INDENT%%CURSOR%\n} catch() {\n$%INDENT%\n}\n"); 1610 ADD_KEYWORD2("catch", "catch(%CURSOR%) {\n%INDENT%\n}\n"); 1611 ADD_KEYWORD2("clone", "clone %CURSOR%;\n"); 1612 ADD_KEYWORD2("continue", "continue;\n"); 1613 ADD_KEYWORD2("declare", "declare(%CURSOR%);\n"); 1614 ADD_KEYWORD2("default", "default:\n%INDENT%%CURSOR%\n%INDENT%break;\n"); 1615 ADD_KEYWORD2("do", "do {\n%INDENT%%CURSOR%\n} while();\n"); 1616 ADD_KEYWORD2("echo", "echo %CURSOR%;\n"); 1617 ADD_KEYWORD2("else", "else {\n%INDENT%%CURSOR%\n}\n"); 1618 ADD_KEYWORD2("elseif", "elseif (%CURSOR%) {\n%INDENT%\n}\n"); 1619 ADD_KEYWORD2("endif", "endif;"); 1620 ADD_KEYWORD2("endforeach", "endforeach;"); 1621 ADD_KEYWORD2("endswitch", "endswitch;"); 1622 ADD_KEYWORD2("endwhile", "endwhile;"); 1623 ADD_KEYWORD2("endfor", "endfor;"); 1624 ADD_KEYWORD2("enddeclare", "enddeclare;"); 1625 ADD_KEYWORD2("empty", "empty(%CURSOR%)"); 1626 ADD_KEYWORD2("eval", "eval(%CURSOR%)"); 1627 ADD_KEYWORD2("die", "die(%CURSOR%);\n"); 1628 ADD_KEYWORD2("exit", "exit(%CURSOR%);\n"); 1629 ///TODO: only activate when after "class NAME " 1630 ADD_KEYWORD("extends"); 1631 ADD_KEYWORD("implements"); 1632 ADD_KEYWORD2("for", "for(%CURSOR%;;) {\n%INDENT%\n}\n"); 1633 ADD_KEYWORD2("foreach", "foreach(%CURSOR%) {\n%INDENT%\n}\n"); 1634 ADD_KEYWORD2("function", "function %SELECT%NAME%ENDSELECT%() {\n%INDENT%\n}\n"); 1635 ADD_KEYWORD2("global", "global $%CURSOR%;"); 1636 ADD_KEYWORD2("if", "if (%CURSOR%) {\n%INDENT%\n}\n"); 1637 ADD_KEYWORD2("include", "include '%CURSOR%';\n"); 1638 ADD_KEYWORD2("include_once", "include_once '%CURSOR%';\n"); 1639 ADD_KEYWORD2("require", "require '%CURSOR%';\n"); 1640 ADD_KEYWORD2("require_once", "require_once '%CURSOR%';\n"); 1641 ADD_KEYWORD2("isset", "isset(%CURSOR%)"); 1642 ADD_KEYWORD2("list", "list(%CURSOR%)"); 1643 ADD_KEYWORD2("print", "print %CURSOR%;\n"); 1644 ADD_KEYWORD2("return", "return %CURSOR%;\n"); 1645 ADD_KEYWORD2("static", "static $%CURSOR%%;\n"); 1646 ADD_KEYWORD2("unset", "unset(%CURSOR%);\n"); 1647 ADD_KEYWORD2("while", "while (%CURSOR%) {\n%INDENT%\n}\n"); 1648 ADD_KEYWORD2("switch", "switch (%CURSOR%) {\n%INDENT%\n}\n"); 1649 } 1650 1651 return items; 1652 } 1653 1654 inline bool CodeCompletionContext::isValidCompletionItem(Declaration* dec) 1655 { 1656 if ( !dec || dec->range().isEmpty() ) { 1657 // hack for included files 1658 return false; 1659 } 1660 if ( dec->kind() == Declaration::Type && dec->qualifiedIdentifier().isEmpty() ) { 1661 // filter closures 1662 return false; 1663 } 1664 1665 static DUChainPointer<ClassDeclaration> exceptionDecl; 1666 if (!exceptionDecl) { 1667 /// Qualified identifier for 'exception' 1668 static const KDevelop::QualifiedIdentifier exceptionQId(QStringLiteral("exception")); 1669 QList<Declaration*> decs = dec->context()->findDeclarations(exceptionQId); 1670 Q_ASSERT(decs.count()); 1671 if (!decs.isEmpty()) { // additional safe-guard, see e.g. https://bugs.kde.org/show_bug.cgi?id=294218 1672 exceptionDecl = dynamic_cast<ClassDeclaration*>(decs.first()); 1673 Q_ASSERT(exceptionDecl); 1674 } 1675 } 1676 if (!exceptionDecl) { 1677 // safe-guard, see: https://bugs.kde.org/show_bug.cgi?id=294218 1678 qWarning() << "could not find PHP-Exception declaration, related code completion will be broken."; 1679 } 1680 1681 if (m_memberAccessOperation == ExceptionChoose 1682 || m_memberAccessOperation == NewClassChoose 1683 || m_memberAccessOperation == InterfaceChoose 1684 || m_memberAccessOperation == ClassExtendsChoose 1685 || m_memberAccessOperation == InstanceOfChoose) { 1686 // filter current class 1687 if (!m_forbiddenIdentifiers.isEmpty() && m_forbiddenIdentifiers.contains(dec->qualifiedIdentifier().index())) { 1688 return false; 1689 } 1690 ClassDeclaration* classDec = dynamic_cast<ClassDeclaration*>(dec); 1691 1692 // filter non-classes 1693 if (!classDec) { 1694 return false; 1695 } 1696 // show non-interface and non-abstract 1697 else if (m_memberAccessOperation == NewClassChoose) { 1698 return !(classDec->classModifier() & ClassDeclarationData::Abstract) 1699 && classDec->classType() == ClassDeclarationData::Class; 1700 } 1701 // filter non-exception classes 1702 else if (m_memberAccessOperation == ExceptionChoose) { 1703 if (!exceptionDecl) { 1704 // safe-guard, see: https://bugs.kde.org/show_bug.cgi?id=294218 1705 return false; 1706 } 1707 return classDec->equalQualifiedIdentifier(exceptionDecl.data()) 1708 || classDec->isPublicBaseClass(exceptionDecl.data(), m_duContext->topContext()); 1709 } 1710 // show interfaces 1711 else if (m_memberAccessOperation == InterfaceChoose) { 1712 return classDec->classType() == ClassDeclarationData::Interface; 1713 } 1714 // show anything but final classes and interfaces 1715 else if (m_memberAccessOperation == ClassExtendsChoose) { 1716 return !(classDec->classModifier() & ClassDeclarationData::Final) 1717 && classDec->classType() == ClassDeclarationData::Class; 1718 } 1719 else if (m_memberAccessOperation == InstanceOfChoose) { 1720 return true; 1721 } 1722 } 1723 1724 if (m_memberAccessOperation == ExceptionInstanceChoose) { 1725 if (!exceptionDecl) { 1726 // safe-guard, see: https://bugs.kde.org/show_bug.cgi?id=294218 1727 return false; 1728 } 1729 if (dec->kind() != Declaration::Instance) { 1730 return false; 1731 } 1732 StructureType::Ptr structType = dec->type<StructureType>(); 1733 if (!structType) { 1734 return false; 1735 } 1736 ClassDeclaration* classDec = dynamic_cast<ClassDeclaration*>(structType->declaration(dec->topContext())); 1737 if (!classDec) { 1738 return false; 1739 } 1740 return classDec->isPublicBaseClass(exceptionDecl.data(), m_duContext->topContext()); 1741 } 1742 1743 if (m_memberAccessOperation == NoMemberAccess) { 1744 // filter private methods and class members when doing a global completion 1745 // when we are inside a class function, don't filter the private stuff 1746 // of the current class 1747 // NOTE: ClassFunctionDeclaration inherits ClassMemberDeclaration 1748 // NOTE: both have to have a parent context with type class 1749 if ( dec->context() && dec->context()->type() == DUContext::Class 1750 && m_duContext->parentContext() != dec->context() ) 1751 { 1752 if ( ClassMemberDeclaration* memberDec = dynamic_cast<ClassMemberDeclaration*>(dec) ) { 1753 if ( memberDec->accessPolicy() == Declaration::Private ) { 1754 return false; 1755 } 1756 } 1757 } 1758 if ( !dec->isFunctionDeclaration() && m_duContext.data() == dec->context() && m_position < dec->range().start ) { 1759 return false; 1760 } 1761 } 1762 1763 if (m_memberAccessOperation == NamespaceChoose) { 1764 return dec->kind() == Declaration::Namespace; 1765 } 1766 1767 return true; 1768 } 1769 1770 QList<QSet<IndexedString> > CodeCompletionContext::completionFiles() 1771 { 1772 QList<QSet<IndexedString> > ret; 1773 if (ICore::self()) { 1774 auto projects = ICore::self()->projectController()->projects(); 1775 ret.reserve(projects.size()); 1776 foreach(IProject* project, projects) { 1777 ret << project->fileSet(); 1778 } 1779 } 1780 return ret; 1781 } 1782 1783 }