File indexing completed on 2024-05-12 04:37:45

0001 /*
0002     SPDX-FileCopyrightText: 2006 Hamish Rodda <rodda@kde.org>
0003     SPDX-FileCopyrightText: 2007-2009 David Nolden <david.nolden.kdevelop@art-master.de>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-only
0006 */
0007 
0008 #ifndef KDEVPLATFORM_CODECOMPLETIONTESTHELPER_H
0009 #define KDEVPLATFORM_CODECOMPLETIONTESTHELPER_H
0010 
0011 #include <QTest>
0012 #include <QStandardItemModel>
0013 
0014 #include "../duchain/declaration.h"
0015 #include "../duchain/duchain.h"
0016 #include "codecompletionitem.h"
0017 #include <language/codegen/coderepresentation.h>
0018 #include <language/duchain/duchainlock.h>
0019 #include <language/duchain/parsingenvironment.h>
0020 
0021 #include <tests/testhelpers.h>
0022 
0023 using namespace KTextEditor;
0024 
0025 using namespace KDevelop;
0026 
0027 /**
0028  * Helper-class for testing completion-items
0029  * Just initialize it with the context and the text, and then use the members, for simple cases only "names"
0030  * the template parameter is your language specific CodeCompletionContext
0031  */
0032 template <class T>
0033 struct CodeCompletionItemTester
0034 {
0035     using Element = QExplicitlySharedDataPointer<KDevelop::CompletionTreeElement>;
0036     using Item = QExplicitlySharedDataPointer<KDevelop::CompletionTreeItem>;
0037     using Context = QExplicitlySharedDataPointer<T>;
0038 
0039     //Standard constructor
0040     CodeCompletionItemTester(DUContext* context, const QString& text = QStringLiteral( "; " ),
0041                              const QString& followingText = QString(),
0042                              const CursorInRevision& position = CursorInRevision::invalid())
0043         : completionContext(new T(DUContextPointer(context), text, followingText,
0044                 position.isValid() ? position : context->range().end))
0045     {
0046         init();
0047     }
0048 
0049     //Can be used if you already have the completion context
0050     CodeCompletionItemTester(const Context& context)
0051         : completionContext(context)
0052     {
0053         init();
0054     }
0055 
0056     //Creates a CodeCompletionItemTester for the parent context
0057     CodeCompletionItemTester parent() const
0058     {
0059         Context parent = Context(dynamic_cast<T*>(completionContext->parentContext()));
0060         Q_ASSERT(parent);
0061         return CodeCompletionItemTester(parent);
0062     }
0063 
0064     void addElements(const QList<Element>& elements)
0065     {
0066         for (auto& element : elements) {
0067             Item item(dynamic_cast<CompletionTreeItem*>(element.data()));
0068             if (item)
0069                 items << item;
0070             auto* node = dynamic_cast<CompletionTreeNode*>(element.data());
0071             if (node)
0072                 addElements(node->children);
0073         }
0074     }
0075 
0076     bool containsDeclaration(Declaration* dec) const
0077     {
0078         for (auto& item : items) {
0079             if (item->declaration().data() == dec) {
0080                 return true;
0081             }
0082         }
0083 
0084         return false;
0085     }
0086 
0087     QList<Item> items; ///< All items retrieved
0088     QStringList names; ///< Names of all completion-items
0089     Context completionContext;
0090 
0091     //Convenience-function to retrieve data from completion-items by name
0092     QVariant itemData(const QString& itemName, int column = KTextEditor::CodeCompletionModel::Name,
0093                       int role = Qt::DisplayRole) const
0094     {
0095         return itemData(names.indexOf(itemName), column, role);
0096     }
0097 
0098     QVariant itemData(int itemNumber, int column = KTextEditor::CodeCompletionModel::Name,
0099                       int role = Qt::DisplayRole) const
0100     {
0101         if (itemNumber < 0 || itemNumber >= items.size())
0102             return QVariant();
0103 
0104         return itemData(items[itemNumber], column, role);
0105     }
0106 
0107     QVariant itemData(Item item, int column = KTextEditor::CodeCompletionModel::Name, int role = Qt::DisplayRole) const
0108     {
0109         return item->data(fakeModel().index(0, column), role, nullptr);
0110     }
0111 
0112     Item findItem(const QString& itemName) const
0113     {
0114         const auto idx = names.indexOf(itemName);
0115         if (idx < 0) {
0116             return {};
0117         }
0118         return items[idx];
0119     }
0120 
0121 private:
0122     void init()
0123     {
0124         if (!completionContext || !completionContext->isValid()) {
0125             qWarning() << "invalid completion context";
0126             return;
0127         }
0128 
0129         bool abort = false;
0130         items = completionContext->completionItems(abort);
0131 
0132         addElements(completionContext->ungroupedElements());
0133 
0134         names.reserve(items.size());
0135         for (const Item& i : qAsConst(items)) {
0136             names <<
0137                 i->data(fakeModel().index(0, KTextEditor::CodeCompletionModel::Name), Qt::DisplayRole,
0138                         nullptr).toString();
0139         }
0140     }
0141 
0142     static QStandardItemModel& fakeModel()
0143     {
0144         static QStandardItemModel model;
0145         model.setColumnCount(10);
0146         model.setRowCount(10);
0147         return model;
0148     }
0149 };
0150 
0151 /**
0152  * Helper class that inserts the given text into the duchain under the specified name,
0153  * allows parsing it with a simple call to parse(), and automatically releases the top-context
0154  *
0155  * The duchain must not be locked when this object is destroyed
0156  */
0157 struct InsertIntoDUChain
0158 {
0159     ///Artificially inserts a file called @p name with the text @p text
0160     InsertIntoDUChain(const QString& name, const QString& text) : m_insertedCode(IndexedString(name), text)
0161         , m_topContext(nullptr)
0162     {
0163     }
0164 
0165     ~InsertIntoDUChain()
0166     {
0167         get();
0168         release();
0169     }
0170 
0171     ///The duchain must not be locked when this is called
0172     void release()
0173     {
0174         if (m_topContext) {
0175             DUChainWriteLocker lock;
0176 
0177             m_topContext = nullptr;
0178 
0179             const QList<TopDUContext*> chains = DUChain::self()->chainsForDocument(m_insertedCode.file());
0180             for (TopDUContext* top : chains) {
0181                 DUChain::self()->removeDocumentChain(top);
0182             }
0183         }
0184     }
0185 
0186     TopDUContext* operator->()
0187     {
0188         get();
0189         return m_topContext.data();
0190     }
0191 
0192     TopDUContext* tryGet()
0193     {
0194         DUChainReadLocker lock;
0195         return DUChain::self()->chainForDocument(m_insertedCode.file(), false);
0196     }
0197 
0198     void get()
0199     {
0200         if (!m_topContext)
0201             m_topContext = tryGet();
0202     }
0203 
0204     ///Helper function: get a declaration based on its qualified identifier
0205     Declaration* declaration(const QString& id)
0206     {
0207         get();
0208         if (!topContext())
0209             return nullptr;
0210         return DeclarationId(IndexedQualifiedIdentifier(QualifiedIdentifier(id))).declaration(topContext());
0211     }
0212 
0213     TopDUContext* topContext()
0214     {
0215         return m_topContext.data();
0216     }
0217 
0218     /**
0219      * Parses this inserted code as a stand-alone top-context
0220      * The duchain must not be locked when this is called
0221      *
0222      * @param features The features that should be requested for the top-context
0223      * @param update Whether the top-context should be updated if it already exists. Else it will be deleted.
0224      */
0225     void parse(TopDUContext::Features features = TopDUContext::AllDeclarationsContextsAndUses, bool update = false)
0226     {
0227         if (!update)
0228             release();
0229         m_topContext = DUChain::self()->waitForUpdate(m_insertedCode.file(), features, false);
0230         Q_ASSERT(m_topContext);
0231         DUChainReadLocker lock;
0232         Q_ASSERT(!m_topContext->parsingEnvironmentFile()->isProxyContext());
0233     }
0234 
0235     InsertArtificialCodeRepresentation m_insertedCode;
0236     ReferencedTopDUContext m_topContext;
0237 };
0238 
0239 #endif // KDEVPLATFORM_CODECOMPLETIONTESTHELPER_H