// Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "findimplementation.h" #include #include #include #include #include #include #include #include #include #include namespace { using namespace QmlJS; class FindImplementationVisitor: protected AST::Visitor { public: using Results = QList; FindImplementationVisitor(const Document::Ptr &doc, const ContextPtr &context) : m_document(doc) , m_context(context) , m_scopeChain(doc, context) , m_scopeBuilder(&m_scopeChain) { } Results operator()(const QString &name, const QString &itemId, const ObjectValue *typeValue) { m_typeName = name; m_itemId = itemId; m_typeValue = typeValue; m_implemenations.clear(); if (m_document) AST::Node::accept(m_document->ast(), this); m_implemenations.append(m_formLocation); return m_implemenations; } protected: QString textAt(const SourceLocation &location) { return m_document->source().mid(location.offset, location.length); } QString textAt(const SourceLocation &from, const SourceLocation &to) { return m_document->source().mid(from.offset, to.end() - from.begin()); } void accept(AST::Node *node) { AST::Node::accept(node, this); } using AST::Visitor::visit; bool visit(AST::UiPublicMember *node) override { if (node->memberType && node->memberType->name == m_typeName){ const ObjectValue * objectValue = m_context->lookupType(m_document.data(), QStringList(m_typeName)); if (objectValue == m_typeValue) m_implemenations.append(node->typeToken); } if (AST::cast(node->statement)) { m_scopeBuilder.push(node); AST::Node::accept(node->statement, this); m_scopeBuilder.pop(); return false; } return true; } bool visit(AST::UiObjectDefinition *node) override { bool oldInside = m_insideObject; if (checkTypeName(node->qualifiedTypeNameId)) m_insideObject = true; m_scopeBuilder.push(node); AST::Node::accept(node->initializer, this); m_insideObject = oldInside; m_scopeBuilder.pop(); return false; } bool visit(AST::UiObjectBinding *node) override { bool oldInside = m_insideObject; if (checkTypeName(node->qualifiedTypeNameId)) m_insideObject = true; m_scopeBuilder.push(node); AST::Node::accept(node->initializer, this); m_insideObject = oldInside; m_scopeBuilder.pop(); return false; } bool visit(AST::UiScriptBinding *node) override { if (m_insideObject) { QStringList stringList = textAt(node->qualifiedId->firstSourceLocation(), node->qualifiedId->lastSourceLocation()).split(QLatin1String(".")); const QString itemid = stringList.isEmpty() ? QString() : stringList.constFirst(); if (itemid == m_itemId) { m_implemenations.append(node->statement->firstSourceLocation()); } } if (AST::cast(node->statement)) { AST::Node::accept(node->qualifiedId, this); m_scopeBuilder.push(node); AST::Node::accept(node->statement, this); m_scopeBuilder.pop(); return false; } return true; } bool visit(AST::TemplateLiteral *node) override { AST::Node::accept(node->expression, this); return true; } bool visit(AST::IdentifierExpression *node) override { if (node->name != m_typeName) return false; const ObjectValue *scope; const Value *objectValue = m_scopeChain.lookup(m_typeName, &scope); if (objectValue == m_typeValue) m_implemenations.append(node->identifierToken); return false; } bool visit(AST::FieldMemberExpression *node) override { if (node->name != m_typeName) return true; Evaluate evaluate(&m_scopeChain); const Value *lhsValue = evaluate(node->base); if (!lhsValue) return true; const ObjectValue *lhsObj = lhsValue->asObjectValue(); if (lhsObj && lhsObj->lookupMember(m_typeName, m_context) == m_typeValue) m_implemenations.append(node->identifierToken); return true; } bool visit(AST::FunctionDeclaration *node) override { return visit(static_cast(node)); } bool visit(AST::FunctionExpression *node) override { AST::Node::accept(node->formals, this); m_scopeBuilder.push(node); AST::Node::accept(node->body, this); m_scopeBuilder.pop(); return false; } bool visit(AST::PatternElement *node) override { if (node->isVariableDeclaration()) AST::Node::accept(node->initializer, this); return false; } bool visit(AST::UiImport *ast) override { if (ast && ast->importId == m_typeName) { const Imports *imp = m_context->imports(m_document.data()); if (!imp) return false; if (m_context->lookupType(m_document.data(), QStringList(m_typeName)) == m_typeValue) m_implemenations.append(ast->importIdToken); } return false; } void throwRecursionDepthError() override { qWarning("Warning: Hit maximum recursion depth while visiting AST in FindImplementationVisitor"); } private: bool checkTypeName(AST::UiQualifiedId *id) { for (AST::UiQualifiedId *qualifiedId = id; qualifiedId; qualifiedId = qualifiedId->next){ if (qualifiedId->name == m_typeName) { const ObjectValue *objectValue = m_context->lookupType(m_document.data(), id, qualifiedId->next); if (m_typeValue == objectValue){ m_formLocation = qualifiedId->identifierToken; return true; } } } return false; } Results m_implemenations; SourceLocation m_formLocation; Document::Ptr m_document; ContextPtr m_context; ScopeChain m_scopeChain; ScopeBuilder m_scopeBuilder; QString m_typeName; QString m_itemId; const ObjectValue *m_typeValue = nullptr; bool m_insideObject = false; }; QString matchingLine(unsigned position, const QString &source) { int start = source.lastIndexOf(QLatin1Char('\n'), position); start += 1; int end = source.indexOf(QLatin1Char('\n'), position); return source.mid(start, end - start); } } //namespace FindImplementation::FindImplementation() = default; QList FindImplementation::run(const QString &fileNameStr, const QString &typeName, const QString &itemName) { QList usages; QmlJS::ModelManagerInterface *modelManager = ModelManagerInterface::instance(); Utils::FilePath fileName = Utils::FilePath::fromString(fileNameStr); //Parse always the latest version of document QmlJS::Dialect dialect = QmlJS::ModelManagerInterface::guessLanguageOfFile(fileName); QmlJS::Document::MutablePtr documentUpdate = QmlJS::Document::create(fileName, dialect); documentUpdate->setSource(QmlJS::ModelManagerInterface::workingCopy().source(fileName)); if (documentUpdate->parseQml()) modelManager->updateDocument(documentUpdate); Document::Ptr document = modelManager->snapshot().document(fileName); if (!document) return usages; QmlJS::Link link(modelManager->snapshot(), modelManager->defaultVContext(document->language(), document), modelManager->builtins(document)); ContextPtr context = link(); ScopeChain scopeChain(document, context); const ObjectValue *targetValue = scopeChain.context()->lookupType(document.data(), QStringList(typeName)); FindImplementationVisitor visitor(document, context); const FindImplementationVisitor::Results results = visitor(typeName, itemName, targetValue); for (const SourceLocation &location : results) { usages.append(QmlJSEditor::FindReferences::Usage(fileName, matchingLine(location.offset, document->source()), location.startLine, location.startColumn - 1, location.length)); } return usages; }