File indexing completed on 2024-05-19 15:46:44
0001 /* 0002 SPDX-FileCopyrightText: 2013 Andrea Scarpino <scarpino@kde.org> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "expressionvisitor.h" 0008 0009 #include <language/duchain/topducontext.h> 0010 #include <language/duchain/declaration.h> 0011 #include <language/duchain/persistentsymboltable.h> 0012 #include <language/duchain/duchainlock.h> 0013 #include <language/duchain/types/structuretype.h> 0014 #include <util/path.h> 0015 0016 #include "helper.h" 0017 #include "functiontype.h" 0018 #include "parsesession.h" 0019 #include "frameworks/nodejs.h" 0020 0021 #include <cmath> 0022 0023 using namespace KDevelop; 0024 0025 ExpressionVisitor::ExpressionVisitor(DUContext* context) 0026 : DynamicLanguageExpressionVisitor(context) 0027 , m_prototypeDepth(0) 0028 { 0029 } 0030 0031 void ExpressionVisitor::postVisit(QmlJS::AST::Node* node) 0032 { 0033 // Each time a node is closed, decrement the prototype depth. This way, 0034 // if a "prototype" node has been encountered, ExpressionVisitor can know 0035 // whether it appeared at the top of the tree ("foo.bar.prototype") or 0036 // somewhere else ("foo.prototype.bar"). 0037 --m_prototypeDepth; 0038 0039 QmlJS::AST::Visitor::postVisit(node); 0040 } 0041 0042 bool ExpressionVisitor::isPrototype() const 0043 { 0044 return m_prototypeDepth == 1; 0045 } 0046 0047 /* 0048 * Literals 0049 */ 0050 bool ExpressionVisitor::visit(QmlJS::AST::NumericLiteral* node) 0051 { 0052 int num_int_digits = (int)std::log10(node->value) + 1; 0053 0054 encounter( 0055 num_int_digits == (int)node->literalToken.length ? 0056 IntegralType::TypeInt : 0057 IntegralType::TypeDouble 0058 ); 0059 return false; 0060 } 0061 0062 bool ExpressionVisitor::visit(QmlJS::AST::StringLiteral*) 0063 { 0064 encounter(IntegralType::TypeString); 0065 return false; 0066 } 0067 0068 bool ExpressionVisitor::visit(QmlJS::AST::RegExpLiteral*) 0069 { 0070 encounter(QStringLiteral("RegExp")); 0071 0072 if (lastDeclaration()) { 0073 instantiateCurrentDeclaration(); 0074 } 0075 0076 return false; 0077 } 0078 0079 bool ExpressionVisitor::visit(QmlJS::AST::TrueLiteral*) 0080 { 0081 encounter(IntegralType::TypeBoolean); 0082 return false; 0083 } 0084 0085 bool ExpressionVisitor::visit(QmlJS::AST::FalseLiteral*) 0086 { 0087 encounter(IntegralType::TypeBoolean); 0088 return false; 0089 } 0090 0091 /* 0092 * Object and arrays 0093 */ 0094 bool ExpressionVisitor::visit(QmlJS::AST::ArrayLiteral*) 0095 { 0096 encounter(AbstractType::Ptr(new IntegralType(IntegralType::TypeArray))); 0097 return false; 0098 } 0099 0100 bool ExpressionVisitor::visit(QmlJS::AST::ObjectLiteral* node) 0101 { 0102 encounterObjectAtLocation(node->lbraceToken); 0103 return false; 0104 } 0105 0106 bool ExpressionVisitor::visit(QmlJS::AST::ArrayMemberExpression* node) 0107 { 0108 // array["string_literal"] is equivalent to array.string_literal 0109 auto literal = QmlJS::AST::cast<QmlJS::AST::StringLiteral*>(node->expression); 0110 0111 if (literal) { 0112 node->base->accept(this); 0113 encounterFieldMember(literal->value.toString()); 0114 } 0115 0116 return false; 0117 } 0118 0119 bool ExpressionVisitor::visit(QmlJS::AST::FieldMemberExpression* node) 0120 { 0121 // Find the type of the base, and if this type has a declaration, use 0122 // its inner context to get the type of the field member 0123 node->base->accept(this); 0124 encounterFieldMember(node->name.toString()); 0125 0126 return false; 0127 } 0128 0129 /* 0130 * Identifiers and common expressions 0131 */ 0132 bool ExpressionVisitor::visit(QmlJS::AST::BinaryExpression* node) 0133 { 0134 switch (node->op) { 0135 case QSOperator::BitAnd: 0136 case QSOperator::BitOr: 0137 case QSOperator::BitXor: 0138 case QSOperator::LShift: 0139 case QSOperator::RShift: 0140 case QSOperator::URShift: 0141 encounter(IntegralType::TypeInt); 0142 break; 0143 case QSOperator::And: 0144 case QSOperator::Equal: 0145 case QSOperator::Ge: 0146 case QSOperator::Gt: 0147 case QSOperator::In: 0148 case QSOperator::InstanceOf: 0149 case QSOperator::Le: 0150 case QSOperator::Lt: 0151 case QSOperator::Or: 0152 case QSOperator::StrictEqual: 0153 case QSOperator::StrictNotEqual: 0154 encounter(IntegralType::TypeBoolean); 0155 break; 0156 case QSOperator::Assign: 0157 node->right->accept(this); 0158 break; 0159 default: 0160 encounterNothing(); 0161 break; 0162 } 0163 0164 return false; 0165 } 0166 0167 bool ExpressionVisitor::visit(QmlJS::AST::IdentifierExpression* node) 0168 { 0169 encounter(node->name.toString()); 0170 return false; 0171 } 0172 0173 bool ExpressionVisitor::visit(QmlJS::AST::UiQualifiedId* node) 0174 { 0175 // "anchors.parent" results in an UiQualifiedId id having a "next" attribute. 0176 // This node reprensents "anchors", the next one is for "parent" 0177 encounter(node->name.toString()); 0178 0179 for (node = node->next; node && lastDeclaration(); node = node->next) { 0180 encounterFieldMember(node->name.toString()); 0181 } 0182 0183 return false; 0184 } 0185 0186 bool ExpressionVisitor::visit(QmlJS::AST::ThisExpression* node) 0187 { 0188 Q_UNUSED(node) 0189 DUChainReadLocker lock; 0190 DUContext* paramsContext; 0191 DUContext* internalContext; 0192 Declaration* owner; 0193 0194 // "this" points to the current function (not semantically valid in JS, 0195 // but this allows ExpressionVisitor to see the declarations of the 0196 // function's prototype) 0197 if (m_context->type() == DUContext::Other && // Code of the function 0198 (paramsContext = m_context->parentContext()) && // Parameters of the function (this context has the function as owner) 0199 (owner = QmlJS::getOwnerOfContext(paramsContext)) && // The function itself (owner of its parameters) 0200 (internalContext = QmlJS::getInternalContext(DeclarationPointer(owner))) && // The prototype context of the function 0201 (owner = QmlJS::getOwnerOfContext(internalContext)) && // The function that declared the prototype context (paramsContext may belong to a method of a class) 0202 owner->abstractType()) { 0203 encounterLvalue(DeclarationPointer(owner)); 0204 instantiateCurrentDeclaration(); 0205 } else { 0206 encounterNothing(); 0207 } 0208 0209 return false; 0210 } 0211 0212 /* 0213 * Functions 0214 */ 0215 bool ExpressionVisitor::visit(QmlJS::AST::FunctionExpression* node) 0216 { 0217 encounterObjectAtLocation(node->lparenToken); 0218 return false; 0219 } 0220 0221 bool ExpressionVisitor::visit(QmlJS::AST::CallExpression* node) 0222 { 0223 // Special-case functions that have a specific meaning in some frameworks 0224 auto functionIdentifier = QmlJS::AST::cast<QmlJS::AST::IdentifierExpression*>(node->base); 0225 0226 if (functionIdentifier && 0227 node->arguments && // One argument 0228 !node->arguments->next && // But not two 0229 functionIdentifier->name.toString() == QLatin1String("require")) { 0230 auto moduleName = QmlJS::AST::cast<QmlJS::AST::StringLiteral*>(node->arguments->expression); 0231 0232 if (moduleName) { 0233 encounterLvalue(QmlJS::NodeJS::instance().moduleExports( 0234 moduleName->value.toString(), 0235 m_context->topContext()->url() 0236 )); 0237 } else { 0238 encounterNothing(); 0239 } 0240 0241 return false; 0242 } 0243 0244 // Find the type of the function called 0245 node->base->accept(this); 0246 0247 auto func = m_lastType.dynamicCast<QmlJS::FunctionType>(); 0248 if (func && func->returnType()) { 0249 encounter(func->returnType()); 0250 } else { 0251 encounterNothing(); 0252 } 0253 0254 return false; 0255 } 0256 0257 bool ExpressionVisitor::visit(QmlJS::AST::NewMemberExpression* node) 0258 { 0259 // Find the type of the function used as constructor, and build a 0260 // StructureType representing an instance of this function. 0261 node->base->accept(this); 0262 0263 if (lastDeclaration()) { 0264 instantiateCurrentDeclaration(); 0265 } else { 0266 encounterNothing(); 0267 } 0268 0269 return false; 0270 } 0271 0272 void ExpressionVisitor::encounterNothing() 0273 { 0274 encounter(AbstractType::Ptr(), DeclarationPointer()); 0275 } 0276 0277 void ExpressionVisitor::encounter(IntegralType::CommonIntegralTypes type) 0278 { 0279 encounter(AbstractType::Ptr(new IntegralType(type))); 0280 } 0281 0282 void ExpressionVisitor::encounter(const QString& declaration, KDevelop::DUContext* context) 0283 { 0284 QualifiedIdentifier name(declaration); 0285 DUChainReadLocker lock; 0286 0287 if (!encounterParent(declaration) && 0288 !encounterDeclarationInContext(name, context) && 0289 !(!QmlJS::isQmlFile(m_context) && encounterDeclarationInNodeModule(name, QStringLiteral("__builtin_dom"))) && 0290 !encounterDeclarationInNodeModule(name, QStringLiteral("__builtin_ecmascript")) && 0291 !(context == nullptr && encounterGlobalDeclaration(name))) { 0292 encounterNothing(); 0293 } 0294 } 0295 0296 bool ExpressionVisitor::encounterParent(const QString& declaration) 0297 { 0298 if (declaration != QLatin1String("parent") || 0299 !QmlJS::isQmlFile(m_context)) { 0300 return false; 0301 } 0302 0303 // Go up until we find a class context (the enclosing QML component) 0304 const DUContext* parent = m_context; 0305 Declaration* owner; 0306 0307 while (parent && parent->type() != DUContext::Class) { 0308 parent = parent->parentContext(); 0309 } 0310 0311 // Take the parent context of the current QML component, it is its parent 0312 // component 0313 if (parent) { 0314 parent = parent->parentContext(); 0315 } 0316 0317 // Parent now points to the parent QML component. This is not always what 0318 // the user wants when typing "parent", but already works well for 0319 // "anchors.centerIn: parent" and things like that. 0320 if (parent && 0321 (owner = QmlJS::getOwnerOfContext(parent)) && 0322 owner->abstractType()) { 0323 encounterLvalue(DeclarationPointer(owner)); 0324 instantiateCurrentDeclaration(); 0325 return true; 0326 } 0327 0328 return false; 0329 } 0330 0331 bool ExpressionVisitor::encounterDeclarationInContext(const QualifiedIdentifier& id, DUContext* context) 0332 { 0333 DeclarationPointer dec = QmlJS::getDeclarationOrSignal(id, 0334 context ? context : m_context, 0335 context == nullptr); 0336 0337 if (dec) { 0338 encounterLvalue(dec); 0339 return true; 0340 } 0341 0342 return false; 0343 } 0344 0345 bool ExpressionVisitor::encounterDeclarationInNodeModule(const QualifiedIdentifier& id, 0346 const QString& module) 0347 { 0348 return encounterDeclarationInContext( 0349 id, 0350 QmlJS::getInternalContext( 0351 QmlJS::NodeJS::instance().moduleExports(module, m_context->url()) 0352 ) 0353 ); 0354 } 0355 0356 bool ExpressionVisitor::encounterGlobalDeclaration(const QualifiedIdentifier& id) 0357 { 0358 bool ret = false; 0359 // Use the persistent symbol table to find this declaration, even if it is in another file 0360 // Explore the declarations and filter-out those that come from a file outside the current directory 0361 PersistentSymbolTable::self().visitDeclarations( 0362 IndexedQualifiedIdentifier(id), [&](const IndexedDeclaration& decl) { 0363 if (!m_currentDir.isValid()) { 0364 m_currentDir = Path(m_context->topContext()->url().str()).parent(); 0365 } 0366 0367 IndexedTopDUContext declTopContext = decl.indexedTopContext(); 0368 0369 if (!declTopContext.isValid()) { 0370 return PersistentSymbolTable::VisitorState::Continue; 0371 } 0372 0373 if (m_currentDir.isDirectParentOf(Path(declTopContext.url().str()))) { 0374 encounterLvalue(DeclarationPointer(decl.declaration())); 0375 ret = true; 0376 return PersistentSymbolTable::VisitorState::Break; 0377 } 0378 return PersistentSymbolTable::VisitorState::Continue; 0379 }); 0380 0381 return ret; 0382 } 0383 0384 void ExpressionVisitor::encounterFieldMember(const QString& name) 0385 { 0386 if (QmlJS::isPrototypeIdentifier(name)) { 0387 // "prototype" is transparent: "object.prototype.foo" = "object.foo", and 0388 // "function.prototype" should point to "function". 0389 m_prototypeDepth = 2; 0390 return; 0391 } 0392 0393 DeclarationPointer declaration = lastDeclaration(); 0394 DUContext* context = QmlJS::getInternalContext(declaration); 0395 0396 if (context) { 0397 encounter(name, context); 0398 } else { 0399 encounterNothing(); 0400 } 0401 } 0402 0403 void ExpressionVisitor::encounterObjectAtLocation(const QmlJS::AST::SourceLocation& location) 0404 { 0405 DUChainReadLocker lock; 0406 0407 // Find the anonymous declaration corresponding to the function. This is 0408 // the owner of the current context (function expressions create new contexts) 0409 Declaration* dec = QmlJS::getOwnerOfContext(m_context->topContext()->findContextAt( 0410 CursorInRevision(location.startLine-1, location.startColumn) 0411 )); 0412 0413 if (dec && dec->abstractType()) { 0414 encounterLvalue(DeclarationPointer(dec)); 0415 } else { 0416 encounterNothing(); 0417 } 0418 } 0419 0420 void ExpressionVisitor::instantiateCurrentDeclaration() 0421 { 0422 StructureType::Ptr type = StructureType::Ptr(new StructureType); 0423 DeclarationPointer decl = lastDeclaration(); 0424 0425 { 0426 DUChainReadLocker lock; 0427 auto funcType = decl->abstractType().dynamicCast<QmlJS::FunctionType>(); 0428 0429 if (funcType) { 0430 decl = funcType->declaration(topContext()); 0431 } 0432 0433 type->setDeclaration(decl.data()); 0434 } 0435 0436 encounter(type, decl); 0437 }