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