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 "helper.h"
0008 #include "functiondeclaration.h"
0009 #include "functiontype.h"
0010 #include "parsesession.h"
0011 #include "frameworks/nodejs.h"
0012 #include "qmljsducontext.h"
0013 
0014 #include <language/duchain/duchain.h>
0015 #include <language/duchain/duchainlock.h>
0016 #include <language/duchain/duchainregister.h>
0017 #include <language/duchain/functiondeclaration.h>
0018 #include <language/duchain/classfunctiondeclaration.h>
0019 #include <language/duchain/namespacealiasdeclaration.h>
0020 #include <language/duchain/types/unsuretype.h>
0021 #include <language/duchain/types/integraltype.h>
0022 #include <language/duchain/types/structuretype.h>
0023 #include <language/duchain/types/functiontype.h>
0024 #include <language/duchain/types/typeutils.h>
0025 #include <language/duchain/types/typeregister.h>
0026 
0027 namespace QmlJS
0028 {
0029 using namespace KDevelop;
0030 
0031 AbstractType::Ptr mergeTypes(AbstractType::Ptr type, const AbstractType::Ptr& newType)
0032 {
0033     if (newType && newType->whichType() == AbstractType::TypeFunction) {
0034         return newType;
0035     } else {
0036         return TypeUtils::mergeTypes(std::move(type), newType);
0037     }
0038 }
0039 
0040 DeclarationPointer getDeclaration(const QualifiedIdentifier& id, const DUContext* context, bool searchInParent)
0041 {
0042     DUChainReadLocker lock;
0043     if (context) {
0044         auto declarations = context->findDeclarations(
0045             id.indexedLast(),
0046             CursorInRevision(INT_MAX, INT_MAX),
0047             nullptr,
0048             searchInParent ? DUContext::NoSearchFlags : DUContext::DontSearchInParent
0049         );
0050 
0051         if (declarations.count() > 0) {
0052             return DeclarationPointer(declarations.last());
0053         }
0054     }
0055     return DeclarationPointer();
0056 }
0057 
0058 DeclarationPointer getDeclarationOrSignal(const QualifiedIdentifier& id, const DUContext* context, bool searchInParent)
0059 {
0060     QString identifier = id.last().toString();
0061 
0062     if (identifier.startsWith(QLatin1String("on")) && identifier.size() > 2) {
0063         // The use may have typed the name of a QML slot (onFoo), try to get
0064         // the declaration of its corresponding signal (foo)
0065         identifier = identifier.at(2).toLower() + identifier.midRef(3);
0066         DeclarationPointer decl = getDeclaration(QualifiedIdentifier(identifier), context, searchInParent);
0067 
0068         if (decl) {
0069             auto* classFuncDecl = dynamic_cast<ClassFunctionDeclaration *>(decl.data());
0070 
0071             if (classFuncDecl && classFuncDecl->isSignal()) {
0072                 // Removing "on" has given the identifier of a QML signal, return
0073                 // it instead of the name of its slot
0074                 return decl;
0075             }
0076         }
0077     }
0078 
0079     // No signal found, fall back to normal behavior
0080     return getDeclaration(id, context, searchInParent);
0081 }
0082 
0083 QmlJS::AST::Statement* getQMLAttribute(QmlJS::AST::UiObjectMemberList* members, const QString& attribute)
0084 {
0085     for (QmlJS::AST::UiObjectMemberList *it = members; it; it = it->next) {
0086         // The member needs to be a script binding whose name matches attribute
0087         auto* binding = QmlJS::AST::cast<QmlJS::AST::UiScriptBinding*>(it->member);
0088 
0089         if (binding && binding->qualifiedId && binding->qualifiedId->name == attribute) {
0090             return binding->statement;
0091         }
0092     }
0093 
0094     return nullptr;
0095 }
0096 
0097 QString getNodeValue(AST::Node* node)
0098 {
0099     auto identifier = QmlJS::AST::cast<QmlJS::AST::IdentifierExpression*>(node);
0100     auto identifier_name = QmlJS::AST::cast<QmlJS::AST::IdentifierPropertyName*>(node);
0101     auto string = QmlJS::AST::cast<QmlJS::AST::StringLiteral*>(node);
0102     auto string_name = QmlJS::AST::cast<QmlJS::AST::StringLiteralPropertyName*>(node);
0103     auto true_literal = QmlJS::AST::cast<QmlJS::AST::TrueLiteral*>(node);
0104     auto false_literal = QmlJS::AST::cast<QmlJS::AST::FalseLiteral*>(node);
0105 
0106     if (identifier) {
0107         return identifier->name.toString();
0108     } else if (identifier_name) {
0109         return identifier_name->id.toString();
0110     } else if (string) {
0111         return string->value.toString();
0112     } else if (string_name) {
0113         return string_name->id.toString();
0114     } else if (true_literal) {
0115         return QStringLiteral("true");
0116     } else if (false_literal) {
0117         return QStringLiteral("false");
0118     } else {
0119         return QString();
0120     }
0121 }
0122 
0123 QMLAttributeValue getQMLAttributeValue(QmlJS::AST::UiObjectMemberList* members, const QString& attribute)
0124 {
0125     QMLAttributeValue res;
0126     QmlJS::AST::Statement* node = getQMLAttribute(members, attribute);
0127 
0128     // The value of the binding must be an expression
0129     auto* statement = QmlJS::AST::cast<QmlJS::AST::ExpressionStatement*>(node);
0130 
0131     if (!statement) {
0132         return res;
0133     }
0134 
0135     // The expression must be an identifier or a string literal
0136     res.value = getNodeValue(statement->expression);
0137 
0138     if (res.value.isEmpty()) {
0139         return res;
0140     }
0141 
0142     res.location = statement->expression->firstSourceLocation();
0143 
0144     return res;
0145 }
0146 
0147 DUContext* getInternalContext(const DeclarationPointer& declaration)
0148 {
0149     DUChainReadLocker lock;
0150 
0151     if (!declaration) {
0152         return nullptr;
0153     }
0154 
0155     // The internal context of declarations having a function type is the prototype
0156     // context of the function (if any), or the internal context of Function
0157     auto functionType = declaration->abstractType().dynamicCast<QmlJS::FunctionType>();
0158 
0159     if (functionType) {
0160         Declaration* decl = functionType->declaration(declaration->topContext());
0161         QmlJS::FunctionDeclaration* funcDecl;
0162 
0163         if (decl && (funcDecl = dynamic_cast<QmlJS::FunctionDeclaration*>(decl)) &&
0164             funcDecl->prototypeContext()) {
0165             return funcDecl->prototypeContext();
0166         }
0167     }
0168 
0169     // The declaration can either be a class definition (its internal context
0170     // can be used) or an instance (use the internal context of its type)
0171     switch (declaration->kind()) {
0172     case Declaration::Type:
0173     case Declaration::Namespace:
0174         return declaration->internalContext();
0175 
0176     case Declaration::NamespaceAlias:
0177     {
0178         auto alias = declaration.dynamicCast<NamespaceAliasDeclaration>();
0179 
0180         return getInternalContext(getDeclaration(alias->importIdentifier(), alias->context()));
0181     }
0182 
0183     default:
0184     {
0185         auto structureType = declaration->abstractType().dynamicCast<StructureType>();
0186         auto integralType = declaration->abstractType().dynamicCast<IntegralType>();
0187 
0188         static const IndexedIdentifier indexedObject(Identifier(QStringLiteral("Object")));
0189         if (structureType) {
0190             auto structureDeclaration = structureType->declaration(declaration->topContext());
0191 
0192             if (structureDeclaration != declaration.data()) {
0193                 return getInternalContext(
0194                     DeclarationPointer(structureDeclaration)
0195                 );
0196             } else {
0197                 return nullptr;
0198             }
0199         } else if ((integralType || functionType) && declaration->indexedIdentifier() != indexedObject) {
0200             QString baseClass;
0201 
0202             // Compute from which base Javascript class a type inherits
0203             if (integralType) {
0204                 switch (integralType->dataType()) {
0205                     case IntegralType::TypeBoolean:
0206                         baseClass = QStringLiteral("Boolean");
0207                         break;
0208                     case IntegralType::TypeString:
0209                         baseClass = QStringLiteral("String");
0210                         break;
0211                     case IntegralType::TypeInt:
0212                     case IntegralType::TypeHalf:
0213                     case IntegralType::TypeFloat:
0214                     case IntegralType::TypeDouble:
0215                         baseClass = QStringLiteral("Number");
0216                         break;
0217                     case IntegralType::TypeArray:
0218                         baseClass = QStringLiteral("Array");
0219                         break;
0220                     default:
0221                         baseClass = QStringLiteral("Object");
0222                         break;
0223                 }
0224             } else if (functionType) {
0225                 baseClass = QStringLiteral("Function");
0226             }
0227 
0228             return getInternalContext(
0229                 NodeJS::instance().moduleMember(QStringLiteral("__builtin_ecmascript"), baseClass, declaration->topContext()->url())
0230             );
0231         } else {
0232             return nullptr;
0233         }
0234     }
0235     }
0236 }
0237 
0238 Declaration* getOwnerOfContext(const DUContext* context)
0239 {
0240     if (context->owner()) {
0241         return context->owner();
0242     } else if (context->type() == DUContext::Function && context->parentContext()) {
0243         return context->parentContext()->owner();
0244     } else {
0245         return nullptr;
0246     }
0247 }
0248 
0249 RangeInRevision emptyRangeOnLine(const AST::SourceLocation& location)
0250 {
0251     return RangeInRevision(location.startLine - 1, 0, location.startLine - 1, 0);
0252 }
0253 
0254 void importDeclarationInContext(DUContext* context, const DeclarationPointer& declaration)
0255 {
0256     DUContext* importedContext = QmlJS::getInternalContext(declaration);
0257 
0258     if (!importedContext || importedContext == context) {
0259         return;
0260     }
0261 
0262     {
0263         DUChainWriteLocker lock;
0264         context->addImportedParentContext(importedContext);
0265     }
0266 }
0267 
0268 void importObjectContext(DUContext* context, TopDUContext* topContext)
0269 {
0270     DeclarationPointer objectDeclaration =
0271         NodeJS::instance().moduleMember(QStringLiteral("__builtin_ecmascript"), QStringLiteral("Object"), topContext->url());
0272 
0273     if (objectDeclaration) {
0274         importDeclarationInContext(context, objectDeclaration);
0275     }
0276 }
0277 
0278 bool isPrototypeIdentifier(const QString& identifier)
0279 {
0280     return (identifier == QLatin1String("prototype") ||
0281             identifier == QLatin1String("__proto__"));
0282 }
0283 
0284 bool isQmlFile(const DUContext* context)
0285 {
0286     DUChainReadLocker lock;
0287     return ParseSession::guessLanguageFromSuffix(context->topContext()->url().str()) == Dialect::Qml;
0288 }
0289 
0290 void registerDUChainItems()
0291 {
0292     duchainRegisterType<QmlJSTopDUContext>();
0293     duchainRegisterType<QmlJSNormalDUContext>();
0294     duchainRegisterType<FunctionDeclaration>();
0295 
0296     TypeSystem::self().registerTypeClass<FunctionType>();
0297 }
0298 
0299 void unregisterDUChainItems()
0300 {
0301     TypeSystem::self().unregisterTypeClass<FunctionType>();
0302 
0303     // rest not supported, see comment in kdev-clang
0304 }
0305 
0306 } // End of namespace QmlJS