File indexing completed on 2024-05-19 04:42:00

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 }