diff options
Diffstat (limited to 'src/plugins/cppeditor/quickfixes/cppinsertvirtualmethods.cpp')
-rw-r--r-- | src/plugins/cppeditor/quickfixes/cppinsertvirtualmethods.cpp | 1994 |
1 files changed, 1994 insertions, 0 deletions
diff --git a/src/plugins/cppeditor/quickfixes/cppinsertvirtualmethods.cpp b/src/plugins/cppeditor/quickfixes/cppinsertvirtualmethods.cpp new file mode 100644 index 0000000000..1a2ee5ab87 --- /dev/null +++ b/src/plugins/cppeditor/quickfixes/cppinsertvirtualmethods.cpp @@ -0,0 +1,1994 @@ +// 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 "cppinsertvirtualmethods.h" + +#include "../cppcodestylesettings.h" +#include "../cppeditortr.h" +#include "../cpptoolsreuse.h" +#include "../functionutils.h" +#include "../insertionpointlocator.h" +#include "cppquickfixassistant.h" +#include "cppquickfixhelpers.h" + +#include <coreplugin/icore.h> +#include <texteditor/fontsettings.h> +#include <texteditor/texteditorsettings.h> + +#include <cplusplus/CppRewriter.h> +#include <cplusplus/Overview.h> + +#include <utils/algorithm.h> +#include <utils/changeset.h> +#include <utils/qtcassert.h> +#include <utils/utilsicons.h> + +#include <QAction> +#include <QCheckBox> +#include <QComboBox> +#include <QCoreApplication> +#include <QDialog> +#include <QDialogButtonBox> +#include <QGroupBox> +#include <QLineEdit> +#include <QPointer> +#include <QQueue> +#include <QSortFilterProxyModel> +#include <QStandardItemModel> +#include <QTextDocument> +#include <QToolButton> +#include <QTreeView> +#include <QVBoxLayout> + +#ifdef WITH_TESTS +#include "cppquickfix_test.h" +#include <QtTest> +#endif + +using namespace CPlusPlus; +using namespace TextEditor; +using namespace Utils; + +namespace CppEditor { +namespace Internal { + +class InsertVirtualMethodsModel; +class VirtualMethodsSettings; + +class InsertVirtualMethodsDialog : public QDialog +{ + Q_OBJECT +public: + enum CustomItemRoles { + Reimplemented = Qt::UserRole + }; + + enum ImplementationMode { + ModeOnlyDeclarations = 0x00000001, + ModeInsideClass = 0x00000002, + ModeOutsideClass = 0x00000004, + ModeImplementationFile = 0x00000008 + }; + + InsertVirtualMethodsDialog(QWidget *parent = nullptr); + ~InsertVirtualMethodsDialog() override; + void initGui(); + void initData(); + virtual void saveSettings(); + const VirtualMethodsSettings *settings() const; + + void setHasImplementationFile(bool file); + void setHasReimplementedFunctions(bool functions); + + virtual bool gather(); + +protected: + void setInsertOverrideReplacement(bool insert); + void setOverrideReplacement(const QString &replacements); + +private: + void setHideReimplementedFunctions(bool hide); + void updateOverrideReplacementsComboBox(); + +private: + QTreeView *m_view = nullptr; + QLineEdit *m_filter = nullptr; + QCheckBox *m_hideReimplementedFunctions = nullptr; + QComboBox *m_insertMode = nullptr; + QCheckBox *m_virtualKeyword = nullptr; + QCheckBox *m_overrideReplacementCheckBox = nullptr; + QComboBox *m_overrideReplacementComboBox = nullptr; + QToolButton *m_clearUserAddedReplacementsButton = nullptr; + QDialogButtonBox *m_buttons = nullptr; + QList<bool> m_expansionStateNormal; + QList<bool> m_expansionStateReimp; + QStringList m_availableOverrideReplacements; + bool m_hasImplementationFile = false; + bool m_hasReimplementedFunctions = false; + +protected: + VirtualMethodsSettings *m_settings; + + void saveExpansionState(); + void restoreExpansionState(); + +public: + InsertVirtualMethodsModel *classFunctionModel; + QSortFilterProxyModel *classFunctionFilterModel; +}; + +void registerInsertVirtualMethodsQuickfix() +{ + CppQuickFixFactory::registerFactory<InsertVirtualMethods>(); +} + +} // namespace Internal +} // namespace CppEditor + +Q_DECLARE_METATYPE(CppEditor::Internal::InsertVirtualMethodsDialog::ImplementationMode) + +namespace { + +class InsertVirtualMethodsItem +{ +public: + InsertVirtualMethodsItem(InsertVirtualMethodsItem *parent) + : m_parent(parent) + {} + + virtual ~InsertVirtualMethodsItem() = default; + + virtual QString description() const = 0; + virtual Qt::ItemFlags flags() const = 0; + virtual Qt::CheckState checkState() const = 0; + + InsertVirtualMethodsItem *parent() { return m_parent; } + + int row = -1; + +private: + InsertVirtualMethodsItem *m_parent = nullptr; +}; + +class FunctionItem; + +class ClassItem : public InsertVirtualMethodsItem +{ +public: + ClassItem(const QString &className, const Class *clazz); + ~ClassItem() override; + + QString description() const override { return name; } + Qt::ItemFlags flags() const override; + Qt::CheckState checkState() const override; + void removeFunction(int row); + + const Class *klass; + const QString name; + QList<FunctionItem *> functions; +}; + +class FunctionItem : public InsertVirtualMethodsItem +{ +public: + FunctionItem(const Function *func, const QString &functionName, ClassItem *parent); + QString description() const override; + Qt::ItemFlags flags() const override; + Qt::CheckState checkState() const override { return checked ? Qt::Checked : Qt::Unchecked; } + + const Function *function = nullptr; + CppEditor::InsertionPointLocator::AccessSpec accessSpec + = CppEditor::InsertionPointLocator::Invalid; + bool reimplemented = false; + bool alreadyFound = false; + bool checked = false; + FunctionItem *nextOverride = nullptr; + +private: + QString name; +}; + +ClassItem::ClassItem(const QString &className, const Class *clazz) : + InsertVirtualMethodsItem(nullptr), + klass(clazz), + name(className) +{ +} + +ClassItem::~ClassItem() +{ + qDeleteAll(functions); + functions.clear(); +} + +Qt::ItemFlags ClassItem::flags() const +{ + for (FunctionItem *func : std::as_const(functions)) { + if (!func->alreadyFound) + return Qt::ItemIsSelectable | Qt::ItemIsUserCheckable | Qt::ItemIsEnabled; + } + + return Qt::ItemIsSelectable; +} + +Qt::CheckState ClassItem::checkState() const +{ + if (functions.isEmpty()) + return Qt::Unchecked; + Qt::CheckState state = functions.first()->checkState(); + for (FunctionItem *function : std::as_const(functions)) { + Qt::CheckState functionState = function->checkState(); + if (functionState != state) + return Qt::PartiallyChecked; + } + return state; +} + +void ClassItem::removeFunction(int row) +{ + QTC_ASSERT(row >= 0 && row < functions.count(), return); + functions.removeAt(row); + // Update row number for all the following functions + for (int r = row, total = functions.count(); r < total; ++r) + functions[r]->row = r; +} + +FunctionItem::FunctionItem(const Function *func, const QString &functionName, ClassItem *parent) : + InsertVirtualMethodsItem(parent), + function(func), + nextOverride(this) +{ + name = functionName; +} + +QString FunctionItem::description() const +{ + return name; +} + +Qt::ItemFlags FunctionItem::flags() const +{ + Qt::ItemFlags res = Qt::NoItemFlags; + if (!alreadyFound) + res |= Qt::ItemIsSelectable | Qt::ItemIsUserCheckable | Qt::ItemIsEnabled; + return res; +} + +QStringList defaultOverrideReplacements() +{ + return { + QLatin1String("override"), + QLatin1String("Q_DECL_OVERRIDE") + }; +} + +QStringList sortedAndTrimmedStringListWithoutEmptyElements(const QStringList &list) +{ + QStringList result; + for (const QString &replacement : list) { + const QString trimmedReplacement = replacement.trimmed(); + if (!trimmedReplacement.isEmpty()) + result << trimmedReplacement; + } + result.sort(); + return result; +} + +} // namespace + +namespace CppEditor { +namespace Internal { + +const bool kInsertVirtualKeywordDefault = false; +const bool kHideReimplementedFunctionsDefault = false; +const bool kInsertOVerrideReplacementDefault = false; +const int kOverrideReplacementIndexDefault = 0; +const InsertVirtualMethodsDialog::ImplementationMode kImplementationModeDefault + = InsertVirtualMethodsDialog::ModeOnlyDeclarations; + +class VirtualMethodsSettings +{ +public: + void read() + { + QtcSettings *s = Core::ICore::settings(); + s->beginGroup(group()); + insertVirtualKeyword = s->value(insertVirtualKeywordKey(), kInsertVirtualKeywordDefault) + .toBool(); + hideReimplementedFunctions + = s->value(hideReimplementedFunctionsKey(), kHideReimplementedFunctionsDefault).toBool(); + insertOverrideReplacement + = s->value(insertOverrideReplacementKey(), kInsertOVerrideReplacementDefault).toBool(); + overrideReplacementIndex + = s->value(overrideReplacementIndexKey(), kOverrideReplacementIndexDefault).toInt(); + userAddedOverrideReplacements = s->value(userAddedOverrideReplacementsKey()).toStringList(); + implementationMode = static_cast<InsertVirtualMethodsDialog::ImplementationMode>( + s->value(implementationModeKey(), int(kImplementationModeDefault)).toInt()); + s->endGroup(); + } + + void write() const + { + QtcSettings *s = Core::ICore::settings(); + s->beginGroup(group()); + s->setValueWithDefault(insertVirtualKeywordKey(), + insertVirtualKeyword, + kInsertVirtualKeywordDefault); + s->setValueWithDefault(hideReimplementedFunctionsKey(), + hideReimplementedFunctions, + kHideReimplementedFunctionsDefault); + s->setValueWithDefault(insertOverrideReplacementKey(), + insertOverrideReplacement, + kInsertOVerrideReplacementDefault); + s->setValueWithDefault(overrideReplacementIndexKey(), + overrideReplacementIndex, + kOverrideReplacementIndexDefault); + s->setValueWithDefault(userAddedOverrideReplacementsKey(), userAddedOverrideReplacements); + s->setValueWithDefault(implementationModeKey(), + int(implementationMode), + int(kImplementationModeDefault)); + s->endGroup(); + } + + QString overrideReplacement; // internal + QStringList userAddedOverrideReplacements; + InsertVirtualMethodsDialog::ImplementationMode implementationMode = kImplementationModeDefault; + int overrideReplacementIndex = kOverrideReplacementIndexDefault; + bool insertVirtualKeyword = kInsertVirtualKeywordDefault; + bool hideReimplementedFunctions = kHideReimplementedFunctionsDefault; + bool insertOverrideReplacement = kInsertOVerrideReplacementDefault; + +private: + static Key group() { return "QuickFix/InsertVirtualMethods"; } + static Key insertVirtualKeywordKey() { return "insertKeywordVirtual"; } + static Key insertOverrideReplacementKey() { return "insertOverrideReplacement"; } + static Key overrideReplacementIndexKey() { return "overrideReplacementIndex"; } + static Key userAddedOverrideReplacementsKey() { return "userAddedOverrideReplacements"; } + static Key implementationModeKey() { return "implementationMode"; } + static Key hideReimplementedFunctionsKey() { return "hideReimplementedFunctions"; } +}; + +class InsertVirtualMethodsModel : public QAbstractItemModel +{ +public: + InsertVirtualMethodsModel(QObject *parent = nullptr) : QAbstractItemModel(parent) + { + const FontSettings &fs = TextEditorSettings::fontSettings(); + formatReimpFunc = fs.formatFor(C_DISABLED_CODE); + } + + ~InsertVirtualMethodsModel() override + { + clear(); + } + + void clear() + { + beginResetModel(); + qDeleteAll(classes); + classes.clear(); + endResetModel(); + } + + QModelIndex index(int row, int column, const QModelIndex &parent) const override + { + if (column != 0) + return {}; + if (parent.isValid()) { + auto classItem = static_cast<ClassItem *>(parent.internalPointer()); + return createIndex(row, column, classItem->functions.at(row)); + } + return createIndex(row, column, classes.at(row)); + } + + QModelIndex parent(const QModelIndex &child) const override + { + if (!child.isValid()) + return {}; + InsertVirtualMethodsItem *parent = itemForIndex(child)->parent(); + return parent ? createIndex(parent->row, 0, parent) : QModelIndex(); + } + + int rowCount(const QModelIndex &parent) const override + { + if (!parent.isValid()) + return classes.count(); + InsertVirtualMethodsItem *item = itemForIndex(parent); + if (item->parent()) // function -> no children + return 0; + return static_cast<ClassItem *>(item)->functions.count(); + } + + int columnCount(const QModelIndex &) const override + { + return 1; + } + + void addClass(ClassItem *classItem) + { + int row = classes.count(); + classItem->row = row; + beginInsertRows(QModelIndex(), row, row); + classes.append(classItem); + endInsertRows(); + } + + void removeFunction(FunctionItem *funcItem) + { + auto classItem = static_cast<ClassItem *>(funcItem->parent()); + beginRemoveRows(createIndex(classItem->row, 0, classItem), funcItem->row, funcItem->row); + classItem->removeFunction(funcItem->row); + endRemoveRows(); + } + + QVariant data(const QModelIndex &index, int role) const override + { + if (!index.isValid()) + return QVariant(); + + InsertVirtualMethodsItem *item = itemForIndex(index); + switch (role) { + case Qt::DisplayRole: + return item->description(); + case Qt::CheckStateRole: + return item->checkState(); + case Qt::ForegroundRole: + if (item->parent() && static_cast<FunctionItem *>(item)->alreadyFound) + return formatReimpFunc.foreground(); + break; + case Qt::BackgroundRole: + if (item->parent() && static_cast<FunctionItem *>(item)->alreadyFound) { + const QColor background = formatReimpFunc.background(); + if (background.isValid()) + return background; + } + break; + case InsertVirtualMethodsDialog::Reimplemented: + if (item->parent()) { + auto function = static_cast<FunctionItem *>(item); + return QVariant(function->alreadyFound); + } + + } + return QVariant(); + } + + bool setData(const QModelIndex &index, const QVariant &value, int role) override + { + if (!index.isValid()) + return false; + + InsertVirtualMethodsItem *item = itemForIndex(index); + switch (role) { + case Qt::CheckStateRole: { + bool checked = value.toInt() == Qt::Checked; + if (item->parent()) { + auto funcItem = static_cast<FunctionItem *>(item); + while (funcItem->checked != checked) { + funcItem->checked = checked; + const QModelIndex funcIndex = createIndex(funcItem->row, 0, funcItem); + emit dataChanged(funcIndex, funcIndex); + const QModelIndex parentIndex = + createIndex(funcItem->parent()->row, 0, funcItem->parent()); + emit dataChanged(parentIndex, parentIndex); + funcItem = funcItem->nextOverride; + } + } else { + auto classItem = static_cast<ClassItem *>(item); + for (FunctionItem *funcItem : std::as_const(classItem->functions)) { + if (funcItem->alreadyFound || funcItem->checked == checked) + continue; + QModelIndex funcIndex = createIndex(funcItem->row, 0, funcItem); + setData(funcIndex, value, role); + } + } + return true; + } + } + return QAbstractItemModel::setData(index, value, role); + } + + Qt::ItemFlags flags(const QModelIndex &index) const override + { + if (!index.isValid()) + return Qt::NoItemFlags; + return itemForIndex(index)->flags(); + } + + QList<ClassItem *> classes; + +private: + Format formatReimpFunc; + + InsertVirtualMethodsItem *itemForIndex(const QModelIndex &index) const + { + return static_cast<InsertVirtualMethodsItem *>(index.internalPointer()); + } +}; + +class InsertVirtualMethodsOp : public CppQuickFixOperation +{ +public: + InsertVirtualMethodsOp(const CppQuickFixInterface &interface, + InsertVirtualMethodsDialog *factory) + : CppQuickFixOperation(interface, 0) + , m_factory(factory) + { + setDescription(Tr::tr("Insert Virtual Functions of Base Classes")); + + const QList<AST *> &path = interface.path(); + const int pathSize = path.size(); + if (pathSize < 2) + return; + + // Determine if cursor is on a class or a base class + if (SimpleNameAST *nameAST = path.at(pathSize - 1)->asSimpleName()) { + if (!interface.isCursorOn(nameAST)) + return; + + if (!(m_classAST = path.at(pathSize - 2)->asClassSpecifier())) { // normal class + int index = pathSize - 2; + const BaseSpecifierAST *baseAST = path.at(index)->asBaseSpecifier();// simple bclass + if (!baseAST) { + if (index > 0 && path.at(index)->asQualifiedName()) // namespaced base class + baseAST = path.at(--index)->asBaseSpecifier(); + } + --index; + if (baseAST && index >= 0) + m_classAST = path.at(index)->asClassSpecifier(); + } + } + + // Also offer the operation if we are on some "empty" part of the class declaration. + if (!m_classAST) + m_classAST = path.at(pathSize - 1)->asClassSpecifier(); + + if (!m_classAST || !m_classAST->base_clause_list) + return; + + // Determine insert positions + const int endOfClassAST = interface.currentFile()->endOf(m_classAST); + m_insertPosDecl = endOfClassAST - 1; // Skip last "}" + m_insertPosOutside = endOfClassAST + 1; // Step over ";" + + // Determine base classes + QList<const Class *> baseClasses; + QQueue<ClassOrNamespace *> baseClassQueue; + QSet<ClassOrNamespace *> visitedBaseClasses; + if (ClassOrNamespace *clazz = interface.context().lookupType(m_classAST->symbol)) + baseClassQueue.enqueue(clazz); + while (!baseClassQueue.isEmpty()) { + ClassOrNamespace *clazz = baseClassQueue.dequeue(); + visitedBaseClasses.insert(clazz); + const QList<ClassOrNamespace *> bases = clazz->usings(); + for (const ClassOrNamespace *baseClass : bases) { + const QList<Symbol *> symbols = baseClass->symbols(); + for (Symbol *symbol : symbols) { + Class *base = symbol->asClass(); + if (base + && (clazz = interface.context().lookupType(symbol)) + && !visitedBaseClasses.contains(clazz) + && !baseClasses.contains(base)) { + baseClasses.prepend(base); + baseClassQueue.enqueue(clazz); + } + } + } + } + + // Determine virtual functions + m_factory->classFunctionModel->clear(); + Overview printer = CppCodeStyleSettings::currentProjectCodeStyleOverview(); + printer.showFunctionSignatures = true; + QHash<const Function *, FunctionItem *> virtualFunctions; + for (const Class *clazz : std::as_const(baseClasses)) { + ClassItem *itemBase = new ClassItem(printer.prettyName(clazz->name()), clazz); + for (Scope::iterator it = clazz->memberBegin(); it != clazz->memberEnd(); ++it) { + if (const Function *func = (*it)->type()->asFunctionType()) { + // Filter virtual destructors + const Name *name = func->name(); + if (!name || name->asDestructorNameId()) + continue; + + QList<const Function * > firstVirtuals; + const bool isVirtual = FunctionUtils::isVirtualFunction( + func, interface.context(), &firstVirtuals); + if (!isVirtual) + continue; + + if (func->isFinal()) { + for (const Function *firstVirtual : std::as_const(firstVirtuals)) { + if (FunctionItem *first = virtualFunctions[firstVirtual]) { + FunctionItem *next = nullptr; + for (FunctionItem *removed = first; next != first; removed = next) { + next = removed->nextOverride; + m_factory->classFunctionModel->removeFunction(removed); + delete removed; + }; + virtualFunctions.remove(firstVirtual); + } + } + continue; + } + // Filter OQbject's + // - virtual const QMetaObject *metaObject() const; + // - virtual void *qt_metacast(const char *); + // - virtual int qt_metacall(QMetaObject::Call, int, void **); + bool skip = false; + for (const Function *firstVirtual : std::as_const(firstVirtuals)) { + if (printer.prettyName(firstVirtual->enclosingClass()->name()) == "QObject" + && magicQObjectFunctions().contains( + printer.prettyName(func->name()))) { + skip = true; + break; + } + } + if (skip) + continue; + + // Do not implement existing functions inside target class + bool funcExistsInClass = false; + const Name *funcName = func->name(); + const OperatorNameId * const opName = funcName->asOperatorNameId(); + Symbol *symbol = opName ? m_classAST->symbol->find(opName->kind()) + : m_classAST->symbol->find(funcName->identifier()); + for (; symbol; symbol = symbol->next()) { + if (!opName && (!symbol->name() + || !funcName->identifier()->match(symbol->identifier()))) { + continue; + } + if (symbol->type().match(func->type())) { + funcExistsInClass = true; + break; + } + } + + // Construct function item + const bool isReimplemented = !firstVirtuals.contains(func); + const bool isPureVirtual = func->isPureVirtual(); + QString itemName = printer.prettyType(func->type(), func->name()); + if (isPureVirtual) + itemName += QLatin1String(" = 0"); + const QString itemReturnTypeString = printer.prettyType(func->returnType()); + itemName += QLatin1String(" : ") + itemReturnTypeString; + if (isReimplemented) + itemName += QLatin1String(" (redeclared)"); + auto funcItem = new FunctionItem(func, itemName, itemBase); + if (isReimplemented) { + factory->setHasReimplementedFunctions(true); + funcItem->reimplemented = true; + funcItem->alreadyFound = funcExistsInClass; + for (const Function *firstVirtual : std::as_const(firstVirtuals)) { + if (FunctionItem *first = virtualFunctions[firstVirtual]) { + if (!first->alreadyFound) { + while (first->checked != isPureVirtual) { + first->checked = isPureVirtual; + first = first->nextOverride; + } + } + funcItem->checked = first->checked; + + FunctionItem *prev = funcItem; + for (FunctionItem *next = funcItem->nextOverride; + next && next != funcItem; next = next->nextOverride) { + prev = next; + } + prev->nextOverride = first->nextOverride; + first->nextOverride = funcItem; + } + } + } else { + if (!funcExistsInClass) { + funcItem->checked = isPureVirtual; + } else { + funcItem->alreadyFound = true; + funcItem->checked = true; + factory->setHasReimplementedFunctions(true); + } + } + + funcItem->accessSpec = acessSpec(*it); + funcItem->row = itemBase->functions.count(); + itemBase->functions.append(funcItem); + + virtualFunctions[func] = funcItem; + + // update internal counters + if (!funcExistsInClass) + ++m_functionCount; + } + } + + if (itemBase->functions.isEmpty()) + delete itemBase; + else + m_factory->classFunctionModel->addClass(itemBase); + } + if (m_factory->classFunctionModel->classes.isEmpty() || m_functionCount == 0) + return; + + bool isHeaderFile = false; + m_cppFilePath = correspondingHeaderOrSource(interface.filePath(), &isHeaderFile); + m_factory->setHasImplementationFile(isHeaderFile && !m_cppFilePath.isEmpty()); + + m_valid = true; + } + + bool isValid() const + { + return m_valid; + } + + InsertionPointLocator::AccessSpec acessSpec(const Symbol *symbol) + { + const Function *func = symbol->type()->asFunctionType(); + if (!func) + return InsertionPointLocator::Invalid; + if (func->isSignal()) + return InsertionPointLocator::Signals; + + InsertionPointLocator::AccessSpec spec = InsertionPointLocator::Invalid; + if (symbol->isPrivate()) + spec = InsertionPointLocator::Private; + else if (symbol->isProtected()) + spec = InsertionPointLocator::Protected; + else if (symbol->isPublic()) + spec = InsertionPointLocator::Public; + else + return InsertionPointLocator::Invalid; + + if (func->isSlot()) { + switch (spec) { + case InsertionPointLocator::Private: + return InsertionPointLocator::PrivateSlot; + case InsertionPointLocator::Protected: + return InsertionPointLocator::ProtectedSlot; + case InsertionPointLocator::Public: + return InsertionPointLocator::PublicSlot; + default: + return spec; + } + } + return spec; + } + + void perform() override + { + if (!m_factory->gather()) + return; + + m_factory->saveSettings(); + + // Insert declarations (and definition if Inside-/OutsideClass) + Overview printer = CppCodeStyleSettings::currentProjectCodeStyleOverview(); + printer.showFunctionSignatures = true; + printer.showReturnTypes = true; + printer.showArgumentNames = true; + printer.showTemplateParameters = true; + Utils::ChangeSet headerChangeSet; + const CppRefactoringChanges refactoring(snapshot()); + const CppRefactoringFilePtr headerFile = currentFile(); + const LookupContext targetContext(headerFile->cppDocument(), snapshot()); + + const Class *targetClass = m_classAST->symbol; + ClassOrNamespace *targetCoN = targetContext.lookupType(targetClass->enclosingScope()); + if (!targetCoN) + targetCoN = targetContext.globalNamespace(); + UseMinimalNames useMinimalNames(targetCoN); + Control *control = context().bindings()->control().get(); + QList<const Function *> insertedFunctions; + for (ClassItem *classItem : std::as_const(m_factory->classFunctionModel->classes)) { + if (classItem->checkState() == Qt::Unchecked) + continue; + + // Insert Declarations (+ definitions) + QString lastAccessSpecString; + bool first = true; + for (FunctionItem *funcItem : std::as_const(classItem->functions)) { + if (funcItem->reimplemented || funcItem->alreadyFound || !funcItem->checked) + continue; + + const auto cmp = [funcItem](const Function *f) { + return f->name()->match(funcItem->function->name()) + && f->type().match(funcItem->function->type()); + }; + if (Utils::contains(insertedFunctions, cmp)) + continue; + insertedFunctions.append(funcItem->function); + + if (first) { + // Add comment + const QString comment = QLatin1String("\n// ") + + printer.prettyName(classItem->klass->name()) + + QLatin1String(" interface\n"); + headerChangeSet.insert(m_insertPosDecl, comment); + first = false; + } + + // Function type minimalization: As base class and derived class could be in + // different namespaces, we must first make the type fully qualified before + // it can get minimized. + Clone cloner(control); + Function newFunc(&cloner, nullptr, const_cast<Function *>(funcItem->function)); + newFunc.setEnclosingScope(const_cast<Class *>(targetClass)); + SubstitutionEnvironment envQualified; + envQualified.setContext(context()); + envQualified.switchScope(classItem->klass->enclosingScope()); + UseQualifiedNames useQualifiedNames; + envQualified.enter(&useQualifiedNames); + newFunc.setReturnType(rewriteType(newFunc.returnType(), &envQualified, control)); + const int argc = newFunc.argumentCount(); + for (int i = 0; i < argc; ++i) { + Argument * const arg = newFunc.argumentAt(i)->asArgument(); + QTC_ASSERT(arg, continue); + arg->setType(rewriteType(arg->type(), &envQualified, control)); + } + SubstitutionEnvironment envMinimized; + envMinimized.setContext(context()); + envMinimized.switchScope(targetClass->enclosingScope()); + envMinimized.enter(&useMinimalNames); + const FullySpecifiedType tn = rewriteType(newFunc.type(), &envMinimized, control); + QString declaration = printer.prettyType(tn, newFunc.unqualifiedName()); + + if (m_factory->settings()->insertVirtualKeyword) + declaration = QLatin1String("virtual ") + declaration; + if (m_factory->settings()->insertOverrideReplacement) { + const QString overrideReplacement = m_factory->settings()->overrideReplacement; + if (!overrideReplacement.isEmpty()) + declaration += QLatin1Char(' ') + overrideReplacement; + } + if (m_factory->settings()->implementationMode + & InsertVirtualMethodsDialog::ModeInsideClass) { + declaration += QLatin1String("\n{\n}\n"); + } else { + declaration += QLatin1String(";\n"); + } + + const QString accessSpecString = + InsertionPointLocator::accessSpecToString(funcItem->accessSpec); + if (accessSpecString != lastAccessSpecString) { + declaration = accessSpecString + QLatin1String(":\n") + declaration; + if (!lastAccessSpecString.isEmpty()) // separate if not direct after the comment + declaration = QLatin1String("\n") + declaration; + lastAccessSpecString = accessSpecString; + } + headerChangeSet.insert(m_insertPosDecl, declaration); + + // Insert definition outside class + if (m_factory->settings()->implementationMode + & InsertVirtualMethodsDialog::ModeOutsideClass) { + const QString name = printer.prettyName(targetClass->name()) + + QLatin1String("::") + printer.prettyName(funcItem->function->name()); + const QString defText = printer.prettyType(tn, name) + QLatin1String("\n{\n}"); + headerChangeSet.insert(m_insertPosOutside, QLatin1String("\n\n") + defText); + } + } + } + + // Write header file + if (!headerChangeSet.isEmpty()) { + headerFile->setOpenEditor(true, m_insertPosDecl); + headerFile->apply(headerChangeSet); + } + + // Insert in implementation file + if (m_factory->settings()->implementationMode + & InsertVirtualMethodsDialog::ModeImplementationFile) { + const Symbol *symbol = headerFile->cppDocument()->lastVisibleSymbolAt( + targetClass->line(), targetClass->column()); + if (!symbol) + return; + const Class *clazz = symbol->asClass(); + if (!clazz) + return; + + CppRefactoringFilePtr implementationFile = refactoring.cppFile(m_cppFilePath); + Utils::ChangeSet implementationChangeSet; + const int insertPos = qMax(0, implementationFile->document()->characterCount() - 1); + + // make target lookup context + Document::Ptr implementationDoc = implementationFile->cppDocument(); + int line, column; + implementationDoc->translationUnit()->getPosition(insertPos, &line, &column); + Scope *targetScope = implementationDoc->scopeAt(line, column); + const LookupContext targetContext(implementationDoc, snapshot()); + ClassOrNamespace *targetCoN = targetContext.lookupType(targetScope); + if (!targetCoN) + targetCoN = targetContext.globalNamespace(); + + // Loop through inserted declarations + for (int i = targetClass->memberCount(); i < clazz->memberCount(); ++i) { + Declaration *decl = clazz->memberAt(i)->asDeclaration(); + if (!decl) + continue; + + // setup rewriting to get minimally qualified names + SubstitutionEnvironment env; + env.setContext(context()); + env.switchScope(decl->enclosingScope()); + UseMinimalNames q(targetCoN); + env.enter(&q); + Control *control = context().bindings()->control().get(); + + // rewrite the function type and name + create definition + const FullySpecifiedType type = rewriteType(decl->type(), &env, control); + const QString name = printer.prettyName( + LookupContext::minimalName(decl, targetCoN, control)); + const QString defText = printer.prettyType(type, name) + QLatin1String("\n{\n}"); + + implementationChangeSet.insert(insertPos, QLatin1String("\n\n") + defText); + } + + implementationFile->apply(implementationChangeSet); + } + } + +private: + InsertVirtualMethodsDialog *m_factory = nullptr; + const ClassSpecifierAST *m_classAST = nullptr; + bool m_valid = false; + FilePath m_cppFilePath; + int m_insertPosDecl = 0; + int m_insertPosOutside = 0; + unsigned m_functionCount = 0; +}; + +class InsertVirtualMethodsFilterModel : public QSortFilterProxyModel +{ + Q_OBJECT +public: + InsertVirtualMethodsFilterModel(QObject *parent = nullptr) + : QSortFilterProxyModel(parent) + {} + + bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override + { + QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent); + + // Handle base class + if (!sourceParent.isValid()) { + // check if any child is valid + if (!sourceModel()->hasChildren(index)) + return false; + if (!m_hideReimplemented) + return true; + + for (int i = 0; i < sourceModel()->rowCount(index); ++i) { + const QModelIndex child = sourceModel()->index(i, 0, index); + if (!child.data(InsertVirtualMethodsDialog::Reimplemented).toBool()) + return true; + } + return false; + } + + if (!QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent)) + return false; + if (m_hideReimplemented) + return !index.data(InsertVirtualMethodsDialog::Reimplemented).toBool(); + return true; + } + + bool hideReimplemented() const + { + return m_hideReimplemented; + } + + void setHideReimplementedFunctions(bool show) + { + m_hideReimplemented = show; + invalidateFilter(); + } + +private: + bool m_hideReimplemented = false; +}; + +InsertVirtualMethodsDialog::InsertVirtualMethodsDialog(QWidget *parent) + : QDialog(parent) + , m_settings(new VirtualMethodsSettings) + , classFunctionModel(new InsertVirtualMethodsModel(this)) + , classFunctionFilterModel(new InsertVirtualMethodsFilterModel(this)) +{ + classFunctionFilterModel->setSourceModel(classFunctionModel); + classFunctionFilterModel->setFilterCaseSensitivity(Qt::CaseInsensitive); +} + +InsertVirtualMethodsDialog::~InsertVirtualMethodsDialog() +{ + delete m_settings; +} + +void InsertVirtualMethodsDialog::initGui() +{ + if (m_view) + return; + + setWindowTitle(Tr::tr("Insert Virtual Functions")); + auto globalVerticalLayout = new QVBoxLayout; + + // View + QGroupBox *groupBoxView = new QGroupBox(Tr::tr("&Functions to insert:"), this); + auto groupBoxViewLayout = new QVBoxLayout(groupBoxView); + m_filter = new QLineEdit(this); + m_filter->setClearButtonEnabled(true); + m_filter->setPlaceholderText(Tr::tr("Filter")); + groupBoxViewLayout->addWidget(m_filter); + m_view = new QTreeView(this); + m_view->setEditTriggers(QAbstractItemView::NoEditTriggers); + m_view->setHeaderHidden(true); + groupBoxViewLayout->addWidget(m_view); + m_hideReimplementedFunctions = + new QCheckBox(Tr::tr("&Hide reimplemented functions"), this); + groupBoxViewLayout->addWidget(m_hideReimplementedFunctions); + + // Insertion options + QGroupBox *groupBoxImplementation = new QGroupBox(Tr::tr("&Insertion options:"), this); + auto groupBoxImplementationLayout = new QVBoxLayout(groupBoxImplementation); + m_insertMode = new QComboBox(this); + m_insertMode->addItem(Tr::tr("Insert only declarations"), ModeOnlyDeclarations); + m_insertMode->addItem(Tr::tr("Insert definitions inside class"), ModeInsideClass); + m_insertMode->addItem(Tr::tr("Insert definitions outside class"), ModeOutsideClass); + m_insertMode->addItem(Tr::tr("Insert definitions in implementation file"), ModeImplementationFile); + m_virtualKeyword = new QCheckBox(Tr::tr("Add \"&virtual\" to function declaration"), this); + m_overrideReplacementCheckBox = new QCheckBox( + Tr::tr("Add \"override\" equivalent to function declaration:"), this); + m_overrideReplacementComboBox = new QComboBox(this); + QSizePolicy sizePolicy = m_overrideReplacementComboBox->sizePolicy(); + sizePolicy.setHorizontalPolicy(QSizePolicy::Expanding); + m_overrideReplacementComboBox->setSizePolicy(sizePolicy); + m_overrideReplacementComboBox->setEditable(true); + connect(m_overrideReplacementCheckBox, &QCheckBox::clicked, + m_overrideReplacementComboBox, &QComboBox::setEnabled); + + auto clearUserAddedReplacements = new QAction(this); + clearUserAddedReplacements->setIcon(Utils::Icons::CLEAN_TOOLBAR.icon()); + clearUserAddedReplacements->setText(Tr::tr("Clear Added \"override\" Equivalents")); + connect(clearUserAddedReplacements, &QAction::triggered, this, [this] { + m_availableOverrideReplacements = defaultOverrideReplacements(); + updateOverrideReplacementsComboBox(); + m_clearUserAddedReplacementsButton->setEnabled(false); + }); + m_clearUserAddedReplacementsButton = new QToolButton(this); + m_clearUserAddedReplacementsButton->setDefaultAction(clearUserAddedReplacements); + + auto overrideWidgetsLayout = new QHBoxLayout(this); + overrideWidgetsLayout->setSpacing(0); + overrideWidgetsLayout->setContentsMargins(0, 0, 0, 0); + overrideWidgetsLayout->addWidget(m_overrideReplacementCheckBox); + overrideWidgetsLayout->addWidget(m_overrideReplacementComboBox); + overrideWidgetsLayout->addWidget(m_clearUserAddedReplacementsButton); + QWidget *overrideWidgets = new QWidget(groupBoxImplementation); + overrideWidgets->setLayout(overrideWidgetsLayout); + + groupBoxImplementationLayout->addWidget(m_insertMode); + groupBoxImplementationLayout->addWidget(m_virtualKeyword); + groupBoxImplementationLayout->addWidget(overrideWidgets); + groupBoxImplementationLayout->addStretch(99); + + // Bottom button box + m_buttons = new QDialogButtonBox(this); + m_buttons->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + connect(m_buttons, &QDialogButtonBox::accepted, this, &QDialog::accept); + connect(m_buttons, &QDialogButtonBox::rejected, this, &QDialog::reject); + + globalVerticalLayout->addWidget(groupBoxView, 9); + globalVerticalLayout->addWidget(groupBoxImplementation, 0); + globalVerticalLayout->addWidget(m_buttons, 0); + setLayout(globalVerticalLayout); + + connect(m_hideReimplementedFunctions, &QAbstractButton::toggled, + this, &InsertVirtualMethodsDialog::setHideReimplementedFunctions); + connect(m_filter, &QLineEdit::textChanged, + classFunctionFilterModel, &QSortFilterProxyModel::setFilterWildcard); +} + +void InsertVirtualMethodsDialog::initData() +{ + m_settings->read(); + m_filter->clear(); + m_hideReimplementedFunctions->setChecked(m_settings->hideReimplementedFunctions); + const QStringList alwaysPresentReplacements = defaultOverrideReplacements(); + m_availableOverrideReplacements = alwaysPresentReplacements; + m_availableOverrideReplacements += m_settings->userAddedOverrideReplacements; + + m_view->setModel(classFunctionFilterModel); + m_expansionStateNormal.clear(); + m_expansionStateReimp.clear(); + m_hideReimplementedFunctions->setEnabled(m_hasReimplementedFunctions); + m_virtualKeyword->setChecked(m_settings->insertVirtualKeyword); + m_insertMode->setCurrentIndex(m_insertMode->findData(m_settings->implementationMode)); + + m_overrideReplacementCheckBox->setChecked(m_settings->insertOverrideReplacement); + updateOverrideReplacementsComboBox(); + const bool canClear = m_availableOverrideReplacements.size() > alwaysPresentReplacements.size(); + m_clearUserAddedReplacementsButton->setEnabled(canClear); + int overrideReplacementIndex = m_settings->overrideReplacementIndex; + if (overrideReplacementIndex >= m_overrideReplacementComboBox->count()) + overrideReplacementIndex = 0; + m_overrideReplacementComboBox->setCurrentIndex(overrideReplacementIndex); + + setHideReimplementedFunctions(m_hideReimplementedFunctions->isChecked()); + + if (m_hasImplementationFile) { + if (m_insertMode->count() == 3) { + m_insertMode->addItem(Tr::tr("Insert definitions in implementation file"), + ModeImplementationFile); + } + } else { + if (m_insertMode->count() == 4) + m_insertMode->removeItem(3); + } +} + +void InsertVirtualMethodsDialog::saveSettings() +{ + m_settings->insertVirtualKeyword = m_virtualKeyword->isChecked(); + m_settings->implementationMode = static_cast<InsertVirtualMethodsDialog::ImplementationMode>( + m_insertMode->itemData(m_insertMode->currentIndex()).toInt()); + m_settings->hideReimplementedFunctions = m_hideReimplementedFunctions->isChecked(); + m_settings->insertOverrideReplacement = m_overrideReplacementCheckBox->isChecked(); + m_settings->overrideReplacementIndex = m_overrideReplacementComboBox->currentIndex(); + if (m_overrideReplacementComboBox && m_overrideReplacementComboBox->isEnabled()) + m_settings->overrideReplacement = m_overrideReplacementComboBox->currentText().trimmed(); + QSet<QString> addedReplacements = Utils::toSet(m_availableOverrideReplacements); + addedReplacements.insert(m_settings->overrideReplacement); + addedReplacements.subtract(Utils::toSet(defaultOverrideReplacements())); + m_settings->userAddedOverrideReplacements = + sortedAndTrimmedStringListWithoutEmptyElements(Utils::toList(addedReplacements)); + m_settings->write(); +} + +const VirtualMethodsSettings *InsertVirtualMethodsDialog::settings() const +{ + return m_settings; +} + +bool InsertVirtualMethodsDialog::gather() +{ + initGui(); + initData(); + m_filter->setFocus(); + + // Expand the dialog a little bit + adjustSize(); + resize(size() * 1.5); + + QPointer<InsertVirtualMethodsDialog> that(this); + const int ret = exec(); + if (!that) + return false; + + return (ret == QDialog::Accepted); +} + +void InsertVirtualMethodsDialog::setHasImplementationFile(bool file) +{ + m_hasImplementationFile = file; +} + +void InsertVirtualMethodsDialog::setHasReimplementedFunctions(bool functions) +{ + m_hasReimplementedFunctions = functions; +} + +void InsertVirtualMethodsDialog::setHideReimplementedFunctions(bool hide) +{ + auto model = qobject_cast<InsertVirtualMethodsFilterModel *>(classFunctionFilterModel); + + if (m_expansionStateNormal.isEmpty() && m_expansionStateReimp.isEmpty()) { + model->setHideReimplementedFunctions(hide); + m_view->expandAll(); + saveExpansionState(); + return; + } + + if (model->hideReimplemented() == hide) + return; + + saveExpansionState(); + model->setHideReimplementedFunctions(hide); + restoreExpansionState(); +} + +void InsertVirtualMethodsDialog::updateOverrideReplacementsComboBox() +{ + m_overrideReplacementComboBox->clear(); + for (const QString &replacement : std::as_const(m_availableOverrideReplacements)) + m_overrideReplacementComboBox->addItem(replacement); +} + +void InsertVirtualMethodsDialog::saveExpansionState() +{ + auto model = qobject_cast<InsertVirtualMethodsFilterModel *>(classFunctionFilterModel); + + QList<bool> &state = model->hideReimplemented() ? m_expansionStateReimp + : m_expansionStateNormal; + state.clear(); + for (int i = 0; i < model->rowCount(); ++i) + state << m_view->isExpanded(model->index(i, 0)); +} + +void InsertVirtualMethodsDialog::restoreExpansionState() +{ + auto model = qobject_cast<InsertVirtualMethodsFilterModel *>(classFunctionFilterModel); + + const QList<bool> &state = model->hideReimplemented() ? m_expansionStateReimp + : m_expansionStateNormal; + const int stateCount = state.count(); + for (int i = 0; i < model->rowCount(); ++i) { + if (i < stateCount && !state.at(i)) { + m_view->collapse(model->index(i, 0)); + continue; + } + m_view->expand(model->index(i, 0)); + } +} + +InsertVirtualMethods::InsertVirtualMethods(InsertVirtualMethodsDialog *dialog) + : m_dialog(dialog) +{ + if (!dialog) + m_dialog = new InsertVirtualMethodsDialog; +} + +InsertVirtualMethods::~InsertVirtualMethods() +{ + m_dialog->deleteLater(); +} + +void InsertVirtualMethods::doMatch(const CppQuickFixInterface &interface, + QuickFixOperations &result) +{ + QSharedPointer<InsertVirtualMethodsOp> op(new InsertVirtualMethodsOp(interface, m_dialog)); + if (op->isValid()) + result.append(op); +} + +#ifdef WITH_TESTS + +namespace Tests { + +typedef QByteArray _; + +/// Stub dialog of InsertVirtualMethodsDialog that does not pop up anything. +class InsertVirtualMethodsDialogTest : public InsertVirtualMethodsDialog +{ +public: + InsertVirtualMethodsDialogTest(ImplementationMode mode, + bool insertVirtualKeyword, + bool insertOverrideKeyword, + QWidget *parent = 0) + : InsertVirtualMethodsDialog(parent) + { + m_settings->implementationMode = mode; + m_settings->insertVirtualKeyword = insertVirtualKeyword; + m_settings->insertOverrideReplacement = insertOverrideKeyword; + m_settings->overrideReplacement = QLatin1String("override"); + } + + bool gather() override { return true; } + void saveSettings() override { } +}; + +class InsertVirtualMethodsTest : public QObject +{ + Q_OBJECT + +private slots: + void test_data(); + void test(); + void testImplementationFile(); + void testBaseClassInNamespace(); +}; + +void InsertVirtualMethodsTest::test_data() +{ + QTest::addColumn<InsertVirtualMethodsDialog::ImplementationMode>("implementationMode"); + QTest::addColumn<bool>("insertVirtualKeyword"); + QTest::addColumn<bool>("insertOverrideKeyword"); + QTest::addColumn<QByteArray>("original"); + QTest::addColumn<QByteArray>("expected"); + + // Check: Insert only declarations + QTest::newRow("onlyDecl") + << InsertVirtualMethodsDialog::ModeOnlyDeclarations << true << false << _( + "class BaseA {\n" + "public:\n" + " virtual int virtualFuncA() = 0;\n" + "};\n\n" + "class Derived : public Bas@eA {\n" + "};\n" + ) << _( + "class BaseA {\n" + "public:\n" + " virtual int virtualFuncA() = 0;\n" + "};\n\n" + "class Derived : public BaseA {\n" + "\n" + " // BaseA interface\n" + "public:\n" + " virtual int virtualFuncA();\n" + "};\n" + ); + + // Check: Insert only declarations without virtual keyword but with override + QTest::newRow("onlyDeclWithoutVirtual") + << InsertVirtualMethodsDialog::ModeOnlyDeclarations << false << true << _( + "class BaseA {\n" + "public:\n" + " virtual int virtualFuncA() = 0;\n" + "};\n\n" + "class Derived : public Bas@eA {\n" + "};\n" + ) << _( + "class BaseA {\n" + "public:\n" + " virtual int virtualFuncA() = 0;\n" + "};\n\n" + "class Derived : public BaseA {\n" + "\n" + " // BaseA interface\n" + "public:\n" + " int virtualFuncA() override;\n" + "};\n" + ); + + // Check: Are access specifiers considered + QTest::newRow("Access") + << InsertVirtualMethodsDialog::ModeOnlyDeclarations << true << false << _( + "class BaseA {\n" + "public:\n" + " virtual int a() = 0;\n" + "protected:\n" + " virtual int b() = 0;\n" + "private:\n" + " virtual int c() = 0;\n" + "public slots:\n" + " virtual int d() = 0;\n" + "protected slots:\n" + " virtual int e() = 0;\n" + "private slots:\n" + " virtual int f() = 0;\n" + "signals:\n" + " virtual int g() = 0;\n" + "};\n\n" + "class Der@ived : public BaseA {\n" + "};\n" + ) << _( + "class BaseA {\n" + "public:\n" + " virtual int a() = 0;\n" + "protected:\n" + " virtual int b() = 0;\n" + "private:\n" + " virtual int c() = 0;\n" + "public slots:\n" + " virtual int d() = 0;\n" + "protected slots:\n" + " virtual int e() = 0;\n" + "private slots:\n" + " virtual int f() = 0;\n" + "signals:\n" + " virtual int g() = 0;\n" + "};\n\n" + "class Derived : public BaseA {\n" + "\n" + " // BaseA interface\n" + "public:\n" + " virtual int a();\n\n" + "protected:\n" + " virtual int b();\n\n" + "private:\n" + " virtual int c();\n\n" + "public slots:\n" + " virtual int d();\n\n" + "protected slots:\n" + " virtual int e();\n\n" + "private slots:\n" + " virtual int f();\n\n" + "signals:\n" + " virtual int g();\n" + "};\n" + ); + + // Check: Is a base class of a base class considered. + QTest::newRow("Superclass") + << InsertVirtualMethodsDialog::ModeOnlyDeclarations << true << false << _( + "class BaseA {\n" + "public:\n" + " virtual int a() = 0;\n" + "};\n\n" + "class BaseB : public BaseA {\n" + "public:\n" + " virtual int b() = 0;\n" + "};\n\n" + "class Der@ived : public BaseB {\n" + "};\n" + ) << _( + "class BaseA {\n" + "public:\n" + " virtual int a() = 0;\n" + "};\n\n" + "class BaseB : public BaseA {\n" + "public:\n" + " virtual int b() = 0;\n" + "};\n\n" + "class Der@ived : public BaseB {\n" + "\n" + " // BaseA interface\n" + "public:\n" + " virtual int a();\n" + "\n" + " // BaseB interface\n" + "public:\n" + " virtual int b();\n" + "};\n" + ); + + + // Check: Do not insert reimplemented functions twice. + QTest::newRow("SuperclassOverride") + << InsertVirtualMethodsDialog::ModeOnlyDeclarations << true << false << _( + "class BaseA {\n" + "public:\n" + " virtual int a() = 0;\n" + "};\n\n" + "class BaseB : public BaseA {\n" + "public:\n" + " virtual int a() = 0;\n" + "};\n\n" + "class Der@ived : public BaseB {\n" + "};\n" + ) << _( + "class BaseA {\n" + "public:\n" + " virtual int a() = 0;\n" + "};\n\n" + "class BaseB : public BaseA {\n" + "public:\n" + " virtual int a() = 0;\n" + "};\n\n" + "class Der@ived : public BaseB {\n" + "\n" + " // BaseA interface\n" + "public:\n" + " virtual int a();\n" + "};\n" + ); + + // Check: Insert only declarations for pure virtual function + QTest::newRow("PureVirtualOnlyDecl") + << InsertVirtualMethodsDialog::ModeOnlyDeclarations << true << false << _( + "class BaseA {\n" + "public:\n" + " virtual int virtualFuncA() = 0;\n" + "};\n\n" + "class Derived : public Bas@eA {\n" + "};\n" + ) << _( + "class BaseA {\n" + "public:\n" + " virtual int virtualFuncA() = 0;\n" + "};\n\n" + "class Derived : public BaseA {\n" + "\n" + " // BaseA interface\n" + "public:\n" + " virtual int virtualFuncA();\n" + "};\n" + ); + + // Check: Insert pure virtual functions inside class + QTest::newRow("PureVirtualInside") + << InsertVirtualMethodsDialog::ModeInsideClass << true << false << _( + "class BaseA {\n" + "public:\n" + " virtual int virtualFuncA() = 0;\n" + "};\n\n" + "class Derived : public Bas@eA {\n" + "};\n" + ) << _( + "class BaseA {\n" + "public:\n" + " virtual int virtualFuncA() = 0;\n" + "};\n\n" + "class Derived : public BaseA {\n" + "\n" + " // BaseA interface\n" + "public:\n" + " virtual int virtualFuncA()\n" + " {\n" + " }\n" + "};\n" + ); + + // Check: Overloads + QTest::newRow("Overloads") + << InsertVirtualMethodsDialog::ModeInsideClass << true << false << _( + "class BaseA {\n" + "public:\n" + " virtual int virt(int i) = 0;\n" + " virtual int virt(double d) = 0;\n" + "};\n\n" + "class Derived : public Bas@eA {\n" + "};\n" + ) << _( + "class BaseA {\n" + "public:\n" + " virtual int virt(int i) = 0;\n" + " virtual int virt(double d) = 0;\n" + "};\n\n" + "class Derived : public BaseA {\n" + "\n" + " // BaseA interface\n" + "public:\n" + " virtual int virt(int i)\n" + " {\n" + " }\n" + " virtual int virt(double d)\n" + " {\n" + " }\n" + "};\n" + ); + + // Check: Insert inside class + QTest::newRow("inside") + << InsertVirtualMethodsDialog::ModeInsideClass << true << false << _( + "class BaseA {\n" + "public:\n" + " virtual int virtualFuncA() = 0;\n" + "};\n\n" + "class Derived : public Bas@eA {\n" + "};\n" + ) << _( + "class BaseA {\n" + "public:\n" + " virtual int virtualFuncA() = 0;\n" + "};\n\n" + "class Derived : public BaseA {\n" + "\n" + " // BaseA interface\n" + "public:\n" + " virtual int virtualFuncA()\n" + " {\n" + " }\n" + "};\n" + ); + + // Check: Insert outside class + QTest::newRow("outside") + << InsertVirtualMethodsDialog::ModeOutsideClass << true << false << _( + "class BaseA {\n" + "public:\n" + " virtual int virtualFuncA() = 0;\n" + "};\n\n" + "class Derived : public Bas@eA {\n" + "};\n" + ) << _( + "class BaseA {\n" + "public:\n" + " virtual int virtualFuncA() = 0;\n" + "};\n\n" + "class Derived : public BaseA {\n" + "\n" + " // BaseA interface\n" + "public:\n" + " virtual int virtualFuncA();\n" + "};\n\n" + "int Derived::virtualFuncA()\n" + "{\n" + "}\n" + ); + + // Check: No trigger: all implemented + QTest::newRow("notrigger_allImplemented") + << InsertVirtualMethodsDialog::ModeOutsideClass << true << false << _( + "class BaseA {\n" + "public:\n" + " virtual int virtualFuncA();\n" + " virtual operator==(const BaseA &);\n" + "};\n\n" + "class Derived : public Bas@eA {\n" + "public:\n" + " virtual int virtualFuncA() = 0;\n" + " virtual operator==(const BaseA &);\n" + "};\n" + ) << _(); + + // Check: One pure, one not + QTest::newRow("Some_Pure") + << InsertVirtualMethodsDialog::ModeOnlyDeclarations << true << false << _( + "class BaseA {\n" + "public:\n" + " virtual int virtualFuncA() = 0;\n" + " virtual int virtualFuncB();\n" + "};\n\n" + "class Derived : public Bas@eA {\n" + "};\n" + ) << _( + "class BaseA {\n" + "public:\n" + " virtual int virtualFuncA() = 0;\n" + " virtual int virtualFuncB();\n" + "};\n\n" + "class Derived : public BaseA {\n" + "\n" + " // BaseA interface\n" + "public:\n" + " virtual int virtualFuncA();\n" + "};\n" + ); + + // Check: Pure function in derived class + QTest::newRow("Pure_in_Derived") + << InsertVirtualMethodsDialog::ModeOnlyDeclarations << true << false << _( + "class BaseA {\n" + "public:\n" + " virtual int a();\n" + "};\n\n" + "class BaseB : public BaseA {\n" + "public:\n" + " virtual int a() = 0;\n" + "};\n\n" + "class Der@ived : public BaseB {\n" + "};\n" + ) << _( + "class BaseA {\n" + "public:\n" + " virtual int a();\n" + "};\n\n" + "class BaseB : public BaseA {\n" + "public:\n" + " virtual int a() = 0;\n" + "};\n\n" + "class Der@ived : public BaseB {\n" + "\n" + " // BaseA interface\n" + "public:\n" + " virtual int a();\n" + "};\n" + ); + + // Check: One pure function in base class, one in derived + QTest::newRow("Pure_in_Base_And_Derived") + << InsertVirtualMethodsDialog::ModeOnlyDeclarations << true << false << _( + "class BaseA {\n" + "public:\n" + " virtual int a() = 0;\n" + " virtual int b();\n" + "};\n\n" + "class BaseB : public BaseA {\n" + "public:\n" + " virtual int b() = 0;\n" + "};\n\n" + "class Der@ived : public BaseB {\n" + "};\n" + ) << _( + "class BaseA {\n" + "public:\n" + " virtual int a() = 0;\n" + " virtual int b();\n" + "};\n\n" + "class BaseB : public BaseA {\n" + "public:\n" + " virtual int b() = 0;\n" + "};\n\n" + "class Der@ived : public BaseB {\n" + "\n" + " // BaseA interface\n" + "public:\n" + " virtual int a();\n" + " virtual int b();\n" + "};\n" + ); + + // Check: One pure function in base class, two in derived + QTest::newRow("Pure_in_Base_And_Derived_2") + << InsertVirtualMethodsDialog::ModeOnlyDeclarations << true << false << _( + "class BaseA {\n" + "public:\n" + " virtual int a() = 0;\n" + " virtual int b();\n" + "};\n\n" + "class BaseB : public BaseA {\n" + "public:\n" + " virtual int b() = 0;\n" + " virtual int c() = 0;\n" + "};\n\n" + "class Der@ived : public BaseB {\n" + "};\n" + ) << _( + "class BaseA {\n" + "public:\n" + " virtual int a() = 0;\n" + " virtual int b();\n" + "};\n\n" + "class BaseB : public BaseA {\n" + "public:\n" + " virtual int b() = 0;\n" + " virtual int c() = 0;\n" + "};\n\n" + "class Der@ived : public BaseB {\n" + "\n" + " // BaseA interface\n" + "public:\n" + " virtual int a();\n" + " virtual int b();\n" + "\n" + " // BaseB interface\n" + "public:\n" + " virtual int c();\n" + "};\n" + ); + + // Check: Remove final function + QTest::newRow("final_function_removed") + << InsertVirtualMethodsDialog::ModeOnlyDeclarations << true << false << _( + "class BaseA {\n" + "public:\n" + " virtual int a() = 0;\n" + " virtual int b() = 0;\n" + "};\n\n" + "class BaseB : public BaseA {\n" + "public:\n" + " virtual int a() final = 0;\n" + "};\n\n" + "class Der@ived : public BaseB {\n" + "};\n" + ) << _( + "class BaseA {\n" + "public:\n" + " virtual int a() = 0;\n" + " virtual int b() = 0;\n" + "};\n\n" + "class BaseB : public BaseA {\n" + "public:\n" + " virtual int a() final = 0;\n" + "};\n\n" + "class Der@ived : public BaseB {\n" + "\n" + " // BaseA interface\n" + "public:\n" + " virtual int b();\n" + "};\n" + ); + + // Check: Remove multiple final functions + QTest::newRow("multiple_final_functions_removed") + << InsertVirtualMethodsDialog::ModeOnlyDeclarations << true << false << _( + "class BaseA {\n" + "public:\n" + " virtual int a() = 0;\n" + " virtual int b() = 0;\n" + "};\n\n" + "class BaseB : public BaseA {\n" + "public:\n" + " virtual int a() = 0;\n" + " virtual int c() = 0;\n" + "};\n\n" + "class BaseC : public BaseB {\n" + "public:\n" + " virtual int a() final = 0;\n" + " virtual int d() = 0;\n" + "};\n\n" + "class Der@ived : public BaseC {\n" + "};\n" + ) << _( + "class BaseA {\n" + "public:\n" + " virtual int a() = 0;\n" + " virtual int b() = 0;\n" + "};\n\n" + "class BaseB : public BaseA {\n" + "public:\n" + " virtual int a() = 0;\n" + " virtual int c() = 0;\n" + "};\n\n" + "class BaseC : public BaseB {\n" + "public:\n" + " virtual int a() final = 0;\n" + " virtual int d() = 0;\n" + "};\n\n" + "class Der@ived : public BaseC {\n" + "\n" + " // BaseA interface\n" + "public:\n" + " virtual int b();\n" + "\n" + " // BaseB interface\n" + "public:\n" + " virtual int c();\n" + "\n" + " // BaseC interface\n" + "public:\n" + " virtual int d();\n" + "};\n" + ); + + // Check: Insert multiply-inherited virtual function only once. + QTest::newRow("multiple_inheritance_insert") + << InsertVirtualMethodsDialog::ModeOnlyDeclarations << false << true << _( + "struct Base1 {\n" + " virtual void virt() = 0;\n" + "};\n\n" + "struct Base2 {\n" + " virtual void virt() = 0;\n" + "};\n\n" + "struct @Derived : Base1, Base2 {\n" + "};\n") << _( + "struct Base1 {\n" + " virtual void virt() = 0;\n" + "};\n\n" + "struct Base2 {\n" + " virtual void virt() = 0;\n" + "};\n\n" + "struct Derived : Base1, Base2 {\n\n" + " // Base2 interface\n" + "public:\n" + " void virt() override;\n" + "};\n"); + + // Check: Do not insert multiply-inherited virtual function that has been re-implemented + // along the way. + QTest::newRow("multiple_inheritance_no_insert") + << InsertVirtualMethodsDialog::ModeOnlyDeclarations << false << true << _( + "struct Base1 {\n" + " virtual void virt() = 0;\n" + "};\n\n" + "struct Base2 {\n" + " virtual void virt() = 0;\n" + "};\n\n" + "struct Derived1 : Base1, Base2 {\n" + " void virt() override;\n" + "};\n\n" + "struct @Derived2 : Derived1\n" + "};\n") << _( + "struct Base1 {\n" + " virtual void virt() = 0;\n" + "};\n\n" + "struct Base2 {\n" + " virtual void virt() = 0;\n" + "};\n\n" + "struct Derived1 : Base1, Base2 {\n" + " void virt() override;\n" + "};\n\n" + "struct Derived2 : Derived1\n" + "};\n"); +} + +void InsertVirtualMethodsTest::test() +{ + QFETCH(InsertVirtualMethodsDialog::ImplementationMode, implementationMode); + QFETCH(bool, insertVirtualKeyword); + QFETCH(bool, insertOverrideKeyword); + QFETCH(QByteArray, original); + QFETCH(QByteArray, expected); + + InsertVirtualMethods factory( + new Tests::InsertVirtualMethodsDialogTest(implementationMode, + insertVirtualKeyword, + insertOverrideKeyword)); + Tests::QuickFixOperationTest(Tests::singleDocument(original, expected), &factory); +} + +/// Check: Insert in implementation file +void InsertVirtualMethodsTest::testImplementationFile() +{ + QList<Tests::TestDocumentPtr> testFiles; + QByteArray original; + QByteArray expected; + + // Header File + original = + "class BaseA {\n" + "public:\n" + " virtual int a(const std::vector<int> &v) = 0;\n" + "};\n\n" + "class Derived : public Bas@eA {\n" + "public:\n" + " Derived();\n" + "};\n"; + expected = + "class BaseA {\n" + "public:\n" + " virtual int a(const std::vector<int> &v) = 0;\n" + "};\n\n" + "class Derived : public BaseA {\n" + "public:\n" + " Derived();\n" + "\n" + " // BaseA interface\n" + "public:\n" + " virtual int a(const std::vector<int> &v);\n" + "};\n"; + testFiles << Tests::CppTestDocument::create("file.h", original, expected); + + // Source File + original = "#include \"file.h\"\n"; + expected = + "#include \"file.h\"\n" + "\n\n" + "int Derived::a(const std::vector<int> &v)\n" + "{\n}"; + testFiles << Tests::CppTestDocument::create("file.cpp", original, expected); + + InsertVirtualMethods factory(new Tests::InsertVirtualMethodsDialogTest( + InsertVirtualMethodsDialog::ModeImplementationFile, + true, + false)); + Tests::QuickFixOperationTest(testFiles, &factory); +} + +/// Check: Qualified names. +void InsertVirtualMethodsTest::testBaseClassInNamespace() +{ + QList<Tests::TestDocumentPtr> testFiles; + QByteArray original; + QByteArray expected; + + // Header File + original = + "namespace BaseNS {enum BaseEnum {EnumA = 1};}\n" + "namespace BaseNS {\n" + "class Base {\n" + "public:\n" + " virtual BaseEnum a(BaseEnum e) = 0;\n" + "};\n" + "}\n" + "class Deri@ved : public BaseNS::Base {\n" + "public:\n" + " Derived();\n" + "};\n"; + expected = + "namespace BaseNS {enum BaseEnum {EnumA = 1};}\n" + "namespace BaseNS {\n" + "class Base {\n" + "public:\n" + " virtual BaseEnum a(BaseEnum e) = 0;\n" + "};\n" + "}\n" + "class Deri@ved : public BaseNS::Base {\n" + "public:\n" + " Derived();\n" + "\n" + " // Base interface\n" + "public:\n" + " virtual BaseNS::BaseEnum a(BaseNS::BaseEnum e);\n" + "};\n"; + testFiles << Tests::CppTestDocument::create("file.h", original, expected); + + // Source File + original = "#include \"file.h\"\n"; + expected = + "#include \"file.h\"\n" + "\n\n" + "BaseNS::BaseEnum Derived::a(BaseNS::BaseEnum e)\n" + "{\n}"; + testFiles << Tests::CppTestDocument::create("file.cpp", original, expected); + + InsertVirtualMethods factory(new Tests::InsertVirtualMethodsDialogTest( + InsertVirtualMethodsDialog::ModeImplementationFile, + true, + false)); + Tests::QuickFixOperationTest(testFiles, &factory); +} + +} // namespace Tests + +InsertVirtualMethods *InsertVirtualMethods::createTestFactory() +{ + return new InsertVirtualMethods(new Tests::InsertVirtualMethodsDialogTest( + InsertVirtualMethodsDialog::ModeOutsideClass, true, false)); +} + +QObject *InsertVirtualMethods::createTest() +{ + return new Tests::InsertVirtualMethodsTest; +} + +#endif // WITH_TESTS + +} // namespace Internal +} // namespace CppEditor + +#include "cppinsertvirtualmethods.moc" |