diff --git a/src/plugins/cppeditor/CMakeLists.txt b/src/plugins/cppeditor/CMakeLists.txt index 1399c80db9a..4e123e6f042 100644 --- a/src/plugins/cppeditor/CMakeLists.txt +++ b/src/plugins/cppeditor/CMakeLists.txt @@ -95,6 +95,7 @@ add_qtc_plugin(CppEditor insertionpointlocator.cpp insertionpointlocator.h projectinfo.cpp projectinfo.h projectpart.cpp projectpart.h + quickfixes/cppcodegenerationquickfixes.cpp quickfixes/cppcodegenerationquickfixes.h quickfixes/cppinsertvirtualmethods.cpp quickfixes/cppinsertvirtualmethods.h quickfixes/cppquickfix.cpp quickfixes/cppquickfix.h quickfixes/cppquickfixassistant.cpp quickfixes/cppquickfixassistant.h diff --git a/src/plugins/cppeditor/cppeditor.qbs b/src/plugins/cppeditor/cppeditor.qbs index 1d78c92b0c9..7d83a20004c 100644 --- a/src/plugins/cppeditor/cppeditor.qbs +++ b/src/plugins/cppeditor/cppeditor.qbs @@ -219,6 +219,8 @@ QtcPlugin { name: "Quickfixes" prefix: "quickfixes/" files: [ + "cppcodegenerationquickfixes.cpp", + "cppcodegenerationquickfixes.h", "cppinsertvirtualmethods.cpp", "cppinsertvirtualmethods.h", "cppquickfix.cpp", diff --git a/src/plugins/cppeditor/quickfixes/cppcodegenerationquickfixes.cpp b/src/plugins/cppeditor/quickfixes/cppcodegenerationquickfixes.cpp new file mode 100644 index 00000000000..ba622151aa1 --- /dev/null +++ b/src/plugins/cppeditor/quickfixes/cppcodegenerationquickfixes.cpp @@ -0,0 +1,5079 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "cppcodegenerationquickfixes.h" + +#include "../cppcodestylesettings.h" +#include "../cppeditortr.h" +#include "../cpprefactoringchanges.h" +#include "../cpptoolstestcase.h" +#include "../insertionpointlocator.h" +#include "cppquickfix.h" +#include "cppquickfixhelpers.h" +#include "cppquickfixprojectsettings.h" +#include "cppquickfixsettings.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef WITH_TESTS +#include "cppquickfix_test.h" +#include +#include +#endif + +using namespace CPlusPlus; +using namespace ProjectExplorer; +using namespace TextEditor; +using namespace Utils; + +namespace CppEditor::Internal { +namespace { + +static QStringList toStringList(const QList names) +{ + QStringList list; + list.reserve(names.size()); + for (const auto symbol : names) { + const Identifier *const id = symbol->identifier(); + list << QString::fromUtf8(id->chars(), id->size()); + } + return list; +} + +static QList getMemberFunctions(const Class *clazz) +{ + QList memberFunctions; + for (auto it = clazz->memberBegin(); it != clazz->memberEnd(); ++it) { + Symbol *const s = *it; + if (!s->identifier() || !s->type()) + continue; + if ((s->asDeclaration() && s->type()->asFunctionType()) || s->asFunction()) + memberFunctions << s; + } + return memberFunctions; +} + +static QString symbolAtDifferentLocation( + const CppQuickFixInterface &interface, + Symbol *symbol, + const CppRefactoringFilePtr &targetFile, + InsertionLocation targetLocation) +{ + QTC_ASSERT(symbol, return QString()); + Scope *scopeAtInsertPos = targetFile->cppDocument()->scopeAt(targetLocation.line(), + targetLocation.column()); + + LookupContext cppContext(targetFile->cppDocument(), interface.snapshot()); + ClassOrNamespace *cppCoN = cppContext.lookupType(scopeAtInsertPos); + if (!cppCoN) + cppCoN = cppContext.globalNamespace(); + SubstitutionEnvironment env; + env.setContext(interface.context()); + env.switchScope(symbol->enclosingScope()); + UseMinimalNames q(cppCoN); + env.enter(&q); + Control *control = interface.context().bindings()->control().get(); + Overview oo = CppCodeStyleSettings::currentProjectCodeStyleOverview(); + return oo.prettyName(LookupContext::minimalName(symbol, cppCoN, control)); +} + +static FullySpecifiedType typeAtDifferentLocation( + const CppQuickFixInterface &interface, + FullySpecifiedType type, + Scope *originalScope, + const CppRefactoringFilePtr &targetFile, + InsertionLocation targetLocation, + const QStringList &newNamespaceNamesAtLoc = {}) +{ + Scope *scopeAtInsertPos = targetFile->cppDocument()->scopeAt(targetLocation.line(), + targetLocation.column()); + for (const QString &nsName : newNamespaceNamesAtLoc) { + const QByteArray utf8Name = nsName.toUtf8(); + Control *control = targetFile->cppDocument()->control(); + const Name *name = control->identifier(utf8Name.data(), utf8Name.size()); + Namespace *ns = control->newNamespace(0, name); + ns->setEnclosingScope(scopeAtInsertPos); + scopeAtInsertPos = ns; + } + LookupContext cppContext(targetFile->cppDocument(), interface.snapshot()); + ClassOrNamespace *cppCoN = cppContext.lookupType(scopeAtInsertPos); + if (!cppCoN) + cppCoN = cppContext.globalNamespace(); + SubstitutionEnvironment env; + env.setContext(interface.context()); + env.switchScope(originalScope); + UseMinimalNames q(cppCoN); + env.enter(&q); + Control *control = interface.context().bindings()->control().get(); + return rewriteType(type, &env, control); +} + +static QString memberBaseName(const QString &name) +{ + const auto validName = [](const QString &name) { + return !name.isEmpty() && !name.at(0).isDigit(); + }; + QString baseName = name; + + CppQuickFixSettings *settings = CppQuickFixProjectsSettings::getQuickFixSettings( + ProjectExplorer::ProjectTree::currentProject()); + const QString &nameTemplate = settings->memberVariableNameTemplate; + const QString prefix = nameTemplate.left(nameTemplate.indexOf('<')); + const QString postfix = nameTemplate.mid(nameTemplate.lastIndexOf('>') + 1); + if (name.startsWith(prefix) && name.endsWith(postfix)) { + const QString base = name.mid(prefix.length(), name.length() - postfix.length()); + if (validName(base)) + return base; + } + + // Remove leading and trailing "_" + while (baseName.startsWith(QLatin1Char('_'))) + baseName.remove(0, 1); + while (baseName.endsWith(QLatin1Char('_'))) + baseName.chop(1); + if (baseName != name && validName(baseName)) + return baseName; + + // If no leading/trailing "_": remove "m_" and "m" prefix + if (baseName.startsWith(QLatin1String("m_"))) { + baseName.remove(0, 2); + } else if (baseName.startsWith(QLatin1Char('m')) && baseName.length() > 1 + && baseName.at(1).isUpper()) { + baseName.remove(0, 1); + baseName[0] = baseName.at(0).toLower(); + } + + return validName(baseName) ? baseName : name; +} + +static std::optional getFirstTemplateParameter(const Name *name) +{ + if (const QualifiedNameId *qualifiedName = name->asQualifiedNameId()) + return getFirstTemplateParameter(qualifiedName->name()); + + if (const TemplateNameId *templateName = name->asTemplateNameId()) { + if (templateName->templateArgumentCount() > 0) + return templateName->templateArgumentAt(0).type(); + } + return {}; +} + +static std::optional getFirstTemplateParameter(Type *type) +{ + if (NamedType *namedType = type->asNamedType()) + return getFirstTemplateParameter(namedType->name()); + + return {}; +} + +static std::optional getFirstTemplateParameter(FullySpecifiedType type) +{ + return getFirstTemplateParameter(type.type()); +} + +struct ExistingGetterSetterData +{ + Class *clazz = nullptr; + Declaration *declarationSymbol = nullptr; + QString getterName; + QString setterName; + QString resetName; + QString signalName; + QString qPropertyName; + QString memberVariableName; + Document::Ptr doc; + + int computePossibleFlags() const; +}; + +// FIXME: Should be a member? +static void findExistingFunctions(ExistingGetterSetterData &existing, QStringList memberFunctionNames) +{ + const CppQuickFixSettings *settings = CppQuickFixProjectsSettings::getQuickFixSettings( + ProjectExplorer::ProjectTree::currentProject()); + const QString lowerBaseName = memberBaseName(existing.memberVariableName).toLower(); + const QStringList getterNames{lowerBaseName, + "get_" + lowerBaseName, + "get" + lowerBaseName, + "is_" + lowerBaseName, + "is" + lowerBaseName, + settings->getGetterName(lowerBaseName)}; + const QStringList setterNames{"set_" + lowerBaseName, + "set" + lowerBaseName, + settings->getSetterName(lowerBaseName)}; + const QStringList resetNames{"reset_" + lowerBaseName, + "reset" + lowerBaseName, + settings->getResetName(lowerBaseName)}; + const QStringList signalNames{lowerBaseName + "_changed", + lowerBaseName + "changed", + settings->getSignalName(lowerBaseName)}; + for (const auto &memberFunctionName : memberFunctionNames) { + const QString lowerName = memberFunctionName.toLower(); + if (getterNames.contains(lowerName)) + existing.getterName = memberFunctionName; + else if (setterNames.contains(lowerName)) + existing.setterName = memberFunctionName; + else if (resetNames.contains(lowerName)) + existing.resetName = memberFunctionName; + else if (signalNames.contains(lowerName)) + existing.signalName = memberFunctionName; + } +} + +static void extractNames(const CppRefactoringFilePtr &file, + QtPropertyDeclarationAST *qtPropertyDeclaration, + ExistingGetterSetterData &data) +{ + QtPropertyDeclarationItemListAST *it = qtPropertyDeclaration->property_declaration_item_list; + for (; it; it = it->next) { + const char *tokenString = file->tokenAt(it->value->item_name_token).spell(); + if (!qstrcmp(tokenString, "READ")) { + data.getterName = file->textOf(it->value->expression); + } else if (!qstrcmp(tokenString, "WRITE")) { + data.setterName = file->textOf(it->value->expression); + } else if (!qstrcmp(tokenString, "RESET")) { + data.resetName = file->textOf(it->value->expression); + } else if (!qstrcmp(tokenString, "NOTIFY")) { + data.signalName = file->textOf(it->value->expression); + } else if (!qstrcmp(tokenString, "MEMBER")) { + data.memberVariableName = file->textOf(it->value->expression); + } + } +} + +class GetterSetterRefactoringHelper +{ +public: + GetterSetterRefactoringHelper(CppQuickFixOperation *operation, + const FilePath &filePath, + Class *clazz) + : m_operation(operation) + , m_changes(m_operation->snapshot()) + , m_locator(m_changes) + , m_headerFile(m_changes.cppFile(filePath)) + , m_sourceFile([&] { + FilePath cppFilePath = correspondingHeaderOrSource(filePath, &m_isHeaderHeaderFile); + if (!m_isHeaderHeaderFile || !cppFilePath.exists()) { + // there is no "source" file + return m_headerFile; + } else { + return m_changes.cppFile(cppFilePath); + } + }()) + , m_class(clazz) + {} + + void performGeneration(ExistingGetterSetterData data, int generationFlags); + + void applyChanges() + { + const auto classLayout = { + InsertionPointLocator::Public, + InsertionPointLocator::PublicSlot, + InsertionPointLocator::Signals, + InsertionPointLocator::Protected, + InsertionPointLocator::ProtectedSlot, + InsertionPointLocator::PrivateSlot, + InsertionPointLocator::Private, + }; + for (auto spec : classLayout) { + const auto iter = m_headerFileCode.find(spec); + if (iter != m_headerFileCode.end()) { + const InsertionLocation loc = headerLocationFor(spec); + m_headerFile->setOpenEditor(true, m_headerFile->position(loc.line(), loc.column())); + insertAndIndent(m_headerFile, loc, *iter); + } + } + if (!m_sourceFileCode.isEmpty() && m_sourceFileInsertionPoint.isValid()) { + m_sourceFile->setOpenEditor(true, m_sourceFile->position( + m_sourceFileInsertionPoint.line(), + m_sourceFileInsertionPoint.column())); + insertAndIndent(m_sourceFile, m_sourceFileInsertionPoint, m_sourceFileCode); + } + + if (!m_headerFileChangeSet.isEmpty()) { + m_headerFile->setChangeSet(m_headerFileChangeSet); + m_headerFile->apply(); + } + if (!m_sourceFileChangeSet.isEmpty()) { + m_sourceFile->setChangeSet(m_sourceFileChangeSet); + m_sourceFile->apply(); + } + } + + bool hasSourceFile() const { return m_headerFile != m_sourceFile; } + +protected: + void insertAndIndent(const RefactoringFilePtr &file, + const InsertionLocation &loc, + const QString &text) + { + int targetPosition = file->position(loc.line(), loc.column()); + ChangeSet &changeSet = file == m_headerFile ? m_headerFileChangeSet : m_sourceFileChangeSet; + changeSet.insert(targetPosition, loc.prefix() + text + loc.suffix()); + } + + FullySpecifiedType makeConstRef(FullySpecifiedType type) + { + type.setConst(true); + return m_operation->currentFile()->cppDocument()->control()->referenceType(type, false); + } + + FullySpecifiedType addConstToReference(FullySpecifiedType type) + { + if (ReferenceType *ref = type.type()->asReferenceType()) { + FullySpecifiedType elemType = ref->elementType(); + if (elemType.isConst()) + return type; + elemType.setConst(true); + return m_operation->currentFile()->cppDocument()->control()->referenceType(elemType, + false); + } + return type; + } + + QString symbolAt(Symbol *symbol, + const CppRefactoringFilePtr &targetFile, + InsertionLocation targetLocation) + { + return symbolAtDifferentLocation(*m_operation, symbol, targetFile, targetLocation); + } + + FullySpecifiedType typeAt(FullySpecifiedType type, + Scope *originalScope, + const CppRefactoringFilePtr &targetFile, + InsertionLocation targetLocation, + const QStringList &newNamespaceNamesAtLoc = {}) + { + return typeAtDifferentLocation(*m_operation, + type, + originalScope, + targetFile, + targetLocation, + newNamespaceNamesAtLoc); + } + + /** + * @brief checks if the type in the enclosing scope in the header is a value type + * @param type a type in the m_headerFile + * @param enclosingScope the enclosing scope + * @param customValueType if not nullptr set to true when value type comes + * from CppQuickFixSettings::isValueType + * @return true if it is a pointer, enum, integer, floating point, reference, custom value type + */ + bool isValueType(FullySpecifiedType type, Scope *enclosingScope, bool *customValueType = nullptr) + { + if (customValueType) + *customValueType = false; + // a type is a value type if it is one of the following + const auto isTypeValueType = [](const FullySpecifiedType &t) { + return t->asPointerType() || t->asEnumType() || t->asIntegerType() || t->asFloatType() + || t->asReferenceType(); + }; + if (type->asNamedType()) { + // we need a recursive search and a lookup context + LookupContext context(m_headerFile->cppDocument(), m_changes.snapshot()); + auto isValueType = [settings = m_settings, + &customValueType, + &context, + &isTypeValueType](const Name *name, + Scope *scope, + auto &isValueType) { + // maybe the type is a custom value type by name + if (const Identifier *id = name->identifier()) { + if (settings->isValueType(QString::fromUtf8(id->chars(), id->size()))) { + if (customValueType) + *customValueType = true; + return true; + } + } + // search for the type declaration + QList localLookup = context.lookup(name, scope); + for (auto &&i : localLookup) { + if (isTypeValueType(i.type())) + return true; + if (i.type()->asNamedType()) { // check if we have to search recursively + const Name *newName = i.type()->asNamedType()->name(); + Scope *newScope = i.declaration()->enclosingScope(); + if (Matcher::match(newName, name) + && Matcher::match(newScope->name(), scope->name())) { + continue; // we have found the start location of the search + } + return isValueType(newName, newScope, isValueType); + } + return false; + } + return false; + }; + // start recursion + return isValueType(type->asNamedType()->name(), enclosingScope, isValueType); + } + return isTypeValueType(type); + } + + bool isValueType(Symbol *symbol, bool *customValueType = nullptr) + { + return isValueType(symbol->type(), symbol->enclosingScope(), customValueType); + } + + void addHeaderCode(InsertionPointLocator::AccessSpec spec, QString code) + { + QString &existing = m_headerFileCode[spec]; + existing += code; + if (!existing.endsWith('\n')) + existing += '\n'; + } + + InsertionLocation headerLocationFor(InsertionPointLocator::AccessSpec spec) + { + const auto insertionPoint = m_headerInsertionPoints.find(spec); + if (insertionPoint != m_headerInsertionPoints.end()) + return *insertionPoint; + const InsertionLocation loc = m_locator.methodDeclarationInClass( + m_headerFile->filePath(), m_class, spec, + InsertionPointLocator::ForceAccessSpec::Yes); + m_headerInsertionPoints.insert(spec, loc); + return loc; + } + + InsertionLocation sourceLocationFor(Symbol *symbol, QStringList *insertedNamespaces = nullptr) + { + if (m_sourceFileInsertionPoint.isValid()) + return m_sourceFileInsertionPoint; + m_sourceFileInsertionPoint + = insertLocationForMethodDefinition(symbol, + false, + m_settings->createMissingNamespacesinCppFile() + ? NamespaceHandling::CreateMissing + : NamespaceHandling::Ignore, + m_changes, + m_sourceFile->filePath(), + insertedNamespaces); + if (m_settings->addUsingNamespaceinCppFile()) { + // check if we have to insert a using namespace ... + auto requiredNamespaces = getNamespaceNames( + symbol->asClass() ? symbol : symbol->enclosingClass()); + NSCheckerVisitor visitor(m_sourceFile.get(), + requiredNamespaces, + m_sourceFile->position(m_sourceFileInsertionPoint.line(), + m_sourceFileInsertionPoint.column())); + visitor.accept(m_sourceFile->cppDocument()->translationUnit()->ast()); + if (insertedNamespaces) + insertedNamespaces->clear(); + if (auto rns = visitor.remainingNamespaces(); !rns.empty()) { + QString ns = "using namespace "; + for (auto &n : rns) { + if (!n.isEmpty()) { // we have to ignore unnamed namespaces + ns += n; + ns += "::"; + if (insertedNamespaces) + insertedNamespaces->append(n); + } + } + ns.resize(ns.size() - 2); // remove last '::' + ns += ";\n"; + const auto &loc = m_sourceFileInsertionPoint; + m_sourceFileInsertionPoint = InsertionLocation(loc.filePath(), + loc.prefix() + ns, + loc.suffix(), + loc.line(), + loc.column()); + } + } + return m_sourceFileInsertionPoint; + } + + void addSourceFileCode(QString code) + { + while (!m_sourceFileCode.isEmpty() && !m_sourceFileCode.endsWith("\n\n")) + m_sourceFileCode += '\n'; + m_sourceFileCode += code; + } + +protected: + CppQuickFixOperation *const m_operation; + const CppRefactoringChanges m_changes; + const InsertionPointLocator m_locator; + const CppRefactoringFilePtr m_headerFile; + bool m_isHeaderHeaderFile = false; // the "header" (where the class is defined) can be a source file + const CppRefactoringFilePtr m_sourceFile; + CppQuickFixSettings *const m_settings = CppQuickFixProjectsSettings::getQuickFixSettings( + ProjectTree::currentProject()); + Class *const m_class; + +private: + ChangeSet m_headerFileChangeSet; + ChangeSet m_sourceFileChangeSet; + QMap m_headerInsertionPoints; + InsertionLocation m_sourceFileInsertionPoint; + QString m_sourceFileCode; + QMap m_headerFileCode; +}; + +struct ParentClassConstructorInfo; + +class ConstructorMemberInfo +{ +public: + ConstructorMemberInfo(const QString &name, Symbol *symbol, int numberOfMember) + : memberVariableName(name) + , parameterName(memberBaseName(name)) + , symbol(symbol) + , type(symbol->type()) + , numberOfMember(numberOfMember) + {} + ConstructorMemberInfo(const QString &memberName, + const QString ¶mName, + const QString &defaultValue, + Symbol *symbol, + const ParentClassConstructorInfo *parentClassConstructor) + : parentClassConstructor(parentClassConstructor) + , memberVariableName(memberName) + , parameterName(paramName) + , defaultValue(defaultValue) + , init(defaultValue.isEmpty()) + , symbol(symbol) + , type(symbol->type()) + {} + const ParentClassConstructorInfo *parentClassConstructor = nullptr; + QString memberVariableName; + QString parameterName; + QString defaultValue; + bool init = true; + bool customValueType; // for the generation later + Symbol *symbol; // for the right type later + FullySpecifiedType type; + int numberOfMember; // first member, second member, ... +}; + +class ConstructorParams : public QAbstractTableModel +{ + Q_OBJECT + std::list candidates; + std::vector infos; + + void validateOrder() + { + // parameters with default values must be at the end + bool foundWithDefault = false; + for (auto info : infos) { + if (info->init) { + if (foundWithDefault && info->defaultValue.isEmpty()) { + emit validOrder(false); + return; + } + foundWithDefault |= !info->defaultValue.isEmpty(); + } + } + emit validOrder(true); + } + +public: + enum Column { ShouldInitColumn, MemberNameColumn, ParameterNameColumn, DefaultValueColumn }; + template + void emplaceBackParameter(_Args &&...__args) + { + candidates.emplace_back(std::forward<_Args>(__args)...); + infos.push_back(&candidates.back()); + } + const std::vector &getInfos() const { return infos; } + void addRow(ConstructorMemberInfo *info) + { + beginInsertRows({}, rowCount(), rowCount()); + infos.push_back(info); + endInsertRows(); + validateOrder(); + } + void removeRow(ConstructorMemberInfo *info) + { + for (auto iter = infos.begin(); iter != infos.end(); ++iter) { + if (*iter == info) { + const auto index = iter - infos.begin(); + beginRemoveRows({}, index, index); + infos.erase(iter); + endRemoveRows(); + validateOrder(); + return; + } + } + } + + int selectedCount() const + { + return Utils::count(infos, [](const ConstructorMemberInfo *mi) { + return mi->init && !mi->parentClassConstructor; + }); + } + int memberCount() const + { + return Utils::count(infos, [](const ConstructorMemberInfo *mi) { + return !mi->parentClassConstructor; + }); + } + + int rowCount(const QModelIndex & /*parent*/ = {}) const override { return int(infos.size()); } + int columnCount(const QModelIndex & /*parent*/ = {}) const override { return 4; } + QVariant data(const QModelIndex &index, int role) const override + { + if (index.row() < 0 || index.row() >= rowCount()) + return {}; + if (role == Qt::CheckStateRole && index.column() == ShouldInitColumn + && !infos[index.row()]->parentClassConstructor) + return infos[index.row()]->init ? Qt::Checked : Qt::Unchecked; + if (role == Qt::DisplayRole && index.column() == MemberNameColumn) + return infos[index.row()]->memberVariableName; + if ((role == Qt::DisplayRole || role == Qt::EditRole) + && index.column() == ParameterNameColumn) + return infos[index.row()]->parameterName; + if ((role == Qt::DisplayRole || role == Qt::EditRole) + && index.column() == DefaultValueColumn) + return infos[index.row()]->defaultValue; + if ((role == Qt::ToolTipRole) && index.column() > 0) + return Overview{}.prettyType(infos[index.row()]->symbol->type()); + return {}; + } + bool setData(const QModelIndex &index, const QVariant &value, int role) override + { + if (index.column() == ShouldInitColumn && role == Qt::CheckStateRole) { + if (infos[index.row()]->parentClassConstructor) + return false; + infos[index.row()]->init = value.toInt() == Qt::Checked; + emit dataChanged(this->index(index.row(), 0), this->index(index.row(), columnCount())); + validateOrder(); + return true; + } + if (index.column() == ParameterNameColumn && role == Qt::EditRole) { + infos[index.row()]->parameterName = value.toString(); + return true; + } + if (index.column() == DefaultValueColumn && role == Qt::EditRole) { + infos[index.row()]->defaultValue = value.toString(); + validateOrder(); + return true; + } + return false; + } + Qt::DropActions supportedDropActions() const override { return Qt::MoveAction; } + Qt::ItemFlags flags(const QModelIndex &index) const override + { + if (!index.isValid()) + return Qt::ItemIsSelectable | Qt::ItemIsDropEnabled; + + Qt::ItemFlags f{}; + if (infos[index.row()]->init) { + f |= Qt::ItemIsDragEnabled; + f |= Qt::ItemIsSelectable; + } + + if (index.column() == ShouldInitColumn && !infos[index.row()]->parentClassConstructor) + return f | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable; + if (!infos[index.row()]->init) + return f; + if (index.column() == MemberNameColumn) + return f | Qt::ItemIsEnabled; + if (index.column() == ParameterNameColumn || index.column() == DefaultValueColumn) + return f | Qt::ItemIsEnabled | Qt::ItemIsEditable; + return {}; + } + + QVariant headerData(int section, Qt::Orientation orientation, int role) const override + { + if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { + switch (section) { + case ShouldInitColumn: + return Tr::tr("Initialize in Constructor"); + case MemberNameColumn: + return Tr::tr("Member Name"); + case ParameterNameColumn: + return Tr::tr("Parameter Name"); + case DefaultValueColumn: + return Tr::tr("Default Value"); + } + } + return {}; + } + bool dropMimeData(const QMimeData *data, + Qt::DropAction /*action*/, + int row, + int /*column*/, + const QModelIndex & /*parent*/) override + { + if (row == -1) + row = rowCount(); + bool ok; + int sourceRow = data->data("application/x-qabstractitemmodeldatalist").toInt(&ok); + if (ok) { + if (sourceRow == row || row == sourceRow + 1) + return false; + beginMoveRows({}, sourceRow, sourceRow, {}, row); + infos.insert(infos.begin() + row, infos.at(sourceRow)); + if (row < sourceRow) + ++sourceRow; + infos.erase(infos.begin() + sourceRow); + validateOrder(); + return true; + } + return false; + } + + QMimeData *mimeData(const QModelIndexList &indexes) const override + { + for (const auto &i : indexes) { + if (!i.isValid()) + continue; + auto data = new QMimeData(); + data->setData("application/x-qabstractitemmodeldatalist", + QString::number(i.row()).toLatin1()); + return data; + } + return nullptr; + } + + class TableViewStyle : public QProxyStyle + { + public: + TableViewStyle(QStyle *style) + : QProxyStyle(style) + {} + + void drawPrimitive(PrimitiveElement element, + const QStyleOption *option, + QPainter *painter, + const QWidget *widget) const override + { + if (element == QStyle::PE_IndicatorItemViewItemDrop && !option->rect.isNull()) { + QStyleOption opt(*option); + opt.rect.setLeft(0); + if (widget) + opt.rect.setRight(widget->width()); + QProxyStyle::drawPrimitive(element, &opt, painter, widget); + return; + } + QProxyStyle::drawPrimitive(element, option, painter, widget); + } + }; +signals: + void validOrder(bool valid); +}; + +class TopMarginDelegate : public QStyledItemDelegate +{ +public: + TopMarginDelegate(QObject *parent = nullptr) + : QStyledItemDelegate(parent) + {} + + void paint(QPainter *painter, + const QStyleOptionViewItem &option, + const QModelIndex &index) const override + { + Q_ASSERT(index.isValid()); + QStyleOptionViewItem opt = option; + initStyleOption(&opt, index); + const QWidget *widget = option.widget; + QStyle *style = widget ? widget->style() : QApplication::style(); + if (opt.rect.height() > 20) + opt.rect.adjust(0, 5, 0, 0); + style->drawControl(QStyle::CE_ItemViewItem, &opt, painter, widget); + } +}; + +struct ParentClassConstructorParameter : public ConstructorMemberInfo +{ + QString originalDefaultValue; + QString declaration; // displayed in the treeView + ParentClassConstructorParameter(const QString &name, + const QString &defaultValue, + Symbol *symbol, + const ParentClassConstructorInfo *parentClassConstructor); + + ParentClassConstructorParameter(const ParentClassConstructorParameter &) = delete; + ParentClassConstructorParameter(ParentClassConstructorParameter &&) = default; +}; + +struct ParentClassConstructorInfo +{ + ParentClassConstructorInfo(const QString &name, ConstructorParams &model) + : className(name) + , model(model) + {} + bool useInConstructor = false; + const QString className; + QString declaration; + std::vector parameters; + ConstructorParams &model; + + ParentClassConstructorInfo(const ParentClassConstructorInfo &) = delete; + ParentClassConstructorInfo(ParentClassConstructorInfo &&) = default; + + void addParameter(ParentClassConstructorParameter ¶m) { model.addRow(¶m); } + void removeParameter(ParentClassConstructorParameter ¶m) { model.removeRow(¶m); } + void removeAllParameters() + { + for (auto ¶m : parameters) + model.removeRow(¶m); + } +}; + +ParentClassConstructorParameter::ParentClassConstructorParameter( + const QString &name, + const QString &defaultValue, + Symbol *symbol, + const ParentClassConstructorInfo *parentClassConstructor) + : ConstructorMemberInfo(parentClassConstructor->className + "::" + name, + name, + defaultValue, + symbol, + parentClassConstructor) + , originalDefaultValue(defaultValue) + , declaration(Overview{}.prettyType(symbol->type(), name) + + (defaultValue.isEmpty() ? QString{} : " = " + defaultValue)) +{} + +using ParentClassConstructors = std::vector; + +class ParentClassesModel : public QAbstractItemModel +{ + ParentClassConstructors &constructors; + +public: + ParentClassesModel(QObject *parent, ParentClassConstructors &constructors) + : QAbstractItemModel(parent) + , constructors(constructors) + {} + QModelIndex index(int row, int column, const QModelIndex &parent = {}) const override + { + if (!parent.isValid()) + return createIndex(row, column, nullptr); + if (parent.internalPointer()) + return {}; + auto index = createIndex(row, column, &constructors.at(parent.row())); + return index; + } + QModelIndex parent(const QModelIndex &index) const override + { + if (!index.isValid()) + return {}; + auto *parent = static_cast(index.internalPointer()); + if (!parent) + return {}; + int i = 0; + for (const auto &info : constructors) { + if (&info == parent) + return createIndex(i, 0, nullptr); + ++i; + } + return {}; + } + int rowCount(const QModelIndex &parent = {}) const override + { + if (!parent.isValid()) + return static_cast(constructors.size()); + auto info = static_cast(parent.internalPointer()); + if (!info) + return static_cast(constructors.at(parent.row()).parameters.size()); + return 0; + } + int columnCount(const QModelIndex & /*parent*/ = {}) const override { return 1; } + QVariant data(const QModelIndex &index, int role) const override + { + if (!index.isValid()) + return {}; + auto info = static_cast(index.internalPointer()); + + if (info) { + const auto ¶meter = info->parameters.at(index.row()); + if (role == Qt::CheckStateRole) + return parameter.init ? Qt::Checked : Qt::Unchecked; + if (role == Qt::DisplayRole) + return parameter.declaration; + return {}; + } + const auto &constructor = constructors.at(index.row()); + if (role == Qt::CheckStateRole) + return constructor.useInConstructor ? Qt::PartiallyChecked : Qt::Unchecked; + if (role == Qt::DisplayRole) + return constructor.declaration; + + // Highlight the selected items + if (role == Qt::FontRole && constructor.useInConstructor) { + QFont font = QApplication::font(); + font.setBold(true); + return font; + } + // Create a margin between sets of constructors for base classes + if (role == Qt::SizeHintRole && index.row() > 0 + && constructor.className != constructors.at(index.row() - 1).className) { + return QSize(-1, 25); + } + return {}; + } + bool setData(const QModelIndex &index, const QVariant &value, int /*role*/) override + { + if (index.isValid() && index.column() == 0) { + auto info = static_cast(index.internalPointer()); + if (info) { + const bool nowUse = value.toBool(); + auto ¶m = info->parameters.at(index.row()); + param.init = nowUse; + if (nowUse) + info->addParameter(param); + else + info->removeParameter(param); + return true; + } + auto &newConstructor = constructors.at(index.row()); + // You have to select a base class constructor + if (newConstructor.useInConstructor) + return false; + auto c = std::find_if(constructors.begin(), constructors.end(), [&](const auto &c) { + return c.className == newConstructor.className && c.useInConstructor; + }); + QTC_ASSERT(c == constructors.end(), return false;); + c->useInConstructor = false; + newConstructor.useInConstructor = true; + emit dataChanged(this->index(index.row(), 0), this->index(index.row(), columnCount())); + auto parentIndex = this->index(index.row(), 0); + emit dataChanged(this->index(0, 0, parentIndex), + this->index(rowCount(parentIndex), columnCount())); + const int oldIndex = c - constructors.begin(); + emit dataChanged(this->index(oldIndex, 0), this->index(oldIndex, columnCount())); + parentIndex = this->index(oldIndex, 0); + emit dataChanged(this->index(0, 0, parentIndex), + this->index(rowCount(parentIndex), columnCount())); + // update other table + c->removeAllParameters(); + for (auto &p : newConstructor.parameters) + if (p.init) + newConstructor.addParameter(p); + return true; + } + return false; + } + QVariant headerData(int section, Qt::Orientation orientation, int role) const override + { + if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { + switch (section) { + case 0: + return Tr::tr("Base Class Constructors"); + } + } + return {}; + } + Qt::ItemFlags flags(const QModelIndex &index) const override + { + if (index.isValid()) { + Qt::ItemFlags f; + auto info = static_cast(index.internalPointer()); + if (!info || info->useInConstructor) { + f |= Qt::ItemIsEnabled; + } + f |= Qt::ItemIsUserCheckable; + + return f; + } + return {}; + } +}; + +class GenerateConstructorDialog : public QDialog +{ +public: + GenerateConstructorDialog(ConstructorParams *constructorParamsModel, + ParentClassConstructors &constructors) + { + setWindowTitle(Tr::tr("Constructor")); + + const auto treeModel = new ParentClassesModel(this, constructors); + const auto treeView = new QTreeView(this); + treeView->setModel(treeModel); + treeView->setItemDelegate(new TopMarginDelegate(this)); + treeView->expandAll(); + + const auto view = new QTableView(this); + view->setModel(constructorParamsModel); + int optimalWidth = 0; + for (int i = 0; i < constructorParamsModel->columnCount(QModelIndex{}); ++i) { + view->resizeColumnToContents(i); + optimalWidth += view->columnWidth(i); + } + view->resizeRowsToContents(); + view->verticalHeader()->setDefaultSectionSize(view->rowHeight(0)); + view->setSelectionBehavior(QAbstractItemView::SelectRows); + view->setSelectionMode(QAbstractItemView::SingleSelection); + view->setDragEnabled(true); + view->setDropIndicatorShown(true); + view->setDefaultDropAction(Qt::MoveAction); + view->setDragDropMode(QAbstractItemView::InternalMove); + view->setDragDropOverwriteMode(false); + view->horizontalHeader()->setStretchLastSection(true); + view->setStyle(new ConstructorParams::TableViewStyle(view->style())); + + const auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); + connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); + + const auto errorLabel = new QLabel( + Tr::tr("Parameters without default value must come before parameters with default value.")); + errorLabel->setStyleSheet("color: #ff0000"); + errorLabel->setVisible(false); + QSizePolicy labelSizePolicy = errorLabel->sizePolicy(); + labelSizePolicy.setRetainSizeWhenHidden(true); + errorLabel->setSizePolicy(labelSizePolicy); + connect(constructorParamsModel, &ConstructorParams::validOrder, this, + [errorLabel, button = buttonBox->button(QDialogButtonBox::Ok)](bool valid) { + button->setEnabled(valid); + errorLabel->setVisible(!valid); + }); + + // setup select all/none checkbox + QCheckBox *const checkBox = new QCheckBox(Tr::tr("Initialize all members")); + checkBox->setChecked(true); + connect(checkBox, &QCheckBox::stateChanged, this, + [model = constructorParamsModel](int state) { + if (state != Qt::PartiallyChecked) { + for (int i = 0; i < model->rowCount(); ++i) + model->setData(model->index(i, ConstructorParams::ShouldInitColumn), + state, + Qt::CheckStateRole); + } + }); + connect(checkBox, &QCheckBox::clicked, this, [checkBox] { + if (checkBox->checkState() == Qt::PartiallyChecked) + checkBox->setCheckState(Qt::Checked); + }); + connect(constructorParamsModel, + &QAbstractItemModel::dataChanged, + this, + [model = constructorParamsModel, checkBox] { + const auto state = [model, selectedCount = model->selectedCount()]() { + if (selectedCount == 0) + return Qt::Unchecked; + if (static_cast(model->memberCount() == selectedCount)) + return Qt::Checked; + return Qt::PartiallyChecked; + }(); + checkBox->setCheckState(state); + }); + + using A = InsertionPointLocator::AccessSpec; + auto accessCombo = new QComboBox; + connect(accessCombo, &QComboBox::currentIndexChanged, this, [this, accessCombo] { + const auto data = accessCombo->currentData(); + m_accessSpec = static_cast(data.toInt()); + }); + for (auto a : {A::Public, A::Protected, A::Private}) + accessCombo->addItem(InsertionPointLocator::accessSpecToString(a), a); + const auto row = new QHBoxLayout(); + row->addWidget(new QLabel(Tr::tr("Access") + ":")); + row->addWidget(accessCombo); + row->addSpacerItem(new QSpacerItem(1, 1, QSizePolicy::Expanding, QSizePolicy::Minimum)); + + const auto mainLayout = new QVBoxLayout(this); + mainLayout->addWidget( + new QLabel(Tr::tr("Select the members to be initialized in the constructor.\n" + "Use drag and drop to change the order of the parameters."))); + mainLayout->addLayout(row); + mainLayout->addWidget(checkBox); + mainLayout->addWidget(view); + mainLayout->addWidget(treeView); + mainLayout->addWidget(errorLabel); + mainLayout->addWidget(buttonBox); + int left, right; + mainLayout->getContentsMargins(&left, nullptr, &right, nullptr); + optimalWidth += left + right; + resize(optimalWidth, mainLayout->sizeHint().height()); + } + + InsertionPointLocator::AccessSpec accessSpec() const { return m_accessSpec; } + +private: + InsertionPointLocator::AccessSpec m_accessSpec; +}; + +class MemberInfo +{ +public: + MemberInfo(ExistingGetterSetterData data, int possibleFlags) + : data(data) + , possibleFlags(possibleFlags) + {} + + ExistingGetterSetterData data; + int possibleFlags; + int requestedFlags = 0; +}; +using GetterSetterCandidates = std::vector; + +class GenerateConstructorOperation : public CppQuickFixOperation +{ +public: + GenerateConstructorOperation(const CppQuickFixInterface &interface) + : CppQuickFixOperation(interface) + { + setDescription(Tr::tr("Generate Constructor")); + + m_classAST = astForClassOperations(interface); + if (!m_classAST) + return; + Class *const theClass = m_classAST->symbol; + if (!theClass) + return; + + // Go through all members and find member variable declarations + int memberCounter = 0; + for (auto it = theClass->memberBegin(); it != theClass->memberEnd(); ++it) { + Symbol *const s = *it; + if (!s->identifier() || !s->type() || s->type().isTypedef()) + continue; + if ((s->asDeclaration() && s->type()->asFunctionType()) || s->asFunction()) + continue; + if (s->asDeclaration() && (s->isPrivate() || s->isProtected()) && !s->isStatic()) { + const auto name = QString::fromUtf8(s->identifier()->chars(), + s->identifier()->size()); + parameterModel.emplaceBackParameter(name, s, memberCounter++); + } + } + Overview o = CppCodeStyleSettings::currentProjectCodeStyleOverview(); + o.showArgumentNames = true; + o.showReturnTypes = true; + o.showDefaultArguments = true; + o.showTemplateParameters = true; + o.showFunctionSignatures = true; + LookupContext context(currentFile()->cppDocument(), interface.snapshot()); + for (BaseClass *bc : theClass->baseClasses()) { + const QString className = o.prettyName(bc->name()); + + ClassOrNamespace *localLookupType = context.lookupType(bc); + QList localLookup = localLookupType->lookup(bc->name()); + for (auto &li : localLookup) { + Symbol *d = li.declaration(); + if (!d->asClass()) + continue; + for (auto i = d->asClass()->memberBegin(); i != d->asClass()->memberEnd(); ++i) { + Symbol *s = *i; + if (s->isProtected() || s->isPublic()) { + if (s->name()->match(d->name())) { + // we have found a constructor + Function *func = s->type().type()->asFunctionType(); + if (!func) + continue; + const bool isFirst = parentClassConstructors.empty() + || parentClassConstructors.back().className + != className; + parentClassConstructors.emplace_back(className, parameterModel); + ParentClassConstructorInfo &constructor = parentClassConstructors.back(); + constructor.declaration = className + o.prettyType(func->type()); + constructor.declaration.replace("std::__1::__get_nullptr_t()", + "nullptr"); + constructor.useInConstructor = isFirst; + for (auto arg = func->memberBegin(); arg != func->memberEnd(); ++arg) { + Symbol *param = *arg; + Argument *argument = param->asArgument(); + if (!argument) // can also be a block + continue; + const QString name = o.prettyName(param->name()); + const StringLiteral *ini = argument->initializer(); + QString defaultValue; + if (ini) + defaultValue = QString::fromUtf8(ini->chars(), ini->size()) + .replace("std::__1::__get_nullptr_t()", + "nullptr"); + constructor.parameters.emplace_back(name, + defaultValue, + param, + &constructor); + // do not show constructors like QObject(QObjectPrivate & dd, ...) + ReferenceType *ref = param->type()->asReferenceType(); + if (ref && name == "dd") { + auto type = o.prettyType(ref->elementType()); + if (type.startsWith("Q") && type.endsWith("Private")) { + parentClassConstructors.pop_back(); + break; + } + } + } + } + } + } + } + } + + // add params to parameter lists + for (auto &c : parentClassConstructors) + if (c.useInConstructor) + for (auto &p : c.parameters) + if (p.init) + c.addParameter(p); + } + + bool isApplicable() const + { + return parameterModel.rowCount() > 0 + || Utils::anyOf(parentClassConstructors, + [](const auto &parent) { return !parent.parameters.empty(); }); + } + + void setTest(bool isTest = true) { m_test = isTest; } + +private: + void perform() override + { + auto infos = parameterModel.getInfos(); + + InsertionPointLocator::AccessSpec accessSpec = InsertionPointLocator::Public; + if (!m_test) { + GenerateConstructorDialog dlg(¶meterModel, parentClassConstructors); + if (dlg.exec() == QDialog::Rejected) + return; + accessSpec = dlg.accessSpec(); + infos = parameterModel.getInfos(); + } else { +#ifdef WITH_TESTS + ParentClassesModel model(nullptr, parentClassConstructors); + QAbstractItemModelTester tester(&model); +#endif + if (infos.size() >= 3) { + // if we are testing and have 3 or more members => change the order + // move first element to the back + infos.push_back(infos[0]); + infos.erase(infos.begin()); + } + for (auto info : infos) { + if (info->memberVariableName.startsWith("di_")) + info->defaultValue = "42"; + } + for (auto &c : parentClassConstructors) { + if (c.useInConstructor) { + for (auto &p : c.parameters) { + if (!p.init && p.parameterName.startsWith("use_")) { + infos.push_back(&p); + p.init = true; + } + } + } + } + } + if (infos.empty()) + return; + struct GenerateConstructorRefactoringHelper : public GetterSetterRefactoringHelper + { + const ClassSpecifierAST *m_classAST; + InsertionPointLocator::AccessSpec m_accessSpec; + GenerateConstructorRefactoringHelper(CppQuickFixOperation *operation, + const FilePath &filePath, + Class *clazz, + const ClassSpecifierAST *classAST, + InsertionPointLocator::AccessSpec accessSpec) + : GetterSetterRefactoringHelper(operation, filePath, clazz) + , m_classAST(classAST) + , m_accessSpec(accessSpec) + {} + void generateConstructor(std::vector members, + const ParentClassConstructors &parentClassConstructors) + { + auto constructorLocation = m_settings->determineSetterLocation(int(members.size())); + if (constructorLocation == CppQuickFixSettings::FunctionLocation::CppFile + && !hasSourceFile()) + constructorLocation = CppQuickFixSettings::FunctionLocation::OutsideClass; + + Overview overview = CppCodeStyleSettings::currentProjectCodeStyleOverview(); + overview.showTemplateParameters = true; + + InsertionLocation implLoc; + QString implCode; + CppRefactoringFilePtr implFile; + QString className = overview.prettyName(m_class->name()); + QStringList insertedNamespaces; + if (constructorLocation == CppQuickFixSettings::FunctionLocation::CppFile) { + implLoc = sourceLocationFor(m_class, &insertedNamespaces); + implFile = m_sourceFile; + if (m_settings->rewriteTypesinCppFile()) + implCode = symbolAt(m_class, m_sourceFile, implLoc); + else + implCode = className; + implCode += "::" + className + "("; + } else if (constructorLocation + == CppQuickFixSettings::FunctionLocation::OutsideClass) { + implLoc = insertLocationForMethodDefinition(m_class, + false, + NamespaceHandling::Ignore, + m_changes, + m_headerFile->filePath(), + &insertedNamespaces); + implFile = m_headerFile; + implCode = symbolAt(m_class, m_headerFile, implLoc); + implCode += "::" + className + "("; + } + + QString inClassDeclaration = overview.prettyName(m_class->name()) + "("; + QString constructorBody = members.empty() ? QString(") {}") : QString(") : "); + for (auto &member : members) { + if (isValueType(member->symbol, &member->customValueType)) + member->type.setConst(false); + else + member->type = makeConstRef(member->type); + + inClassDeclaration += overview.prettyType(member->type, member->parameterName); + if (!member->defaultValue.isEmpty()) + inClassDeclaration += " = " + member->defaultValue; + inClassDeclaration += ", "; + if (implFile) { + FullySpecifiedType type = typeAt(member->type, + m_class, + implFile, + implLoc, + insertedNamespaces); + implCode += overview.prettyType(type, member->parameterName) + ", "; + } + } + Utils::sort(members, &ConstructorMemberInfo::numberOfMember); + // first, do the base classes + for (const auto &parent : parentClassConstructors) { + if (!parent.useInConstructor) + continue; + // Check if we really need a constructor + if (Utils::anyOf(parent.parameters, [](const auto ¶m) { + return param.init || param.originalDefaultValue.isEmpty(); + })) { + int defaultAtEndCount = 0; + for (auto i = parent.parameters.crbegin(); i != parent.parameters.crend(); + ++i) { + if (i->init || i->originalDefaultValue.isEmpty()) + break; + ++defaultAtEndCount; + } + const int numberOfParameters = static_cast(parent.parameters.size()) + - defaultAtEndCount; + constructorBody += parent.className + "("; + int counter = 0; + for (const auto ¶m : parent.parameters) { + if (++counter > numberOfParameters) + break; + if (param.init) { + if (param.customValueType) + constructorBody += "std::move(" + param.parameterName + ')'; + else + constructorBody += param.parameterName; + } else if (!param.originalDefaultValue.isEmpty()) + constructorBody += param.originalDefaultValue; + else + constructorBody += "/* insert value */"; + constructorBody += ", "; + } + constructorBody.resize(constructorBody.length() - 2); + constructorBody += "),\n"; + } + } + for (auto &member : members) { + if (member->parentClassConstructor) + continue; + QString param = member->parameterName; + if (member->customValueType) + param = "std::move(" + member->parameterName + ')'; + constructorBody += member->memberVariableName + '(' + param + "),\n"; + } + if (!members.empty()) { + inClassDeclaration.resize(inClassDeclaration.length() - 2); + constructorBody.remove(constructorBody.length() - 2, 1); // ..),\n => ..)\n + constructorBody += "{}"; + if (!implCode.isEmpty()) + implCode.resize(implCode.length() - 2); + } + implCode += constructorBody; + + if (constructorLocation == CppQuickFixSettings::FunctionLocation::InsideClass) + inClassDeclaration += constructorBody; + else + inClassDeclaration += QLatin1String(");"); + + TranslationUnit *tu = m_headerFile->cppDocument()->translationUnit(); + insertAndIndent(m_headerFile, + m_locator.constructorDeclarationInClass(tu, + m_classAST, + m_accessSpec, + int(members.size())), + inClassDeclaration); + + if (constructorLocation == CppQuickFixSettings::FunctionLocation::CppFile) { + addSourceFileCode(implCode); + } else if (constructorLocation + == CppQuickFixSettings::FunctionLocation::OutsideClass) { + if (m_isHeaderHeaderFile) + implCode.prepend("inline "); + insertAndIndent(m_headerFile, implLoc, implCode); + } + } + }; + GenerateConstructorRefactoringHelper helper(this, + currentFile()->filePath(), + m_classAST->symbol, + m_classAST, + accessSpec); + + auto members = Utils::filtered(infos, [](const auto mi) { + return mi->init || mi->parentClassConstructor; + }); + helper.generateConstructor(std::move(members), parentClassConstructors); + helper.applyChanges(); + } + + ConstructorParams parameterModel; + ParentClassConstructors parentClassConstructors; + const ClassSpecifierAST *m_classAST = nullptr; + bool m_test = false; +}; + +class GenerateGetterSetterOp : public CppQuickFixOperation +{ +public: + enum GenerateFlag { + GenerateGetter = 1 << 0, + GenerateSetter = 1 << 1, + GenerateSignal = 1 << 2, + GenerateMemberVariable = 1 << 3, + GenerateReset = 1 << 4, + GenerateProperty = 1 << 5, + GenerateConstantProperty = 1 << 6, + HaveExistingQProperty = 1 << 7, + Invalid = -1, + }; + + GenerateGetterSetterOp(const CppQuickFixInterface &interface, + ExistingGetterSetterData data, + int generateFlags, + int priority, + const QString &description) + : CppQuickFixOperation(interface) + , m_generateFlags(generateFlags) + , m_data(data) + { + setDescription(description); + setPriority(priority); + } + + static void generateQuickFixes(QuickFixOperations &results, + const CppQuickFixInterface &interface, + const ExistingGetterSetterData &data, + const int possibleFlags) + { + // flags can have the value HaveExistingQProperty or a combination of all other values + // of the enum 'GenerateFlag' + int p = 0; + if (possibleFlags & HaveExistingQProperty) { + const QString desc = Tr::tr("Generate Missing Q_PROPERTY Members"); + results << new GenerateGetterSetterOp(interface, data, possibleFlags, ++p, desc); + } else { + if (possibleFlags & GenerateSetter) { + const QString desc = Tr::tr("Generate Setter"); + results << new GenerateGetterSetterOp(interface, data, GenerateSetter, ++p, desc); + } + if (possibleFlags & GenerateGetter) { + const QString desc = Tr::tr("Generate Getter"); + results << new GenerateGetterSetterOp(interface, data, GenerateGetter, ++p, desc); + } + if (possibleFlags & GenerateGetter && possibleFlags & GenerateSetter) { + const QString desc = Tr::tr("Generate Getter and Setter"); + const int flags = GenerateGetter | GenerateSetter; + results << new GenerateGetterSetterOp(interface, data, flags, ++p, desc); + } + + if (possibleFlags & GenerateConstantProperty) { + const QString desc = Tr::tr("Generate Constant Q_PROPERTY and Missing Members"); + const int flags = possibleFlags & ~(GenerateSetter | GenerateSignal | GenerateReset); + results << new GenerateGetterSetterOp(interface, data, flags, ++p, desc); + } + if (possibleFlags & GenerateProperty) { + if (possibleFlags & GenerateReset) { + const QString desc = Tr::tr( + "Generate Q_PROPERTY and Missing Members with Reset Function"); + const int flags = possibleFlags & ~GenerateConstantProperty; + results << new GenerateGetterSetterOp(interface, data, flags, ++p, desc); + } + const QString desc = Tr::tr("Generate Q_PROPERTY and Missing Members"); + const int flags = possibleFlags & ~GenerateConstantProperty & ~GenerateReset; + results << new GenerateGetterSetterOp(interface, data, flags, ++p, desc); + } + } + } + + void perform() override + { + GetterSetterRefactoringHelper helper(this, currentFile()->filePath(), m_data.clazz); + helper.performGeneration(m_data, m_generateFlags); + helper.applyChanges(); + } + +private: + int m_generateFlags; + ExistingGetterSetterData m_data; +}; + +class CandidateTreeItem : public TreeItem +{ +public: + enum Column { + NameColumn, + GetterColumn, + SetterColumn, + SignalColumn, + ResetColumn, + QPropertyColumn, + ConstantQPropertyColumn + }; + using Flag = GenerateGetterSetterOp::GenerateFlag; + constexpr static Flag ColumnFlag[] = { + Flag::Invalid, + Flag::GenerateGetter, + Flag::GenerateSetter, + Flag::GenerateSignal, + Flag::GenerateReset, + Flag::GenerateProperty, + Flag::GenerateConstantProperty, + }; + + CandidateTreeItem(MemberInfo *memberInfo) + : m_memberInfo(memberInfo) + {} + +private: + QVariant data(int column, int role) const override + { + if (role == Qt::DisplayRole && column == NameColumn) + return m_memberInfo->data.memberVariableName; + if (role == Qt::CheckStateRole && column > 0 + && column <= static_cast(std::size(ColumnFlag))) { + return m_memberInfo->requestedFlags & ColumnFlag[column] ? Qt::Checked : Qt::Unchecked; + } + return {}; + } + + bool setData(int column, const QVariant &data, int role) override + { + if (column < 1 || column > static_cast(std::size(ColumnFlag))) + return false; + if (role != Qt::CheckStateRole) + return false; + if (!(m_memberInfo->possibleFlags & ColumnFlag[column])) + return false; + const bool nowChecked = data.toInt() == Qt::Checked; + if (nowChecked) + m_memberInfo->requestedFlags |= ColumnFlag[column]; + else + m_memberInfo->requestedFlags &= ~ColumnFlag[column]; + + if (nowChecked) { + if (column == QPropertyColumn) { + m_memberInfo->requestedFlags |= Flag::GenerateGetter; + m_memberInfo->requestedFlags |= Flag::GenerateSetter; + m_memberInfo->requestedFlags |= Flag::GenerateSignal; + m_memberInfo->requestedFlags &= ~Flag::GenerateConstantProperty; + } else if (column == ConstantQPropertyColumn) { + m_memberInfo->requestedFlags |= Flag::GenerateGetter; + m_memberInfo->requestedFlags &= ~Flag::GenerateSetter; + m_memberInfo->requestedFlags &= ~Flag::GenerateSignal; + m_memberInfo->requestedFlags &= ~Flag::GenerateReset; + m_memberInfo->requestedFlags &= ~Flag::GenerateProperty; + } else if (column == SetterColumn || column == SignalColumn || column == ResetColumn) { + m_memberInfo->requestedFlags &= ~Flag::GenerateConstantProperty; + } + } else { + if (column == SignalColumn) + m_memberInfo->requestedFlags &= ~Flag::GenerateProperty; + } + for (int i = 0; i < 16; ++i) { + const bool allowed = m_memberInfo->possibleFlags & (1 << i); + if (!allowed) + m_memberInfo->requestedFlags &= ~(1 << i); // clear bit + } + update(); + return true; + } + + Qt::ItemFlags flags(int column) const override + { + if (column == NameColumn) + return Qt::ItemIsEnabled; + if (column < 1 || column > static_cast(std::size(ColumnFlag))) + return {}; + if (m_memberInfo->possibleFlags & ColumnFlag[column]) + return Qt::ItemIsEnabled | Qt::ItemIsUserCheckable; + return {}; + } + + MemberInfo *const m_memberInfo; +}; + +class GenerateGettersSettersDialog : public QDialog +{ + static constexpr CandidateTreeItem::Column CheckBoxColumn[4] + = {CandidateTreeItem::Column::GetterColumn, + CandidateTreeItem::Column::SetterColumn, + CandidateTreeItem::Column::SignalColumn, + CandidateTreeItem::Column::QPropertyColumn}; + +public: + GenerateGettersSettersDialog(const GetterSetterCandidates &candidates) + : QDialog() + , m_candidates(candidates) + { + using Flags = GenerateGetterSetterOp::GenerateFlag; + setWindowTitle(Tr::tr("Getters and Setters")); + const auto model = new TreeModel(this); + model->setHeader(QStringList({ + Tr::tr("Member"), + Tr::tr("Getter"), + Tr::tr("Setter"), + Tr::tr("Signal"), + Tr::tr("Reset"), + Tr::tr("QProperty"), + Tr::tr("Constant QProperty"), + })); + for (MemberInfo &candidate : m_candidates) + model->rootItem()->appendChild(new CandidateTreeItem(&candidate)); + const auto view = new BaseTreeView(this); + view->setModel(model); + int optimalWidth = 0; + for (int i = 0; i < model->columnCount(QModelIndex{}); ++i) { + view->resizeColumnToContents(i); + optimalWidth += view->columnWidth(i); + } + + const auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); + connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); + + const auto setCheckStateForAll = [model](int column, int checkState) { + for (int i = 0; i < model->rowCount(); ++i) + model->setData(model->index(i, column), checkState, Qt::CheckStateRole); + }; + const auto preventPartiallyChecked = [](QCheckBox *checkbox) { + if (checkbox->checkState() == Qt::PartiallyChecked) + checkbox->setCheckState(Qt::Checked); + }; + using Column = CandidateTreeItem::Column; + const auto createConnections = [this, setCheckStateForAll, preventPartiallyChecked]( + QCheckBox *checkbox, Column column) { + connect(checkbox, &QCheckBox::stateChanged, this, [setCheckStateForAll, column](int state) { + if (state != Qt::PartiallyChecked) + setCheckStateForAll(column, state); + }); + connect(checkbox, &QCheckBox::clicked, this, [checkbox, preventPartiallyChecked] { + preventPartiallyChecked(checkbox); + }); + }; + std::array checkBoxes = {}; + + static_assert(std::size(CheckBoxColumn) == checkBoxes.size(), + "Must contain the same number of elements"); + for (std::size_t i = 0; i < checkBoxes.size(); ++i) { + if (Utils::anyOf(candidates, [i](const MemberInfo &mi) { + return mi.possibleFlags & CandidateTreeItem::ColumnFlag[CheckBoxColumn[i]]; + })) { + const Column column = CheckBoxColumn[i]; + if (column == Column::GetterColumn) + checkBoxes[i] = new QCheckBox(Tr::tr("Create getters for all members")); + else if (column == Column::SetterColumn) + checkBoxes[i] = new QCheckBox(Tr::tr("Create setters for all members")); + else if (column == Column::SignalColumn) + checkBoxes[i] = new QCheckBox(Tr::tr("Create signals for all members")); + else if (column == Column::QPropertyColumn) + checkBoxes[i] = new QCheckBox(Tr::tr("Create Q_PROPERTY for all members")); + + createConnections(checkBoxes[i], column); + } + } + connect(model, &QAbstractItemModel::dataChanged, this, [this, checkBoxes] { + const auto countExisting = [this](Flags flag) { + return Utils::count(m_candidates, [flag](const MemberInfo &mi) { + return !(mi.possibleFlags & flag); + }); + }; + const auto countRequested = [this](Flags flag) { + return Utils::count(m_candidates, [flag](const MemberInfo &mi) { + return mi.requestedFlags & flag; + }); + }; + const auto countToState = [this](int requestedCount, int alreadyExistsCount) { + if (requestedCount == 0) + return Qt::Unchecked; + if (int(m_candidates.size()) - requestedCount == alreadyExistsCount) + return Qt::Checked; + return Qt::PartiallyChecked; + }; + for (std::size_t i = 0; i < checkBoxes.size(); ++i) { + if (checkBoxes[i]) { + const Flags flag = CandidateTreeItem::ColumnFlag[CheckBoxColumn[i]]; + checkBoxes[i]->setCheckState( + countToState(countRequested(flag), countExisting(flag))); + } + } + }); + + const auto mainLayout = new QVBoxLayout(this); + mainLayout->addWidget(new QLabel(Tr::tr("Select the getters and setters " + "to be created."))); + for (auto checkBox : checkBoxes) { + if (checkBox) + mainLayout->addWidget(checkBox); + } + mainLayout->addWidget(view); + mainLayout->addWidget(buttonBox); + int left, right; + mainLayout->getContentsMargins(&left, nullptr, &right, nullptr); + optimalWidth += left + right; + resize(optimalWidth, mainLayout->sizeHint().height()); + } + + GetterSetterCandidates candidates() const { return m_candidates; } + +private: + GetterSetterCandidates m_candidates; +}; + +class GenerateGettersSettersOperation : public CppQuickFixOperation +{ +public: + GenerateGettersSettersOperation(const CppQuickFixInterface &interface) + : CppQuickFixOperation(interface) + { + setDescription(Tr::tr("Create Getter and Setter Member Functions")); + + m_classAST = astForClassOperations(interface); + if (!m_classAST) + return; + Class * const theClass = m_classAST->symbol; + if (!theClass) + return; + + // Go through all data members and try to find out whether they have getters and/or setters. + QList dataMembers; + QList memberFunctions; + for (auto it = theClass->memberBegin(); it != theClass->memberEnd(); ++it) { + Symbol *const s = *it; + if (!s->identifier() || !s->type() || s->type().isTypedef()) + continue; + if ((s->asDeclaration() && s->type()->asFunctionType()) || s->asFunction()) + memberFunctions << s; + else if (s->asDeclaration() && (s->isPrivate() || s->isProtected())) + dataMembers << s; + } + + auto file = interface.currentFile(); + QStringList qPropertyNames; // name after MEMBER or name of the property + for (auto it = m_classAST->member_specifier_list; it; it = it->next) { + if (it->value->asQtPropertyDeclaration()) { + auto propDecl = it->value->asQtPropertyDeclaration(); + // iterator over 'READ ...', ... and check if we have a MEMBER + for (auto p = propDecl->property_declaration_item_list; p; p = p->next) { + const char *tokenString = file->tokenAt(p->value->item_name_token).spell(); + if (!qstrcmp(tokenString, "MEMBER")) + qPropertyNames << file->textOf(p->value->expression); + } + // no MEMBER, but maybe the property name is the same + qPropertyNames << file->textOf(propDecl->property_name); + } + } + const QStringList memberFunctionsAsStrings = toStringList(memberFunctions); + + for (Symbol *const member : std::as_const(dataMembers)) { + ExistingGetterSetterData existing; + existing.memberVariableName = QString::fromUtf8(member->identifier()->chars(), + member->identifier()->size()); + existing.declarationSymbol = member->asDeclaration(); + existing.clazz = theClass; + + // check if a Q_PROPERTY exist + const QString baseName = memberBaseName(existing.memberVariableName); + if (qPropertyNames.contains(baseName) + || qPropertyNames.contains(existing.memberVariableName)) + continue; + + findExistingFunctions(existing, memberFunctionsAsStrings); + existing.qPropertyName = baseName; + + int possibleFlags = existing.computePossibleFlags(); + if (possibleFlags == 0) + continue; + m_candidates.emplace_back(existing, possibleFlags); + } + } + + GetterSetterCandidates candidates() const { return m_candidates; } + bool isApplicable() const { return !m_candidates.empty(); } + + void setGetterSetterData(const GetterSetterCandidates &data) + { + m_candidates = data; + m_hasData = true; + } + +private: + void perform() override + { + if (!m_hasData) { + GenerateGettersSettersDialog dlg(m_candidates); + if (dlg.exec() == QDialog::Rejected) + return; + m_candidates = dlg.candidates(); + } + if (m_candidates.empty()) + return; + GetterSetterRefactoringHelper helper(this, + currentFile()->filePath(), + m_candidates.front().data.clazz); + for (MemberInfo &mi : m_candidates) { + if (mi.requestedFlags != 0) { + helper.performGeneration(mi.data, mi.requestedFlags); + } + } + helper.applyChanges(); + } + + GetterSetterCandidates m_candidates; + const ClassSpecifierAST *m_classAST = nullptr; + bool m_hasData = false; +}; + +int ExistingGetterSetterData::computePossibleFlags() const +{ + const bool isConst = declarationSymbol->type().isConst(); + const bool isStatic = declarationSymbol->type().isStatic(); + using Flag = GenerateGetterSetterOp::GenerateFlag; + int generateFlags = 0; + if (getterName.isEmpty()) + generateFlags |= Flag::GenerateGetter; + if (!isConst) { + if (resetName.isEmpty()) + generateFlags |= Flag::GenerateReset; + if (!isStatic && signalName.isEmpty() && setterName.isEmpty()) + generateFlags |= Flag::GenerateSignal; + if (setterName.isEmpty()) + generateFlags |= Flag::GenerateSetter; + } + if (!isStatic) { + const bool hasSignal = !signalName.isEmpty() || generateFlags & Flag::GenerateSignal; + if (!isConst && hasSignal) + generateFlags |= Flag::GenerateProperty; + } + if (setterName.isEmpty() && signalName.isEmpty()) + generateFlags |= Flag::GenerateConstantProperty; + return generateFlags; +} + +void GetterSetterRefactoringHelper::performGeneration(ExistingGetterSetterData data, int generateFlags) +{ + using Flag = GenerateGetterSetterOp::GenerateFlag; + + if (generateFlags & Flag::GenerateGetter && data.getterName.isEmpty()) { + data.getterName = m_settings->getGetterName(data.qPropertyName); + if (data.getterName == data.memberVariableName) { + data.getterName = "get" + data.memberVariableName.left(1).toUpper() + + data.memberVariableName.mid(1); + } + } + if (generateFlags & Flag::GenerateSetter && data.setterName.isEmpty()) + data.setterName = m_settings->getSetterName(data.qPropertyName); + if (generateFlags & Flag::GenerateSignal && data.signalName.isEmpty()) + data.signalName = m_settings->getSignalName(data.qPropertyName); + if (generateFlags & Flag::GenerateReset && data.resetName.isEmpty()) + data.resetName = m_settings->getResetName(data.qPropertyName); + + FullySpecifiedType memberVariableType = data.declarationSymbol->type(); + memberVariableType.setConst(false); + const bool isMemberVariableStatic = memberVariableType.isStatic(); + memberVariableType.setStatic(false); + Overview overview = CppCodeStyleSettings::currentProjectCodeStyleOverview(); + overview.showTemplateParameters = false; + // TODO does not work with using. e.g. 'using foo = std::unique_ptr' + // TODO must be fully qualified + auto getSetTemplate = m_settings->findGetterSetterTemplate(overview.prettyType(memberVariableType)); + overview.showTemplateParameters = true; + + // Ok... - If a type is a Named type we have to search recusive for the real type + const bool isValueType = this->isValueType(memberVariableType, + data.declarationSymbol->enclosingScope()); + const FullySpecifiedType parameterType = isValueType ? memberVariableType + : makeConstRef(memberVariableType); + + QString baseName = memberBaseName(data.memberVariableName); + if (baseName.isEmpty()) + baseName = data.memberVariableName; + + const QString parameterName = m_settings->getSetterParameterName(baseName); + if (parameterName == data.memberVariableName) + data.memberVariableName = "this->" + data.memberVariableName; + + getSetTemplate.replacePlaceholders(data.memberVariableName, parameterName); + + using Pattern = CppQuickFixSettings::GetterSetterTemplate; + std::optional returnTypeTemplateParameter; + if (getSetTemplate.returnTypeTemplate.has_value()) { + QString returnTypeTemplate = getSetTemplate.returnTypeTemplate.value(); + if (returnTypeTemplate.contains(Pattern::TEMPLATE_PARAMETER_PATTERN)) { + returnTypeTemplateParameter = getFirstTemplateParameter(data.declarationSymbol->type()); + if (!returnTypeTemplateParameter.has_value()) + return; // Maybe report error to the user + } + } + const FullySpecifiedType returnTypeHeader = [&] { + if (!getSetTemplate.returnTypeTemplate.has_value()) + return m_settings->returnByConstRef ? parameterType : memberVariableType; + QString typeTemplate = getSetTemplate.returnTypeTemplate.value(); + if (returnTypeTemplateParameter.has_value()) + typeTemplate.replace(Pattern::TEMPLATE_PARAMETER_PATTERN, + overview.prettyType(returnTypeTemplateParameter.value())); + if (typeTemplate.contains(Pattern::TYPE_PATTERN)) + typeTemplate.replace(Pattern::TYPE_PATTERN, + overview.prettyType(data.declarationSymbol->type())); + Control *control = m_operation->currentFile()->cppDocument()->control(); + std::string utf8TypeName = typeTemplate.toUtf8().toStdString(); + return FullySpecifiedType(control->namedType(control->identifier(utf8TypeName.c_str()))); + }(); + + // getter declaration + if (generateFlags & Flag::GenerateGetter) { + // maybe we added 'this->' to memberVariableName because of a collision with parameterName + // but here the 'this->' is not needed + const QString returnExpression = QString{getSetTemplate.returnExpression}.replace("this->", + ""); + QString getterInClassDeclaration = overview.prettyType(returnTypeHeader, data.getterName) + + QLatin1String("()"); + if (isMemberVariableStatic) + getterInClassDeclaration.prepend(QLatin1String("static ")); + else + getterInClassDeclaration += QLatin1String(" const"); + getterInClassDeclaration.prepend(m_settings->getterAttributes + QLatin1Char(' ')); + + auto getterLocation = m_settings->determineGetterLocation(1); + // if we have an anonymous class we must add code inside the class + if (data.clazz->name()->asAnonymousNameId()) + getterLocation = CppQuickFixSettings::FunctionLocation::InsideClass; + + if (getterLocation == CppQuickFixSettings::FunctionLocation::InsideClass) { + getterInClassDeclaration += QLatin1String("\n{\nreturn ") + returnExpression + + QLatin1String(";\n}\n"); + } else { + getterInClassDeclaration += QLatin1String(";\n"); + } + addHeaderCode(InsertionPointLocator::Public, getterInClassDeclaration); + if (getterLocation == CppQuickFixSettings::FunctionLocation::CppFile && !hasSourceFile()) + getterLocation = CppQuickFixSettings::FunctionLocation::OutsideClass; + + if (getterLocation != CppQuickFixSettings::FunctionLocation::InsideClass) { + const auto getReturnTypeAt = [&](CppRefactoringFilePtr targetFile, + InsertionLocation targetLoc) { + if (getSetTemplate.returnTypeTemplate.has_value()) { + QString returnType = getSetTemplate.returnTypeTemplate.value(); + if (returnTypeTemplateParameter.has_value()) { + const QString templateTypeName = overview.prettyType(typeAt( + returnTypeTemplateParameter.value(), data.clazz, targetFile, targetLoc)); + returnType.replace(Pattern::TEMPLATE_PARAMETER_PATTERN, templateTypeName); + } + if (returnType.contains(Pattern::TYPE_PATTERN)) { + const QString declarationType = overview.prettyType( + typeAt(memberVariableType, data.clazz, targetFile, targetLoc)); + returnType.replace(Pattern::TYPE_PATTERN, declarationType); + } + Control *control = m_operation->currentFile()->cppDocument()->control(); + std::string utf8String = returnType.toUtf8().toStdString(); + return FullySpecifiedType( + control->namedType(control->identifier(utf8String.c_str()))); + } else { + FullySpecifiedType returnType = typeAt(memberVariableType, + data.clazz, + targetFile, + targetLoc); + if (m_settings->returnByConstRef && !isValueType) + return makeConstRef(returnType); + return returnType; + } + }; + const QString constSpec = isMemberVariableStatic ? QLatin1String("") + : QLatin1String(" const"); + if (getterLocation == CppQuickFixSettings::FunctionLocation::CppFile) { + InsertionLocation loc = sourceLocationFor(data.declarationSymbol); + FullySpecifiedType returnType; + QString clazz; + if (m_settings->rewriteTypesinCppFile()) { + returnType = getReturnTypeAt(m_sourceFile, loc); + clazz = symbolAt(data.clazz, m_sourceFile, loc); + } else { + returnType = returnTypeHeader; + const Identifier *identifier = data.clazz->name()->identifier(); + clazz = QString::fromUtf8(identifier->chars(), identifier->size()); + } + const QString code = overview.prettyType(returnType, clazz + "::" + data.getterName) + + "()" + constSpec + "\n{\nreturn " + returnExpression + ";\n}"; + addSourceFileCode(code); + } else if (getterLocation == CppQuickFixSettings::FunctionLocation::OutsideClass) { + InsertionLocation loc + = insertLocationForMethodDefinition(data.declarationSymbol, + false, + NamespaceHandling::Ignore, + m_changes, + m_headerFile->filePath()); + const FullySpecifiedType returnType = getReturnTypeAt(m_headerFile, loc); + const QString clazz = symbolAt(data.clazz, m_headerFile, loc); + QString code = overview.prettyType(returnType, clazz + "::" + data.getterName) + + "()" + constSpec + "\n{\nreturn " + returnExpression + ";\n}"; + if (m_isHeaderHeaderFile) + code.prepend("inline "); + insertAndIndent(m_headerFile, loc, code); + } + } + } + + // setter declaration + InsertionPointLocator::AccessSpec setterAccessSpec = InsertionPointLocator::Public; + if (m_settings->setterAsSlot) { + const QByteArray connectName = "connect"; + const Identifier connectId(connectName.data(), connectName.size()); + const QList items = m_operation->context().lookup(&connectId, data.clazz); + for (const LookupItem &item : items) { + if (item.declaration() && item.declaration()->enclosingClass() + && overview.prettyName(item.declaration()->enclosingClass()->name()) + == "QObject") { + setterAccessSpec = InsertionPointLocator::PublicSlot; + break; + } + } + } + const auto createSetterBodyWithSignal = [this, &getSetTemplate, &data] { + QString body; + QTextStream setter(&body); + setter << "if (" << getSetTemplate.equalComparison << ")\nreturn;\n"; + + setter << getSetTemplate.assignment << ";\n"; + if (m_settings->signalWithNewValue) + setter << "emit " << data.signalName << "(" << getSetTemplate.returnExpression << ");\n"; + else + setter << "emit " << data.signalName << "();\n"; + + return body; + }; + if (generateFlags & Flag::GenerateSetter) { + QString headerDeclaration = "void " + data.setterName + '(' + + overview.prettyType(addConstToReference(parameterType), + parameterName) + + ")"; + if (isMemberVariableStatic) + headerDeclaration.prepend("static "); + QString body = "\n{\n"; + if (data.signalName.isEmpty()) + body += getSetTemplate.assignment + ";\n"; + else + body += createSetterBodyWithSignal(); + + body += "}"; + + auto setterLocation = m_settings->determineSetterLocation(body.count('\n') - 2); + // if we have an anonymous class we must add code inside the class + if (data.clazz->name()->asAnonymousNameId()) + setterLocation = CppQuickFixSettings::FunctionLocation::InsideClass; + + if (setterLocation == CppQuickFixSettings::FunctionLocation::CppFile && !hasSourceFile()) + setterLocation = CppQuickFixSettings::FunctionLocation::OutsideClass; + + if (setterLocation == CppQuickFixSettings::FunctionLocation::InsideClass) { + headerDeclaration += body; + } else { + headerDeclaration += ";\n"; + if (setterLocation == CppQuickFixSettings::FunctionLocation::CppFile) { + InsertionLocation loc = sourceLocationFor(data.declarationSymbol); + QString clazz; + FullySpecifiedType newParameterType = parameterType; + if (m_settings->rewriteTypesinCppFile()) { + newParameterType = typeAt(memberVariableType, data.clazz, m_sourceFile, loc); + if (!isValueType) + newParameterType = makeConstRef(newParameterType); + clazz = symbolAt(data.clazz, m_sourceFile, loc); + } else { + const Identifier *identifier = data.clazz->name()->identifier(); + clazz = QString::fromUtf8(identifier->chars(), identifier->size()); + } + newParameterType = addConstToReference(newParameterType); + const QString code = "void " + clazz + "::" + data.setterName + '(' + + overview.prettyType(newParameterType, parameterName) + ')' + + body; + addSourceFileCode(code); + } else if (setterLocation == CppQuickFixSettings::FunctionLocation::OutsideClass) { + InsertionLocation loc + = insertLocationForMethodDefinition(data.declarationSymbol, + false, + NamespaceHandling::Ignore, + m_changes, + m_headerFile->filePath()); + + FullySpecifiedType newParameterType = typeAt(data.declarationSymbol->type(), + data.clazz, + m_headerFile, + loc); + if (!isValueType) + newParameterType = makeConstRef(newParameterType); + newParameterType = addConstToReference(newParameterType); + QString clazz = symbolAt(data.clazz, m_headerFile, loc); + + QString code = "void " + clazz + "::" + data.setterName + '(' + + overview.prettyType(newParameterType, parameterName) + ')' + body; + if (m_isHeaderHeaderFile) + code.prepend("inline "); + insertAndIndent(m_headerFile, loc, code); + } + } + addHeaderCode(setterAccessSpec, headerDeclaration); + } + + // reset declaration + if (generateFlags & Flag::GenerateReset) { + QString headerDeclaration = "void " + data.resetName + "()"; + if (isMemberVariableStatic) + headerDeclaration.prepend("static "); + QString body = "\n{\n"; + if (!data.setterName.isEmpty()) { + body += data.setterName + "({}); // TODO: Adapt to use your actual default value\n"; + } else { + body += "static $TYPE defaultValue{}; " + "// TODO: Adapt to use your actual default value\n"; + if (data.signalName.isEmpty()) + body += getSetTemplate.assignment + ";\n"; + else + body += createSetterBodyWithSignal(); + } + body += "}"; + + // the template use as new value name, but we want to use 'defaultValue' + body.replace(QRegularExpression("\\b" + parameterName + "\\b"), "defaultValue"); + // body.count('\n') - 2 : do not count the 2 at start + auto resetLocation = m_settings->determineSetterLocation(body.count('\n') - 2); + // if we have an anonymous class we must add code inside the class + if (data.clazz->name()->asAnonymousNameId()) + resetLocation = CppQuickFixSettings::FunctionLocation::InsideClass; + + if (resetLocation == CppQuickFixSettings::FunctionLocation::CppFile && !hasSourceFile()) + resetLocation = CppQuickFixSettings::FunctionLocation::OutsideClass; + + if (resetLocation == CppQuickFixSettings::FunctionLocation::InsideClass) { + headerDeclaration += body.replace("$TYPE", overview.prettyType(memberVariableType)); + } else { + headerDeclaration += ";\n"; + if (resetLocation == CppQuickFixSettings::FunctionLocation::CppFile) { + const InsertionLocation loc = sourceLocationFor(data.declarationSymbol); + QString clazz; + FullySpecifiedType type = memberVariableType; + if (m_settings->rewriteTypesinCppFile()) { + type = typeAt(memberVariableType, data.clazz, m_sourceFile, loc); + clazz = symbolAt(data.clazz, m_sourceFile, loc); + } else { + const Identifier *identifier = data.clazz->name()->identifier(); + clazz = QString::fromUtf8(identifier->chars(), identifier->size()); + } + const QString code = "void " + clazz + "::" + data.resetName + "()" + + body.replace("$TYPE", overview.prettyType(type)); + addSourceFileCode(code); + } else if (resetLocation == CppQuickFixSettings::FunctionLocation::OutsideClass) { + const InsertionLocation loc = insertLocationForMethodDefinition( + data.declarationSymbol, + false, + NamespaceHandling::Ignore, + m_changes, + m_headerFile->filePath()); + const FullySpecifiedType type = typeAt(data.declarationSymbol->type(), + data.clazz, + m_headerFile, + loc); + const QString clazz = symbolAt(data.clazz, m_headerFile, loc); + QString code = "void " + clazz + "::" + data.resetName + "()" + + body.replace("$TYPE", overview.prettyType(type)); + if (m_isHeaderHeaderFile) + code.prepend("inline "); + insertAndIndent(m_headerFile, loc, code); + } + } + addHeaderCode(setterAccessSpec, headerDeclaration); + } + + // signal declaration + if (generateFlags & Flag::GenerateSignal) { + const auto ¶meter = overview.prettyType(returnTypeHeader, data.qPropertyName); + const QString newValue = m_settings->signalWithNewValue ? parameter : QString(); + const QString declaration = QString("void %1(%2);\n").arg(data.signalName, newValue); + addHeaderCode(InsertionPointLocator::Signals, declaration); + } + + // member variable + if (generateFlags & Flag::GenerateMemberVariable) { + QString storageDeclaration = overview.prettyType(memberVariableType, data.memberVariableName); + if (memberVariableType->asPointerType() + && m_operation->semanticInfo().doc->translationUnit()->languageFeatures().cxx11Enabled) { + storageDeclaration.append(" = nullptr"); + } + storageDeclaration.append(";\n"); + addHeaderCode(InsertionPointLocator::Private, storageDeclaration); + } + + // Q_PROPERTY + if (generateFlags & Flag::GenerateProperty || generateFlags & Flag::GenerateConstantProperty) { + // Use the returnTypeHeader as base because of custom types in getSetTemplates. + // Remove const reference from type. + FullySpecifiedType type = returnTypeHeader; + if (ReferenceType *ref = type.type()->asReferenceType()) + type = ref->elementType(); + type.setConst(false); + + QString propertyDeclaration = QLatin1String("Q_PROPERTY(") + + overview.prettyType(type, + memberBaseName(data.memberVariableName)); + bool needMember = false; + if (data.getterName.isEmpty()) + needMember = true; + else + propertyDeclaration += QLatin1String(" READ ") + data.getterName; + if (generateFlags & Flag::GenerateConstantProperty) { + if (needMember) + propertyDeclaration += QLatin1String(" MEMBER ") + data.memberVariableName; + propertyDeclaration.append(QLatin1String(" CONSTANT")); + } else { + if (data.setterName.isEmpty()) { + needMember = true; + } else if (!getSetTemplate.returnTypeTemplate.has_value()) { + // if the return type of the getter and then Q_PROPERTY is different than + // the setter type, we should not add WRITE to the Q_PROPERTY + propertyDeclaration.append(QLatin1String(" WRITE ")).append(data.setterName); + } + if (needMember) + propertyDeclaration += QLatin1String(" MEMBER ") + data.memberVariableName; + if (!data.resetName.isEmpty()) + propertyDeclaration += QLatin1String(" RESET ") + data.resetName; + propertyDeclaration.append(QLatin1String(" NOTIFY ")).append(data.signalName); + } + + propertyDeclaration.append(QLatin1String(" FINAL)\n")); + addHeaderCode(InsertionPointLocator::Private, propertyDeclaration); + } +} + +//! Generate constructor +class GenerateConstructor : public CppQuickFixFactory +{ +public: +#ifdef WITH_TESTS + static QObject* createTest(); +#endif + +protected: + void setTest() { m_test = true; } + +private: + void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override + { + const auto op = QSharedPointer::create(interface); + if (!op->isApplicable()) + return; + op->setTest(m_test); + result << op; + } + + bool m_test = false; +}; + +//! Adds getter and setter functions for a member variable +class GenerateGetterSetter : public CppQuickFixFactory +{ +public: +#ifdef WITH_TESTS + static QObject* createTest(); +#endif + +private: + void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override + { + ExistingGetterSetterData existing; + + const QList &path = interface.path(); + // We expect something like + // [0] TranslationUnitAST + // [1] NamespaceAST + // [2] LinkageBodyAST + // [3] SimpleDeclarationAST + // [4] ClassSpecifierAST + // [5] SimpleDeclarationAST + // [6] DeclaratorAST + // [7] DeclaratorIdAST + // [8] SimpleNameAST + + const int n = path.size(); + if (n < 6) + return; + + int i = 1; + const auto variableNameAST = path.at(n - i++)->asSimpleName(); + const auto declaratorId = path.at(n - i++)->asDeclaratorId(); + // DeclaratorAST might be preceded by PointerAST, e.g. for the case + // "class C { char *@s; };", where '@' denotes the text cursor position. + auto declarator = path.at(n - i++)->asDeclarator(); + if (!declarator) { + --i; + if (path.at(n - i++)->asPointer()) { + if (n < 7) + return; + declarator = path.at(n - i++)->asDeclarator(); + } + if (!declarator) + return; + } + const auto variableDecl = path.at(n - i++)->asSimpleDeclaration(); + const auto classSpecifier = path.at(n - i++)->asClassSpecifier(); + const auto classDecl = path.at(n - i++)->asSimpleDeclaration(); + + if (!(variableNameAST && declaratorId && variableDecl && classSpecifier && classDecl)) + return; + + // Do not get triggered on member functconstions and arrays + if (declarator->postfix_declarator_list) { + return; + } + + // Construct getter and setter names + const Name *variableName = variableNameAST->name; + if (!variableName) { + return; + } + const Identifier *variableId = variableName->identifier(); + if (!variableId) { + return; + } + existing.memberVariableName = QString::fromUtf8(variableId->chars(), variableId->size()); + + // Find the right symbol (for typeName) in the simple declaration + Symbol *symbol = nullptr; + const List *symbols = variableDecl->symbols; + QTC_ASSERT(symbols, return ); + for (; symbols; symbols = symbols->next) { + Symbol *s = symbols->value; + if (const Name *name = s->name()) { + if (const Identifier *id = name->identifier()) { + const QString symbolName = QString::fromUtf8(id->chars(), id->size()); + if (symbolName == existing.memberVariableName) { + symbol = s; + break; + } + } + } + } + if (!symbol) { + // no type can be determined + return; + } + if (!symbol->asDeclaration()) { + return; + } + existing.declarationSymbol = symbol->asDeclaration(); + + existing.clazz = classSpecifier->symbol; + if (!existing.clazz) + return; + + auto file = interface.currentFile(); + // check if a Q_PROPERTY exist + const QString baseName = memberBaseName(existing.memberVariableName); + // eg: we have 'int m_test' and now 'Q_PROPERTY(int foo WRITE setTest MEMBER m_test NOTIFY tChanged)' + for (auto it = classSpecifier->member_specifier_list; it; it = it->next) { + if (it->value->asQtPropertyDeclaration()) { + auto propDecl = it->value->asQtPropertyDeclaration(); + // iterator over 'READ ...', ... + auto p = propDecl->property_declaration_item_list; + // first check, if we have a MEMBER and the member is equal to the baseName + for (; p; p = p->next) { + const char *tokenString = file->tokenAt(p->value->item_name_token).spell(); + if (!qstrcmp(tokenString, "MEMBER")) { + if (baseName == file->textOf(p->value->expression)) + return; + } + } + // no MEMBER, but maybe the property name is the same + const QString propertyName = file->textOf(propDecl->property_name); + // we compare the baseName. e.g. 'test' instead of 'm_test' + if (propertyName == baseName) + return; // TODO Maybe offer quick fix "Add missing Q_PROPERTY Members" + } + } + + findExistingFunctions(existing, toStringList(getMemberFunctions(existing.clazz))); + existing.qPropertyName = memberBaseName(existing.memberVariableName); + + const int possibleFlags = existing.computePossibleFlags(); + GenerateGetterSetterOp::generateQuickFixes(result, interface, existing, possibleFlags); + } +}; + +//! Adds getter and setter functions for several member variables +class GenerateGettersSettersForClass : public CppQuickFixFactory +{ +public: +#ifdef WITH_TESTS + static QObject* createTest(); +#endif + +protected: + void setTest() { m_test = true; } + +private: + void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override + { + const auto op = QSharedPointer::create(interface); + if (!op->isApplicable()) + return; + if (m_test) { + GetterSetterCandidates candidates = op->candidates(); + for (MemberInfo &mi : candidates) { + mi.requestedFlags = mi.possibleFlags; + using Flag = GenerateGetterSetterOp::GenerateFlag; + mi.requestedFlags &= ~Flag::GenerateConstantProperty; + } + op->setGetterSetterData(candidates); + } + result << op; + } + + bool m_test = false; +}; + +//! Adds missing members for a Q_PROPERTY +class InsertQtPropertyMembers : public CppQuickFixFactory +{ +public: +#ifdef WITH_TESTS + static QObject* createTest(); +#endif + +private: + void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override + { + using Flag = GenerateGetterSetterOp::GenerateFlag; + ExistingGetterSetterData existing; + // check for Q_PROPERTY + + const QList &path = interface.path(); + if (path.isEmpty()) + return; + + AST *const ast = path.last(); + QtPropertyDeclarationAST *qtPropertyDeclaration = ast->asQtPropertyDeclaration(); + if (!qtPropertyDeclaration || !qtPropertyDeclaration->type_id) + return; + + ClassSpecifierAST *klass = nullptr; + for (int i = path.size() - 2; i >= 0; --i) { + klass = path.at(i)->asClassSpecifier(); + if (klass) + break; + } + if (!klass) + return; + existing.clazz = klass->symbol; + + CppRefactoringFilePtr file = interface.currentFile(); + const QString propertyName = file->textOf(qtPropertyDeclaration->property_name); + existing.qPropertyName = propertyName; + extractNames(file, qtPropertyDeclaration, existing); + + Control *control = interface.currentFile()->cppDocument()->control(); + + existing.declarationSymbol = control->newDeclaration(ast->firstToken(), + qtPropertyDeclaration->property_name->name); + existing.declarationSymbol->setVisibility(Symbol::Private); + existing.declarationSymbol->setEnclosingScope(existing.clazz); + + { + // create a 'right' Type Object + // if we have Q_PROPERTY(int test ...) then we only get a NamedType for 'int', but we want + // a IntegerType. So create a new dummy file with a dummy declaration to get the right + // object + QByteArray type = file->textOf(qtPropertyDeclaration->type_id).toUtf8(); + QByteArray newSource = file->document() + ->toPlainText() + .insert(file->startOf(qtPropertyDeclaration), + QString::fromUtf8(type + " __dummy;\n")) + .toUtf8(); + + Document::Ptr doc = interface.snapshot().preprocessedDocument(newSource, "___quickfix.h"); + if (!doc->parse(Document::ParseTranlationUnit)) + return; + doc->check(); + class TypeFinder : public ASTVisitor + { + public: + FullySpecifiedType type; + TypeFinder(TranslationUnit *u) + : ASTVisitor(u) + {} + bool visit(SimpleDeclarationAST *ast) override + { + if (ast->symbols && !ast->symbols->next) { + const Name *name = ast->symbols->value->name(); + if (name && name->asNameId() && name->asNameId()->identifier()) { + const Identifier *id = name->asNameId()->identifier(); + if (QString::fromUtf8(id->chars(), id->size()) == "__dummy") + type = ast->symbols->value->type(); + } + } + return true; + } + }; + TypeFinder finder(doc->translationUnit()); + finder.accept(doc->translationUnit()->ast()); + if (finder.type.type()->isUndefinedType()) + return; + existing.declarationSymbol->setType(finder.type); + existing.doc = doc; // to hold type + } + // check which methods are already there + const bool haveFixMemberVariableName = !existing.memberVariableName.isEmpty(); + int generateFlags = Flag::GenerateMemberVariable; + if (!existing.resetName.isEmpty()) + generateFlags |= Flag::GenerateReset; + if (!existing.setterName.isEmpty()) + generateFlags |= Flag::GenerateSetter; + if (!existing.getterName.isEmpty()) + generateFlags |= Flag::GenerateGetter; + if (!existing.signalName.isEmpty()) + generateFlags |= Flag::GenerateSignal; + Overview overview; + for (int i = 0; i < existing.clazz->memberCount(); ++i) { + Symbol *member = existing.clazz->memberAt(i); + FullySpecifiedType type = member->type(); + if (member->asFunction() || (type.isValid() && type->asFunctionType())) { + const QString name = overview.prettyName(member->name()); + if (name == existing.getterName) + generateFlags &= ~Flag::GenerateGetter; + else if (name == existing.setterName) + generateFlags &= ~Flag::GenerateSetter; + else if (name == existing.resetName) + generateFlags &= ~Flag::GenerateReset; + else if (name == existing.signalName) + generateFlags &= ~Flag::GenerateSignal; + } else if (member->asDeclaration()) { + const QString name = overview.prettyName(member->name()); + if (haveFixMemberVariableName) { + if (name == existing.memberVariableName) { + generateFlags &= ~Flag::GenerateMemberVariable; + } + } else { + const QString baseName = memberBaseName(name); + if (existing.qPropertyName == baseName) { + existing.memberVariableName = name; + generateFlags &= ~Flag::GenerateMemberVariable; + } + } + } + } + if (generateFlags & Flag::GenerateMemberVariable) { + CppQuickFixSettings *settings = CppQuickFixProjectsSettings::getQuickFixSettings( + ProjectExplorer::ProjectTree::currentProject()); + existing.memberVariableName = settings->getMemberVariableName(existing.qPropertyName); + } + if (generateFlags == 0) { + // everything is already there + return; + } + generateFlags |= Flag::HaveExistingQProperty; + GenerateGetterSetterOp::generateQuickFixes(result, interface, existing, generateFlags); + } +}; + + +#ifdef WITH_TESTS +using namespace Tests; + +class GenerateGetterSetterTest : public QObject +{ + Q_OBJECT + +private slots: + void testNamespaceHandlingCreate_data() + { + QTest::addColumn("headers"); + QTest::addColumn("sources"); + + QByteArray originalSource; + QByteArray expectedSource; + + const QByteArray originalHeader = + "namespace N1 {\n" + "namespace N2 {\n" + "class Something\n" + "{\n" + " int @it;\n" + "};\n" + "}\n" + "}\n"; + const QByteArray expectedHeader = + "namespace N1 {\n" + "namespace N2 {\n" + "class Something\n" + "{\n" + " int it;\n" + "\n" + "public:\n" + " int getIt() const;\n" + " void setIt(int value);\n" + "};\n" + "}\n" + "}\n"; + + originalSource = "#include \"file.h\"\n"; + expectedSource = + "#include \"file.h\"\n\n\n" + "namespace N1 {\n" + "namespace N2 {\n" + "int Something::getIt() const\n" + "{\n" + " return it;\n" + "}\n" + "\n" + "void Something::setIt(int value)\n" + "{\n" + " it = value;\n" + "}\n\n" + "}\n" + "}\n"; + QTest::addRow("insert new namespaces") + << QByteArrayList{originalHeader, expectedHeader} + << QByteArrayList{originalSource, expectedSource}; + + originalSource = + "#include \"file.h\"\n" + "namespace N2 {} // decoy\n"; + expectedSource = + "#include \"file.h\"\n" + "namespace N2 {} // decoy\n\n\n" + "namespace N1 {\n" + "namespace N2 {\n" + "int Something::getIt() const\n" + "{\n" + " return it;\n" + "}\n" + "\n" + "void Something::setIt(int value)\n" + "{\n" + " it = value;\n" + "}\n\n" + "}\n" + "}\n"; + QTest::addRow("insert new namespaces (with decoy)") + << QByteArrayList{originalHeader, expectedHeader} + << QByteArrayList{originalSource, expectedSource}; + + originalSource = "#include \"file.h\"\n" + "namespace N2 {} // decoy\n" + "namespace {\n" + "namespace N1 {\n" + "namespace {\n" + "}\n" + "}\n" + "}\n"; + expectedSource = "#include \"file.h\"\n" + "namespace N2 {} // decoy\n" + "namespace {\n" + "namespace N1 {\n" + "namespace {\n" + "}\n" + "}\n" + "}\n" + "\n" + "\n" + "namespace N1 {\n" + "namespace N2 {\n" + "int Something::getIt() const\n" + "{\n" + " return it;\n" + "}\n" + "\n" + "void Something::setIt(int value)\n" + "{\n" + " it = value;\n" + "}\n" + "\n" + "}\n" + "}\n"; + QTest::addRow("insert inner namespace (with decoy and unnamed)") + << QByteArrayList{originalHeader, expectedHeader} + << QByteArrayList{originalSource, expectedSource}; + + const QByteArray unnamedOriginalHeader = "namespace {\n" + originalHeader + "}\n"; + const QByteArray unnamedExpectedHeader = "namespace {\n" + expectedHeader + "}\n"; + + originalSource = "#include \"file.h\"\n" + "namespace N2 {} // decoy\n" + "namespace {\n" + "namespace N1 {\n" + "namespace {\n" + "}\n" + "}\n" + "}\n"; + expectedSource = "#include \"file.h\"\n" + "namespace N2 {} // decoy\n" + "namespace {\n" + "namespace N1 {\n" + "\n" + "namespace N2 {\n" + "int Something::getIt() const\n" + "{\n" + " return it;\n" + "}\n" + "\n" + "void Something::setIt(int value)\n" + "{\n" + " it = value;\n" + "}\n" + "\n" + "}\n" + "\n" + "namespace {\n" + "}\n" + "}\n" + "}\n"; + QTest::addRow("insert inner namespace in unnamed (with decoy)") + << QByteArrayList{unnamedOriginalHeader, unnamedExpectedHeader} + << QByteArrayList{originalSource, expectedSource}; + + originalSource = + "#include \"file.h\"\n" + "namespace N1 {\n" + "namespace N2 {\n" + "namespace N3 {\n" + "}\n" + "}\n" + "}\n"; + expectedSource = + "#include \"file.h\"\n" + "namespace N1 {\n" + "namespace N2 {\n" + "namespace N3 {\n" + "}\n\n" + "int Something::getIt() const\n" + "{\n" + " return it;\n" + "}\n" + "\n" + "void Something::setIt(int value)\n" + "{\n" + " it = value;\n" + "}\n\n" + "}\n" + "}\n"; + QTest::addRow("all namespaces already present") + << QByteArrayList{originalHeader, expectedHeader} + << QByteArrayList{originalSource, expectedSource}; + + originalSource = "#include \"file.h\"\n" + "namespace N1 {\n" + "using namespace N2::N3;\n" + "using namespace N2;\n" + "using namespace N2;\n" + "using namespace N3;\n" + "}\n"; + expectedSource = "#include \"file.h\"\n" + "namespace N1 {\n" + "using namespace N2::N3;\n" + "using namespace N2;\n" + "using namespace N2;\n" + "using namespace N3;\n" + "\n" + "int Something::getIt() const\n" + "{\n" + " return it;\n" + "}\n" + "\n" + "void Something::setIt(int value)\n" + "{\n" + " it = value;\n" + "}\n\n" + "}\n"; + QTest::addRow("namespaces present and using namespace") + << QByteArrayList{originalHeader, expectedHeader} + << QByteArrayList{originalSource, expectedSource}; + + originalSource = "#include \"file.h\"\n" + "using namespace N1::N2::N3;\n" + "using namespace N1::N2;\n" + "namespace N1 {\n" + "using namespace N3;\n" + "}\n"; + expectedSource = "#include \"file.h\"\n" + "using namespace N1::N2::N3;\n" + "using namespace N1::N2;\n" + "namespace N1 {\n" + "using namespace N3;\n" + "\n" + "int Something::getIt() const\n" + "{\n" + " return it;\n" + "}\n" + "\n" + "void Something::setIt(int value)\n" + "{\n" + " it = value;\n" + "}\n" + "\n" + "}\n"; + QTest::addRow("namespaces present and outer using namespace") + << QByteArrayList{originalHeader, expectedHeader} + << QByteArrayList{originalSource, expectedSource}; + + originalSource = "#include \"file.h\"\n" + "using namespace N1;\n" + "using namespace N2;\n" + "namespace N3 {\n" + "}\n"; + expectedSource = "#include \"file.h\"\n" + "using namespace N1;\n" + "using namespace N2;\n" + "namespace N3 {\n" + "}\n" + "\n" + "int Something::getIt() const\n" + "{\n" + " return it;\n" + "}\n" + "\n" + "void Something::setIt(int value)\n" + "{\n" + " it = value;\n" + "}\n"; + QTest::addRow("namespaces present and outer using namespace") + << QByteArrayList{originalHeader, expectedHeader} + << QByteArrayList{originalSource, expectedSource}; + } + + void testNamespaceHandlingCreate() + { + QFETCH(QByteArrayList, headers); + QFETCH(QByteArrayList, sources); + + QList testDocuments( + {CppTestDocument::create("file.h", headers.at(0), headers.at(1)), + CppTestDocument::create("file.cpp", sources.at(0), sources.at(1))}); + + QuickFixSettings s; + s->cppFileNamespaceHandling = CppQuickFixSettings::MissingNamespaceHandling::CreateMissing; + s->setterParameterNameTemplate = "value"; + s->getterNameTemplate = "get"; + s->setterInCppFileFrom = 1; + s->getterInCppFileFrom = 1; + GenerateGetterSetter factory; + QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), 2); + } + + void testNamespaceHandlingAddUsing_data() + { + QTest::addColumn("headers"); + QTest::addColumn("sources"); + + QByteArray originalSource; + QByteArray expectedSource; + + const QByteArray originalHeader = "namespace N1 {\n" + "namespace N2 {\n" + "class Something\n" + "{\n" + " int @it;\n" + "};\n" + "}\n" + "}\n"; + const QByteArray expectedHeader = "namespace N1 {\n" + "namespace N2 {\n" + "class Something\n" + "{\n" + " int it;\n" + "\n" + "public:\n" + " void setIt(int value);\n" + "};\n" + "}\n" + "}\n"; + + originalSource = "#include \"file.h\"\n"; + expectedSource = "#include \"file.h\"\n\n" + "using namespace N1::N2;\n" + "void Something::setIt(int value)\n" + "{\n" + " it = value;\n" + "}\n"; + QTest::addRow("add using namespaces") << QByteArrayList{originalHeader, expectedHeader} + << QByteArrayList{originalSource, expectedSource}; + + const QByteArray unnamedOriginalHeader = "namespace {\n" + originalHeader + "}\n"; + const QByteArray unnamedExpectedHeader = "namespace {\n" + expectedHeader + "}\n"; + + originalSource = "#include \"file.h\"\n" + "namespace N2 {} // decoy\n" + "namespace {\n" + "namespace N1 {\n" + "namespace {\n" + "}\n" + "}\n" + "}\n"; + expectedSource = "#include \"file.h\"\n" + "namespace N2 {} // decoy\n" + "namespace {\n" + "namespace N1 {\n" + "using namespace N2;\n" + "void Something::setIt(int value)\n" + "{\n" + " it = value;\n" + "}\n" + "namespace {\n" + "}\n" + "}\n" + "}\n"; + QTest::addRow("insert using namespace into unnamed nested (with decoy)") + << QByteArrayList{unnamedOriginalHeader, unnamedExpectedHeader} + << QByteArrayList{originalSource, expectedSource}; + + originalSource = "#include \"file.h\"\n"; + expectedSource = "#include \"file.h\"\n\n" + "using namespace N1::N2;\n" + "void Something::setIt(int value)\n" + "{\n" + " it = value;\n" + "}\n"; + QTest::addRow("insert using namespace into unnamed") + << QByteArrayList{unnamedOriginalHeader, unnamedExpectedHeader} + << QByteArrayList{originalSource, expectedSource}; + + originalSource = "#include \"file.h\"\n" + "namespace N2 {} // decoy\n" + "namespace {\n" + "namespace N1 {\n" + "namespace {\n" + "}\n" + "}\n" + "}\n"; + expectedSource = "#include \"file.h\"\n" + "namespace N2 {} // decoy\n" + "namespace {\n" + "namespace N1 {\n" + "namespace {\n" + "}\n" + "}\n" + "}\n" + "\n" + "using namespace N1::N2;\n" + "void Something::setIt(int value)\n" + "{\n" + " it = value;\n" + "}\n"; + QTest::addRow("insert using namespace (with decoy)") + << QByteArrayList{originalHeader, expectedHeader} + << QByteArrayList{originalSource, expectedSource}; + } + + void testNamespaceHandlingAddUsing() + { + QFETCH(QByteArrayList, headers); + QFETCH(QByteArrayList, sources); + + QList testDocuments( + {CppTestDocument::create("file.h", headers.at(0), headers.at(1)), + CppTestDocument::create("file.cpp", sources.at(0), sources.at(1))}); + + QuickFixSettings s; + s->cppFileNamespaceHandling = CppQuickFixSettings::MissingNamespaceHandling::AddUsingDirective; + s->setterParameterNameTemplate = "value"; + s->setterInCppFileFrom = 1; + + if (std::strstr(QTest::currentDataTag(), "unnamed nested") != nullptr) + QSKIP("TODO"); // FIXME + GenerateGetterSetter factory; + QuickFixOperationTest(testDocuments, &factory); + } + + void testNamespaceHandlingFullyQualify_data() + { + QTest::addColumn("headers"); + QTest::addColumn("sources"); + + QByteArray originalSource; + QByteArray expectedSource; + + const QByteArray originalHeader = "namespace N1 {\n" + "namespace N2 {\n" + "class Something\n" + "{\n" + " int @it;\n" + "};\n" + "}\n" + "}\n"; + const QByteArray expectedHeader = "namespace N1 {\n" + "namespace N2 {\n" + "class Something\n" + "{\n" + " int it;\n" + "\n" + "public:\n" + " void setIt(int value);\n" + "};\n" + "}\n" + "}\n"; + + originalSource = "#include \"file.h\"\n"; + expectedSource = "#include \"file.h\"\n\n" + "void N1::N2::Something::setIt(int value)\n" + "{\n" + " it = value;\n" + "}\n"; + QTest::addRow("fully qualify") << QByteArrayList{originalHeader, expectedHeader} + << QByteArrayList{originalSource, expectedSource}; + + originalSource = "#include \"file.h\"\n" + "namespace N2 {} // decoy\n"; + expectedSource = "#include \"file.h\"\n" + "namespace N2 {} // decoy\n" + "\n" + "void N1::N2::Something::setIt(int value)\n" + "{\n" + " it = value;\n" + "}\n"; + QTest::addRow("fully qualify (with decoy)") << QByteArrayList{originalHeader, expectedHeader} + << QByteArrayList{originalSource, expectedSource}; + + originalSource = "#include \"file.h\"\n" + "namespace N2 {} // decoy\n" + "namespace {\n" + "namespace N1 {\n" + "namespace {\n" + "}\n" + "}\n" + "}\n"; + expectedSource = "#include \"file.h\"\n" + "namespace N2 {} // decoy\n" + "namespace {\n" + "namespace N1 {\n" + "namespace {\n" + "}\n" + "}\n" + "}\n" + "\n" + "void N1::N2::Something::setIt(int value)\n" + "{\n" + " it = value;\n" + "}\n"; + QTest::addRow("qualify in inner namespace (with decoy)") + << QByteArrayList{originalHeader, expectedHeader} + << QByteArrayList{originalSource, expectedSource}; + + const QByteArray unnamedOriginalHeader = "namespace {\n" + originalHeader + "}\n"; + const QByteArray unnamedExpectedHeader = "namespace {\n" + expectedHeader + "}\n"; + + originalSource = "#include \"file.h\"\n" + "namespace N2 {} // decoy\n" + "namespace {\n" + "namespace N1 {\n" + "namespace {\n" + "}\n" + "}\n" + "}\n"; + expectedSource = "#include \"file.h\"\n" + "namespace N2 {} // decoy\n" + "namespace {\n" + "namespace N1 {\n" + "void N2::Something::setIt(int value)\n" + "{\n" + " it = value;\n" + "}\n" + "namespace {\n" + "}\n" + "}\n" + "}\n"; + QTest::addRow("qualify in inner namespace unnamed nested (with decoy)") + << QByteArrayList{unnamedOriginalHeader, unnamedExpectedHeader} + << QByteArrayList{originalSource, expectedSource}; + + originalSource = "#include \"file.h\"\n"; + expectedSource = "#include \"file.h\"\n\n" + "void N1::N2::Something::setIt(int value)\n" + "{\n" + " it = value;\n" + "}\n"; + QTest::addRow("qualify in unnamed namespace") + << QByteArrayList{unnamedOriginalHeader, unnamedExpectedHeader} + << QByteArrayList{originalSource, expectedSource}; + } + + void testNamespaceHandlingFullyQualify() + { + QFETCH(QByteArrayList, headers); + QFETCH(QByteArrayList, sources); + + QList testDocuments( + {CppTestDocument::create("file.h", headers.at(0), headers.at(1)), + CppTestDocument::create("file.cpp", sources.at(0), sources.at(1))}); + + QuickFixSettings s; + s->cppFileNamespaceHandling = CppQuickFixSettings::MissingNamespaceHandling::RewriteType; + s->setterParameterNameTemplate = "value"; + s->setterInCppFileFrom = 1; + + if (std::strstr(QTest::currentDataTag(), "unnamed nested") != nullptr) + QSKIP("TODO"); // FIXME + GenerateGetterSetter factory; + QuickFixOperationTest(testDocuments, &factory); + } + + void testCustomNames_data() + { + QTest::addColumn("headers"); + QTest::addColumn("operation"); + + QByteArray originalSource; + QByteArray expectedSource; + + // Check if right names are created + originalSource = R"-( + class Test { + int m_fooBar_test@; + }; +)-"; + expectedSource = R"-( + class Test { + int m_fooBar_test; + + public: + int give_me_foo_bar_test() const + { + return m_fooBar_test; + } + void Seet_FooBar_test(int New_Foo_Bar_Test) + { + if (m_fooBar_test == New_Foo_Bar_Test) + return; + m_fooBar_test = New_Foo_Bar_Test; + emit newFooBarTestValue(); + } + void set_fooBarTest_toDefault() + { + Seet_FooBar_test({}); // TODO: Adapt to use your actual default value + } + + signals: + void newFooBarTestValue(); + + private: + Q_PROPERTY(int fooBar_test READ give_me_foo_bar_test WRITE Seet_FooBar_test RESET set_fooBarTest_toDefault NOTIFY newFooBarTestValue FINAL) + }; +)-"; + QTest::addRow("create right names") << QByteArrayList{originalSource, expectedSource} << 4; + + // Check if not triggered with custom names + originalSource = R"-( + class Test { + int m_fooBar_test@; + + public: + int give_me_foo_bar_test() const + { + return m_fooBar_test; + } + void Seet_FooBar_test(int New_Foo_Bar_Test) + { + if (m_fooBar_test == New_Foo_Bar_Test) + return; + m_fooBar_test = New_Foo_Bar_Test; + emit newFooBarTestValue(); + } + void set_fooBarTest_toDefault() + { + Seet_FooBar_test({}); // TODO: Adapt to use your actual default value + } + + signals: + void newFooBarTestValue(); + + private: + Q_PROPERTY(int fooBar_test READ give_me_foo_bar_test WRITE Seet_FooBar_test RESET set_fooBarTest_toDefault NOTIFY newFooBarTestValue FINAL) + }; +)-"; + expectedSource = ""; + QTest::addRow("everything already exists") << QByteArrayList{originalSource, expectedSource} << 4; + + // create from Q_PROPERTY with custom names + originalSource = R"-( + class Test { + Q_PROPER@TY(int fooBar_test READ give_me_foo_bar_test WRITE Seet_FooBar_test RESET set_fooBarTest_toDefault NOTIFY newFooBarTestValue FINAL) + + public: + int give_me_foo_bar_test() const + { + return mem_fooBar_test; + } + void Seet_FooBar_test(int New_Foo_Bar_Test) + { + if (mem_fooBar_test == New_Foo_Bar_Test) + return; + mem_fooBar_test = New_Foo_Bar_Test; + emit newFooBarTestValue(); + } + void set_fooBarTest_toDefault() + { + Seet_FooBar_test({}); // TODO: Adapt to use your actual default value + } + + signals: + void newFooBarTestValue(); + }; +)-"; + expectedSource = R"-( + class Test { + Q_PROPERTY(int fooBar_test READ give_me_foo_bar_test WRITE Seet_FooBar_test RESET set_fooBarTest_toDefault NOTIFY newFooBarTestValue FINAL) + + public: + int give_me_foo_bar_test() const + { + return mem_fooBar_test; + } + void Seet_FooBar_test(int New_Foo_Bar_Test) + { + if (mem_fooBar_test == New_Foo_Bar_Test) + return; + mem_fooBar_test = New_Foo_Bar_Test; + emit newFooBarTestValue(); + } + void set_fooBarTest_toDefault() + { + Seet_FooBar_test({}); // TODO: Adapt to use your actual default value + } + + signals: + void newFooBarTestValue(); + private: + int mem_fooBar_test; + }; +)-"; + QTest::addRow("create only member variable") + << QByteArrayList{originalSource, expectedSource} << 0; + + // create from Q_PROPERTY with custom names + originalSource = R"-( + class Test { + Q_PROPE@RTY(int fooBar_test READ give_me_foo_bar_test WRITE Seet_FooBar_test RESET set_fooBarTest_toDefault NOTIFY newFooBarTestValue FINAL) + int mem_fooBar_test; + public: + }; +)-"; + expectedSource = R"-( + class Test { + Q_PROPERTY(int fooBar_test READ give_me_foo_bar_test WRITE Seet_FooBar_test RESET set_fooBarTest_toDefault NOTIFY newFooBarTestValue FINAL) + int mem_fooBar_test; + public: + int give_me_foo_bar_test() const + { + return mem_fooBar_test; + } + void Seet_FooBar_test(int New_Foo_Bar_Test) + { + if (mem_fooBar_test == New_Foo_Bar_Test) + return; + mem_fooBar_test = New_Foo_Bar_Test; + emit newFooBarTestValue(); + } + void set_fooBarTest_toDefault() + { + Seet_FooBar_test({}); // TODO: Adapt to use your actual default value + } + signals: + void newFooBarTestValue(); + }; +)-"; + QTest::addRow("create methods with given member variable") + << QByteArrayList{originalSource, expectedSource} << 0; + } + + void testCustomNames() + { + QFETCH(QByteArrayList, headers); + QFETCH(int, operation); + + QList testDocuments( + {CppTestDocument::create("file.h", headers.at(0), headers.at(1))}); + + QuickFixSettings s; + s->setterInCppFileFrom = 0; + s->getterInCppFileFrom = 0; + s->setterNameTemplate = "Seet_"; + s->getterNameTemplate = "give_me_"; + s->signalNameTemplate = "newValue"; + s->setterParameterNameTemplate = "New_"; + s->resetNameTemplate = "set__toDefault"; + s->memberVariableNameTemplate = "mem_"; + if (operation == 0) { + InsertQtPropertyMembers factory; + QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), operation); + } else { + GenerateGetterSetter factory; + QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), operation); + } + } + + void testValueTypes_data() + { + QTest::addColumn("headers"); + QTest::addColumn("operation"); + + QByteArray originalSource; + QByteArray expectedSource; + + // int should be a value type + originalSource = R"-( + class Test { + int i@; + }; +)-"; + expectedSource = R"-( + class Test { + int i; + + public: + int getI() const + { + return i; + } + }; +)-"; + QTest::addRow("int") << QByteArrayList{originalSource, expectedSource} << 1; + + // return type should be only int without const + originalSource = R"-( + class Test { + const int i@; + }; +)-"; + expectedSource = R"-( + class Test { + const int i; + + public: + int getI() const + { + return i; + } + }; +)-"; + QTest::addRow("const int") << QByteArrayList{originalSource, expectedSource} << 0; + + // float should be a value type + originalSource = R"-( + class Test { + float f@; + }; +)-"; + expectedSource = R"-( + class Test { + float f; + + public: + float getF() const + { + return f; + } + }; +)-"; + QTest::addRow("float") << QByteArrayList{originalSource, expectedSource} << 1; + + // pointer should be a value type + originalSource = R"-( + class Test { + void* v@; + }; +)-"; + expectedSource = R"-( + class Test { + void* v; + + public: + void *getV() const + { + return v; + } + }; +)-"; + QTest::addRow("pointer") << QByteArrayList{originalSource, expectedSource} << 1; + + // reference should be a value type (setter is const ref) + originalSource = R"-( + class Test { + int& r@; + }; +)-"; + expectedSource = R"-( + class Test { + int& r; + + public: + int &getR() const + { + return r; + } + void setR(const int &newR) + { + r = newR; + } + }; +)-"; + QTest::addRow("reference to value type") << QByteArrayList{originalSource, expectedSource} << 2; + + // reference should be a value type + originalSource = R"-( + using bar = int; + class Test { + bar i@; + }; +)-"; + expectedSource = R"-( + using bar = int; + class Test { + bar i; + + public: + bar getI() const + { + return i; + } + }; +)-"; + QTest::addRow("value type through using") << QByteArrayList{originalSource, expectedSource} << 1; + + // enum should be a value type + originalSource = R"-( + enum Foo{V1, V2}; + class Test { + Foo e@; + }; +)-"; + expectedSource = R"-( + enum Foo{V1, V2}; + class Test { + Foo e; + + public: + Foo getE() const + { + return e; + } + }; +)-"; + QTest::addRow("enum") << QByteArrayList{originalSource, expectedSource} << 1; + + // class should not be a value type + originalSource = R"-( + class NoVal{}; + class Test { + NoVal n@; + }; +)-"; + expectedSource = R"-( + class NoVal{}; + class Test { + NoVal n; + + public: + const NoVal &getN() const + { + return n; + } + }; +)-"; + QTest::addRow("class") << QByteArrayList{originalSource, expectedSource} << 1; + + // custom classes can be a value type + originalSource = R"-( + class Value{}; + class Test { + Value v@; + }; +)-"; + expectedSource = R"-( + class Value{}; + class Test { + Value v; + + public: + Value getV() const + { + return v; + } + }; +)-"; + QTest::addRow("value class") << QByteArrayList{originalSource, expectedSource} << 1; + + // custom classes (in namespace) can be a value type + originalSource = R"-( + namespace N1{ + class Value{}; + } + class Test { + N1::Value v@; + }; +)-"; + expectedSource = R"-( + namespace N1{ + class Value{}; + } + class Test { + N1::Value v; + + public: + N1::Value getV() const + { + return v; + } + }; +)-"; + QTest::addRow("value class in namespace") << QByteArrayList{originalSource, expectedSource} << 1; + + // custom template class can be a value type + originalSource = R"-( + template + class Value{}; + class Test { + Value v@; + }; +)-"; + expectedSource = R"-( + template + class Value{}; + class Test { + Value v; + + public: + Value getV() const + { + return v; + } + }; +)-"; + QTest::addRow("value template class") << QByteArrayList{originalSource, expectedSource} << 1; + } + + void testValueTypes() + { + QFETCH(QByteArrayList, headers); + QFETCH(int, operation); + + QList testDocuments( + {CppTestDocument::create("file.h", headers.at(0), headers.at(1))}); + + QuickFixSettings s; + s->setterInCppFileFrom = 0; + s->getterInCppFileFrom = 0; + s->getterNameTemplate = "get"; + s->valueTypes << "Value"; + s->returnByConstRef = true; + + GenerateGetterSetter factory; + QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), operation); + } + + /// Checks: Use template for a custom type + void testCustomTemplate() + { + QList testDocuments; + QByteArray original; + QByteArray expected; + + const QByteArray customTypeDecl = R"--( +namespace N1 { +namespace N2 { +struct test{}; +} +template +struct custom { + void assign(const custom&); + bool equals(const custom&); + T* get(); +}; +)--"; + // Header File + original = customTypeDecl + R"--( +class Foo +{ +public: + custom bar@; +}; +})--"; + expected = customTypeDecl + R"--( +class Foo +{ +public: + custom bar@; + N2::test *getBar() const; + void setBar(const custom &newBar); +signals: + void barChanged(N2::test *bar); +private: + Q_PROPERTY(N2::test *bar READ getBar NOTIFY barChanged FINAL) +}; +})--"; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File + original = ""; + expected = R"-( +using namespace N1; +N2::test *Foo::getBar() const +{ + return bar.get(); +} + +void Foo::setBar(const custom &newBar) +{ + if (bar.equals(newBar)) + return; + bar.assign(newBar); + emit barChanged(bar.get()); +} +)-"; + + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + QuickFixSettings s; + s->cppFileNamespaceHandling = CppQuickFixSettings::MissingNamespaceHandling::AddUsingDirective; + s->getterNameTemplate = "get"; + s->getterInCppFileFrom = 1; + s->signalWithNewValue = true; + CppQuickFixSettings::CustomTemplate t; + t.types.append("custom"); + t.equalComparison = ".equals()"; + t.returnExpression = ".get()"; + t.returnType = " *"; + t.assignment = ".assign()"; + s->customTemplates.push_back(t); + + GenerateGetterSetter factory; + QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), 5); + } + + /// Checks: if the setter parameter name is the same as the member variable name, this-> is needed + void testNeedThis() + { + QList testDocuments; + + // Header File + const QByteArray original = R"-( + class Foo { + int bar@; + public: + }; +)-"; + const QByteArray expected = R"-( + class Foo { + int bar@; + public: + void setBar(int bar) + { + this->bar = bar; + } + }; +)-"; + testDocuments << CppTestDocument::create("file.h", original, expected); + + QuickFixSettings s; + s->setterParameterNameTemplate = ""; + s->setterInCppFileFrom = 0; + + GenerateGetterSetter factory; + QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), 0); + } + + void testOfferedFixes_data() + { + QTest::addColumn("header"); + QTest::addColumn("offered"); + + QByteArray header; + QStringList offered; + const QString setter = QStringLiteral("Generate Setter"); + const QString getter = QStringLiteral("Generate Getter"); + const QString getset = QStringLiteral("Generate Getter and Setter"); + const QString constQandMissing = QStringLiteral( + "Generate Constant Q_PROPERTY and Missing Members"); + const QString qAndResetAndMissing = QStringLiteral( + "Generate Q_PROPERTY and Missing Members with Reset Function"); + const QString qAndMissing = QStringLiteral("Generate Q_PROPERTY and Missing Members"); + const QStringList all{setter, getter, getset, constQandMissing, qAndResetAndMissing, qAndMissing}; + + header = R"-( + class Foo { + static int bar@; + }; +)-"; + offered = QStringList{setter, getter, getset, constQandMissing}; + QTest::addRow("static") << header << offered; + + header = R"-( + class Foo { + static const int bar@; + }; +)-"; + offered = QStringList{getter, constQandMissing}; + QTest::addRow("const static") << header << offered; + + header = R"-( + class Foo { + const int bar@; + }; +)-"; + offered = QStringList{getter, constQandMissing}; + QTest::addRow("const") << header << offered; + + header = R"-( + class Foo { + const int bar@; + int getBar() const; + }; +)-"; + offered = QStringList{constQandMissing}; + QTest::addRow("const + getter") << header << offered; + + header = R"-( + class Foo { + const int bar@; + int getBar() const; + void setBar(int value); + }; +)-"; + offered = QStringList{}; + QTest::addRow("const + getter + setter") << header << offered; + + header = R"-( + class Foo { + const int* bar@; + }; +)-"; + offered = all; + QTest::addRow("pointer to const") << header << offered; + + header = R"-( + class Foo { + int bar@; + public: + int bar(); + }; +)-"; + offered = QStringList{setter, constQandMissing, qAndResetAndMissing, qAndMissing}; + QTest::addRow("existing getter") << header << offered; + + header = R"-( + class Foo { + int bar@; + public: + set setBar(int); + }; +)-"; + offered = QStringList{getter}; + QTest::addRow("existing setter") << header << offered; + + header = R"-( + class Foo { + int bar@; + signals: + void barChanged(int); + }; +)-"; + offered = QStringList{setter, getter, getset, qAndResetAndMissing, qAndMissing}; + QTest::addRow("existing signal (no const Q_PROPERTY)") << header << offered; + + header = R"-( + class Foo { + int m_bar@; + Q_PROPERTY(int bar) + }; +)-"; + offered = QStringList{}; // user should use "InsertQPropertyMembers", no duplicated code + QTest::addRow("existing Q_PROPERTY") << header << offered; + } + + void testOfferedFixes() + { + QFETCH(QByteArray, header); + QFETCH(QStringList, offered); + + QList testDocuments( + {CppTestDocument::create("file.h", header, header)}); + + GenerateGetterSetter factory; + QuickFixOfferedOperationsTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), offered); + } + + void testGeneral_data() + { + QTest::addColumn("operation"); + QTest::addColumn("original"); + QTest::addColumn("expected"); + + QTest::newRow("GenerateGetterSetter_referenceToNonConst") + << 2 + << QByteArray("\n" + "class Something\n" + "{\n" + " int &it@;\n" + "};\n") + << QByteArray("\n" + "class Something\n" + "{\n" + " int ⁢\n" + "\n" + "public:\n" + " int &getIt() const;\n" + " void setIt(const int &it);\n" + "};\n" + "\n" + "int &Something::getIt() const\n" + "{\n" + " return it;\n" + "}\n" + "\n" + "void Something::setIt(const int &it)\n" + "{\n" + " this->it = it;\n" + "}\n"); + + // Checks: No special treatment for reference to const. + QTest::newRow("GenerateGetterSetter_referenceToConst") + << 2 + << QByteArray("\n" + "class Something\n" + "{\n" + " const int &it@;\n" + "};\n") + << QByteArray("\n" + "class Something\n" + "{\n" + " const int ⁢\n" + "\n" + "public:\n" + " const int &getIt() const;\n" + " void setIt(const int &it);\n" + "};\n" + "\n" + "const int &Something::getIt() const\n" + "{\n" + " return it;\n" + "}\n" + "\n" + "void Something::setIt(const int &it)\n" + "{\n" + " this->it = it;\n" + "}\n"); + + // Checks: + // 1. Setter: Setter is a static function. + // 2. Getter: Getter is a static, non const function. + QTest::newRow("GenerateGetterSetter_staticMember") + << 2 + << QByteArray("\n" + "class Something\n" + "{\n" + " static int @m_member;\n" + "};\n") + << QByteArray("\n" + "class Something\n" + "{\n" + " static int m_member;\n" + "\n" + "public:\n" + " static int member();\n" + " static void setMember(int member);\n" + "};\n" + "\n" + "int Something::member()\n" + "{\n" + " return m_member;\n" + "}\n" + "\n" + "void Something::setMember(int member)\n" + "{\n" + " m_member = member;\n" + "}\n"); + + // Check: Check if it works on the second declarator + // clang-format off + QTest::newRow("GenerateGetterSetter_secondDeclarator") << 2 + << QByteArray("\n" + "class Something\n" + "{\n" + " int *foo, @it;\n" + "};\n") + << QByteArray("\n" + "class Something\n" + "{\n" + " int *foo, it;\n" + "\n" + "public:\n" + " int getIt() const;\n" + " void setIt(int it);\n" + "};\n" + "\n" + "int Something::getIt() const\n" + "{\n" + " return it;\n" + "}\n" + "\n" + "void Something::setIt(int it)\n" + "{\n" + " this->it = it;\n" + "}\n"); + // clang-format on + + // Check: Quick fix is offered for "int *@it;" ('@' denotes the text cursor position) + QTest::newRow("GenerateGetterSetter_triggeringRightAfterPointerSign") + << 2 + << QByteArray("\n" + "class Something\n" + "{\n" + " int *@it;\n" + "};\n") + << QByteArray("\n" + "class Something\n" + "{\n" + " int *it;\n" + "\n" + "public:\n" + " int *getIt() const;\n" + " void setIt(int *it);\n" + "};\n" + "\n" + "int *Something::getIt() const\n" + "{\n" + " return it;\n" + "}\n" + "\n" + "void Something::setIt(int *it)\n" + "{\n" + " this->it = it;\n" + "}\n"); + + // Checks if "m_" is recognized as "m" with the postfix "_" and not simply as "m_" prefix. + QTest::newRow("GenerateGetterSetter_recognizeMasVariableName") + << 2 + << QByteArray("\n" + "class Something\n" + "{\n" + " int @m_;\n" + "};\n") + << QByteArray("\n" + "class Something\n" + "{\n" + " int m_;\n" + "\n" + "public:\n" + " int m() const;\n" + " void setM(int m);\n" + "};\n" + "\n" + "int Something::m() const\n" + "{\n" + " return m_;\n" + "}\n" + "\n" + "void Something::setM(int m)\n" + "{\n" + " m_ = m;\n" + "}\n"); + + // Checks if "m" followed by an upper character is recognized as a prefix + QTest::newRow("GenerateGetterSetter_recognizeMFollowedByCapital") + << 2 + << QByteArray("\n" + "class Something\n" + "{\n" + " int @mFoo;\n" + "};\n") + << QByteArray("\n" + "class Something\n" + "{\n" + " int mFoo;\n" + "\n" + "public:\n" + " int foo() const;\n" + " void setFoo(int foo);\n" + "};\n" + "\n" + "int Something::foo() const\n" + "{\n" + " return mFoo;\n" + "}\n" + "\n" + "void Something::setFoo(int foo)\n" + "{\n" + " mFoo = foo;\n" + "}\n"); + } + + void testGeneral() + { + QFETCH(int, operation); + QFETCH(QByteArray, original); + QFETCH(QByteArray, expected); + + QuickFixSettings s; + s->setterParameterNameTemplate = ""; + s->getterInCppFileFrom = 1; + s->setterInCppFileFrom = 1; + + GenerateGetterSetter factory; + QuickFixOperationTest(singleDocument(original, expected), + &factory, + ProjectExplorer::HeaderPaths(), + operation); + } + + /// Checks: Only generate getter + void testOnlyGetter() + { + QList testDocuments; + QByteArray original; + QByteArray expected; + + // Header File + original = + "class Foo\n" + "{\n" + "public:\n" + " int bar@;\n" + "};\n"; + expected = + "class Foo\n" + "{\n" + "public:\n" + " int bar@;\n" + " int getBar() const;\n" + "};\n"; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File + original.resize(0); + expected = + "\n" + "int Foo::getBar() const\n" + "{\n" + " return bar;\n" + "}\n"; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + QuickFixSettings s; + s->getterInCppFileFrom = 1; + s->getterNameTemplate = "get"; + GenerateGetterSetter factory; + QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), 1); + } + + /// Checks: Only generate setter + void testOnlySetter() + { + QList testDocuments; + QByteArray original; + QByteArray expected; + QuickFixSettings s; + s->setterAsSlot = true; // To be ignored, as we don't have QObjects here. + + // Header File + original = + "class Foo\n" + "{\n" + "public:\n" + " int bar@;\n" + "};\n"; + expected = + "class Foo\n" + "{\n" + "public:\n" + " int bar@;\n" + " void setBar(int value);\n" + "};\n"; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File + original.resize(0); + expected = + "\n" + "void Foo::setBar(int value)\n" + "{\n" + " bar = value;\n" + "}\n"; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + s->setterInCppFileFrom = 1; + s->setterParameterNameTemplate = "value"; + + GenerateGetterSetter factory; + QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), 0); + } + + void testAnonymousClass() + { + QList testDocuments; + QByteArray original; + QByteArray expected; + QuickFixSettings s; + s->setterInCppFileFrom = 1; + s->setterParameterNameTemplate = "value"; + + // Header File + original = R"( + class { + int @m_foo; + } bar; +)"; + expected = R"( + class { + int m_foo; + + public: + int foo() const + { + return m_foo; + } + void setFoo(int value) + { + if (m_foo == value) + return; + m_foo = value; + emit fooChanged(); + } + void resetFoo() + { + setFoo({}); // TODO: Adapt to use your actual default defaultValue + } + + signals: + void fooChanged(); + + private: + Q_PROPERTY(int foo READ foo WRITE setFoo RESET resetFoo NOTIFY fooChanged FINAL) + } bar; +)"; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File + testDocuments << CppTestDocument::create("file.cpp", {}, {}); + + GenerateGetterSetter factory; + QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), 4); + } + + void testInlineInHeaderFile() + { + QList testDocuments; + const QByteArray original = R"-( + class Foo { + public: + int bar@; + }; +)-"; + const QByteArray expected = R"-( + class Foo { + public: + int bar; + int getBar() const; + void setBar(int value); + void resetBar(); + signals: + void barChanged(); + private: + Q_PROPERTY(int bar READ getBar WRITE setBar RESET resetBar NOTIFY barChanged FINAL) + }; + + inline int Foo::getBar() const + { + return bar; + } + + inline void Foo::setBar(int value) + { + if (bar == value) + return; + bar = value; + emit barChanged(); + } + + inline void Foo::resetBar() + { + setBar({}); // TODO: Adapt to use your actual default defaultValue + } +)-"; + testDocuments << CppTestDocument::create("file.h", original, expected); + + QuickFixSettings s; + s->setterOutsideClassFrom = 1; + s->getterOutsideClassFrom = 1; + s->setterParameterNameTemplate = "value"; + s->getterNameTemplate = "get"; + + GenerateGetterSetter factory; + QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), 4); + } + + void testOnlySetterHeaderFileWithIncludeGuard() + { + QList testDocuments; + const QByteArray original = + "#ifndef FILE__H__DECLARED\n" + "#define FILE__H__DECLARED\n" + "class Foo\n" + "{\n" + "public:\n" + " int bar@;\n" + "};\n" + "#endif\n"; + const QByteArray expected = + "#ifndef FILE__H__DECLARED\n" + "#define FILE__H__DECLARED\n" + "class Foo\n" + "{\n" + "public:\n" + " int bar@;\n" + " void setBar(int value);\n" + "};\n\n" + "inline void Foo::setBar(int value)\n" + "{\n" + " bar = value;\n" + "}\n" + "#endif\n"; + + testDocuments << CppTestDocument::create("file.h", original, expected); + + QuickFixSettings s; + s->setterOutsideClassFrom = 1; + s->setterParameterNameTemplate = "value"; + + GenerateGetterSetter factory; + QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), 0); + } + + void testFunctionAsTemplateArg() + { + QList testDocuments; + const QByteArray original = R"( + template class TS {}; + template class TS {}; + + class S2 { + TS @member; + }; +)"; + const QByteArray expected = R"( + template class TS {}; + template class TS {}; + + class S2 { + TS member; + + public: + const TS &getMember() const + { + return member; + } + }; +)"; + + testDocuments << CppTestDocument::create("file.h", original, expected); + + QuickFixSettings s; + s->getterOutsideClassFrom = 0; + s->getterInCppFileFrom = 0; + s->getterNameTemplate = "get"; + s->returnByConstRef = true; + + GenerateGetterSetter factory; + QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), 1); + } + + void testNotTriggeringOnMemberFunction() + { + const QByteArray input = "class Something { void @f(); };\n"; + GenerateGetterSetter factory; + QuickFixOperationTest({CppTestDocument::create("file.h", input, {})}, &factory); + } + + void testNotTriggeringOnMemberArray() + { + const QByteArray input = "class Something { void @a[10]; };\n"; + GenerateGetterSetter factory; + QuickFixOperationTest({CppTestDocument::create("file.h", input, {})}, &factory); + } +}; + +class GenerateGettersSettersTest : public QObject +{ + Q_OBJECT + +private slots: + void test_data() + { + QTest::addColumn("original"); + QTest::addColumn("expected"); + + const QByteArray onlyReset = R"( + class Foo { + public: + int bar() const; + void setBar(int bar); + private: + int m_bar; + @};)"; + + const QByteArray onlyResetAfter = R"( + class @Foo { + public: + int bar() const; + void setBar(int bar); + void resetBar(); + + private: + int m_bar; + }; + inline void Foo::resetBar() + { + setBar({}); // TODO: Adapt to use your actual default defaultValue + } +)"; + QTest::addRow("only reset") << onlyReset << onlyResetAfter; + + const QByteArray withCandidates = R"( + class @Foo { + public: + int bar() const; + void setBar(int bar) { m_bar = bar; } + + int getBar2() const; + + int m_alreadyPublic; + + private: + friend void distraction(); + class AnotherDistraction {}; + enum EvenMoreDistraction { val1, val2 }; + + int m_bar; + int bar2_; + QString bar3; + };)"; + const QByteArray after = R"( + class Foo { + public: + int bar() const; + void setBar(int bar) { m_bar = bar; } + + int getBar2() const; + + int m_alreadyPublic; + + void resetBar(); + void setBar2(int value); + void resetBar2(); + const QString &getBar3() const; + void setBar3(const QString &value); + void resetBar3(); + + signals: + void bar2Changed(); + void bar3Changed(); + + private: + friend void distraction(); + class AnotherDistraction {}; + enum EvenMoreDistraction { val1, val2 }; + + int m_bar; + int bar2_; + QString bar3; + Q_PROPERTY(int bar2 READ getBar2 WRITE setBar2 RESET resetBar2 NOTIFY bar2Changed FINAL) + Q_PROPERTY(QString bar3 READ getBar3 WRITE setBar3 RESET resetBar3 NOTIFY bar3Changed FINAL) + }; + inline void Foo::resetBar() + { + setBar({}); // TODO: Adapt to use your actual default defaultValue + } + + inline void Foo::setBar2(int value) + { + if (bar2_ == value) + return; + bar2_ = value; + emit bar2Changed(); + } + + inline void Foo::resetBar2() + { + setBar2({}); // TODO: Adapt to use your actual default defaultValue + } + + inline const QString &Foo::getBar3() const + { + return bar3; + } + + inline void Foo::setBar3(const QString &value) + { + if (bar3 == value) + return; + bar3 = value; + emit bar3Changed(); + } + + inline void Foo::resetBar3() + { + setBar3({}); // TODO: Adapt to use your actual default defaultValue + } +)"; + QTest::addRow("with candidates") << withCandidates << after; + } + + void test() + { + class TestFactory : public GenerateGettersSettersForClass + { + public: + TestFactory() { setTest(); } + }; + + QFETCH(QByteArray, original); + QFETCH(QByteArray, expected); + + QuickFixSettings s; + s->getterNameTemplate = "get"; + s->setterParameterNameTemplate = "value"; + s->setterOutsideClassFrom = 1; + s->getterOutsideClassFrom = 1; + s->returnByConstRef = true; + + TestFactory factory; + QuickFixOperationTest({CppTestDocument::create("file.h", original, expected)}, &factory); + } +}; + +class InsertQtPropertyMembersTest : public QObject +{ + Q_OBJECT + +private slots: + void test_data() + { + QTest::addColumn("original"); + QTest::addColumn("expected"); + + QTest::newRow("InsertQtPropertyMembers") + << QByteArray("struct QObject { void connect(); }\n" + "struct XmarksTheSpot : public QObject {\n" + " @Q_PROPERTY(int it READ getIt WRITE setIt RESET resetIt NOTIFY itChanged)\n" + "};\n") + << QByteArray("struct QObject { void connect(); }\n" + "struct XmarksTheSpot : public QObject {\n" + " Q_PROPERTY(int it READ getIt WRITE setIt RESET resetIt NOTIFY itChanged)\n" + "\n" + "public:\n" + " int getIt() const;\n" + "\n" + "public slots:\n" + " void setIt(int it)\n" + " {\n" + " if (m_it == it)\n" + " return;\n" + " m_it = it;\n" + " emit itChanged(m_it);\n" + " }\n" + " void resetIt()\n" + " {\n" + " setIt({}); // TODO: Adapt to use your actual default value\n" + " }\n" + "\n" + "signals:\n" + " void itChanged(int it);\n" + "\n" + "private:\n" + " int m_it;\n" + "};\n" + "\n" + "int XmarksTheSpot::getIt() const\n" + "{\n" + " return m_it;\n" + "}\n"); + + QTest::newRow("InsertQtPropertyMembersResetWithoutSet") + << QByteArray("struct QObject { void connect(); }\n" + "struct XmarksTheSpot : public QObject {\n" + " @Q_PROPERTY(int it READ getIt RESET resetIt NOTIFY itChanged)\n" + "};\n") + << QByteArray("struct QObject { void connect(); }\n" + "struct XmarksTheSpot : public QObject {\n" + " Q_PROPERTY(int it READ getIt RESET resetIt NOTIFY itChanged)\n" + "\n" + "public:\n" + " int getIt() const;\n" + "\n" + "public slots:\n" + " void resetIt()\n" + " {\n" + " static int defaultValue{}; // TODO: Adapt to use your actual default " + "value\n" + " if (m_it == defaultValue)\n" + " return;\n" + " m_it = defaultValue;\n" + " emit itChanged(m_it);\n" + " }\n" + "\n" + "signals:\n" + " void itChanged(int it);\n" + "\n" + "private:\n" + " int m_it;\n" + "};\n" + "\n" + "int XmarksTheSpot::getIt() const\n" + "{\n" + " return m_it;\n" + "}\n"); + + QTest::newRow("InsertQtPropertyMembersResetWithoutSetAndNotify") + << QByteArray("struct QObject { void connect(); }\n" + "struct XmarksTheSpot : public QObject {\n" + " @Q_PROPERTY(int it READ getIt RESET resetIt)\n" + "};\n") + << QByteArray("struct QObject { void connect(); }\n" + "struct XmarksTheSpot : public QObject {\n" + " Q_PROPERTY(int it READ getIt RESET resetIt)\n" + "\n" + "public:\n" + " int getIt() const;\n" + "\n" + "public slots:\n" + " void resetIt()\n" + " {\n" + " static int defaultValue{}; // TODO: Adapt to use your actual default " + "value\n" + " m_it = defaultValue;\n" + " }\n" + "\n" + "private:\n" + " int m_it;\n" + "};\n" + "\n" + "int XmarksTheSpot::getIt() const\n" + "{\n" + " return m_it;\n" + "}\n"); + + QTest::newRow("InsertQtPropertyMembersPrivateBeforePublic") + << QByteArray("struct QObject { void connect(); }\n" + "class XmarksTheSpot : public QObject {\n" + "private:\n" + " @Q_PROPERTY(int it READ getIt WRITE setIt NOTIFY itChanged)\n" + "public:\n" + " void find();\n" + "};\n") + << QByteArray("struct QObject { void connect(); }\n" + "class XmarksTheSpot : public QObject {\n" + "private:\n" + " Q_PROPERTY(int it READ getIt WRITE setIt NOTIFY itChanged)\n" + " int m_it;\n" + "\n" + "public:\n" + " void find();\n" + " int getIt() const;\n" + "public slots:\n" + " void setIt(int it)\n" + " {\n" + " if (m_it == it)\n" + " return;\n" + " m_it = it;\n" + " emit itChanged(m_it);\n" + " }\n" + "signals:\n" + " void itChanged(int it);\n" + "};\n" + "\n" + "int XmarksTheSpot::getIt() const\n" + "{\n" + " return m_it;\n" + "}\n"); + } + + void test() + { + QFETCH(QByteArray, original); + QFETCH(QByteArray, expected); + + QuickFixSettings s; + s->setterAsSlot = true; + s->setterInCppFileFrom = 0; + s->setterParameterNameTemplate = ""; + s->signalWithNewValue = true; + + InsertQtPropertyMembers factory; + QuickFixOperationTest({CppTestDocument::create("file.cpp", original, expected)}, &factory); + } + + void testNotTriggeringOnInvalidCode() + { + const QByteArray input = "class C { @Q_PROPERTY(typeid foo READ foo) };\n"; + InsertQtPropertyMembers factory; + QuickFixOperationTest({CppTestDocument::create("file.h", input, {})}, &factory); + } +}; + +class GenerateConstructorTest : public QObject +{ + Q_OBJECT + +private slots: + void test_data() + { + QTest::addColumn("original_header"); + QTest::addColumn("expected_header"); + QTest::addColumn("original_source"); + QTest::addColumn("expected_source"); + QTest::addColumn("location"); + const int Inside = ConstructorLocation::Inside; + const int Outside = ConstructorLocation::Outside; + const int CppGenNamespace = ConstructorLocation::CppGenNamespace; + const int CppGenUsingDirective = ConstructorLocation::CppGenUsingDirective; + const int CppRewriteType = ConstructorLocation::CppRewriteType; + + QByteArray header = R"--( +class@ Foo{ + int test; + static int s; +}; +)--"; + QByteArray expected = R"--( +class Foo{ + int test; + static int s; +public: + Foo(int test) : test(test) + {} +}; +)--"; + QTest::newRow("ignore static") << header << expected << QByteArray() << QByteArray() << Inside; + + header = R"--( +class@ Foo{ + CustomType test; +}; +)--"; + expected = R"--( +class Foo{ + CustomType test; +public: + Foo(CustomType test) : test(std::move(test)) + {} +}; +)--"; + QTest::newRow("Move custom value types") + << header << expected << QByteArray() << QByteArray() << Inside; + + header = R"--( +class@ Foo{ + int test; +protected: + Foo() = default; +}; +)--"; + expected = R"--( +class Foo{ + int test; +public: + Foo(int test) : test(test) + {} + +protected: + Foo() = default; +}; +)--"; + + QTest::newRow("new section before existing") + << header << expected << QByteArray() << QByteArray() << Inside; + + header = R"--( +class@ Foo{ + int test; +}; +)--"; + expected = R"--( +class Foo{ + int test; +public: + Foo(int test) : test(test) + {} +}; +)--"; + QTest::newRow("new section at end") + << header << expected << QByteArray() << QByteArray() << Inside; + + header = R"--( +class@ Foo{ + int test; +public: + /** + * Random comment + */ + Foo(int i, int i2); +}; +)--"; + expected = R"--( +class Foo{ + int test; +public: + Foo(int test) : test(test) + {} + /** + * Random comment + */ + Foo(int i, int i2); +}; +)--"; + QTest::newRow("in section before") + << header << expected << QByteArray() << QByteArray() << Inside; + + header = R"--( +class@ Foo{ + int test; +public: + Foo() = default; +}; +)--"; + expected = R"--( +class Foo{ + int test; +public: + Foo() = default; + Foo(int test) : test(test) + {} +}; +)--"; + QTest::newRow("in section after") + << header << expected << QByteArray() << QByteArray() << Inside; + + header = R"--( +class@ Foo{ + int test1; + int test2; + int test3; +public: +}; +)--"; + expected = R"--( +class Foo{ + int test1; + int test2; + int test3; +public: + Foo(int test2, int test3, int test1) : test1(test1), + test2(test2), + test3(test3) + {} +}; +)--"; + // No worry, that is not the default behavior. + // Move first member to the back when testing with 3 or more members + QTest::newRow("changed parameter order") + << header << expected << QByteArray() << QByteArray() << Inside; + + header = R"--( +class@ Foo{ + int test; + int di_test; +public: +}; +)--"; + expected = R"--( +class Foo{ + int test; + int di_test; +public: + Foo(int test, int di_test = 42) : test(test), + di_test(di_test) + {} +}; +)--"; + QTest::newRow("default parameters") + << header << expected << QByteArray() << QByteArray() << Inside; + + header = R"--( +struct Bar{ + Bar(int i); +}; +class@ Foo : public Bar{ + int test; +public: +}; +)--"; + expected = R"--( +struct Bar{ + Bar(int i); +}; +class Foo : public Bar{ + int test; +public: + Foo(int test, int i) : Bar(i), + test(test) + {} +}; +)--"; + QTest::newRow("parent constructor") + << header << expected << QByteArray() << QByteArray() << Inside; + + header = R"--( +struct Bar{ + Bar(int use_i = 6); +}; +class@ Foo : public Bar{ + int test; +public: +}; +)--"; + expected = R"--( +struct Bar{ + Bar(int use_i = 6); +}; +class Foo : public Bar{ + int test; +public: + Foo(int test, int use_i = 6) : Bar(use_i), + test(test) + {} +}; +)--"; + QTest::newRow("parent constructor with default") + << header << expected << QByteArray() << QByteArray() << Inside; + + header = R"--( +struct Bar{ + Bar(int use_i = L'A', int use_i2 = u8"B"); +}; +class@ Foo : public Bar{ +public: +}; +)--"; + expected = R"--( +struct Bar{ + Bar(int use_i = L'A', int use_i2 = u8"B"); +}; +class Foo : public Bar{ +public: + Foo(int use_i = L'A', int use_i2 = u8"B") : Bar(use_i, use_i2) + {} +}; +)--"; + QTest::newRow("parent constructor with char/string default value") + << header << expected << QByteArray() << QByteArray() << Inside; + + const QByteArray common = R"--( +namespace N{ + template + struct vector{ + }; +} +)--"; + header = common + R"--( +namespace M{ +enum G{g}; +class@ Foo{ + N::vector g; + enum E{e}e; +public: +}; +} +)--"; + + expected = common + R"--( +namespace M{ +enum G{g}; +class@ Foo{ + N::vector g; + enum E{e}e; +public: + Foo(const N::vector &g, E e); +}; + +Foo::Foo(const N::vector &g, Foo::E e) : g(g), + e(e) +{} + +} +)--"; + QTest::newRow("source: right type outside class ") + << QByteArray() << QByteArray() << header << expected << Outside; + expected = common + R"--( +namespace M{ +enum G{g}; +class@ Foo{ + N::vector g; + enum E{e}e; +public: + Foo(const N::vector &g, E e); +}; +} + + +inline M::Foo::Foo(const N::vector &g, M::Foo::E e) : g(g), + e(e) +{} + +)--"; + QTest::newRow("header: right type outside class ") + << header << expected << QByteArray() << QByteArray() << Outside; + + expected = common + R"--( +namespace M{ +enum G{g}; +class@ Foo{ + N::vector g; + enum E{e}e; +public: + Foo(const N::vector &g, E e); +}; +} +)--"; + const QByteArray source = R"--( +#include "file.h" +)--"; + QByteArray expected_source = R"--( +#include "file.h" + + +namespace M { +Foo::Foo(const N::vector &g, Foo::E e) : g(g), + e(e) +{} + +} +)--"; + QTest::newRow("source: right type inside namespace") + << header << expected << source << expected_source << CppGenNamespace; + + expected_source = R"--( +#include "file.h" + +using namespace M; +Foo::Foo(const N::vector &g, Foo::E e) : g(g), + e(e) +{} +)--"; + QTest::newRow("source: right type with using directive") + << header << expected << source << expected_source << CppGenUsingDirective; + + expected_source = R"--( +#include "file.h" + +M::Foo::Foo(const N::vector &g, M::Foo::E e) : g(g), + e(e) +{} +)--"; + QTest::newRow("source: right type while rewritung types") + << header << expected << source << expected_source << CppRewriteType; + + } + + void test() + { + class TestFactory : public GenerateConstructor + { + public: + TestFactory() { setTest(); } + }; + + QFETCH(QByteArray, original_header); + QFETCH(QByteArray, expected_header); + QFETCH(QByteArray, original_source); + QFETCH(QByteArray, expected_source); + QFETCH(int, location); + + QuickFixSettings s; + s->valueTypes << "CustomType"; + using L = ConstructorLocation; + if (location == L::Inside) { + s->setterInCppFileFrom = -1; + s->setterOutsideClassFrom = -1; + } else if (location == L::Outside) { + s->setterInCppFileFrom = -1; + s->setterOutsideClassFrom = 1; + } else if (location >= L::CppGenNamespace && location <= L::CppRewriteType) { + s->setterInCppFileFrom = 1; + s->setterOutsideClassFrom = -1; + using Handling = CppQuickFixSettings::MissingNamespaceHandling; + if (location == L::CppGenNamespace) + s->cppFileNamespaceHandling = Handling::CreateMissing; + else if (location == L::CppGenUsingDirective) + s->cppFileNamespaceHandling = Handling::AddUsingDirective; + else if (location == L::CppRewriteType) + s->cppFileNamespaceHandling = Handling::RewriteType; + } else { + QFAIL("location is none of the values of the ConstructorLocation enum"); + } + + QList testDocuments; + testDocuments << CppTestDocument::create("file.h", original_header, expected_header); + testDocuments << CppTestDocument::create("file.cpp", original_source, expected_source); + TestFactory factory; + QuickFixOperationTest(testDocuments, &factory); + } + +private: + enum ConstructorLocation { Inside, Outside, CppGenNamespace, CppGenUsingDirective, CppRewriteType }; +}; + +QObject *GenerateGetterSetter::createTest() +{ + return new GenerateGetterSetterTest; +} + +QObject *GenerateGettersSettersForClass::createTest() +{ + return new GenerateGettersSettersTest; +} + +QObject *InsertQtPropertyMembers::createTest() +{ + return new InsertQtPropertyMembersTest; +} + +QObject *GenerateConstructor::createTest() +{ + return new GenerateConstructorTest; +} + +#endif // WITH_TESTS + +} // namespace + +void registerCodeGenerationQuickfixes() +{ + CppQuickFixFactory::registerFactory(); + CppQuickFixFactory::registerFactory(); + CppQuickFixFactory::registerFactory(); + CppQuickFixFactory::registerFactory(); +} + +} // namespace CppEditor::Internal + +#include diff --git a/src/plugins/cppeditor/quickfixes/cppcodegenerationquickfixes.h b/src/plugins/cppeditor/quickfixes/cppcodegenerationquickfixes.h new file mode 100644 index 00000000000..3cef95c7c1c --- /dev/null +++ b/src/plugins/cppeditor/quickfixes/cppcodegenerationquickfixes.h @@ -0,0 +1,8 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +namespace CppEditor::Internal { +void registerCodeGenerationQuickfixes(); +} // namespace CppEditor::Internal diff --git a/src/plugins/cppeditor/quickfixes/cppquickfix_test.cpp b/src/plugins/cppeditor/quickfixes/cppquickfix_test.cpp index 1945be96c84..4f2f3034993 100644 --- a/src/plugins/cppeditor/quickfixes/cppquickfix_test.cpp +++ b/src/plugins/cppeditor/quickfixes/cppquickfix_test.cpp @@ -10,7 +10,6 @@ #include "../cpptoolssettings.h" #include "cppquickfixassistant.h" #include "cppquickfixes.h" -#include "cppquickfixsettings.h" #include #include @@ -44,17 +43,6 @@ namespace CppEditor { namespace Internal { namespace Tests { -/// Tests the offered operations provided by a given CppQuickFixFactory -class QuickFixOfferedOperationsTest : public BaseQuickFixTestCase -{ -public: - QuickFixOfferedOperationsTest(const QList &testDocuments, - CppQuickFixFactory *factory, - const ProjectExplorer::HeaderPaths &headerPaths - = ProjectExplorer::HeaderPaths(), - const QStringList &expectedOperations = QStringList()); -}; - QList singleDocument(const QByteArray &original, const QByteArray &expected) { @@ -161,6 +149,26 @@ BaseQuickFixTestCase::~BaseQuickFixTestCase() QVERIFY(testDocument->filePath().removeFile()); } +QuickFixOfferedOperationsTest::QuickFixOfferedOperationsTest( + const QList &testDocuments, + CppQuickFixFactory *factory, + const ProjectExplorer::HeaderPaths &headerPaths, + const QStringList &expectedOperations) + : BaseQuickFixTestCase(testDocuments, headerPaths) +{ + // Get operations + CppQuickFixInterface quickFixInterface(m_documentWithMarker->m_editorWidget, ExplicitlyInvoked); + QuickFixOperations actualOperations; + factory->match(quickFixInterface, actualOperations); + + // Convert to QStringList + QStringList actualOperationsAsStringList; + for (const QuickFixOperation::Ptr &operation : std::as_const(actualOperations)) + actualOperationsAsStringList << operation->description(); + + QCOMPARE(actualOperationsAsStringList, expectedOperations); +} + /// Leading whitespace is not removed, so we can check if the indetation ranges /// have been set correctly by the quick-fix. static QString &removeTrailingWhitespace(QString &input) @@ -245,26 +253,6 @@ void QuickFixOperationTest::run(const QList &testDocuments, QuickFixOperationTest(testDocuments, factory, headerPaths, operationIndex); } -QuickFixOfferedOperationsTest::QuickFixOfferedOperationsTest( - const QList &testDocuments, - CppQuickFixFactory *factory, - const ProjectExplorer::HeaderPaths &headerPaths, - const QStringList &expectedOperations) - : BaseQuickFixTestCase(testDocuments, headerPaths) -{ - // Get operations - CppQuickFixInterface quickFixInterface(m_documentWithMarker->m_editorWidget, ExplicitlyInvoked); - QuickFixOperations actualOperations; - factory->match(quickFixInterface, actualOperations); - - // Convert to QStringList - QStringList actualOperationsAsStringList; - for (const QuickFixOperation::Ptr &operation : std::as_const(actualOperations)) - actualOperationsAsStringList << operation->description(); - - QCOMPARE(actualOperationsAsStringList, expectedOperations); -} - /// Delegates directly to AddIncludeForUndefinedIdentifierOp for easier testing. class AddIncludeForUndefinedIdentifierTestFactory : public CppQuickFixFactory { @@ -307,15 +295,6 @@ typedef QSharedPointer CppQuickFixFactoryPtr; namespace CppEditor::Internal::Tests { -class QuickFixSettings -{ - const CppQuickFixSettings original = *CppQuickFixSettings::instance(); - -public: - CppQuickFixSettings *operator->() { return CppQuickFixSettings::instance(); } - ~QuickFixSettings() { *CppQuickFixSettings::instance() = original; } -}; - void QuickfixTest::testGeneric_data() { QTest::addColumn("factory"); @@ -900,18 +879,6 @@ void QuickfixTest::testGeneric_data() "};\n" ); - // Checks: No special treatment for reference to non const. - - // Check: Quick fix is not triggered on a member function. - QTest::newRow("GenerateGetterSetter_notTriggeringOnMemberFunction") - << CppQuickFixFactoryPtr(new GenerateGetterSetter) - << _("class Something { void @f(); };\n") << _(); - - // Check: Quick fix is not triggered on an member array; - QTest::newRow("GenerateGetterSetter_notTriggeringOnMemberArray") - << CppQuickFixFactoryPtr(new GenerateGetterSetter) - << _("class Something { void @a[10]; };\n") << _(); - QTest::newRow("MoveDeclarationOutOfIf_ifOnly") << CppQuickFixFactoryPtr(new MoveDeclarationOutOfIf) << _( "void f()\n" @@ -1678,11 +1645,6 @@ void QuickfixTest::testGeneric_data() << _(testObjAndFunc.arg("int").arg("Object @obj(0);").toUtf8()) << _(testObjAndFunc.arg("int").arg("Object *obj = new Object(0);").toUtf8()); - QTest::newRow("InsertQtPropertyMembers_noTriggerInvalidCode") - << CppQuickFixFactoryPtr(new InsertQtPropertyMembers) - << _("class C { @Q_PROPERTY(typeid foo READ foo) };\n") - << _(); - QTest::newRow("convert to camel case: normal") << CppQuickFixFactoryPtr(new ConvertToCamelCase(true)) << _("void @lower_case_function();\n") @@ -1756,1703 +1718,6 @@ void QuickfixTest::testGeneric() QuickFixOperationTest(singleDocument(original, expected), factory.data()); } -void QuickfixTest::testGenerateGetterSetterNamespaceHandlingCreate_data() -{ - QTest::addColumn("headers"); - QTest::addColumn("sources"); - - QByteArray originalSource; - QByteArray expectedSource; - - const QByteArray originalHeader = - "namespace N1 {\n" - "namespace N2 {\n" - "class Something\n" - "{\n" - " int @it;\n" - "};\n" - "}\n" - "}\n"; - const QByteArray expectedHeader = - "namespace N1 {\n" - "namespace N2 {\n" - "class Something\n" - "{\n" - " int it;\n" - "\n" - "public:\n" - " int getIt() const;\n" - " void setIt(int value);\n" - "};\n" - "}\n" - "}\n"; - - originalSource = "#include \"file.h\"\n"; - expectedSource = - "#include \"file.h\"\n\n\n" - "namespace N1 {\n" - "namespace N2 {\n" - "int Something::getIt() const\n" - "{\n" - " return it;\n" - "}\n" - "\n" - "void Something::setIt(int value)\n" - "{\n" - " it = value;\n" - "}\n\n" - "}\n" - "}\n"; - QTest::addRow("insert new namespaces") - << QByteArrayList{originalHeader, expectedHeader} - << QByteArrayList{originalSource, expectedSource}; - - originalSource = - "#include \"file.h\"\n" - "namespace N2 {} // decoy\n"; - expectedSource = - "#include \"file.h\"\n" - "namespace N2 {} // decoy\n\n\n" - "namespace N1 {\n" - "namespace N2 {\n" - "int Something::getIt() const\n" - "{\n" - " return it;\n" - "}\n" - "\n" - "void Something::setIt(int value)\n" - "{\n" - " it = value;\n" - "}\n\n" - "}\n" - "}\n"; - QTest::addRow("insert new namespaces (with decoy)") - << QByteArrayList{originalHeader, expectedHeader} - << QByteArrayList{originalSource, expectedSource}; - - originalSource = "#include \"file.h\"\n" - "namespace N2 {} // decoy\n" - "namespace {\n" - "namespace N1 {\n" - "namespace {\n" - "}\n" - "}\n" - "}\n"; - expectedSource = "#include \"file.h\"\n" - "namespace N2 {} // decoy\n" - "namespace {\n" - "namespace N1 {\n" - "namespace {\n" - "}\n" - "}\n" - "}\n" - "\n" - "\n" - "namespace N1 {\n" - "namespace N2 {\n" - "int Something::getIt() const\n" - "{\n" - " return it;\n" - "}\n" - "\n" - "void Something::setIt(int value)\n" - "{\n" - " it = value;\n" - "}\n" - "\n" - "}\n" - "}\n"; - QTest::addRow("insert inner namespace (with decoy and unnamed)") - << QByteArrayList{originalHeader, expectedHeader} - << QByteArrayList{originalSource, expectedSource}; - - const QByteArray unnamedOriginalHeader = "namespace {\n" + originalHeader + "}\n"; - const QByteArray unnamedExpectedHeader = "namespace {\n" + expectedHeader + "}\n"; - - originalSource = "#include \"file.h\"\n" - "namespace N2 {} // decoy\n" - "namespace {\n" - "namespace N1 {\n" - "namespace {\n" - "}\n" - "}\n" - "}\n"; - expectedSource = "#include \"file.h\"\n" - "namespace N2 {} // decoy\n" - "namespace {\n" - "namespace N1 {\n" - "\n" - "namespace N2 {\n" - "int Something::getIt() const\n" - "{\n" - " return it;\n" - "}\n" - "\n" - "void Something::setIt(int value)\n" - "{\n" - " it = value;\n" - "}\n" - "\n" - "}\n" - "\n" - "namespace {\n" - "}\n" - "}\n" - "}\n"; - QTest::addRow("insert inner namespace in unnamed (with decoy)") - << QByteArrayList{unnamedOriginalHeader, unnamedExpectedHeader} - << QByteArrayList{originalSource, expectedSource}; - - originalSource = - "#include \"file.h\"\n" - "namespace N1 {\n" - "namespace N2 {\n" - "namespace N3 {\n" - "}\n" - "}\n" - "}\n"; - expectedSource = - "#include \"file.h\"\n" - "namespace N1 {\n" - "namespace N2 {\n" - "namespace N3 {\n" - "}\n\n" - "int Something::getIt() const\n" - "{\n" - " return it;\n" - "}\n" - "\n" - "void Something::setIt(int value)\n" - "{\n" - " it = value;\n" - "}\n\n" - "}\n" - "}\n"; - QTest::addRow("all namespaces already present") - << QByteArrayList{originalHeader, expectedHeader} - << QByteArrayList{originalSource, expectedSource}; - - originalSource = "#include \"file.h\"\n" - "namespace N1 {\n" - "using namespace N2::N3;\n" - "using namespace N2;\n" - "using namespace N2;\n" - "using namespace N3;\n" - "}\n"; - expectedSource = "#include \"file.h\"\n" - "namespace N1 {\n" - "using namespace N2::N3;\n" - "using namespace N2;\n" - "using namespace N2;\n" - "using namespace N3;\n" - "\n" - "int Something::getIt() const\n" - "{\n" - " return it;\n" - "}\n" - "\n" - "void Something::setIt(int value)\n" - "{\n" - " it = value;\n" - "}\n\n" - "}\n"; - QTest::addRow("namespaces present and using namespace") - << QByteArrayList{originalHeader, expectedHeader} - << QByteArrayList{originalSource, expectedSource}; - - originalSource = "#include \"file.h\"\n" - "using namespace N1::N2::N3;\n" - "using namespace N1::N2;\n" - "namespace N1 {\n" - "using namespace N3;\n" - "}\n"; - expectedSource = "#include \"file.h\"\n" - "using namespace N1::N2::N3;\n" - "using namespace N1::N2;\n" - "namespace N1 {\n" - "using namespace N3;\n" - "\n" - "int Something::getIt() const\n" - "{\n" - " return it;\n" - "}\n" - "\n" - "void Something::setIt(int value)\n" - "{\n" - " it = value;\n" - "}\n" - "\n" - "}\n"; - QTest::addRow("namespaces present and outer using namespace") - << QByteArrayList{originalHeader, expectedHeader} - << QByteArrayList{originalSource, expectedSource}; - - originalSource = "#include \"file.h\"\n" - "using namespace N1;\n" - "using namespace N2;\n" - "namespace N3 {\n" - "}\n"; - expectedSource = "#include \"file.h\"\n" - "using namespace N1;\n" - "using namespace N2;\n" - "namespace N3 {\n" - "}\n" - "\n" - "int Something::getIt() const\n" - "{\n" - " return it;\n" - "}\n" - "\n" - "void Something::setIt(int value)\n" - "{\n" - " it = value;\n" - "}\n"; - QTest::addRow("namespaces present and outer using namespace") - << QByteArrayList{originalHeader, expectedHeader} - << QByteArrayList{originalSource, expectedSource}; -} - -void QuickfixTest::testGenerateGetterSetterNamespaceHandlingCreate() -{ - QFETCH(QByteArrayList, headers); - QFETCH(QByteArrayList, sources); - - QList testDocuments( - {CppTestDocument::create("file.h", headers.at(0), headers.at(1)), - CppTestDocument::create("file.cpp", sources.at(0), sources.at(1))}); - - QuickFixSettings s; - s->cppFileNamespaceHandling = CppQuickFixSettings::MissingNamespaceHandling::CreateMissing; - s->setterParameterNameTemplate = "value"; - s->getterNameTemplate = "get"; - s->setterInCppFileFrom = 1; - s->getterInCppFileFrom = 1; - GenerateGetterSetter factory; - QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), 2); -} - -void QuickfixTest::testGenerateGetterSetterNamespaceHandlingAddUsing_data() -{ - QTest::addColumn("headers"); - QTest::addColumn("sources"); - - QByteArray originalSource; - QByteArray expectedSource; - - const QByteArray originalHeader = "namespace N1 {\n" - "namespace N2 {\n" - "class Something\n" - "{\n" - " int @it;\n" - "};\n" - "}\n" - "}\n"; - const QByteArray expectedHeader = "namespace N1 {\n" - "namespace N2 {\n" - "class Something\n" - "{\n" - " int it;\n" - "\n" - "public:\n" - " void setIt(int value);\n" - "};\n" - "}\n" - "}\n"; - - originalSource = "#include \"file.h\"\n"; - expectedSource = "#include \"file.h\"\n\n" - "using namespace N1::N2;\n" - "void Something::setIt(int value)\n" - "{\n" - " it = value;\n" - "}\n"; - QTest::addRow("add using namespaces") << QByteArrayList{originalHeader, expectedHeader} - << QByteArrayList{originalSource, expectedSource}; - - const QByteArray unnamedOriginalHeader = "namespace {\n" + originalHeader + "}\n"; - const QByteArray unnamedExpectedHeader = "namespace {\n" + expectedHeader + "}\n"; - - originalSource = "#include \"file.h\"\n" - "namespace N2 {} // decoy\n" - "namespace {\n" - "namespace N1 {\n" - "namespace {\n" - "}\n" - "}\n" - "}\n"; - expectedSource = "#include \"file.h\"\n" - "namespace N2 {} // decoy\n" - "namespace {\n" - "namespace N1 {\n" - "using namespace N2;\n" - "void Something::setIt(int value)\n" - "{\n" - " it = value;\n" - "}\n" - "namespace {\n" - "}\n" - "}\n" - "}\n"; - QTest::addRow("insert using namespace into unnamed nested (with decoy)") - << QByteArrayList{unnamedOriginalHeader, unnamedExpectedHeader} - << QByteArrayList{originalSource, expectedSource}; - - originalSource = "#include \"file.h\"\n"; - expectedSource = "#include \"file.h\"\n\n" - "using namespace N1::N2;\n" - "void Something::setIt(int value)\n" - "{\n" - " it = value;\n" - "}\n"; - QTest::addRow("insert using namespace into unnamed") - << QByteArrayList{unnamedOriginalHeader, unnamedExpectedHeader} - << QByteArrayList{originalSource, expectedSource}; - - originalSource = "#include \"file.h\"\n" - "namespace N2 {} // decoy\n" - "namespace {\n" - "namespace N1 {\n" - "namespace {\n" - "}\n" - "}\n" - "}\n"; - expectedSource = "#include \"file.h\"\n" - "namespace N2 {} // decoy\n" - "namespace {\n" - "namespace N1 {\n" - "namespace {\n" - "}\n" - "}\n" - "}\n" - "\n" - "using namespace N1::N2;\n" - "void Something::setIt(int value)\n" - "{\n" - " it = value;\n" - "}\n"; - QTest::addRow("insert using namespace (with decoy)") - << QByteArrayList{originalHeader, expectedHeader} - << QByteArrayList{originalSource, expectedSource}; -} - -void QuickfixTest::testGenerateGetterSetterNamespaceHandlingAddUsing() -{ - QFETCH(QByteArrayList, headers); - QFETCH(QByteArrayList, sources); - - QList testDocuments( - {CppTestDocument::create("file.h", headers.at(0), headers.at(1)), - CppTestDocument::create("file.cpp", sources.at(0), sources.at(1))}); - - QuickFixSettings s; - s->cppFileNamespaceHandling = CppQuickFixSettings::MissingNamespaceHandling::AddUsingDirective; - s->setterParameterNameTemplate = "value"; - s->setterInCppFileFrom = 1; - - if (std::strstr(QTest::currentDataTag(), "unnamed nested") != nullptr) - QSKIP("TODO"); // FIXME - GenerateGetterSetter factory; - QuickFixOperationTest(testDocuments, &factory); -} - -void QuickfixTest::testGenerateGetterSetterNamespaceHandlingFullyQualify_data() -{ - QTest::addColumn("headers"); - QTest::addColumn("sources"); - - QByteArray originalSource; - QByteArray expectedSource; - - const QByteArray originalHeader = "namespace N1 {\n" - "namespace N2 {\n" - "class Something\n" - "{\n" - " int @it;\n" - "};\n" - "}\n" - "}\n"; - const QByteArray expectedHeader = "namespace N1 {\n" - "namespace N2 {\n" - "class Something\n" - "{\n" - " int it;\n" - "\n" - "public:\n" - " void setIt(int value);\n" - "};\n" - "}\n" - "}\n"; - - originalSource = "#include \"file.h\"\n"; - expectedSource = "#include \"file.h\"\n\n" - "void N1::N2::Something::setIt(int value)\n" - "{\n" - " it = value;\n" - "}\n"; - QTest::addRow("fully qualify") << QByteArrayList{originalHeader, expectedHeader} - << QByteArrayList{originalSource, expectedSource}; - - originalSource = "#include \"file.h\"\n" - "namespace N2 {} // decoy\n"; - expectedSource = "#include \"file.h\"\n" - "namespace N2 {} // decoy\n" - "\n" - "void N1::N2::Something::setIt(int value)\n" - "{\n" - " it = value;\n" - "}\n"; - QTest::addRow("fully qualify (with decoy)") << QByteArrayList{originalHeader, expectedHeader} - << QByteArrayList{originalSource, expectedSource}; - - originalSource = "#include \"file.h\"\n" - "namespace N2 {} // decoy\n" - "namespace {\n" - "namespace N1 {\n" - "namespace {\n" - "}\n" - "}\n" - "}\n"; - expectedSource = "#include \"file.h\"\n" - "namespace N2 {} // decoy\n" - "namespace {\n" - "namespace N1 {\n" - "namespace {\n" - "}\n" - "}\n" - "}\n" - "\n" - "void N1::N2::Something::setIt(int value)\n" - "{\n" - " it = value;\n" - "}\n"; - QTest::addRow("qualify in inner namespace (with decoy)") - << QByteArrayList{originalHeader, expectedHeader} - << QByteArrayList{originalSource, expectedSource}; - - const QByteArray unnamedOriginalHeader = "namespace {\n" + originalHeader + "}\n"; - const QByteArray unnamedExpectedHeader = "namespace {\n" + expectedHeader + "}\n"; - - originalSource = "#include \"file.h\"\n" - "namespace N2 {} // decoy\n" - "namespace {\n" - "namespace N1 {\n" - "namespace {\n" - "}\n" - "}\n" - "}\n"; - expectedSource = "#include \"file.h\"\n" - "namespace N2 {} // decoy\n" - "namespace {\n" - "namespace N1 {\n" - "void N2::Something::setIt(int value)\n" - "{\n" - " it = value;\n" - "}\n" - "namespace {\n" - "}\n" - "}\n" - "}\n"; - QTest::addRow("qualify in inner namespace unnamed nested (with decoy)") - << QByteArrayList{unnamedOriginalHeader, unnamedExpectedHeader} - << QByteArrayList{originalSource, expectedSource}; - - originalSource = "#include \"file.h\"\n"; - expectedSource = "#include \"file.h\"\n\n" - "void N1::N2::Something::setIt(int value)\n" - "{\n" - " it = value;\n" - "}\n"; - QTest::addRow("qualify in unnamed namespace") - << QByteArrayList{unnamedOriginalHeader, unnamedExpectedHeader} - << QByteArrayList{originalSource, expectedSource}; -} - -void QuickfixTest::testGenerateGetterSetterNamespaceHandlingFullyQualify() -{ - QFETCH(QByteArrayList, headers); - QFETCH(QByteArrayList, sources); - - QList testDocuments( - {CppTestDocument::create("file.h", headers.at(0), headers.at(1)), - CppTestDocument::create("file.cpp", sources.at(0), sources.at(1))}); - - QuickFixSettings s; - s->cppFileNamespaceHandling = CppQuickFixSettings::MissingNamespaceHandling::RewriteType; - s->setterParameterNameTemplate = "value"; - s->setterInCppFileFrom = 1; - - if (std::strstr(QTest::currentDataTag(), "unnamed nested") != nullptr) - QSKIP("TODO"); // FIXME - GenerateGetterSetter factory; - QuickFixOperationTest(testDocuments, &factory); -} - -void QuickfixTest::testGenerateGetterSetterCustomNames_data() -{ - QTest::addColumn("headers"); - QTest::addColumn("operation"); - - QByteArray originalSource; - QByteArray expectedSource; - - // Check if right names are created - originalSource = R"-( -class Test { - int m_fooBar_test@; -}; -)-"; - expectedSource = R"-( -class Test { - int m_fooBar_test; - -public: - int give_me_foo_bar_test() const - { - return m_fooBar_test; - } - void Seet_FooBar_test(int New_Foo_Bar_Test) - { - if (m_fooBar_test == New_Foo_Bar_Test) - return; - m_fooBar_test = New_Foo_Bar_Test; - emit newFooBarTestValue(); - } - void set_fooBarTest_toDefault() - { - Seet_FooBar_test({}); // TODO: Adapt to use your actual default value - } - -signals: - void newFooBarTestValue(); - -private: - Q_PROPERTY(int fooBar_test READ give_me_foo_bar_test WRITE Seet_FooBar_test RESET set_fooBarTest_toDefault NOTIFY newFooBarTestValue FINAL) -}; -)-"; - QTest::addRow("create right names") << QByteArrayList{originalSource, expectedSource} << 4; - - // Check if not triggered with custom names - originalSource = R"-( -class Test { - int m_fooBar_test@; - -public: - int give_me_foo_bar_test() const - { - return m_fooBar_test; - } - void Seet_FooBar_test(int New_Foo_Bar_Test) - { - if (m_fooBar_test == New_Foo_Bar_Test) - return; - m_fooBar_test = New_Foo_Bar_Test; - emit newFooBarTestValue(); - } - void set_fooBarTest_toDefault() - { - Seet_FooBar_test({}); // TODO: Adapt to use your actual default value - } - -signals: - void newFooBarTestValue(); - -private: - Q_PROPERTY(int fooBar_test READ give_me_foo_bar_test WRITE Seet_FooBar_test RESET set_fooBarTest_toDefault NOTIFY newFooBarTestValue FINAL) -}; -)-"; - expectedSource = ""; - QTest::addRow("everything already exists") << QByteArrayList{originalSource, expectedSource} << 4; - - // create from Q_PROPERTY with custom names - originalSource = R"-( -class Test { - Q_PROPER@TY(int fooBar_test READ give_me_foo_bar_test WRITE Seet_FooBar_test RESET set_fooBarTest_toDefault NOTIFY newFooBarTestValue FINAL) - -public: - int give_me_foo_bar_test() const - { - return mem_fooBar_test; - } - void Seet_FooBar_test(int New_Foo_Bar_Test) - { - if (mem_fooBar_test == New_Foo_Bar_Test) - return; - mem_fooBar_test = New_Foo_Bar_Test; - emit newFooBarTestValue(); - } - void set_fooBarTest_toDefault() - { - Seet_FooBar_test({}); // TODO: Adapt to use your actual default value - } - -signals: - void newFooBarTestValue(); -}; -)-"; - expectedSource = R"-( -class Test { - Q_PROPERTY(int fooBar_test READ give_me_foo_bar_test WRITE Seet_FooBar_test RESET set_fooBarTest_toDefault NOTIFY newFooBarTestValue FINAL) - -public: - int give_me_foo_bar_test() const - { - return mem_fooBar_test; - } - void Seet_FooBar_test(int New_Foo_Bar_Test) - { - if (mem_fooBar_test == New_Foo_Bar_Test) - return; - mem_fooBar_test = New_Foo_Bar_Test; - emit newFooBarTestValue(); - } - void set_fooBarTest_toDefault() - { - Seet_FooBar_test({}); // TODO: Adapt to use your actual default value - } - -signals: - void newFooBarTestValue(); -private: - int mem_fooBar_test; -}; -)-"; - QTest::addRow("create only member variable") - << QByteArrayList{originalSource, expectedSource} << 0; - - // create from Q_PROPERTY with custom names - originalSource = R"-( -class Test { - Q_PROPE@RTY(int fooBar_test READ give_me_foo_bar_test WRITE Seet_FooBar_test RESET set_fooBarTest_toDefault NOTIFY newFooBarTestValue FINAL) - int mem_fooBar_test; -public: -}; -)-"; - expectedSource = R"-( -class Test { - Q_PROPERTY(int fooBar_test READ give_me_foo_bar_test WRITE Seet_FooBar_test RESET set_fooBarTest_toDefault NOTIFY newFooBarTestValue FINAL) - int mem_fooBar_test; -public: - int give_me_foo_bar_test() const - { - return mem_fooBar_test; - } - void Seet_FooBar_test(int New_Foo_Bar_Test) - { - if (mem_fooBar_test == New_Foo_Bar_Test) - return; - mem_fooBar_test = New_Foo_Bar_Test; - emit newFooBarTestValue(); - } - void set_fooBarTest_toDefault() - { - Seet_FooBar_test({}); // TODO: Adapt to use your actual default value - } -signals: - void newFooBarTestValue(); -}; -)-"; - QTest::addRow("create methods with given member variable") - << QByteArrayList{originalSource, expectedSource} << 0; -} - -void QuickfixTest::testGenerateGetterSetterCustomNames() -{ - QFETCH(QByteArrayList, headers); - QFETCH(int, operation); - - QList testDocuments( - {CppTestDocument::create("file.h", headers.at(0), headers.at(1))}); - - QuickFixSettings s; - s->setterInCppFileFrom = 0; - s->getterInCppFileFrom = 0; - s->setterNameTemplate = "Seet_"; - s->getterNameTemplate = "give_me_"; - s->signalNameTemplate = "newValue"; - s->setterParameterNameTemplate = "New_"; - s->resetNameTemplate = "set__toDefault"; - s->memberVariableNameTemplate = "mem_"; - if (operation == 0) { - InsertQtPropertyMembers factory; - QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), operation); - } else { - GenerateGetterSetter factory; - QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), operation); - } -} - -void QuickfixTest::testGenerateGetterSetterValueTypes_data() -{ - QTest::addColumn("headers"); - QTest::addColumn("operation"); - - QByteArray originalSource; - QByteArray expectedSource; - - // int should be a value type - originalSource = R"-( -class Test { - int i@; -}; -)-"; - expectedSource = R"-( -class Test { - int i; - -public: - int getI() const - { - return i; - } -}; -)-"; - QTest::addRow("int") << QByteArrayList{originalSource, expectedSource} << 1; - - // return type should be only int without const - originalSource = R"-( -class Test { - const int i@; -}; -)-"; - expectedSource = R"-( -class Test { - const int i; - -public: - int getI() const - { - return i; - } -}; -)-"; - QTest::addRow("const int") << QByteArrayList{originalSource, expectedSource} << 0; - - // float should be a value type - originalSource = R"-( -class Test { - float f@; -}; -)-"; - expectedSource = R"-( -class Test { - float f; - -public: - float getF() const - { - return f; - } -}; -)-"; - QTest::addRow("float") << QByteArrayList{originalSource, expectedSource} << 1; - - // pointer should be a value type - originalSource = R"-( -class Test { - void* v@; -}; -)-"; - expectedSource = R"-( -class Test { - void* v; - -public: - void *getV() const - { - return v; - } -}; -)-"; - QTest::addRow("pointer") << QByteArrayList{originalSource, expectedSource} << 1; - - // reference should be a value type (setter is const ref) - originalSource = R"-( -class Test { - int& r@; -}; -)-"; - expectedSource = R"-( -class Test { - int& r; - -public: - int &getR() const - { - return r; - } - void setR(const int &newR) - { - r = newR; - } -}; -)-"; - QTest::addRow("reference to value type") << QByteArrayList{originalSource, expectedSource} << 2; - - // reference should be a value type - originalSource = R"-( -using bar = int; -class Test { - bar i@; -}; -)-"; - expectedSource = R"-( -using bar = int; -class Test { - bar i; - -public: - bar getI() const - { - return i; - } -}; -)-"; - QTest::addRow("value type through using") << QByteArrayList{originalSource, expectedSource} << 1; - - // enum should be a value type - originalSource = R"-( -enum Foo{V1, V2}; -class Test { - Foo e@; -}; -)-"; - expectedSource = R"-( -enum Foo{V1, V2}; -class Test { - Foo e; - -public: - Foo getE() const - { - return e; - } -}; -)-"; - QTest::addRow("enum") << QByteArrayList{originalSource, expectedSource} << 1; - - // class should not be a value type - originalSource = R"-( -class NoVal{}; -class Test { - NoVal n@; -}; -)-"; - expectedSource = R"-( -class NoVal{}; -class Test { - NoVal n; - -public: - const NoVal &getN() const - { - return n; - } -}; -)-"; - QTest::addRow("class") << QByteArrayList{originalSource, expectedSource} << 1; - - // custom classes can be a value type - originalSource = R"-( -class Value{}; -class Test { - Value v@; -}; -)-"; - expectedSource = R"-( -class Value{}; -class Test { - Value v; - -public: - Value getV() const - { - return v; - } -}; -)-"; - QTest::addRow("value class") << QByteArrayList{originalSource, expectedSource} << 1; - - // custom classes (in namespace) can be a value type - originalSource = R"-( -namespace N1{ -class Value{}; -} -class Test { - N1::Value v@; -}; -)-"; - expectedSource = R"-( -namespace N1{ -class Value{}; -} -class Test { - N1::Value v; - -public: - N1::Value getV() const - { - return v; - } -}; -)-"; - QTest::addRow("value class in namespace") << QByteArrayList{originalSource, expectedSource} << 1; - - // custom template class can be a value type - originalSource = R"-( -template -class Value{}; -class Test { - Value v@; -}; -)-"; - expectedSource = R"-( -template -class Value{}; -class Test { - Value v; - -public: - Value getV() const - { - return v; - } -}; -)-"; - QTest::addRow("value template class") << QByteArrayList{originalSource, expectedSource} << 1; -} - -void QuickfixTest::testGenerateGetterSetterValueTypes() -{ - QFETCH(QByteArrayList, headers); - QFETCH(int, operation); - - QList testDocuments( - {CppTestDocument::create("file.h", headers.at(0), headers.at(1))}); - - QuickFixSettings s; - s->setterInCppFileFrom = 0; - s->getterInCppFileFrom = 0; - s->getterNameTemplate = "get"; - s->valueTypes << "Value"; - s->returnByConstRef = true; - - GenerateGetterSetter factory; - QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), operation); -} - -/// Checks: Use template for a custom type -void QuickfixTest::testGenerateGetterSetterCustomTemplate() -{ - QList testDocuments; - QByteArray original; - QByteArray expected; - - const _ customTypeDecl = R"--( -namespace N1 { -namespace N2 { -struct test{}; -} -template -struct custom { - void assign(const custom&); - bool equals(const custom&); - T* get(); -}; -)--"; - // Header File - original = customTypeDecl + R"--( -class Foo -{ -public: - custom bar@; -}; -})--"; - expected = customTypeDecl + R"--( -class Foo -{ -public: - custom bar@; - N2::test *getBar() const; - void setBar(const custom &newBar); -signals: - void barChanged(N2::test *bar); -private: - Q_PROPERTY(N2::test *bar READ getBar NOTIFY barChanged FINAL) -}; -})--"; - testDocuments << CppTestDocument::create("file.h", original, expected); - - // Source File - original = ""; - expected = R"-( -using namespace N1; -N2::test *Foo::getBar() const -{ - return bar.get(); -} - -void Foo::setBar(const custom &newBar) -{ - if (bar.equals(newBar)) - return; - bar.assign(newBar); - emit barChanged(bar.get()); -} -)-"; - - testDocuments << CppTestDocument::create("file.cpp", original, expected); - - QuickFixSettings s; - s->cppFileNamespaceHandling = CppQuickFixSettings::MissingNamespaceHandling::AddUsingDirective; - s->getterNameTemplate = "get"; - s->getterInCppFileFrom = 1; - s->signalWithNewValue = true; - CppQuickFixSettings::CustomTemplate t; - t.types.append("custom"); - t.equalComparison = ".equals()"; - t.returnExpression = ".get()"; - t.returnType = " *"; - t.assignment = ".assign()"; - s->customTemplates.push_back(t); - - GenerateGetterSetter factory; - QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), 5); -} - -/// Checks: if the setter parameter name is the same as the member variable name, this-> is needed -void QuickfixTest::testGenerateGetterSetterNeedThis() -{ - QList testDocuments; - - // Header File - const QByteArray original = R"-( -class Foo { - int bar@; -public: -}; -)-"; - const QByteArray expected = R"-( -class Foo { - int bar@; -public: - void setBar(int bar) - { - this->bar = bar; - } -}; -)-"; - testDocuments << CppTestDocument::create("file.h", original, expected); - - QuickFixSettings s; - s->setterParameterNameTemplate = ""; - s->setterInCppFileFrom = 0; - - GenerateGetterSetter factory; - QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), 0); -} - -void QuickfixTest::testGenerateGetterSetterOfferedFixes_data() -{ - QTest::addColumn("header"); - QTest::addColumn("offered"); - - QByteArray header; - QStringList offered; - const QString setter = QStringLiteral("Generate Setter"); - const QString getter = QStringLiteral("Generate Getter"); - const QString getset = QStringLiteral("Generate Getter and Setter"); - const QString constQandMissing = QStringLiteral( - "Generate Constant Q_PROPERTY and Missing Members"); - const QString qAndResetAndMissing = QStringLiteral( - "Generate Q_PROPERTY and Missing Members with Reset Function"); - const QString qAndMissing = QStringLiteral("Generate Q_PROPERTY and Missing Members"); - const QStringList all{setter, getter, getset, constQandMissing, qAndResetAndMissing, qAndMissing}; - - header = R"-( -class Foo { - static int bar@; -}; -)-"; - offered = QStringList{setter, getter, getset, constQandMissing}; - QTest::addRow("static") << header << offered; - - header = R"-( -class Foo { - static const int bar@; -}; -)-"; - offered = QStringList{getter, constQandMissing}; - QTest::addRow("const static") << header << offered; - - header = R"-( -class Foo { - const int bar@; -}; -)-"; - offered = QStringList{getter, constQandMissing}; - QTest::addRow("const") << header << offered; - - header = R"-( -class Foo { - const int bar@; - int getBar() const; -}; -)-"; - offered = QStringList{constQandMissing}; - QTest::addRow("const + getter") << header << offered; - - header = R"-( -class Foo { - const int bar@; - int getBar() const; - void setBar(int value); -}; -)-"; - offered = QStringList{}; - QTest::addRow("const + getter + setter") << header << offered; - - header = R"-( -class Foo { - const int* bar@; -}; -)-"; - offered = all; - QTest::addRow("pointer to const") << header << offered; - - header = R"-( -class Foo { - int bar@; -public: - int bar(); -}; -)-"; - offered = QStringList{setter, constQandMissing, qAndResetAndMissing, qAndMissing}; - QTest::addRow("existing getter") << header << offered; - - header = R"-( -class Foo { - int bar@; -public: - set setBar(int); -}; -)-"; - offered = QStringList{getter}; - QTest::addRow("existing setter") << header << offered; - - header = R"-( -class Foo { - int bar@; -signals: - void barChanged(int); -}; -)-"; - offered = QStringList{setter, getter, getset, qAndResetAndMissing, qAndMissing}; - QTest::addRow("existing signal (no const Q_PROPERTY)") << header << offered; - - header = R"-( -class Foo { - int m_bar@; - Q_PROPERTY(int bar) -}; -)-"; - offered = QStringList{}; // user should use "InsertQPropertyMembers", no duplicated code - QTest::addRow("existing Q_PROPERTY") << header << offered; -} - -void QuickfixTest::testGenerateGetterSetterOfferedFixes() -{ - QFETCH(QByteArray, header); - QFETCH(QStringList, offered); - - QList testDocuments( - {CppTestDocument::create("file.h", header, header)}); - - GenerateGetterSetter factory; - QuickFixOfferedOperationsTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), offered); -} - -void QuickfixTest::testGenerateGetterSetterGeneralTests_data() -{ - QTest::addColumn("operation"); - QTest::addColumn("original"); - QTest::addColumn("expected"); - - QTest::newRow("GenerateGetterSetter_referenceToNonConst") - << 2 - << _("\n" - "class Something\n" - "{\n" - " int &it@;\n" - "};\n") - << _("\n" - "class Something\n" - "{\n" - " int ⁢\n" - "\n" - "public:\n" - " int &getIt() const;\n" - " void setIt(const int &it);\n" - "};\n" - "\n" - "int &Something::getIt() const\n" - "{\n" - " return it;\n" - "}\n" - "\n" - "void Something::setIt(const int &it)\n" - "{\n" - " this->it = it;\n" - "}\n"); - - // Checks: No special treatment for reference to const. - QTest::newRow("GenerateGetterSetter_referenceToConst") - << 2 - << _("\n" - "class Something\n" - "{\n" - " const int &it@;\n" - "};\n") - << _("\n" - "class Something\n" - "{\n" - " const int ⁢\n" - "\n" - "public:\n" - " const int &getIt() const;\n" - " void setIt(const int &it);\n" - "};\n" - "\n" - "const int &Something::getIt() const\n" - "{\n" - " return it;\n" - "}\n" - "\n" - "void Something::setIt(const int &it)\n" - "{\n" - " this->it = it;\n" - "}\n"); - - // Checks: - // 1. Setter: Setter is a static function. - // 2. Getter: Getter is a static, non const function. - QTest::newRow("GenerateGetterSetter_staticMember") - << 2 - << _("\n" - "class Something\n" - "{\n" - " static int @m_member;\n" - "};\n") - << _("\n" - "class Something\n" - "{\n" - " static int m_member;\n" - "\n" - "public:\n" - " static int member();\n" - " static void setMember(int member);\n" - "};\n" - "\n" - "int Something::member()\n" - "{\n" - " return m_member;\n" - "}\n" - "\n" - "void Something::setMember(int member)\n" - "{\n" - " m_member = member;\n" - "}\n"); - - // Check: Check if it works on the second declarator - // clang-format off - QTest::newRow("GenerateGetterSetter_secondDeclarator") << 2 - << _("\n" - "class Something\n" - "{\n" - " int *foo, @it;\n" - "};\n") - << _("\n" - "class Something\n" - "{\n" - " int *foo, it;\n" - "\n" - "public:\n" - " int getIt() const;\n" - " void setIt(int it);\n" - "};\n" - "\n" - "int Something::getIt() const\n" - "{\n" - " return it;\n" - "}\n" - "\n" - "void Something::setIt(int it)\n" - "{\n" - " this->it = it;\n" - "}\n"); - // clang-format on - - // Check: Quick fix is offered for "int *@it;" ('@' denotes the text cursor position) - QTest::newRow("GenerateGetterSetter_triggeringRightAfterPointerSign") - << 2 - << _("\n" - "class Something\n" - "{\n" - " int *@it;\n" - "};\n") - << _("\n" - "class Something\n" - "{\n" - " int *it;\n" - "\n" - "public:\n" - " int *getIt() const;\n" - " void setIt(int *it);\n" - "};\n" - "\n" - "int *Something::getIt() const\n" - "{\n" - " return it;\n" - "}\n" - "\n" - "void Something::setIt(int *it)\n" - "{\n" - " this->it = it;\n" - "}\n"); - - // Checks if "m_" is recognized as "m" with the postfix "_" and not simply as "m_" prefix. - QTest::newRow("GenerateGetterSetter_recognizeMasVariableName") - << 2 - << _("\n" - "class Something\n" - "{\n" - " int @m_;\n" - "};\n") - << _("\n" - "class Something\n" - "{\n" - " int m_;\n" - "\n" - "public:\n" - " int m() const;\n" - " void setM(int m);\n" - "};\n" - "\n" - "int Something::m() const\n" - "{\n" - " return m_;\n" - "}\n" - "\n" - "void Something::setM(int m)\n" - "{\n" - " m_ = m;\n" - "}\n"); - - // Checks if "m" followed by an upper character is recognized as a prefix - QTest::newRow("GenerateGetterSetter_recognizeMFollowedByCapital") - << 2 - << _("\n" - "class Something\n" - "{\n" - " int @mFoo;\n" - "};\n") - << _("\n" - "class Something\n" - "{\n" - " int mFoo;\n" - "\n" - "public:\n" - " int foo() const;\n" - " void setFoo(int foo);\n" - "};\n" - "\n" - "int Something::foo() const\n" - "{\n" - " return mFoo;\n" - "}\n" - "\n" - "void Something::setFoo(int foo)\n" - "{\n" - " mFoo = foo;\n" - "}\n"); -} -void QuickfixTest::testGenerateGetterSetterGeneralTests() -{ - QFETCH(int, operation); - QFETCH(QByteArray, original); - QFETCH(QByteArray, expected); - - QuickFixSettings s; - s->setterParameterNameTemplate = ""; - s->getterInCppFileFrom = 1; - s->setterInCppFileFrom = 1; - - GenerateGetterSetter factory; - QuickFixOperationTest(singleDocument(original, expected), - &factory, - ProjectExplorer::HeaderPaths(), - operation); -} -/// Checks: Only generate getter -void QuickfixTest::testGenerateGetterSetterOnlyGetter() -{ - QList testDocuments; - QByteArray original; - QByteArray expected; - - // Header File - original = - "class Foo\n" - "{\n" - "public:\n" - " int bar@;\n" - "};\n"; - expected = - "class Foo\n" - "{\n" - "public:\n" - " int bar@;\n" - " int getBar() const;\n" - "};\n"; - testDocuments << CppTestDocument::create("file.h", original, expected); - - // Source File - original.resize(0); - expected = - "\n" - "int Foo::getBar() const\n" - "{\n" - " return bar;\n" - "}\n"; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - - QuickFixSettings s; - s->getterInCppFileFrom = 1; - s->getterNameTemplate = "get"; - GenerateGetterSetter factory; - QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), 1); -} - -/// Checks: Only generate setter -void QuickfixTest::testGenerateGetterSetterOnlySetter() -{ - QList testDocuments; - QByteArray original; - QByteArray expected; - QuickFixSettings s; - s->setterAsSlot = true; // To be ignored, as we don't have QObjects here. - - // Header File - original = - "class Foo\n" - "{\n" - "public:\n" - " int bar@;\n" - "};\n"; - expected = - "class Foo\n" - "{\n" - "public:\n" - " int bar@;\n" - " void setBar(int value);\n" - "};\n"; - testDocuments << CppTestDocument::create("file.h", original, expected); - - // Source File - original.resize(0); - expected = - "\n" - "void Foo::setBar(int value)\n" - "{\n" - " bar = value;\n" - "}\n"; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - - s->setterInCppFileFrom = 1; - s->setterParameterNameTemplate = "value"; - - GenerateGetterSetter factory; - QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), 0); -} - -void QuickfixTest::testGenerateGetterSetterAnonymousClass() -{ - QList testDocuments; - QByteArray original; - QByteArray expected; - QuickFixSettings s; - s->setterInCppFileFrom = 1; - s->setterParameterNameTemplate = "value"; - - // Header File - original = R"( - class { - int @m_foo; - } bar; -)"; - expected = R"( - class { - int m_foo; - - public: - int foo() const - { - return m_foo; - } - void setFoo(int value) - { - if (m_foo == value) - return; - m_foo = value; - emit fooChanged(); - } - void resetFoo() - { - setFoo({}); // TODO: Adapt to use your actual default defaultValue - } - - signals: - void fooChanged(); - - private: - Q_PROPERTY(int foo READ foo WRITE setFoo RESET resetFoo NOTIFY fooChanged FINAL) - } bar; -)"; - testDocuments << CppTestDocument::create("file.h", original, expected); - - // Source File - testDocuments << CppTestDocument::create("file.cpp", {}, {}); - - GenerateGetterSetter factory; - QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), 4); -} - -void QuickfixTest::testGenerateGetterSetterInlineInHeaderFile() -{ - QList testDocuments; - const QByteArray original = R"-( -class Foo { -public: - int bar@; -}; -)-"; - const QByteArray expected = R"-( -class Foo { -public: - int bar; - int getBar() const; - void setBar(int value); - void resetBar(); -signals: - void barChanged(); -private: - Q_PROPERTY(int bar READ getBar WRITE setBar RESET resetBar NOTIFY barChanged FINAL) -}; - -inline int Foo::getBar() const -{ - return bar; -} - -inline void Foo::setBar(int value) -{ - if (bar == value) - return; - bar = value; - emit barChanged(); -} - -inline void Foo::resetBar() -{ - setBar({}); // TODO: Adapt to use your actual default defaultValue -} -)-"; - testDocuments << CppTestDocument::create("file.h", original, expected); - - QuickFixSettings s; - s->setterOutsideClassFrom = 1; - s->getterOutsideClassFrom = 1; - s->setterParameterNameTemplate = "value"; - s->getterNameTemplate = "get"; - - GenerateGetterSetter factory; - QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), 4); -} - -void QuickfixTest::testGenerateGetterSetterOnlySetterHeaderFileWithIncludeGuard() -{ - QList testDocuments; - const QByteArray original = - "#ifndef FILE__H__DECLARED\n" - "#define FILE__H__DECLARED\n" - "class Foo\n" - "{\n" - "public:\n" - " int bar@;\n" - "};\n" - "#endif\n"; - const QByteArray expected = - "#ifndef FILE__H__DECLARED\n" - "#define FILE__H__DECLARED\n" - "class Foo\n" - "{\n" - "public:\n" - " int bar@;\n" - " void setBar(int value);\n" - "};\n\n" - "inline void Foo::setBar(int value)\n" - "{\n" - " bar = value;\n" - "}\n" - "#endif\n"; - - testDocuments << CppTestDocument::create("file.h", original, expected); - - QuickFixSettings s; - s->setterOutsideClassFrom = 1; - s->setterParameterNameTemplate = "value"; - - GenerateGetterSetter factory; - QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), 0); -} - -void QuickfixTest::testGenerateGetterFunctionAsTemplateArg() -{ - QList testDocuments; - const QByteArray original = R"( -template class TS {}; -template class TS {}; - -class S2 { - TS @member; -}; -)"; - const QByteArray expected = R"( -template class TS {}; -template class TS {}; - -class S2 { - TS member; - -public: - const TS &getMember() const - { - return member; - } -}; -)"; - - testDocuments << CppTestDocument::create("file.h", original, expected); - - QuickFixSettings s; - s->getterOutsideClassFrom = 0; - s->getterInCppFileFrom = 0; - s->getterNameTemplate = "get"; - s->returnByConstRef = true; - - GenerateGetterSetter factory; - QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), 1); -} - class CppCodeStyleSettingsChanger { public: CppCodeStyleSettingsChanger(const CppCodeStyleSettings &settings); @@ -3490,305 +1755,6 @@ CppCodeStyleSettings CppCodeStyleSettingsChanger::currentSettings() return CppToolsSettings::cppCodeStyle()->currentDelegate()->value().value(); } -void QuickfixTest::testGenerateGettersSetters_data() -{ - QTest::addColumn("original"); - QTest::addColumn("expected"); - - const QByteArray onlyReset = R"( -class Foo { -public: - int bar() const; - void setBar(int bar); -private: - int m_bar; -@};)"; - - const QByteArray onlyResetAfter = R"( -class @Foo { -public: - int bar() const; - void setBar(int bar); - void resetBar(); - -private: - int m_bar; -}; -inline void Foo::resetBar() -{ - setBar({}); // TODO: Adapt to use your actual default defaultValue -} -)"; - QTest::addRow("only reset") << onlyReset << onlyResetAfter; - - const QByteArray withCandidates = R"( -class @Foo { -public: - int bar() const; - void setBar(int bar) { m_bar = bar; } - - int getBar2() const; - - int m_alreadyPublic; - -private: - friend void distraction(); - class AnotherDistraction {}; - enum EvenMoreDistraction { val1, val2 }; - - int m_bar; - int bar2_; - QString bar3; -};)"; - const QByteArray after = R"( -class Foo { -public: - int bar() const; - void setBar(int bar) { m_bar = bar; } - - int getBar2() const; - - int m_alreadyPublic; - - void resetBar(); - void setBar2(int value); - void resetBar2(); - const QString &getBar3() const; - void setBar3(const QString &value); - void resetBar3(); - -signals: - void bar2Changed(); - void bar3Changed(); - -private: - friend void distraction(); - class AnotherDistraction {}; - enum EvenMoreDistraction { val1, val2 }; - - int m_bar; - int bar2_; - QString bar3; - Q_PROPERTY(int bar2 READ getBar2 WRITE setBar2 RESET resetBar2 NOTIFY bar2Changed FINAL) - Q_PROPERTY(QString bar3 READ getBar3 WRITE setBar3 RESET resetBar3 NOTIFY bar3Changed FINAL) -}; -inline void Foo::resetBar() -{ - setBar({}); // TODO: Adapt to use your actual default defaultValue -} - -inline void Foo::setBar2(int value) -{ - if (bar2_ == value) - return; - bar2_ = value; - emit bar2Changed(); -} - -inline void Foo::resetBar2() -{ - setBar2({}); // TODO: Adapt to use your actual default defaultValue -} - -inline const QString &Foo::getBar3() const -{ - return bar3; -} - -inline void Foo::setBar3(const QString &value) -{ - if (bar3 == value) - return; - bar3 = value; - emit bar3Changed(); -} - -inline void Foo::resetBar3() -{ - setBar3({}); // TODO: Adapt to use your actual default defaultValue -} -)"; - QTest::addRow("with candidates") << withCandidates << after; -} - -void QuickfixTest::testGenerateGettersSetters() -{ - class TestFactory : public GenerateGettersSettersForClass - { - public: - TestFactory() { setTest(); } - }; - - QFETCH(QByteArray, original); - QFETCH(QByteArray, expected); - - QuickFixSettings s; - s->getterNameTemplate = "get"; - s->setterParameterNameTemplate = "value"; - s->setterOutsideClassFrom = 1; - s->getterOutsideClassFrom = 1; - s->returnByConstRef = true; - - TestFactory factory; - QuickFixOperationTest({CppTestDocument::create("file.h", original, expected)}, &factory); -} - -void QuickfixTest::testInsertQtPropertyMembers_data() -{ - QTest::addColumn("original"); - QTest::addColumn("expected"); - - QTest::newRow("InsertQtPropertyMembers") - << _("struct QObject { void connect(); }\n" - "struct XmarksTheSpot : public QObject {\n" - " @Q_PROPERTY(int it READ getIt WRITE setIt RESET resetIt NOTIFY itChanged)\n" - "};\n") - << _("struct QObject { void connect(); }\n" - "struct XmarksTheSpot : public QObject {\n" - " Q_PROPERTY(int it READ getIt WRITE setIt RESET resetIt NOTIFY itChanged)\n" - "\n" - "public:\n" - " int getIt() const;\n" - "\n" - "public slots:\n" - " void setIt(int it)\n" - " {\n" - " if (m_it == it)\n" - " return;\n" - " m_it = it;\n" - " emit itChanged(m_it);\n" - " }\n" - " void resetIt()\n" - " {\n" - " setIt({}); // TODO: Adapt to use your actual default value\n" - " }\n" - "\n" - "signals:\n" - " void itChanged(int it);\n" - "\n" - "private:\n" - " int m_it;\n" - "};\n" - "\n" - "int XmarksTheSpot::getIt() const\n" - "{\n" - " return m_it;\n" - "}\n"); - - QTest::newRow("InsertQtPropertyMembersResetWithoutSet") - << _("struct QObject { void connect(); }\n" - "struct XmarksTheSpot : public QObject {\n" - " @Q_PROPERTY(int it READ getIt RESET resetIt NOTIFY itChanged)\n" - "};\n") - << _("struct QObject { void connect(); }\n" - "struct XmarksTheSpot : public QObject {\n" - " Q_PROPERTY(int it READ getIt RESET resetIt NOTIFY itChanged)\n" - "\n" - "public:\n" - " int getIt() const;\n" - "\n" - "public slots:\n" - " void resetIt()\n" - " {\n" - " static int defaultValue{}; // TODO: Adapt to use your actual default " - "value\n" - " if (m_it == defaultValue)\n" - " return;\n" - " m_it = defaultValue;\n" - " emit itChanged(m_it);\n" - " }\n" - "\n" - "signals:\n" - " void itChanged(int it);\n" - "\n" - "private:\n" - " int m_it;\n" - "};\n" - "\n" - "int XmarksTheSpot::getIt() const\n" - "{\n" - " return m_it;\n" - "}\n"); - - QTest::newRow("InsertQtPropertyMembersResetWithoutSetAndNotify") - << _("struct QObject { void connect(); }\n" - "struct XmarksTheSpot : public QObject {\n" - " @Q_PROPERTY(int it READ getIt RESET resetIt)\n" - "};\n") - << _("struct QObject { void connect(); }\n" - "struct XmarksTheSpot : public QObject {\n" - " Q_PROPERTY(int it READ getIt RESET resetIt)\n" - "\n" - "public:\n" - " int getIt() const;\n" - "\n" - "public slots:\n" - " void resetIt()\n" - " {\n" - " static int defaultValue{}; // TODO: Adapt to use your actual default " - "value\n" - " m_it = defaultValue;\n" - " }\n" - "\n" - "private:\n" - " int m_it;\n" - "};\n" - "\n" - "int XmarksTheSpot::getIt() const\n" - "{\n" - " return m_it;\n" - "}\n"); - - QTest::newRow("InsertQtPropertyMembersPrivateBeforePublic") - << _("struct QObject { void connect(); }\n" - "class XmarksTheSpot : public QObject {\n" - "private:\n" - " @Q_PROPERTY(int it READ getIt WRITE setIt NOTIFY itChanged)\n" - "public:\n" - " void find();\n" - "};\n") - << _("struct QObject { void connect(); }\n" - "class XmarksTheSpot : public QObject {\n" - "private:\n" - " Q_PROPERTY(int it READ getIt WRITE setIt NOTIFY itChanged)\n" - " int m_it;\n" - "\n" - "public:\n" - " void find();\n" - " int getIt() const;\n" - "public slots:\n" - " void setIt(int it)\n" - " {\n" - " if (m_it == it)\n" - " return;\n" - " m_it = it;\n" - " emit itChanged(m_it);\n" - " }\n" - "signals:\n" - " void itChanged(int it);\n" - "};\n" - "\n" - "int XmarksTheSpot::getIt() const\n" - "{\n" - " return m_it;\n" - "}\n"); -} - -void QuickfixTest::testInsertQtPropertyMembers() -{ - QFETCH(QByteArray, original); - QFETCH(QByteArray, expected); - - QuickFixSettings s; - s->setterAsSlot = true; - s->setterInCppFileFrom = 0; - s->setterParameterNameTemplate = ""; - s->signalWithNewValue = true; - - InsertQtPropertyMembers factory; - QuickFixOperationTest({CppTestDocument::create("file.cpp", original, expected)}, &factory); -} - void QuickfixTest::testInsertMemberFromUse_data() { QTest::addColumn("original"); @@ -9098,400 +7064,6 @@ void QuickfixTest::testRemoveUsingNamespaceDifferentSymbols() QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), 0); } -enum ConstructorLocation { Inside, Outside, CppGenNamespace, CppGenUsingDirective, CppRewriteType }; - -void QuickfixTest::testGenerateConstructor_data() -{ - QTest::addColumn("original_header"); - QTest::addColumn("expected_header"); - QTest::addColumn("original_source"); - QTest::addColumn("expected_source"); - QTest::addColumn("location"); - const int Inside = ConstructorLocation::Inside; - const int Outside = ConstructorLocation::Outside; - const int CppGenNamespace = ConstructorLocation::CppGenNamespace; - const int CppGenUsingDirective = ConstructorLocation::CppGenUsingDirective; - const int CppRewriteType = ConstructorLocation::CppRewriteType; - - QByteArray header = R"--( -class@ Foo{ - int test; - static int s; -}; -)--"; - QByteArray expected = R"--( -class Foo{ - int test; - static int s; -public: - Foo(int test) : test(test) - {} -}; -)--"; - QTest::newRow("ignore static") << header << expected << QByteArray() << QByteArray() << Inside; - - header = R"--( -class@ Foo{ - CustomType test; -}; -)--"; - expected = R"--( -class Foo{ - CustomType test; -public: - Foo(CustomType test) : test(std::move(test)) - {} -}; -)--"; - QTest::newRow("Move custom value types") - << header << expected << QByteArray() << QByteArray() << Inside; - - header = R"--( -class@ Foo{ - int test; -protected: - Foo() = default; -}; -)--"; - expected = R"--( -class Foo{ - int test; -public: - Foo(int test) : test(test) - {} - -protected: - Foo() = default; -}; -)--"; - - QTest::newRow("new section before existing") - << header << expected << QByteArray() << QByteArray() << Inside; - - header = R"--( -class@ Foo{ - int test; -}; -)--"; - expected = R"--( -class Foo{ - int test; -public: - Foo(int test) : test(test) - {} -}; -)--"; - QTest::newRow("new section at end") - << header << expected << QByteArray() << QByteArray() << Inside; - - header = R"--( -class@ Foo{ - int test; -public: - /** - * Random comment - */ - Foo(int i, int i2); -}; -)--"; - expected = R"--( -class Foo{ - int test; -public: - Foo(int test) : test(test) - {} - /** - * Random comment - */ - Foo(int i, int i2); -}; -)--"; - QTest::newRow("in section before") - << header << expected << QByteArray() << QByteArray() << Inside; - - header = R"--( -class@ Foo{ - int test; -public: - Foo() = default; -}; -)--"; - expected = R"--( -class Foo{ - int test; -public: - Foo() = default; - Foo(int test) : test(test) - {} -}; -)--"; - QTest::newRow("in section after") - << header << expected << QByteArray() << QByteArray() << Inside; - - header = R"--( -class@ Foo{ - int test1; - int test2; - int test3; -public: -}; -)--"; - expected = R"--( -class Foo{ - int test1; - int test2; - int test3; -public: - Foo(int test2, int test3, int test1) : test1(test1), - test2(test2), - test3(test3) - {} -}; -)--"; - // No worry, that is not the default behavior. - // Move first member to the back when testing with 3 or more members - QTest::newRow("changed parameter order") - << header << expected << QByteArray() << QByteArray() << Inside; - - header = R"--( -class@ Foo{ - int test; - int di_test; -public: -}; -)--"; - expected = R"--( -class Foo{ - int test; - int di_test; -public: - Foo(int test, int di_test = 42) : test(test), - di_test(di_test) - {} -}; -)--"; - QTest::newRow("default parameters") - << header << expected << QByteArray() << QByteArray() << Inside; - - header = R"--( -struct Bar{ - Bar(int i); -}; -class@ Foo : public Bar{ - int test; -public: -}; -)--"; - expected = R"--( -struct Bar{ - Bar(int i); -}; -class Foo : public Bar{ - int test; -public: - Foo(int test, int i) : Bar(i), - test(test) - {} -}; -)--"; - QTest::newRow("parent constructor") - << header << expected << QByteArray() << QByteArray() << Inside; - - header = R"--( -struct Bar{ - Bar(int use_i = 6); -}; -class@ Foo : public Bar{ - int test; -public: -}; -)--"; - expected = R"--( -struct Bar{ - Bar(int use_i = 6); -}; -class Foo : public Bar{ - int test; -public: - Foo(int test, int use_i = 6) : Bar(use_i), - test(test) - {} -}; -)--"; - QTest::newRow("parent constructor with default") - << header << expected << QByteArray() << QByteArray() << Inside; - - header = R"--( -struct Bar{ - Bar(int use_i = L'A', int use_i2 = u8"B"); -}; -class@ Foo : public Bar{ -public: -}; -)--"; - expected = R"--( -struct Bar{ - Bar(int use_i = L'A', int use_i2 = u8"B"); -}; -class Foo : public Bar{ -public: - Foo(int use_i = L'A', int use_i2 = u8"B") : Bar(use_i, use_i2) - {} -}; -)--"; - QTest::newRow("parent constructor with char/string default value") - << header << expected << QByteArray() << QByteArray() << Inside; - - const QByteArray common = R"--( -namespace N{ - template - struct vector{ - }; -} -)--"; - header = common + R"--( -namespace M{ -enum G{g}; -class@ Foo{ - N::vector g; - enum E{e}e; -public: -}; -} -)--"; - - expected = common + R"--( -namespace M{ -enum G{g}; -class@ Foo{ - N::vector g; - enum E{e}e; -public: - Foo(const N::vector &g, E e); -}; - -Foo::Foo(const N::vector &g, Foo::E e) : g(g), - e(e) -{} - -} -)--"; - QTest::newRow("source: right type outside class ") - << QByteArray() << QByteArray() << header << expected << Outside; - expected = common + R"--( -namespace M{ -enum G{g}; -class@ Foo{ - N::vector g; - enum E{e}e; -public: - Foo(const N::vector &g, E e); -}; -} - - -inline M::Foo::Foo(const N::vector &g, M::Foo::E e) : g(g), - e(e) -{} - -)--"; - QTest::newRow("header: right type outside class ") - << header << expected << QByteArray() << QByteArray() << Outside; - - expected = common + R"--( -namespace M{ -enum G{g}; -class@ Foo{ - N::vector g; - enum E{e}e; -public: - Foo(const N::vector &g, E e); -}; -} -)--"; - const QByteArray source = R"--( -#include "file.h" -)--"; - QByteArray expected_source = R"--( -#include "file.h" - - -namespace M { -Foo::Foo(const N::vector &g, Foo::E e) : g(g), - e(e) -{} - -} -)--"; - QTest::newRow("source: right type inside namespace") - << header << expected << source << expected_source << CppGenNamespace; - - expected_source = R"--( -#include "file.h" - -using namespace M; -Foo::Foo(const N::vector &g, Foo::E e) : g(g), - e(e) -{} -)--"; - QTest::newRow("source: right type with using directive") - << header << expected << source << expected_source << CppGenUsingDirective; - - expected_source = R"--( -#include "file.h" - -M::Foo::Foo(const N::vector &g, M::Foo::E e) : g(g), - e(e) -{} -)--"; - QTest::newRow("source: right type while rewritung types") - << header << expected << source << expected_source << CppRewriteType; -} - -void QuickfixTest::testGenerateConstructor() -{ - class TestFactory : public GenerateConstructor - { - public: - TestFactory() { setTest(); } - }; - - QFETCH(QByteArray, original_header); - QFETCH(QByteArray, expected_header); - QFETCH(QByteArray, original_source); - QFETCH(QByteArray, expected_source); - QFETCH(int, location); - - QuickFixSettings s; - s->valueTypes << "CustomType"; - using L = ConstructorLocation; - if (location == L::Inside) { - s->setterInCppFileFrom = -1; - s->setterOutsideClassFrom = -1; - } else if (location == L::Outside) { - s->setterInCppFileFrom = -1; - s->setterOutsideClassFrom = 1; - } else if (location >= L::CppGenNamespace && location <= L::CppRewriteType) { - s->setterInCppFileFrom = 1; - s->setterOutsideClassFrom = -1; - using Handling = CppQuickFixSettings::MissingNamespaceHandling; - if (location == L::CppGenNamespace) - s->cppFileNamespaceHandling = Handling::CreateMissing; - else if (location == L::CppGenUsingDirective) - s->cppFileNamespaceHandling = Handling::AddUsingDirective; - else if (location == L::CppRewriteType) - s->cppFileNamespaceHandling = Handling::RewriteType; - } else { - QFAIL("location is none of the values of the ConstructorLocation enum"); - } - - QList testDocuments; - testDocuments << CppTestDocument::create("file.h", original_header, expected_header); - testDocuments << CppTestDocument::create("file.cpp", original_source, expected_source); - TestFactory factory; - QuickFixOperationTest(testDocuments, &factory); -} - void QuickfixTest::testChangeCommentType_data() { QTest::addColumn("input"); diff --git a/src/plugins/cppeditor/quickfixes/cppquickfix_test.h b/src/plugins/cppeditor/quickfixes/cppquickfix_test.h index 8d4fff6e6fe..7415c930241 100644 --- a/src/plugins/cppeditor/quickfixes/cppquickfix_test.h +++ b/src/plugins/cppeditor/quickfixes/cppquickfix_test.h @@ -5,6 +5,7 @@ #include "../cpptoolstestcase.h" #include "cppquickfix.h" +#include "cppquickfixsettings.h" #include @@ -22,6 +23,15 @@ class CppCodeStylePreferences; namespace Internal { namespace Tests { +class QuickFixSettings +{ + const CppQuickFixSettings original = *CppQuickFixSettings::instance(); + +public: + CppQuickFixSettings *operator->() { return CppQuickFixSettings::instance(); } + ~QuickFixSettings() { *CppQuickFixSettings::instance() = original; } +}; + class BaseQuickFixTestCase : public CppEditor::Tests::TestCase { public: @@ -47,6 +57,17 @@ private: bool m_restoreHeaderPaths; }; +/// Tests the offered operations provided by a given CppQuickFixFactory +class QuickFixOfferedOperationsTest : public BaseQuickFixTestCase +{ +public: + QuickFixOfferedOperationsTest(const QList &testDocuments, + CppQuickFixFactory *factory, + const ProjectExplorer::HeaderPaths &headerPaths + = ProjectExplorer::HeaderPaths(), + const QStringList &expectedOperations = QStringList()); +}; + /// Tests a concrete QuickFixOperation of a given CppQuickFixFactory class QuickFixOperationTest : public BaseQuickFixTestCase { @@ -76,34 +97,6 @@ private slots: void testGeneric_data(); void testGeneric(); - void testGenerateGetterSetterNamespaceHandlingCreate_data(); - void testGenerateGetterSetterNamespaceHandlingCreate(); - void testGenerateGetterSetterNamespaceHandlingAddUsing_data(); - void testGenerateGetterSetterNamespaceHandlingAddUsing(); - void testGenerateGetterSetterNamespaceHandlingFullyQualify_data(); - void testGenerateGetterSetterNamespaceHandlingFullyQualify(); - void testGenerateGetterSetterCustomNames_data(); - void testGenerateGetterSetterCustomNames(); - void testGenerateGetterSetterValueTypes_data(); - void testGenerateGetterSetterValueTypes(); - void testGenerateGetterSetterCustomTemplate(); - void testGenerateGetterSetterNeedThis(); - void testGenerateGetterSetterOfferedFixes_data(); - void testGenerateGetterSetterOfferedFixes(); - void testGenerateGetterSetterGeneralTests_data(); - void testGenerateGetterSetterGeneralTests(); - void testGenerateGetterSetterOnlyGetter(); - void testGenerateGetterSetterOnlySetter(); - void testGenerateGetterSetterAnonymousClass(); - void testGenerateGetterSetterInlineInHeaderFile(); - void testGenerateGetterSetterOnlySetterHeaderFileWithIncludeGuard(); - void testGenerateGetterFunctionAsTemplateArg(); - void testGenerateGettersSetters_data(); - void testGenerateGettersSetters(); - - void testInsertQtPropertyMembers_data(); - void testInsertQtPropertyMembers(); - void testInsertMemberFromUse_data(); void testInsertMemberFromUse(); @@ -218,9 +211,6 @@ private slots: void testRemoveUsingNamespaceSimple(); void testRemoveUsingNamespaceDifferentSymbols(); - void testGenerateConstructor_data(); - void testGenerateConstructor(); - void testChangeCommentType_data(); void testChangeCommentType(); diff --git a/src/plugins/cppeditor/quickfixes/cppquickfixes.cpp b/src/plugins/cppeditor/quickfixes/cppquickfixes.cpp index 12652cb974f..ed811a032e9 100644 --- a/src/plugins/cppeditor/quickfixes/cppquickfixes.cpp +++ b/src/plugins/cppeditor/quickfixes/cppquickfixes.cpp @@ -18,6 +18,7 @@ #include "../indexitem.h" #include "../insertionpointlocator.h" #include "../symbolfinder.h" +#include "cppcodegenerationquickfixes.h" #include "cppinsertvirtualmethods.h" #include "cppquickfixassistant.h" #include "cppquickfixhelpers.h" @@ -53,9 +54,6 @@ #include #include -#ifdef WITH_TESTS -#include -#endif #include #include #include @@ -235,43 +233,6 @@ bool nameIncludesOperatorName(const Name *name) || (name->asQualifiedNameId() && name->asQualifiedNameId()->name()->asOperatorNameId()); } -QString memberBaseName(const QString &name) -{ - const auto validName = [](const QString &name) { - return !name.isEmpty() && !name.at(0).isDigit(); - }; - QString baseName = name; - - CppQuickFixSettings *settings = CppQuickFixProjectsSettings::getQuickFixSettings( - ProjectExplorer::ProjectTree::currentProject()); - const QString &nameTemplate = settings->memberVariableNameTemplate; - const QString prefix = nameTemplate.left(nameTemplate.indexOf('<')); - const QString postfix = nameTemplate.mid(nameTemplate.lastIndexOf('>') + 1); - if (name.startsWith(prefix) && name.endsWith(postfix)) { - const QString base = name.mid(prefix.length(), name.length() - postfix.length()); - if (validName(base)) - return base; - } - - // Remove leading and trailing "_" - while (baseName.startsWith(QLatin1Char('_'))) - baseName.remove(0, 1); - while (baseName.endsWith(QLatin1Char('_'))) - baseName.chop(1); - if (baseName != name && validName(baseName)) - return baseName; - - // If no leading/trailing "_": remove "m_" and "m" prefix - if (baseName.startsWith(QLatin1String("m_"))) { - baseName.remove(0, 2); - } else if (baseName.startsWith(QLatin1Char('m')) && baseName.length() > 1 - && baseName.at(1).isUpper()) { - baseName.remove(0, 1); - baseName[0] = baseName.at(0).toLower(); - } - - return validName(baseName) ? baseName : name; -} QString nameString(const NameAST *name) { @@ -3630,1411 +3591,6 @@ void InsertDefsFromDecls::doMatch(const CppQuickFixInterface &interface, QuickFi result << op; } -namespace { - -std::optional getFirstTemplateParameter(const Name *name) -{ - if (const QualifiedNameId *qualifiedName = name->asQualifiedNameId()) - return getFirstTemplateParameter(qualifiedName->name()); - - if (const TemplateNameId *templateName = name->asTemplateNameId()) { - if (templateName->templateArgumentCount() > 0) - return templateName->templateArgumentAt(0).type(); - } - return {}; -} - -std::optional getFirstTemplateParameter(Type *type) -{ - if (NamedType *namedType = type->asNamedType()) - return getFirstTemplateParameter(namedType->name()); - - return {}; -} - -std::optional getFirstTemplateParameter(FullySpecifiedType type) -{ - return getFirstTemplateParameter(type.type()); -} - -QString symbolAtDifferentLocation(const CppQuickFixInterface &interface, - Symbol *symbol, - const CppRefactoringFilePtr &targetFile, - InsertionLocation targetLocation) -{ - QTC_ASSERT(symbol, return QString()); - Scope *scopeAtInsertPos = targetFile->cppDocument()->scopeAt(targetLocation.line(), - targetLocation.column()); - - LookupContext cppContext(targetFile->cppDocument(), interface.snapshot()); - ClassOrNamespace *cppCoN = cppContext.lookupType(scopeAtInsertPos); - if (!cppCoN) - cppCoN = cppContext.globalNamespace(); - SubstitutionEnvironment env; - env.setContext(interface.context()); - env.switchScope(symbol->enclosingScope()); - UseMinimalNames q(cppCoN); - env.enter(&q); - Control *control = interface.context().bindings()->control().get(); - Overview oo = CppCodeStyleSettings::currentProjectCodeStyleOverview(); - return oo.prettyName(LookupContext::minimalName(symbol, cppCoN, control)); -} - -FullySpecifiedType typeAtDifferentLocation(const CppQuickFixInterface &interface, - FullySpecifiedType type, - Scope *originalScope, - const CppRefactoringFilePtr &targetFile, - InsertionLocation targetLocation, - const QStringList &newNamespaceNamesAtLoc = {}) -{ - Scope *scopeAtInsertPos = targetFile->cppDocument()->scopeAt(targetLocation.line(), - targetLocation.column()); - for (const QString &nsName : newNamespaceNamesAtLoc) { - const QByteArray utf8Name = nsName.toUtf8(); - Control *control = targetFile->cppDocument()->control(); - const Name *name = control->identifier(utf8Name.data(), utf8Name.size()); - Namespace *ns = control->newNamespace(0, name); - ns->setEnclosingScope(scopeAtInsertPos); - scopeAtInsertPos = ns; - } - LookupContext cppContext(targetFile->cppDocument(), interface.snapshot()); - ClassOrNamespace *cppCoN = cppContext.lookupType(scopeAtInsertPos); - if (!cppCoN) - cppCoN = cppContext.globalNamespace(); - SubstitutionEnvironment env; - env.setContext(interface.context()); - env.switchScope(originalScope); - UseMinimalNames q(cppCoN); - env.enter(&q); - Control *control = interface.context().bindings()->control().get(); - return rewriteType(type, &env, control); -} - -struct ExistingGetterSetterData -{ - Class *clazz = nullptr; - Declaration *declarationSymbol = nullptr; - QString getterName; - QString setterName; - QString resetName; - QString signalName; - QString qPropertyName; - QString memberVariableName; - Document::Ptr doc; - - int computePossibleFlags() const; -}; - -class GetterSetterRefactoringHelper -{ -public: - GetterSetterRefactoringHelper(CppQuickFixOperation *operation, - const FilePath &filePath, - Class *clazz) - : m_operation(operation) - , m_changes(m_operation->snapshot()) - , m_locator(m_changes) - , m_headerFile(m_changes.cppFile(filePath)) - , m_sourceFile([&] { - FilePath cppFilePath = correspondingHeaderOrSource(filePath, &m_isHeaderHeaderFile); - if (!m_isHeaderHeaderFile || !cppFilePath.exists()) { - // there is no "source" file - return m_headerFile; - } else { - return m_changes.cppFile(cppFilePath); - } - }()) - , m_class(clazz) - {} - - void performGeneration(ExistingGetterSetterData data, int generationFlags); - - void applyChanges() - { - const auto classLayout = { - InsertionPointLocator::Public, - InsertionPointLocator::PublicSlot, - InsertionPointLocator::Signals, - InsertionPointLocator::Protected, - InsertionPointLocator::ProtectedSlot, - InsertionPointLocator::PrivateSlot, - InsertionPointLocator::Private, - }; - for (auto spec : classLayout) { - const auto iter = m_headerFileCode.find(spec); - if (iter != m_headerFileCode.end()) { - const InsertionLocation loc = headerLocationFor(spec); - m_headerFile->setOpenEditor(true, m_headerFile->position(loc.line(), loc.column())); - insertAndIndent(m_headerFile, loc, *iter); - } - } - if (!m_sourceFileCode.isEmpty() && m_sourceFileInsertionPoint.isValid()) { - m_sourceFile->setOpenEditor(true, m_sourceFile->position( - m_sourceFileInsertionPoint.line(), - m_sourceFileInsertionPoint.column())); - insertAndIndent(m_sourceFile, m_sourceFileInsertionPoint, m_sourceFileCode); - } - - if (!m_headerFileChangeSet.isEmpty()) { - m_headerFile->setChangeSet(m_headerFileChangeSet); - m_headerFile->apply(); - } - if (!m_sourceFileChangeSet.isEmpty()) { - m_sourceFile->setChangeSet(m_sourceFileChangeSet); - m_sourceFile->apply(); - } - } - - bool hasSourceFile() const { return m_headerFile != m_sourceFile; } - -protected: - void insertAndIndent(const RefactoringFilePtr &file, - const InsertionLocation &loc, - const QString &text) - { - int targetPosition = file->position(loc.line(), loc.column()); - ChangeSet &changeSet = file == m_headerFile ? m_headerFileChangeSet : m_sourceFileChangeSet; - changeSet.insert(targetPosition, loc.prefix() + text + loc.suffix()); - } - - FullySpecifiedType makeConstRef(FullySpecifiedType type) - { - type.setConst(true); - return m_operation->currentFile()->cppDocument()->control()->referenceType(type, false); - } - - FullySpecifiedType addConstToReference(FullySpecifiedType type) - { - if (ReferenceType *ref = type.type()->asReferenceType()) { - FullySpecifiedType elemType = ref->elementType(); - if (elemType.isConst()) - return type; - elemType.setConst(true); - return m_operation->currentFile()->cppDocument()->control()->referenceType(elemType, - false); - } - return type; - } - - QString symbolAt(Symbol *symbol, - const CppRefactoringFilePtr &targetFile, - InsertionLocation targetLocation) - { - return symbolAtDifferentLocation(*m_operation, symbol, targetFile, targetLocation); - } - - FullySpecifiedType typeAt(FullySpecifiedType type, - Scope *originalScope, - const CppRefactoringFilePtr &targetFile, - InsertionLocation targetLocation, - const QStringList &newNamespaceNamesAtLoc = {}) - { - return typeAtDifferentLocation(*m_operation, - type, - originalScope, - targetFile, - targetLocation, - newNamespaceNamesAtLoc); - } - - /** - * @brief checks if the type in the enclosing scope in the header is a value type - * @param type a type in the m_headerFile - * @param enclosingScope the enclosing scope - * @param customValueType if not nullptr set to true when value type comes - * from CppQuickFixSettings::isValueType - * @return true if it is a pointer, enum, integer, floating point, reference, custom value type - */ - bool isValueType(FullySpecifiedType type, Scope *enclosingScope, bool *customValueType = nullptr) - { - if (customValueType) - *customValueType = false; - // a type is a value type if it is one of the following - const auto isTypeValueType = [](const FullySpecifiedType &t) { - return t->asPointerType() || t->asEnumType() || t->asIntegerType() || t->asFloatType() - || t->asReferenceType(); - }; - if (type->asNamedType()) { - // we need a recursive search and a lookup context - LookupContext context(m_headerFile->cppDocument(), m_changes.snapshot()); - auto isValueType = [settings = m_settings, - &customValueType, - &context, - &isTypeValueType](const Name *name, - Scope *scope, - auto &isValueType) { - // maybe the type is a custom value type by name - if (const Identifier *id = name->identifier()) { - if (settings->isValueType(QString::fromUtf8(id->chars(), id->size()))) { - if (customValueType) - *customValueType = true; - return true; - } - } - // search for the type declaration - QList localLookup = context.lookup(name, scope); - for (auto &&i : localLookup) { - if (isTypeValueType(i.type())) - return true; - if (i.type()->asNamedType()) { // check if we have to search recursively - const Name *newName = i.type()->asNamedType()->name(); - Scope *newScope = i.declaration()->enclosingScope(); - if (Matcher::match(newName, name) - && Matcher::match(newScope->name(), scope->name())) { - continue; // we have found the start location of the search - } - return isValueType(newName, newScope, isValueType); - } - return false; - } - return false; - }; - // start recursion - return isValueType(type->asNamedType()->name(), enclosingScope, isValueType); - } - return isTypeValueType(type); - } - - bool isValueType(Symbol *symbol, bool *customValueType = nullptr) - { - return isValueType(symbol->type(), symbol->enclosingScope(), customValueType); - } - - void addHeaderCode(InsertionPointLocator::AccessSpec spec, QString code) - { - QString &existing = m_headerFileCode[spec]; - existing += code; - if (!existing.endsWith('\n')) - existing += '\n'; - } - - InsertionLocation headerLocationFor(InsertionPointLocator::AccessSpec spec) - { - const auto insertionPoint = m_headerInsertionPoints.find(spec); - if (insertionPoint != m_headerInsertionPoints.end()) - return *insertionPoint; - const InsertionLocation loc = m_locator.methodDeclarationInClass( - m_headerFile->filePath(), m_class, spec, - InsertionPointLocator::ForceAccessSpec::Yes); - m_headerInsertionPoints.insert(spec, loc); - return loc; - } - - InsertionLocation sourceLocationFor(Symbol *symbol, QStringList *insertedNamespaces = nullptr) - { - if (m_sourceFileInsertionPoint.isValid()) - return m_sourceFileInsertionPoint; - m_sourceFileInsertionPoint - = insertLocationForMethodDefinition(symbol, - false, - m_settings->createMissingNamespacesinCppFile() - ? NamespaceHandling::CreateMissing - : NamespaceHandling::Ignore, - m_changes, - m_sourceFile->filePath(), - insertedNamespaces); - if (m_settings->addUsingNamespaceinCppFile()) { - // check if we have to insert a using namespace ... - auto requiredNamespaces = getNamespaceNames( - symbol->asClass() ? symbol : symbol->enclosingClass()); - NSCheckerVisitor visitor(m_sourceFile.get(), - requiredNamespaces, - m_sourceFile->position(m_sourceFileInsertionPoint.line(), - m_sourceFileInsertionPoint.column())); - visitor.accept(m_sourceFile->cppDocument()->translationUnit()->ast()); - if (insertedNamespaces) - insertedNamespaces->clear(); - if (auto rns = visitor.remainingNamespaces(); !rns.empty()) { - QString ns = "using namespace "; - for (auto &n : rns) { - if (!n.isEmpty()) { // we have to ignore unnamed namespaces - ns += n; - ns += "::"; - if (insertedNamespaces) - insertedNamespaces->append(n); - } - } - ns.resize(ns.size() - 2); // remove last '::' - ns += ";\n"; - const auto &loc = m_sourceFileInsertionPoint; - m_sourceFileInsertionPoint = InsertionLocation(loc.filePath(), - loc.prefix() + ns, - loc.suffix(), - loc.line(), - loc.column()); - } - } - return m_sourceFileInsertionPoint; - } - - void addSourceFileCode(QString code) - { - while (!m_sourceFileCode.isEmpty() && !m_sourceFileCode.endsWith("\n\n")) - m_sourceFileCode += '\n'; - m_sourceFileCode += code; - } - -protected: - CppQuickFixOperation *const m_operation; - const CppRefactoringChanges m_changes; - const InsertionPointLocator m_locator; - const CppRefactoringFilePtr m_headerFile; - bool m_isHeaderHeaderFile = false; // the "header" (where the class is defined) can be a source file - const CppRefactoringFilePtr m_sourceFile; - CppQuickFixSettings *const m_settings = CppQuickFixProjectsSettings::getQuickFixSettings( - ProjectExplorer::ProjectTree::currentProject()); - Class *const m_class; - -private: - ChangeSet m_headerFileChangeSet; - ChangeSet m_sourceFileChangeSet; - QMap m_headerInsertionPoints; - InsertionLocation m_sourceFileInsertionPoint; - QString m_sourceFileCode; - QMap m_headerFileCode; -}; - -class GenerateGetterSetterOp : public CppQuickFixOperation -{ -public: - enum GenerateFlag { - GenerateGetter = 1 << 0, - GenerateSetter = 1 << 1, - GenerateSignal = 1 << 2, - GenerateMemberVariable = 1 << 3, - GenerateReset = 1 << 4, - GenerateProperty = 1 << 5, - GenerateConstantProperty = 1 << 6, - HaveExistingQProperty = 1 << 7, - Invalid = -1, - }; - - GenerateGetterSetterOp(const CppQuickFixInterface &interface, - ExistingGetterSetterData data, - int generateFlags, - int priority, - const QString &description) - : CppQuickFixOperation(interface) - , m_generateFlags(generateFlags) - , m_data(data) - { - setDescription(description); - setPriority(priority); - } - - static void generateQuickFixes(QuickFixOperations &results, - const CppQuickFixInterface &interface, - const ExistingGetterSetterData &data, - const int possibleFlags) - { - // flags can have the value HaveExistingQProperty or a combination of all other values - // of the enum 'GenerateFlag' - int p = 0; - if (possibleFlags & HaveExistingQProperty) { - const QString desc = Tr::tr("Generate Missing Q_PROPERTY Members"); - results << new GenerateGetterSetterOp(interface, data, possibleFlags, ++p, desc); - } else { - if (possibleFlags & GenerateSetter) { - const QString desc = Tr::tr("Generate Setter"); - results << new GenerateGetterSetterOp(interface, data, GenerateSetter, ++p, desc); - } - if (possibleFlags & GenerateGetter) { - const QString desc = Tr::tr("Generate Getter"); - results << new GenerateGetterSetterOp(interface, data, GenerateGetter, ++p, desc); - } - if (possibleFlags & GenerateGetter && possibleFlags & GenerateSetter) { - const QString desc = Tr::tr("Generate Getter and Setter"); - const int flags = GenerateGetter | GenerateSetter; - results << new GenerateGetterSetterOp(interface, data, flags, ++p, desc); - } - - if (possibleFlags & GenerateConstantProperty) { - const QString desc = Tr::tr("Generate Constant Q_PROPERTY and Missing Members"); - const int flags = possibleFlags & ~(GenerateSetter | GenerateSignal | GenerateReset); - results << new GenerateGetterSetterOp(interface, data, flags, ++p, desc); - } - if (possibleFlags & GenerateProperty) { - if (possibleFlags & GenerateReset) { - const QString desc = Tr::tr( - "Generate Q_PROPERTY and Missing Members with Reset Function"); - const int flags = possibleFlags & ~GenerateConstantProperty; - results << new GenerateGetterSetterOp(interface, data, flags, ++p, desc); - } - const QString desc = Tr::tr("Generate Q_PROPERTY and Missing Members"); - const int flags = possibleFlags & ~GenerateConstantProperty & ~GenerateReset; - results << new GenerateGetterSetterOp(interface, data, flags, ++p, desc); - } - } - } - - void perform() override - { - GetterSetterRefactoringHelper helper(this, currentFile()->filePath(), m_data.clazz); - helper.performGeneration(m_data, m_generateFlags); - helper.applyChanges(); - } - -private: - int m_generateFlags; - ExistingGetterSetterData m_data; -}; - -int ExistingGetterSetterData::computePossibleFlags() const -{ - const bool isConst = declarationSymbol->type().isConst(); - const bool isStatic = declarationSymbol->type().isStatic(); - using Flag = GenerateGetterSetterOp::GenerateFlag; - int generateFlags = 0; - if (getterName.isEmpty()) - generateFlags |= Flag::GenerateGetter; - if (!isConst) { - if (resetName.isEmpty()) - generateFlags |= Flag::GenerateReset; - if (!isStatic && signalName.isEmpty() && setterName.isEmpty()) - generateFlags |= Flag::GenerateSignal; - if (setterName.isEmpty()) - generateFlags |= Flag::GenerateSetter; - } - if (!isStatic) { - const bool hasSignal = !signalName.isEmpty() || generateFlags & Flag::GenerateSignal; - if (!isConst && hasSignal) - generateFlags |= Flag::GenerateProperty; - } - if (setterName.isEmpty() && signalName.isEmpty()) - generateFlags |= Flag::GenerateConstantProperty; - return generateFlags; -} - -void GetterSetterRefactoringHelper::performGeneration(ExistingGetterSetterData data, int generateFlags) -{ - using Flag = GenerateGetterSetterOp::GenerateFlag; - - if (generateFlags & Flag::GenerateGetter && data.getterName.isEmpty()) { - data.getterName = m_settings->getGetterName(data.qPropertyName); - if (data.getterName == data.memberVariableName) { - data.getterName = "get" + data.memberVariableName.left(1).toUpper() - + data.memberVariableName.mid(1); - } - } - if (generateFlags & Flag::GenerateSetter && data.setterName.isEmpty()) - data.setterName = m_settings->getSetterName(data.qPropertyName); - if (generateFlags & Flag::GenerateSignal && data.signalName.isEmpty()) - data.signalName = m_settings->getSignalName(data.qPropertyName); - if (generateFlags & Flag::GenerateReset && data.resetName.isEmpty()) - data.resetName = m_settings->getResetName(data.qPropertyName); - - FullySpecifiedType memberVariableType = data.declarationSymbol->type(); - memberVariableType.setConst(false); - const bool isMemberVariableStatic = memberVariableType.isStatic(); - memberVariableType.setStatic(false); - Overview overview = CppCodeStyleSettings::currentProjectCodeStyleOverview(); - overview.showTemplateParameters = false; - // TODO does not work with using. e.g. 'using foo = std::unique_ptr' - // TODO must be fully qualified - auto getSetTemplate = m_settings->findGetterSetterTemplate(overview.prettyType(memberVariableType)); - overview.showTemplateParameters = true; - - // Ok... - If a type is a Named type we have to search recusive for the real type - const bool isValueType = this->isValueType(memberVariableType, - data.declarationSymbol->enclosingScope()); - const FullySpecifiedType parameterType = isValueType ? memberVariableType - : makeConstRef(memberVariableType); - - QString baseName = memberBaseName(data.memberVariableName); - if (baseName.isEmpty()) - baseName = data.memberVariableName; - - const QString parameterName = m_settings->getSetterParameterName(baseName); - if (parameterName == data.memberVariableName) - data.memberVariableName = "this->" + data.memberVariableName; - - getSetTemplate.replacePlaceholders(data.memberVariableName, parameterName); - - using Pattern = CppQuickFixSettings::GetterSetterTemplate; - std::optional returnTypeTemplateParameter; - if (getSetTemplate.returnTypeTemplate.has_value()) { - QString returnTypeTemplate = getSetTemplate.returnTypeTemplate.value(); - if (returnTypeTemplate.contains(Pattern::TEMPLATE_PARAMETER_PATTERN)) { - returnTypeTemplateParameter = getFirstTemplateParameter(data.declarationSymbol->type()); - if (!returnTypeTemplateParameter.has_value()) - return; // Maybe report error to the user - } - } - const FullySpecifiedType returnTypeHeader = [&] { - if (!getSetTemplate.returnTypeTemplate.has_value()) - return m_settings->returnByConstRef ? parameterType : memberVariableType; - QString typeTemplate = getSetTemplate.returnTypeTemplate.value(); - if (returnTypeTemplateParameter.has_value()) - typeTemplate.replace(Pattern::TEMPLATE_PARAMETER_PATTERN, - overview.prettyType(returnTypeTemplateParameter.value())); - if (typeTemplate.contains(Pattern::TYPE_PATTERN)) - typeTemplate.replace(Pattern::TYPE_PATTERN, - overview.prettyType(data.declarationSymbol->type())); - Control *control = m_operation->currentFile()->cppDocument()->control(); - std::string utf8TypeName = typeTemplate.toUtf8().toStdString(); - return FullySpecifiedType(control->namedType(control->identifier(utf8TypeName.c_str()))); - }(); - - // getter declaration - if (generateFlags & Flag::GenerateGetter) { - // maybe we added 'this->' to memberVariableName because of a collision with parameterName - // but here the 'this->' is not needed - const QString returnExpression = QString{getSetTemplate.returnExpression}.replace("this->", - ""); - QString getterInClassDeclaration = overview.prettyType(returnTypeHeader, data.getterName) - + QLatin1String("()"); - if (isMemberVariableStatic) - getterInClassDeclaration.prepend(QLatin1String("static ")); - else - getterInClassDeclaration += QLatin1String(" const"); - getterInClassDeclaration.prepend(m_settings->getterAttributes + QLatin1Char(' ')); - - auto getterLocation = m_settings->determineGetterLocation(1); - // if we have an anonymous class we must add code inside the class - if (data.clazz->name()->asAnonymousNameId()) - getterLocation = CppQuickFixSettings::FunctionLocation::InsideClass; - - if (getterLocation == CppQuickFixSettings::FunctionLocation::InsideClass) { - getterInClassDeclaration += QLatin1String("\n{\nreturn ") + returnExpression - + QLatin1String(";\n}\n"); - } else { - getterInClassDeclaration += QLatin1String(";\n"); - } - addHeaderCode(InsertionPointLocator::Public, getterInClassDeclaration); - if (getterLocation == CppQuickFixSettings::FunctionLocation::CppFile && !hasSourceFile()) - getterLocation = CppQuickFixSettings::FunctionLocation::OutsideClass; - - if (getterLocation != CppQuickFixSettings::FunctionLocation::InsideClass) { - const auto getReturnTypeAt = [&](CppRefactoringFilePtr targetFile, - InsertionLocation targetLoc) { - if (getSetTemplate.returnTypeTemplate.has_value()) { - QString returnType = getSetTemplate.returnTypeTemplate.value(); - if (returnTypeTemplateParameter.has_value()) { - const QString templateTypeName = overview.prettyType(typeAt( - returnTypeTemplateParameter.value(), data.clazz, targetFile, targetLoc)); - returnType.replace(Pattern::TEMPLATE_PARAMETER_PATTERN, templateTypeName); - } - if (returnType.contains(Pattern::TYPE_PATTERN)) { - const QString declarationType = overview.prettyType( - typeAt(memberVariableType, data.clazz, targetFile, targetLoc)); - returnType.replace(Pattern::TYPE_PATTERN, declarationType); - } - Control *control = m_operation->currentFile()->cppDocument()->control(); - std::string utf8String = returnType.toUtf8().toStdString(); - return FullySpecifiedType( - control->namedType(control->identifier(utf8String.c_str()))); - } else { - FullySpecifiedType returnType = typeAt(memberVariableType, - data.clazz, - targetFile, - targetLoc); - if (m_settings->returnByConstRef && !isValueType) - return makeConstRef(returnType); - return returnType; - } - }; - const QString constSpec = isMemberVariableStatic ? QLatin1String("") - : QLatin1String(" const"); - if (getterLocation == CppQuickFixSettings::FunctionLocation::CppFile) { - InsertionLocation loc = sourceLocationFor(data.declarationSymbol); - FullySpecifiedType returnType; - QString clazz; - if (m_settings->rewriteTypesinCppFile()) { - returnType = getReturnTypeAt(m_sourceFile, loc); - clazz = symbolAt(data.clazz, m_sourceFile, loc); - } else { - returnType = returnTypeHeader; - const Identifier *identifier = data.clazz->name()->identifier(); - clazz = QString::fromUtf8(identifier->chars(), identifier->size()); - } - const QString code = overview.prettyType(returnType, clazz + "::" + data.getterName) - + "()" + constSpec + "\n{\nreturn " + returnExpression + ";\n}"; - addSourceFileCode(code); - } else if (getterLocation == CppQuickFixSettings::FunctionLocation::OutsideClass) { - InsertionLocation loc - = insertLocationForMethodDefinition(data.declarationSymbol, - false, - NamespaceHandling::Ignore, - m_changes, - m_headerFile->filePath()); - const FullySpecifiedType returnType = getReturnTypeAt(m_headerFile, loc); - const QString clazz = symbolAt(data.clazz, m_headerFile, loc); - QString code = overview.prettyType(returnType, clazz + "::" + data.getterName) - + "()" + constSpec + "\n{\nreturn " + returnExpression + ";\n}"; - if (m_isHeaderHeaderFile) - code.prepend("inline "); - insertAndIndent(m_headerFile, loc, code); - } - } - } - - // setter declaration - InsertionPointLocator::AccessSpec setterAccessSpec = InsertionPointLocator::Public; - if (m_settings->setterAsSlot) { - const QByteArray connectName = "connect"; - const Identifier connectId(connectName.data(), connectName.size()); - const QList items = m_operation->context().lookup(&connectId, data.clazz); - for (const LookupItem &item : items) { - if (item.declaration() && item.declaration()->enclosingClass() - && overview.prettyName(item.declaration()->enclosingClass()->name()) - == "QObject") { - setterAccessSpec = InsertionPointLocator::PublicSlot; - break; - } - } - } - const auto createSetterBodyWithSignal = [this, &getSetTemplate, &data] { - QString body; - QTextStream setter(&body); - setter << "if (" << getSetTemplate.equalComparison << ")\nreturn;\n"; - - setter << getSetTemplate.assignment << ";\n"; - if (m_settings->signalWithNewValue) - setter << "emit " << data.signalName << "(" << getSetTemplate.returnExpression << ");\n"; - else - setter << "emit " << data.signalName << "();\n"; - - return body; - }; - if (generateFlags & Flag::GenerateSetter) { - QString headerDeclaration = "void " + data.setterName + '(' - + overview.prettyType(addConstToReference(parameterType), - parameterName) - + ")"; - if (isMemberVariableStatic) - headerDeclaration.prepend("static "); - QString body = "\n{\n"; - if (data.signalName.isEmpty()) - body += getSetTemplate.assignment + ";\n"; - else - body += createSetterBodyWithSignal(); - - body += "}"; - - auto setterLocation = m_settings->determineSetterLocation(body.count('\n') - 2); - // if we have an anonymous class we must add code inside the class - if (data.clazz->name()->asAnonymousNameId()) - setterLocation = CppQuickFixSettings::FunctionLocation::InsideClass; - - if (setterLocation == CppQuickFixSettings::FunctionLocation::CppFile && !hasSourceFile()) - setterLocation = CppQuickFixSettings::FunctionLocation::OutsideClass; - - if (setterLocation == CppQuickFixSettings::FunctionLocation::InsideClass) { - headerDeclaration += body; - } else { - headerDeclaration += ";\n"; - if (setterLocation == CppQuickFixSettings::FunctionLocation::CppFile) { - InsertionLocation loc = sourceLocationFor(data.declarationSymbol); - QString clazz; - FullySpecifiedType newParameterType = parameterType; - if (m_settings->rewriteTypesinCppFile()) { - newParameterType = typeAt(memberVariableType, data.clazz, m_sourceFile, loc); - if (!isValueType) - newParameterType = makeConstRef(newParameterType); - clazz = symbolAt(data.clazz, m_sourceFile, loc); - } else { - const Identifier *identifier = data.clazz->name()->identifier(); - clazz = QString::fromUtf8(identifier->chars(), identifier->size()); - } - newParameterType = addConstToReference(newParameterType); - const QString code = "void " + clazz + "::" + data.setterName + '(' - + overview.prettyType(newParameterType, parameterName) + ')' - + body; - addSourceFileCode(code); - } else if (setterLocation == CppQuickFixSettings::FunctionLocation::OutsideClass) { - InsertionLocation loc - = insertLocationForMethodDefinition(data.declarationSymbol, - false, - NamespaceHandling::Ignore, - m_changes, - m_headerFile->filePath()); - - FullySpecifiedType newParameterType = typeAt(data.declarationSymbol->type(), - data.clazz, - m_headerFile, - loc); - if (!isValueType) - newParameterType = makeConstRef(newParameterType); - newParameterType = addConstToReference(newParameterType); - QString clazz = symbolAt(data.clazz, m_headerFile, loc); - - QString code = "void " + clazz + "::" + data.setterName + '(' - + overview.prettyType(newParameterType, parameterName) + ')' + body; - if (m_isHeaderHeaderFile) - code.prepend("inline "); - insertAndIndent(m_headerFile, loc, code); - } - } - addHeaderCode(setterAccessSpec, headerDeclaration); - } - - // reset declaration - if (generateFlags & Flag::GenerateReset) { - QString headerDeclaration = "void " + data.resetName + "()"; - if (isMemberVariableStatic) - headerDeclaration.prepend("static "); - QString body = "\n{\n"; - if (!data.setterName.isEmpty()) { - body += data.setterName + "({}); // TODO: Adapt to use your actual default value\n"; - } else { - body += "static $TYPE defaultValue{}; " - "// TODO: Adapt to use your actual default value\n"; - if (data.signalName.isEmpty()) - body += getSetTemplate.assignment + ";\n"; - else - body += createSetterBodyWithSignal(); - } - body += "}"; - - // the template use as new value name, but we want to use 'defaultValue' - body.replace(QRegularExpression("\\b" + parameterName + "\\b"), "defaultValue"); - // body.count('\n') - 2 : do not count the 2 at start - auto resetLocation = m_settings->determineSetterLocation(body.count('\n') - 2); - // if we have an anonymous class we must add code inside the class - if (data.clazz->name()->asAnonymousNameId()) - resetLocation = CppQuickFixSettings::FunctionLocation::InsideClass; - - if (resetLocation == CppQuickFixSettings::FunctionLocation::CppFile && !hasSourceFile()) - resetLocation = CppQuickFixSettings::FunctionLocation::OutsideClass; - - if (resetLocation == CppQuickFixSettings::FunctionLocation::InsideClass) { - headerDeclaration += body.replace("$TYPE", overview.prettyType(memberVariableType)); - } else { - headerDeclaration += ";\n"; - if (resetLocation == CppQuickFixSettings::FunctionLocation::CppFile) { - const InsertionLocation loc = sourceLocationFor(data.declarationSymbol); - QString clazz; - FullySpecifiedType type = memberVariableType; - if (m_settings->rewriteTypesinCppFile()) { - type = typeAt(memberVariableType, data.clazz, m_sourceFile, loc); - clazz = symbolAt(data.clazz, m_sourceFile, loc); - } else { - const Identifier *identifier = data.clazz->name()->identifier(); - clazz = QString::fromUtf8(identifier->chars(), identifier->size()); - } - const QString code = "void " + clazz + "::" + data.resetName + "()" - + body.replace("$TYPE", overview.prettyType(type)); - addSourceFileCode(code); - } else if (resetLocation == CppQuickFixSettings::FunctionLocation::OutsideClass) { - const InsertionLocation loc = insertLocationForMethodDefinition( - data.declarationSymbol, - false, - NamespaceHandling::Ignore, - m_changes, - m_headerFile->filePath()); - const FullySpecifiedType type = typeAt(data.declarationSymbol->type(), - data.clazz, - m_headerFile, - loc); - const QString clazz = symbolAt(data.clazz, m_headerFile, loc); - QString code = "void " + clazz + "::" + data.resetName + "()" - + body.replace("$TYPE", overview.prettyType(type)); - if (m_isHeaderHeaderFile) - code.prepend("inline "); - insertAndIndent(m_headerFile, loc, code); - } - } - addHeaderCode(setterAccessSpec, headerDeclaration); - } - - // signal declaration - if (generateFlags & Flag::GenerateSignal) { - const auto ¶meter = overview.prettyType(returnTypeHeader, data.qPropertyName); - const QString newValue = m_settings->signalWithNewValue ? parameter : QString(); - const QString declaration = QString("void %1(%2);\n").arg(data.signalName, newValue); - addHeaderCode(InsertionPointLocator::Signals, declaration); - } - - // member variable - if (generateFlags & Flag::GenerateMemberVariable) { - QString storageDeclaration = overview.prettyType(memberVariableType, data.memberVariableName); - if (memberVariableType->asPointerType() - && m_operation->semanticInfo().doc->translationUnit()->languageFeatures().cxx11Enabled) { - storageDeclaration.append(" = nullptr"); - } - storageDeclaration.append(";\n"); - addHeaderCode(InsertionPointLocator::Private, storageDeclaration); - } - - // Q_PROPERTY - if (generateFlags & Flag::GenerateProperty || generateFlags & Flag::GenerateConstantProperty) { - // Use the returnTypeHeader as base because of custom types in getSetTemplates. - // Remove const reference from type. - FullySpecifiedType type = returnTypeHeader; - if (ReferenceType *ref = type.type()->asReferenceType()) - type = ref->elementType(); - type.setConst(false); - - QString propertyDeclaration = QLatin1String("Q_PROPERTY(") - + overview.prettyType(type, - memberBaseName(data.memberVariableName)); - bool needMember = false; - if (data.getterName.isEmpty()) - needMember = true; - else - propertyDeclaration += QLatin1String(" READ ") + data.getterName; - if (generateFlags & Flag::GenerateConstantProperty) { - if (needMember) - propertyDeclaration += QLatin1String(" MEMBER ") + data.memberVariableName; - propertyDeclaration.append(QLatin1String(" CONSTANT")); - } else { - if (data.setterName.isEmpty()) { - needMember = true; - } else if (!getSetTemplate.returnTypeTemplate.has_value()) { - // if the return type of the getter and then Q_PROPERTY is different than - // the setter type, we should not add WRITE to the Q_PROPERTY - propertyDeclaration.append(QLatin1String(" WRITE ")).append(data.setterName); - } - if (needMember) - propertyDeclaration += QLatin1String(" MEMBER ") + data.memberVariableName; - if (!data.resetName.isEmpty()) - propertyDeclaration += QLatin1String(" RESET ") + data.resetName; - propertyDeclaration.append(QLatin1String(" NOTIFY ")).append(data.signalName); - } - - propertyDeclaration.append(QLatin1String(" FINAL)\n")); - addHeaderCode(InsertionPointLocator::Private, propertyDeclaration); - } -} - -QStringList toStringList(const QList names) -{ - QStringList list; - list.reserve(names.size()); - for (const auto symbol : names) { - const Identifier *const id = symbol->identifier(); - list << QString::fromUtf8(id->chars(), id->size()); - } - return list; -} - -void findExistingFunctions(ExistingGetterSetterData &existing, QStringList memberFunctionNames) -{ - const CppQuickFixSettings *settings = CppQuickFixProjectsSettings::getQuickFixSettings( - ProjectExplorer::ProjectTree::currentProject()); - const QString lowerBaseName = memberBaseName(existing.memberVariableName).toLower(); - const QStringList getterNames{lowerBaseName, - "get_" + lowerBaseName, - "get" + lowerBaseName, - "is_" + lowerBaseName, - "is" + lowerBaseName, - settings->getGetterName(lowerBaseName)}; - const QStringList setterNames{"set_" + lowerBaseName, - "set" + lowerBaseName, - settings->getSetterName(lowerBaseName)}; - const QStringList resetNames{"reset_" + lowerBaseName, - "reset" + lowerBaseName, - settings->getResetName(lowerBaseName)}; - const QStringList signalNames{lowerBaseName + "_changed", - lowerBaseName + "changed", - settings->getSignalName(lowerBaseName)}; - for (const auto &memberFunctionName : memberFunctionNames) { - const QString lowerName = memberFunctionName.toLower(); - if (getterNames.contains(lowerName)) - existing.getterName = memberFunctionName; - else if (setterNames.contains(lowerName)) - existing.setterName = memberFunctionName; - else if (resetNames.contains(lowerName)) - existing.resetName = memberFunctionName; - else if (signalNames.contains(lowerName)) - existing.signalName = memberFunctionName; - } -} - -QList getMemberFunctions(const Class *clazz) -{ - QList memberFunctions; - for (auto it = clazz->memberBegin(); it != clazz->memberEnd(); ++it) { - Symbol *const s = *it; - if (!s->identifier() || !s->type()) - continue; - if ((s->asDeclaration() && s->type()->asFunctionType()) || s->asFunction()) - memberFunctions << s; - } - return memberFunctions; -} - -} // anonymous namespace - -void GenerateGetterSetter::doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) -{ - ExistingGetterSetterData existing; - - const QList &path = interface.path(); - // We expect something like - // [0] TranslationUnitAST - // [1] NamespaceAST - // [2] LinkageBodyAST - // [3] SimpleDeclarationAST - // [4] ClassSpecifierAST - // [5] SimpleDeclarationAST - // [6] DeclaratorAST - // [7] DeclaratorIdAST - // [8] SimpleNameAST - - const int n = path.size(); - if (n < 6) - return; - - int i = 1; - const auto variableNameAST = path.at(n - i++)->asSimpleName(); - const auto declaratorId = path.at(n - i++)->asDeclaratorId(); - // DeclaratorAST might be preceded by PointerAST, e.g. for the case - // "class C { char *@s; };", where '@' denotes the text cursor position. - auto declarator = path.at(n - i++)->asDeclarator(); - if (!declarator) { - --i; - if (path.at(n - i++)->asPointer()) { - if (n < 7) - return; - declarator = path.at(n - i++)->asDeclarator(); - } - if (!declarator) - return; - } - const auto variableDecl = path.at(n - i++)->asSimpleDeclaration(); - const auto classSpecifier = path.at(n - i++)->asClassSpecifier(); - const auto classDecl = path.at(n - i++)->asSimpleDeclaration(); - - if (!(variableNameAST && declaratorId && variableDecl && classSpecifier && classDecl)) - return; - - // Do not get triggered on member functconstions and arrays - if (declarator->postfix_declarator_list) { - return; - } - - // Construct getter and setter names - const Name *variableName = variableNameAST->name; - if (!variableName) { - return; - } - const Identifier *variableId = variableName->identifier(); - if (!variableId) { - return; - } - existing.memberVariableName = QString::fromUtf8(variableId->chars(), variableId->size()); - - // Find the right symbol (for typeName) in the simple declaration - Symbol *symbol = nullptr; - const List *symbols = variableDecl->symbols; - QTC_ASSERT(symbols, return ); - for (; symbols; symbols = symbols->next) { - Symbol *s = symbols->value; - if (const Name *name = s->name()) { - if (const Identifier *id = name->identifier()) { - const QString symbolName = QString::fromUtf8(id->chars(), id->size()); - if (symbolName == existing.memberVariableName) { - symbol = s; - break; - } - } - } - } - if (!symbol) { - // no type can be determined - return; - } - if (!symbol->asDeclaration()) { - return; - } - existing.declarationSymbol = symbol->asDeclaration(); - - existing.clazz = classSpecifier->symbol; - if (!existing.clazz) - return; - - auto file = interface.currentFile(); - // check if a Q_PROPERTY exist - const QString baseName = memberBaseName(existing.memberVariableName); - // eg: we have 'int m_test' and now 'Q_PROPERTY(int foo WRITE setTest MEMBER m_test NOTIFY tChanged)' - for (auto it = classSpecifier->member_specifier_list; it; it = it->next) { - if (it->value->asQtPropertyDeclaration()) { - auto propDecl = it->value->asQtPropertyDeclaration(); - // iterator over 'READ ...', ... - auto p = propDecl->property_declaration_item_list; - // first check, if we have a MEMBER and the member is equal to the baseName - for (; p; p = p->next) { - const char *tokenString = file->tokenAt(p->value->item_name_token).spell(); - if (!qstrcmp(tokenString, "MEMBER")) { - if (baseName == file->textOf(p->value->expression)) - return; - } - } - // no MEMBER, but maybe the property name is the same - const QString propertyName = file->textOf(propDecl->property_name); - // we compare the baseName. e.g. 'test' instead of 'm_test' - if (propertyName == baseName) - return; // TODO Maybe offer quick fix "Add missing Q_PROPERTY Members" - } - } - - findExistingFunctions(existing, toStringList(getMemberFunctions(existing.clazz))); - existing.qPropertyName = memberBaseName(existing.memberVariableName); - - const int possibleFlags = existing.computePossibleFlags(); - GenerateGetterSetterOp::generateQuickFixes(result, interface, existing, possibleFlags); -} - -class MemberInfo -{ -public: - MemberInfo(ExistingGetterSetterData data, int possibleFlags) - : data(data) - , possibleFlags(possibleFlags) - {} - - ExistingGetterSetterData data; - int possibleFlags; - int requestedFlags = 0; -}; -using GetterSetterCandidates = std::vector; - -class CandidateTreeItem : public TreeItem -{ -public: - enum Column { - NameColumn, - GetterColumn, - SetterColumn, - SignalColumn, - ResetColumn, - QPropertyColumn, - ConstantQPropertyColumn - }; - using Flag = GenerateGetterSetterOp::GenerateFlag; - constexpr static Flag ColumnFlag[] = { - Flag::Invalid, - Flag::GenerateGetter, - Flag::GenerateSetter, - Flag::GenerateSignal, - Flag::GenerateReset, - Flag::GenerateProperty, - Flag::GenerateConstantProperty, - }; - - CandidateTreeItem(MemberInfo *memberInfo) - : m_memberInfo(memberInfo) - {} - -private: - QVariant data(int column, int role) const override - { - if (role == Qt::DisplayRole && column == NameColumn) - return m_memberInfo->data.memberVariableName; - if (role == Qt::CheckStateRole && column > 0 - && column <= static_cast(std::size(ColumnFlag))) { - return m_memberInfo->requestedFlags & ColumnFlag[column] ? Qt::Checked : Qt::Unchecked; - } - return {}; - } - - bool setData(int column, const QVariant &data, int role) override - { - if (column < 1 || column > static_cast(std::size(ColumnFlag))) - return false; - if (role != Qt::CheckStateRole) - return false; - if (!(m_memberInfo->possibleFlags & ColumnFlag[column])) - return false; - const bool nowChecked = data.toInt() == Qt::Checked; - if (nowChecked) - m_memberInfo->requestedFlags |= ColumnFlag[column]; - else - m_memberInfo->requestedFlags &= ~ColumnFlag[column]; - - if (nowChecked) { - if (column == QPropertyColumn) { - m_memberInfo->requestedFlags |= Flag::GenerateGetter; - m_memberInfo->requestedFlags |= Flag::GenerateSetter; - m_memberInfo->requestedFlags |= Flag::GenerateSignal; - m_memberInfo->requestedFlags &= ~Flag::GenerateConstantProperty; - } else if (column == ConstantQPropertyColumn) { - m_memberInfo->requestedFlags |= Flag::GenerateGetter; - m_memberInfo->requestedFlags &= ~Flag::GenerateSetter; - m_memberInfo->requestedFlags &= ~Flag::GenerateSignal; - m_memberInfo->requestedFlags &= ~Flag::GenerateReset; - m_memberInfo->requestedFlags &= ~Flag::GenerateProperty; - } else if (column == SetterColumn || column == SignalColumn || column == ResetColumn) { - m_memberInfo->requestedFlags &= ~Flag::GenerateConstantProperty; - } - } else { - if (column == SignalColumn) - m_memberInfo->requestedFlags &= ~Flag::GenerateProperty; - } - for (int i = 0; i < 16; ++i) { - const bool allowed = m_memberInfo->possibleFlags & (1 << i); - if (!allowed) - m_memberInfo->requestedFlags &= ~(1 << i); // clear bit - } - update(); - return true; - } - - Qt::ItemFlags flags(int column) const override - { - if (column == NameColumn) - return Qt::ItemIsEnabled; - if (column < 1 || column > static_cast(std::size(ColumnFlag))) - return {}; - if (m_memberInfo->possibleFlags & ColumnFlag[column]) - return Qt::ItemIsEnabled | Qt::ItemIsUserCheckable; - return {}; - } - - MemberInfo *const m_memberInfo; -}; - -class GenerateGettersSettersDialog : public QDialog -{ - static constexpr CandidateTreeItem::Column CheckBoxColumn[4] - = {CandidateTreeItem::Column::GetterColumn, - CandidateTreeItem::Column::SetterColumn, - CandidateTreeItem::Column::SignalColumn, - CandidateTreeItem::Column::QPropertyColumn}; - -public: - GenerateGettersSettersDialog(const GetterSetterCandidates &candidates) - : QDialog() - , m_candidates(candidates) - { - using Flags = GenerateGetterSetterOp::GenerateFlag; - setWindowTitle(Tr::tr("Getters and Setters")); - const auto model = new TreeModel(this); - model->setHeader(QStringList({ - Tr::tr("Member"), - Tr::tr("Getter"), - Tr::tr("Setter"), - Tr::tr("Signal"), - Tr::tr("Reset"), - Tr::tr("QProperty"), - Tr::tr("Constant QProperty"), - })); - for (MemberInfo &candidate : m_candidates) - model->rootItem()->appendChild(new CandidateTreeItem(&candidate)); - const auto view = new BaseTreeView(this); - view->setModel(model); - int optimalWidth = 0; - for (int i = 0; i < model->columnCount(QModelIndex{}); ++i) { - view->resizeColumnToContents(i); - optimalWidth += view->columnWidth(i); - } - - const auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); - connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); - - const auto setCheckStateForAll = [model](int column, int checkState) { - for (int i = 0; i < model->rowCount(); ++i) - model->setData(model->index(i, column), checkState, Qt::CheckStateRole); - }; - const auto preventPartiallyChecked = [](QCheckBox *checkbox) { - if (checkbox->checkState() == Qt::PartiallyChecked) - checkbox->setCheckState(Qt::Checked); - }; - using Column = CandidateTreeItem::Column; - const auto createConnections = [this, setCheckStateForAll, preventPartiallyChecked]( - QCheckBox *checkbox, Column column) { - connect(checkbox, &QCheckBox::stateChanged, this, [setCheckStateForAll, column](int state) { - if (state != Qt::PartiallyChecked) - setCheckStateForAll(column, state); - }); - connect(checkbox, &QCheckBox::clicked, this, [checkbox, preventPartiallyChecked] { - preventPartiallyChecked(checkbox); - }); - }; - std::array checkBoxes = {}; - - static_assert(std::size(CheckBoxColumn) == checkBoxes.size(), - "Must contain the same number of elements"); - for (std::size_t i = 0; i < checkBoxes.size(); ++i) { - if (Utils::anyOf(candidates, [i](const MemberInfo &mi) { - return mi.possibleFlags & CandidateTreeItem::ColumnFlag[CheckBoxColumn[i]]; - })) { - const Column column = CheckBoxColumn[i]; - if (column == Column::GetterColumn) - checkBoxes[i] = new QCheckBox(Tr::tr("Create getters for all members")); - else if (column == Column::SetterColumn) - checkBoxes[i] = new QCheckBox(Tr::tr("Create setters for all members")); - else if (column == Column::SignalColumn) - checkBoxes[i] = new QCheckBox(Tr::tr("Create signals for all members")); - else if (column == Column::QPropertyColumn) - checkBoxes[i] = new QCheckBox(Tr::tr("Create Q_PROPERTY for all members")); - - createConnections(checkBoxes[i], column); - } - } - connect(model, &QAbstractItemModel::dataChanged, this, [this, checkBoxes] { - const auto countExisting = [this](Flags flag) { - return Utils::count(m_candidates, [flag](const MemberInfo &mi) { - return !(mi.possibleFlags & flag); - }); - }; - const auto countRequested = [this](Flags flag) { - return Utils::count(m_candidates, [flag](const MemberInfo &mi) { - return mi.requestedFlags & flag; - }); - }; - const auto countToState = [this](int requestedCount, int alreadyExistsCount) { - if (requestedCount == 0) - return Qt::Unchecked; - if (int(m_candidates.size()) - requestedCount == alreadyExistsCount) - return Qt::Checked; - return Qt::PartiallyChecked; - }; - for (std::size_t i = 0; i < checkBoxes.size(); ++i) { - if (checkBoxes[i]) { - const Flags flag = CandidateTreeItem::ColumnFlag[CheckBoxColumn[i]]; - checkBoxes[i]->setCheckState( - countToState(countRequested(flag), countExisting(flag))); - } - } - }); - - const auto mainLayout = new QVBoxLayout(this); - mainLayout->addWidget(new QLabel(Tr::tr("Select the getters and setters " - "to be created."))); - for (auto checkBox : checkBoxes) { - if (checkBox) - mainLayout->addWidget(checkBox); - } - mainLayout->addWidget(view); - mainLayout->addWidget(buttonBox); - int left, right; - mainLayout->getContentsMargins(&left, nullptr, &right, nullptr); - optimalWidth += left + right; - resize(optimalWidth, mainLayout->sizeHint().height()); - } - - GetterSetterCandidates candidates() const { return m_candidates; } - -private: - GetterSetterCandidates m_candidates; -}; - -class GenerateGettersSettersOperation : public CppQuickFixOperation -{ -public: - GenerateGettersSettersOperation(const CppQuickFixInterface &interface) - : CppQuickFixOperation(interface) - { - setDescription(Tr::tr("Create Getter and Setter Member Functions")); - - m_classAST = astForClassOperations(interface); - if (!m_classAST) - return; - Class * const theClass = m_classAST->symbol; - if (!theClass) - return; - - // Go through all data members and try to find out whether they have getters and/or setters. - QList dataMembers; - QList memberFunctions; - for (auto it = theClass->memberBegin(); it != theClass->memberEnd(); ++it) { - Symbol *const s = *it; - if (!s->identifier() || !s->type() || s->type().isTypedef()) - continue; - if ((s->asDeclaration() && s->type()->asFunctionType()) || s->asFunction()) - memberFunctions << s; - else if (s->asDeclaration() && (s->isPrivate() || s->isProtected())) - dataMembers << s; - } - - auto file = interface.currentFile(); - QStringList qPropertyNames; // name after MEMBER or name of the property - for (auto it = m_classAST->member_specifier_list; it; it = it->next) { - if (it->value->asQtPropertyDeclaration()) { - auto propDecl = it->value->asQtPropertyDeclaration(); - // iterator over 'READ ...', ... and check if we have a MEMBER - for (auto p = propDecl->property_declaration_item_list; p; p = p->next) { - const char *tokenString = file->tokenAt(p->value->item_name_token).spell(); - if (!qstrcmp(tokenString, "MEMBER")) - qPropertyNames << file->textOf(p->value->expression); - } - // no MEMBER, but maybe the property name is the same - qPropertyNames << file->textOf(propDecl->property_name); - } - } - const QStringList memberFunctionsAsStrings = toStringList(memberFunctions); - - for (Symbol *const member : std::as_const(dataMembers)) { - ExistingGetterSetterData existing; - existing.memberVariableName = QString::fromUtf8(member->identifier()->chars(), - member->identifier()->size()); - existing.declarationSymbol = member->asDeclaration(); - existing.clazz = theClass; - - // check if a Q_PROPERTY exist - const QString baseName = memberBaseName(existing.memberVariableName); - if (qPropertyNames.contains(baseName) - || qPropertyNames.contains(existing.memberVariableName)) - continue; - - findExistingFunctions(existing, memberFunctionsAsStrings); - existing.qPropertyName = baseName; - - int possibleFlags = existing.computePossibleFlags(); - if (possibleFlags == 0) - continue; - m_candidates.emplace_back(existing, possibleFlags); - } - } - - GetterSetterCandidates candidates() const { return m_candidates; } - bool isApplicable() const { return !m_candidates.empty(); } - - void setGetterSetterData(const GetterSetterCandidates &data) - { - m_candidates = data; - m_hasData = true; - } - -private: - void perform() override - { - if (!m_hasData) { - GenerateGettersSettersDialog dlg(m_candidates); - if (dlg.exec() == QDialog::Rejected) - return; - m_candidates = dlg.candidates(); - } - if (m_candidates.empty()) - return; - GetterSetterRefactoringHelper helper(this, - currentFile()->filePath(), - m_candidates.front().data.clazz); - for (MemberInfo &mi : m_candidates) { - if (mi.requestedFlags != 0) { - helper.performGeneration(mi.data, mi.requestedFlags); - } - } - helper.applyChanges(); - } - - GetterSetterCandidates m_candidates; - const ClassSpecifierAST *m_classAST = nullptr; - bool m_hasData = false; -}; - -void GenerateGettersSettersForClass::doMatch(const CppQuickFixInterface &interface, - QuickFixOperations &result) -{ - const auto op = QSharedPointer::create(interface); - if (!op->isApplicable()) - return; - if (m_test) { - GetterSetterCandidates candidates = op->candidates(); - for (MemberInfo &mi : candidates) { - mi.requestedFlags = mi.possibleFlags; - using Flag = GenerateGetterSetterOp::GenerateFlag; - mi.requestedFlags &= ~Flag::GenerateConstantProperty; - } - op->setGetterSetterData(candidates); - } - result << op; -} - - namespace { class ExtractFunctionOptions @@ -6301,164 +4857,6 @@ void ConvertFromAndToPointer::doMatch(const CppQuickFixInterface &interface, namespace { -void extractNames(const CppRefactoringFilePtr &file, - QtPropertyDeclarationAST *qtPropertyDeclaration, - ExistingGetterSetterData &data) -{ - QtPropertyDeclarationItemListAST *it = qtPropertyDeclaration->property_declaration_item_list; - for (; it; it = it->next) { - const char *tokenString = file->tokenAt(it->value->item_name_token).spell(); - if (!qstrcmp(tokenString, "READ")) { - data.getterName = file->textOf(it->value->expression); - } else if (!qstrcmp(tokenString, "WRITE")) { - data.setterName = file->textOf(it->value->expression); - } else if (!qstrcmp(tokenString, "RESET")) { - data.resetName = file->textOf(it->value->expression); - } else if (!qstrcmp(tokenString, "NOTIFY")) { - data.signalName = file->textOf(it->value->expression); - } else if (!qstrcmp(tokenString, "MEMBER")) { - data.memberVariableName = file->textOf(it->value->expression); - } - } -} - -} // anonymous namespace - -void InsertQtPropertyMembers::doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) -{ - using Flag = GenerateGetterSetterOp::GenerateFlag; - ExistingGetterSetterData existing; - // check for Q_PROPERTY - - const QList &path = interface.path(); - if (path.isEmpty()) - return; - - AST *const ast = path.last(); - QtPropertyDeclarationAST *qtPropertyDeclaration = ast->asQtPropertyDeclaration(); - if (!qtPropertyDeclaration || !qtPropertyDeclaration->type_id) - return; - - ClassSpecifierAST *klass = nullptr; - for (int i = path.size() - 2; i >= 0; --i) { - klass = path.at(i)->asClassSpecifier(); - if (klass) - break; - } - if (!klass) - return; - existing.clazz = klass->symbol; - - CppRefactoringFilePtr file = interface.currentFile(); - const QString propertyName = file->textOf(qtPropertyDeclaration->property_name); - existing.qPropertyName = propertyName; - extractNames(file, qtPropertyDeclaration, existing); - - Control *control = interface.currentFile()->cppDocument()->control(); - - existing.declarationSymbol = control->newDeclaration(ast->firstToken(), - qtPropertyDeclaration->property_name->name); - existing.declarationSymbol->setVisibility(Symbol::Private); - existing.declarationSymbol->setEnclosingScope(existing.clazz); - - { - // create a 'right' Type Object - // if we have Q_PROPERTY(int test ...) then we only get a NamedType for 'int', but we want - // a IntegerType. So create a new dummy file with a dummy declaration to get the right - // object - QByteArray type = file->textOf(qtPropertyDeclaration->type_id).toUtf8(); - QByteArray newSource = file->document() - ->toPlainText() - .insert(file->startOf(qtPropertyDeclaration), - QString::fromUtf8(type + " __dummy;\n")) - .toUtf8(); - - Document::Ptr doc = interface.snapshot().preprocessedDocument(newSource, "___quickfix.h"); - if (!doc->parse(Document::ParseTranlationUnit)) - return; - doc->check(); - class TypeFinder : public ASTVisitor - { - public: - FullySpecifiedType type; - TypeFinder(TranslationUnit *u) - : ASTVisitor(u) - {} - bool visit(SimpleDeclarationAST *ast) override - { - if (ast->symbols && !ast->symbols->next) { - const Name *name = ast->symbols->value->name(); - if (name && name->asNameId() && name->asNameId()->identifier()) { - const Identifier *id = name->asNameId()->identifier(); - if (QString::fromUtf8(id->chars(), id->size()) == "__dummy") - type = ast->symbols->value->type(); - } - } - return true; - } - }; - TypeFinder finder(doc->translationUnit()); - finder.accept(doc->translationUnit()->ast()); - if (finder.type.type()->isUndefinedType()) - return; - existing.declarationSymbol->setType(finder.type); - existing.doc = doc; // to hold type - } - // check which methods are already there - const bool haveFixMemberVariableName = !existing.memberVariableName.isEmpty(); - int generateFlags = Flag::GenerateMemberVariable; - if (!existing.resetName.isEmpty()) - generateFlags |= Flag::GenerateReset; - if (!existing.setterName.isEmpty()) - generateFlags |= Flag::GenerateSetter; - if (!existing.getterName.isEmpty()) - generateFlags |= Flag::GenerateGetter; - if (!existing.signalName.isEmpty()) - generateFlags |= Flag::GenerateSignal; - Overview overview; - for (int i = 0; i < existing.clazz->memberCount(); ++i) { - Symbol *member = existing.clazz->memberAt(i); - FullySpecifiedType type = member->type(); - if (member->asFunction() || (type.isValid() && type->asFunctionType())) { - const QString name = overview.prettyName(member->name()); - if (name == existing.getterName) - generateFlags &= ~Flag::GenerateGetter; - else if (name == existing.setterName) - generateFlags &= ~Flag::GenerateSetter; - else if (name == existing.resetName) - generateFlags &= ~Flag::GenerateReset; - else if (name == existing.signalName) - generateFlags &= ~Flag::GenerateSignal; - } else if (member->asDeclaration()) { - const QString name = overview.prettyName(member->name()); - if (haveFixMemberVariableName) { - if (name == existing.memberVariableName) { - generateFlags &= ~Flag::GenerateMemberVariable; - } - } else { - const QString baseName = memberBaseName(name); - if (existing.qPropertyName == baseName) { - existing.memberVariableName = name; - generateFlags &= ~Flag::GenerateMemberVariable; - } - } - } - } - if (generateFlags & Flag::GenerateMemberVariable) { - CppQuickFixSettings *settings = CppQuickFixProjectsSettings::getQuickFixSettings( - ProjectExplorer::ProjectTree::currentProject()); - existing.memberVariableName = settings->getMemberVariableName(existing.qPropertyName); - } - if (generateFlags == 0) { - // everything is already there - return; - } - generateFlags |= Flag::HaveExistingQProperty; - GenerateGetterSetterOp::generateQuickFixes(result, interface, existing, generateFlags); -} - -namespace { - class ApplyDeclDefLinkOperation : public CppQuickFixOperation { public: @@ -8516,914 +6914,8 @@ void RemoveUsingNamespace::doMatch(const CppQuickFixInterface &interface, QuickF namespace { -struct ParentClassConstructorInfo; -class ConstructorMemberInfo -{ -public: - ConstructorMemberInfo(const QString &name, Symbol *symbol, int numberOfMember) - : memberVariableName(name) - , parameterName(memberBaseName(name)) - , symbol(symbol) - , type(symbol->type()) - , numberOfMember(numberOfMember) - {} - ConstructorMemberInfo(const QString &memberName, - const QString ¶mName, - const QString &defaultValue, - Symbol *symbol, - const ParentClassConstructorInfo *parentClassConstructor) - : parentClassConstructor(parentClassConstructor) - , memberVariableName(memberName) - , parameterName(paramName) - , defaultValue(defaultValue) - , init(defaultValue.isEmpty()) - , symbol(symbol) - , type(symbol->type()) - {} - const ParentClassConstructorInfo *parentClassConstructor = nullptr; - QString memberVariableName; - QString parameterName; - QString defaultValue; - bool init = true; - bool customValueType; // for the generation later - Symbol *symbol; // for the right type later - FullySpecifiedType type; - int numberOfMember; // first member, second member, ... -}; - -class ConstructorParams : public QAbstractTableModel -{ - Q_OBJECT - std::list candidates; - std::vector infos; - - void validateOrder() - { - // parameters with default values must be at the end - bool foundWithDefault = false; - for (auto info : infos) { - if (info->init) { - if (foundWithDefault && info->defaultValue.isEmpty()) { - emit validOrder(false); - return; - } - foundWithDefault |= !info->defaultValue.isEmpty(); - } - } - emit validOrder(true); - } - -public: - enum Column { ShouldInitColumn, MemberNameColumn, ParameterNameColumn, DefaultValueColumn }; - template - void emplaceBackParameter(_Args &&...__args) - { - candidates.emplace_back(std::forward<_Args>(__args)...); - infos.push_back(&candidates.back()); - } - const std::vector &getInfos() const { return infos; } - void addRow(ConstructorMemberInfo *info) - { - beginInsertRows({}, rowCount(), rowCount()); - infos.push_back(info); - endInsertRows(); - validateOrder(); - } - void removeRow(ConstructorMemberInfo *info) - { - for (auto iter = infos.begin(); iter != infos.end(); ++iter) { - if (*iter == info) { - const auto index = iter - infos.begin(); - beginRemoveRows({}, index, index); - infos.erase(iter); - endRemoveRows(); - validateOrder(); - return; - } - } - } - - int selectedCount() const - { - return Utils::count(infos, [](const ConstructorMemberInfo *mi) { - return mi->init && !mi->parentClassConstructor; - }); - } - int memberCount() const - { - return Utils::count(infos, [](const ConstructorMemberInfo *mi) { - return !mi->parentClassConstructor; - }); - } - - int rowCount(const QModelIndex & /*parent*/ = {}) const override { return int(infos.size()); } - int columnCount(const QModelIndex & /*parent*/ = {}) const override { return 4; } - QVariant data(const QModelIndex &index, int role) const override - { - if (index.row() < 0 || index.row() >= rowCount()) - return {}; - if (role == Qt::CheckStateRole && index.column() == ShouldInitColumn - && !infos[index.row()]->parentClassConstructor) - return infos[index.row()]->init ? Qt::Checked : Qt::Unchecked; - if (role == Qt::DisplayRole && index.column() == MemberNameColumn) - return infos[index.row()]->memberVariableName; - if ((role == Qt::DisplayRole || role == Qt::EditRole) - && index.column() == ParameterNameColumn) - return infos[index.row()]->parameterName; - if ((role == Qt::DisplayRole || role == Qt::EditRole) - && index.column() == DefaultValueColumn) - return infos[index.row()]->defaultValue; - if ((role == Qt::ToolTipRole) && index.column() > 0) - return Overview{}.prettyType(infos[index.row()]->symbol->type()); - return {}; - } - bool setData(const QModelIndex &index, const QVariant &value, int role) override - { - if (index.column() == ShouldInitColumn && role == Qt::CheckStateRole) { - if (infos[index.row()]->parentClassConstructor) - return false; - infos[index.row()]->init = value.toInt() == Qt::Checked; - emit dataChanged(this->index(index.row(), 0), this->index(index.row(), columnCount())); - validateOrder(); - return true; - } - if (index.column() == ParameterNameColumn && role == Qt::EditRole) { - infos[index.row()]->parameterName = value.toString(); - return true; - } - if (index.column() == DefaultValueColumn && role == Qt::EditRole) { - infos[index.row()]->defaultValue = value.toString(); - validateOrder(); - return true; - } - return false; - } - Qt::DropActions supportedDropActions() const override { return Qt::MoveAction; } - Qt::ItemFlags flags(const QModelIndex &index) const override - { - if (!index.isValid()) - return Qt::ItemIsSelectable | Qt::ItemIsDropEnabled; - - Qt::ItemFlags f{}; - if (infos[index.row()]->init) { - f |= Qt::ItemIsDragEnabled; - f |= Qt::ItemIsSelectable; - } - - if (index.column() == ShouldInitColumn && !infos[index.row()]->parentClassConstructor) - return f | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable; - if (!infos[index.row()]->init) - return f; - if (index.column() == MemberNameColumn) - return f | Qt::ItemIsEnabled; - if (index.column() == ParameterNameColumn || index.column() == DefaultValueColumn) - return f | Qt::ItemIsEnabled | Qt::ItemIsEditable; - return {}; - } - - QVariant headerData(int section, Qt::Orientation orientation, int role) const override - { - if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { - switch (section) { - case ShouldInitColumn: - return Tr::tr("Initialize in Constructor"); - case MemberNameColumn: - return Tr::tr("Member Name"); - case ParameterNameColumn: - return Tr::tr("Parameter Name"); - case DefaultValueColumn: - return Tr::tr("Default Value"); - } - } - return {}; - } - bool dropMimeData(const QMimeData *data, - Qt::DropAction /*action*/, - int row, - int /*column*/, - const QModelIndex & /*parent*/) override - { - if (row == -1) - row = rowCount(); - bool ok; - int sourceRow = data->data("application/x-qabstractitemmodeldatalist").toInt(&ok); - if (ok) { - if (sourceRow == row || row == sourceRow + 1) - return false; - beginMoveRows({}, sourceRow, sourceRow, {}, row); - infos.insert(infos.begin() + row, infos.at(sourceRow)); - if (row < sourceRow) - ++sourceRow; - infos.erase(infos.begin() + sourceRow); - validateOrder(); - return true; - } - return false; - } - - QMimeData *mimeData(const QModelIndexList &indexes) const override - { - for (const auto &i : indexes) { - if (!i.isValid()) - continue; - auto data = new QMimeData(); - data->setData("application/x-qabstractitemmodeldatalist", - QString::number(i.row()).toLatin1()); - return data; - } - return nullptr; - } - - class TableViewStyle : public QProxyStyle - { - public: - TableViewStyle(QStyle *style) - : QProxyStyle(style) - {} - - void drawPrimitive(PrimitiveElement element, - const QStyleOption *option, - QPainter *painter, - const QWidget *widget) const override - { - if (element == QStyle::PE_IndicatorItemViewItemDrop && !option->rect.isNull()) { - QStyleOption opt(*option); - opt.rect.setLeft(0); - if (widget) - opt.rect.setRight(widget->width()); - QProxyStyle::drawPrimitive(element, &opt, painter, widget); - return; - } - QProxyStyle::drawPrimitive(element, option, painter, widget); - } - }; -signals: - void validOrder(bool valid); -}; - -class TopMarginDelegate : public QStyledItemDelegate -{ -public: - TopMarginDelegate(QObject *parent = nullptr) - : QStyledItemDelegate(parent) - {} - - void paint(QPainter *painter, - const QStyleOptionViewItem &option, - const QModelIndex &index) const override - { - Q_ASSERT(index.isValid()); - QStyleOptionViewItem opt = option; - initStyleOption(&opt, index); - const QWidget *widget = option.widget; - QStyle *style = widget ? widget->style() : QApplication::style(); - if (opt.rect.height() > 20) - opt.rect.adjust(0, 5, 0, 0); - style->drawControl(QStyle::CE_ItemViewItem, &opt, painter, widget); - } -}; - -struct ParentClassConstructorParameter : public ConstructorMemberInfo -{ - QString originalDefaultValue; - QString declaration; // displayed in the treeView - ParentClassConstructorParameter(const QString &name, - const QString &defaultValue, - Symbol *symbol, - const ParentClassConstructorInfo *parentClassConstructor); - - ParentClassConstructorParameter(const ParentClassConstructorParameter &) = delete; - ParentClassConstructorParameter(ParentClassConstructorParameter &&) = default; -}; - -struct ParentClassConstructorInfo -{ - ParentClassConstructorInfo(const QString &name, ConstructorParams &model) - : className(name) - , model(model) - {} - bool useInConstructor = false; - const QString className; - QString declaration; - std::vector parameters; - ConstructorParams &model; - - ParentClassConstructorInfo(const ParentClassConstructorInfo &) = delete; - ParentClassConstructorInfo(ParentClassConstructorInfo &&) = default; - - void addParameter(ParentClassConstructorParameter ¶m) { model.addRow(¶m); } - void removeParameter(ParentClassConstructorParameter ¶m) { model.removeRow(¶m); } - void removeAllParameters() - { - for (auto ¶m : parameters) - model.removeRow(¶m); - } -}; - -ParentClassConstructorParameter::ParentClassConstructorParameter( - const QString &name, - const QString &defaultValue, - Symbol *symbol, - const ParentClassConstructorInfo *parentClassConstructor) - : ConstructorMemberInfo(parentClassConstructor->className + "::" + name, - name, - defaultValue, - symbol, - parentClassConstructor) - , originalDefaultValue(defaultValue) - , declaration(Overview{}.prettyType(symbol->type(), name) - + (defaultValue.isEmpty() ? QString{} : " = " + defaultValue)) -{} - -using ParentClassConstructors = std::vector; - -class ParentClassesModel : public QAbstractItemModel -{ - ParentClassConstructors &constructors; - -public: - ParentClassesModel(QObject *parent, ParentClassConstructors &constructors) - : QAbstractItemModel(parent) - , constructors(constructors) - {} - QModelIndex index(int row, int column, const QModelIndex &parent = {}) const override - { - if (!parent.isValid()) - return createIndex(row, column, nullptr); - if (parent.internalPointer()) - return {}; - auto index = createIndex(row, column, &constructors.at(parent.row())); - return index; - } - QModelIndex parent(const QModelIndex &index) const override - { - if (!index.isValid()) - return {}; - auto *parent = static_cast(index.internalPointer()); - if (!parent) - return {}; - int i = 0; - for (const auto &info : constructors) { - if (&info == parent) - return createIndex(i, 0, nullptr); - ++i; - } - return {}; - } - int rowCount(const QModelIndex &parent = {}) const override - { - if (!parent.isValid()) - return static_cast(constructors.size()); - auto info = static_cast(parent.internalPointer()); - if (!info) - return static_cast(constructors.at(parent.row()).parameters.size()); - return 0; - } - int columnCount(const QModelIndex & /*parent*/ = {}) const override { return 1; } - QVariant data(const QModelIndex &index, int role) const override - { - if (!index.isValid()) - return {}; - auto info = static_cast(index.internalPointer()); - - if (info) { - const auto ¶meter = info->parameters.at(index.row()); - if (role == Qt::CheckStateRole) - return parameter.init ? Qt::Checked : Qt::Unchecked; - if (role == Qt::DisplayRole) - return parameter.declaration; - return {}; - } - const auto &constructor = constructors.at(index.row()); - if (role == Qt::CheckStateRole) - return constructor.useInConstructor ? Qt::PartiallyChecked : Qt::Unchecked; - if (role == Qt::DisplayRole) - return constructor.declaration; - - // Highlight the selected items - if (role == Qt::FontRole && constructor.useInConstructor) { - QFont font = QApplication::font(); - font.setBold(true); - return font; - } - // Create a margin between sets of constructors for base classes - if (role == Qt::SizeHintRole && index.row() > 0 - && constructor.className != constructors.at(index.row() - 1).className) { - return QSize(-1, 25); - } - return {}; - } - bool setData(const QModelIndex &index, const QVariant &value, int /*role*/) override - { - if (index.isValid() && index.column() == 0) { - auto info = static_cast(index.internalPointer()); - if (info) { - const bool nowUse = value.toBool(); - auto ¶m = info->parameters.at(index.row()); - param.init = nowUse; - if (nowUse) - info->addParameter(param); - else - info->removeParameter(param); - return true; - } - auto &newConstructor = constructors.at(index.row()); - // You have to select a base class constructor - if (newConstructor.useInConstructor) - return false; - auto c = std::find_if(constructors.begin(), constructors.end(), [&](const auto &c) { - return c.className == newConstructor.className && c.useInConstructor; - }); - QTC_ASSERT(c == constructors.end(), return false;); - c->useInConstructor = false; - newConstructor.useInConstructor = true; - emit dataChanged(this->index(index.row(), 0), this->index(index.row(), columnCount())); - auto parentIndex = this->index(index.row(), 0); - emit dataChanged(this->index(0, 0, parentIndex), - this->index(rowCount(parentIndex), columnCount())); - const int oldIndex = c - constructors.begin(); - emit dataChanged(this->index(oldIndex, 0), this->index(oldIndex, columnCount())); - parentIndex = this->index(oldIndex, 0); - emit dataChanged(this->index(0, 0, parentIndex), - this->index(rowCount(parentIndex), columnCount())); - // update other table - c->removeAllParameters(); - for (auto &p : newConstructor.parameters) - if (p.init) - newConstructor.addParameter(p); - return true; - } - return false; - } - QVariant headerData(int section, Qt::Orientation orientation, int role) const override - { - if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { - switch (section) { - case 0: - return Tr::tr("Base Class Constructors"); - } - } - return {}; - } - Qt::ItemFlags flags(const QModelIndex &index) const override - { - if (index.isValid()) { - Qt::ItemFlags f; - auto info = static_cast(index.internalPointer()); - if (!info || info->useInConstructor) { - f |= Qt::ItemIsEnabled; - } - f |= Qt::ItemIsUserCheckable; - - return f; - } - return {}; - } -}; - -class GenerateConstructorDialog : public QDialog -{ -public: - GenerateConstructorDialog(ConstructorParams *constructorParamsModel, - ParentClassConstructors &constructors) - { - setWindowTitle(Tr::tr("Constructor")); - - const auto treeModel = new ParentClassesModel(this, constructors); - const auto treeView = new QTreeView(this); - treeView->setModel(treeModel); - treeView->setItemDelegate(new TopMarginDelegate(this)); - treeView->expandAll(); - - const auto view = new QTableView(this); - view->setModel(constructorParamsModel); - int optimalWidth = 0; - for (int i = 0; i < constructorParamsModel->columnCount(QModelIndex{}); ++i) { - view->resizeColumnToContents(i); - optimalWidth += view->columnWidth(i); - } - view->resizeRowsToContents(); - view->verticalHeader()->setDefaultSectionSize(view->rowHeight(0)); - view->setSelectionBehavior(QAbstractItemView::SelectRows); - view->setSelectionMode(QAbstractItemView::SingleSelection); - view->setDragEnabled(true); - view->setDropIndicatorShown(true); - view->setDefaultDropAction(Qt::MoveAction); - view->setDragDropMode(QAbstractItemView::InternalMove); - view->setDragDropOverwriteMode(false); - view->horizontalHeader()->setStretchLastSection(true); - view->setStyle(new ConstructorParams::TableViewStyle(view->style())); - - const auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); - connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); - - const auto errorLabel = new QLabel( - Tr::tr("Parameters without default value must come before parameters with default value.")); - errorLabel->setStyleSheet("color: #ff0000"); - errorLabel->setVisible(false); - QSizePolicy labelSizePolicy = errorLabel->sizePolicy(); - labelSizePolicy.setRetainSizeWhenHidden(true); - errorLabel->setSizePolicy(labelSizePolicy); - connect(constructorParamsModel, &ConstructorParams::validOrder, this, - [errorLabel, button = buttonBox->button(QDialogButtonBox::Ok)](bool valid) { - button->setEnabled(valid); - errorLabel->setVisible(!valid); - }); - - // setup select all/none checkbox - QCheckBox *const checkBox = new QCheckBox(Tr::tr("Initialize all members")); - checkBox->setChecked(true); - connect(checkBox, &QCheckBox::stateChanged, this, - [model = constructorParamsModel](int state) { - if (state != Qt::PartiallyChecked) { - for (int i = 0; i < model->rowCount(); ++i) - model->setData(model->index(i, ConstructorParams::ShouldInitColumn), - state, - Qt::CheckStateRole); - } - }); - connect(checkBox, &QCheckBox::clicked, this, [checkBox] { - if (checkBox->checkState() == Qt::PartiallyChecked) - checkBox->setCheckState(Qt::Checked); - }); - connect(constructorParamsModel, - &QAbstractItemModel::dataChanged, - this, - [model = constructorParamsModel, checkBox] { - const auto state = [model, selectedCount = model->selectedCount()]() { - if (selectedCount == 0) - return Qt::Unchecked; - if (static_cast(model->memberCount() == selectedCount)) - return Qt::Checked; - return Qt::PartiallyChecked; - }(); - checkBox->setCheckState(state); - }); - - using A = InsertionPointLocator::AccessSpec; - auto accessCombo = new QComboBox; - connect(accessCombo, &QComboBox::currentIndexChanged, this, [this, accessCombo] { - const auto data = accessCombo->currentData(); - m_accessSpec = static_cast(data.toInt()); - }); - for (auto a : {A::Public, A::Protected, A::Private}) - accessCombo->addItem(InsertionPointLocator::accessSpecToString(a), a); - const auto row = new QHBoxLayout(); - row->addWidget(new QLabel(Tr::tr("Access") + ":")); - row->addWidget(accessCombo); - row->addSpacerItem(new QSpacerItem(1, 1, QSizePolicy::Expanding, QSizePolicy::Minimum)); - - const auto mainLayout = new QVBoxLayout(this); - mainLayout->addWidget( - new QLabel(Tr::tr("Select the members to be initialized in the constructor.\n" - "Use drag and drop to change the order of the parameters."))); - mainLayout->addLayout(row); - mainLayout->addWidget(checkBox); - mainLayout->addWidget(view); - mainLayout->addWidget(treeView); - mainLayout->addWidget(errorLabel); - mainLayout->addWidget(buttonBox); - int left, right; - mainLayout->getContentsMargins(&left, nullptr, &right, nullptr); - optimalWidth += left + right; - resize(optimalWidth, mainLayout->sizeHint().height()); - } - - InsertionPointLocator::AccessSpec accessSpec() const { return m_accessSpec; } - -private: - InsertionPointLocator::AccessSpec m_accessSpec; -}; - -class GenerateConstructorOperation : public CppQuickFixOperation -{ -public: - GenerateConstructorOperation(const CppQuickFixInterface &interface) - : CppQuickFixOperation(interface) - { - setDescription(Tr::tr("Generate Constructor")); - - m_classAST = astForClassOperations(interface); - if (!m_classAST) - return; - Class *const theClass = m_classAST->symbol; - if (!theClass) - return; - - // Go through all members and find member variable declarations - int memberCounter = 0; - for (auto it = theClass->memberBegin(); it != theClass->memberEnd(); ++it) { - Symbol *const s = *it; - if (!s->identifier() || !s->type() || s->type().isTypedef()) - continue; - if ((s->asDeclaration() && s->type()->asFunctionType()) || s->asFunction()) - continue; - if (s->asDeclaration() && (s->isPrivate() || s->isProtected()) && !s->isStatic()) { - const auto name = QString::fromUtf8(s->identifier()->chars(), - s->identifier()->size()); - parameterModel.emplaceBackParameter(name, s, memberCounter++); - } - } - Overview o = CppCodeStyleSettings::currentProjectCodeStyleOverview(); - o.showArgumentNames = true; - o.showReturnTypes = true; - o.showDefaultArguments = true; - o.showTemplateParameters = true; - o.showFunctionSignatures = true; - LookupContext context(currentFile()->cppDocument(), interface.snapshot()); - for (BaseClass *bc : theClass->baseClasses()) { - const QString className = o.prettyName(bc->name()); - - ClassOrNamespace *localLookupType = context.lookupType(bc); - QList localLookup = localLookupType->lookup(bc->name()); - for (auto &li : localLookup) { - Symbol *d = li.declaration(); - if (!d->asClass()) - continue; - for (auto i = d->asClass()->memberBegin(); i != d->asClass()->memberEnd(); ++i) { - Symbol *s = *i; - if (s->isProtected() || s->isPublic()) { - if (s->name()->match(d->name())) { - // we have found a constructor - Function *func = s->type().type()->asFunctionType(); - if (!func) - continue; - const bool isFirst = parentClassConstructors.empty() - || parentClassConstructors.back().className - != className; - parentClassConstructors.emplace_back(className, parameterModel); - ParentClassConstructorInfo &constructor = parentClassConstructors.back(); - constructor.declaration = className + o.prettyType(func->type()); - constructor.declaration.replace("std::__1::__get_nullptr_t()", - "nullptr"); - constructor.useInConstructor = isFirst; - for (auto arg = func->memberBegin(); arg != func->memberEnd(); ++arg) { - Symbol *param = *arg; - Argument *argument = param->asArgument(); - if (!argument) // can also be a block - continue; - const QString name = o.prettyName(param->name()); - const StringLiteral *ini = argument->initializer(); - QString defaultValue; - if (ini) - defaultValue = QString::fromUtf8(ini->chars(), ini->size()) - .replace("std::__1::__get_nullptr_t()", - "nullptr"); - constructor.parameters.emplace_back(name, - defaultValue, - param, - &constructor); - // do not show constructors like QObject(QObjectPrivate & dd, ...) - ReferenceType *ref = param->type()->asReferenceType(); - if (ref && name == "dd") { - auto type = o.prettyType(ref->elementType()); - if (type.startsWith("Q") && type.endsWith("Private")) { - parentClassConstructors.pop_back(); - break; - } - } - } - } - } - } - } - } - - // add params to parameter lists - for (auto &c : parentClassConstructors) - if (c.useInConstructor) - for (auto &p : c.parameters) - if (p.init) - c.addParameter(p); - } - - bool isApplicable() const - { - return parameterModel.rowCount() > 0 - || Utils::anyOf(parentClassConstructors, - [](const auto &parent) { return !parent.parameters.empty(); }); - } - - void setTest(bool isTest = true) { m_test = isTest; } - -private: - void perform() override - { - auto infos = parameterModel.getInfos(); - - InsertionPointLocator::AccessSpec accessSpec = InsertionPointLocator::Public; - if (!m_test) { - GenerateConstructorDialog dlg(¶meterModel, parentClassConstructors); - if (dlg.exec() == QDialog::Rejected) - return; - accessSpec = dlg.accessSpec(); - infos = parameterModel.getInfos(); - } else { -#ifdef WITH_TESTS - ParentClassesModel model(nullptr, parentClassConstructors); - QAbstractItemModelTester tester(&model); -#endif - if (infos.size() >= 3) { - // if we are testing and have 3 or more members => change the order - // move first element to the back - infos.push_back(infos[0]); - infos.erase(infos.begin()); - } - for (auto info : infos) { - if (info->memberVariableName.startsWith("di_")) - info->defaultValue = "42"; - } - for (auto &c : parentClassConstructors) { - if (c.useInConstructor) { - for (auto &p : c.parameters) { - if (!p.init && p.parameterName.startsWith("use_")) { - infos.push_back(&p); - p.init = true; - } - } - } - } - } - if (infos.empty()) - return; - struct GenerateConstructorRefactoringHelper : public GetterSetterRefactoringHelper - { - const ClassSpecifierAST *m_classAST; - InsertionPointLocator::AccessSpec m_accessSpec; - GenerateConstructorRefactoringHelper(CppQuickFixOperation *operation, - const FilePath &filePath, - Class *clazz, - const ClassSpecifierAST *classAST, - InsertionPointLocator::AccessSpec accessSpec) - : GetterSetterRefactoringHelper(operation, filePath, clazz) - , m_classAST(classAST) - , m_accessSpec(accessSpec) - {} - void generateConstructor(std::vector members, - const ParentClassConstructors &parentClassConstructors) - { - auto constructorLocation = m_settings->determineSetterLocation(int(members.size())); - if (constructorLocation == CppQuickFixSettings::FunctionLocation::CppFile - && !hasSourceFile()) - constructorLocation = CppQuickFixSettings::FunctionLocation::OutsideClass; - - Overview overview = CppCodeStyleSettings::currentProjectCodeStyleOverview(); - overview.showTemplateParameters = true; - - InsertionLocation implLoc; - QString implCode; - CppRefactoringFilePtr implFile; - QString className = overview.prettyName(m_class->name()); - QStringList insertedNamespaces; - if (constructorLocation == CppQuickFixSettings::FunctionLocation::CppFile) { - implLoc = sourceLocationFor(m_class, &insertedNamespaces); - implFile = m_sourceFile; - if (m_settings->rewriteTypesinCppFile()) - implCode = symbolAt(m_class, m_sourceFile, implLoc); - else - implCode = className; - implCode += "::" + className + "("; - } else if (constructorLocation - == CppQuickFixSettings::FunctionLocation::OutsideClass) { - implLoc = insertLocationForMethodDefinition(m_class, - false, - NamespaceHandling::Ignore, - m_changes, - m_headerFile->filePath(), - &insertedNamespaces); - implFile = m_headerFile; - implCode = symbolAt(m_class, m_headerFile, implLoc); - implCode += "::" + className + "("; - } - - QString inClassDeclaration = overview.prettyName(m_class->name()) + "("; - QString constructorBody = members.empty() ? QString(") {}") : QString(") : "); - for (auto &member : members) { - if (isValueType(member->symbol, &member->customValueType)) - member->type.setConst(false); - else - member->type = makeConstRef(member->type); - - inClassDeclaration += overview.prettyType(member->type, member->parameterName); - if (!member->defaultValue.isEmpty()) - inClassDeclaration += " = " + member->defaultValue; - inClassDeclaration += ", "; - if (implFile) { - FullySpecifiedType type = typeAt(member->type, - m_class, - implFile, - implLoc, - insertedNamespaces); - implCode += overview.prettyType(type, member->parameterName) + ", "; - } - } - Utils::sort(members, &ConstructorMemberInfo::numberOfMember); - // first, do the base classes - for (const auto &parent : parentClassConstructors) { - if (!parent.useInConstructor) - continue; - // Check if we really need a constructor - if (Utils::anyOf(parent.parameters, [](const auto ¶m) { - return param.init || param.originalDefaultValue.isEmpty(); - })) { - int defaultAtEndCount = 0; - for (auto i = parent.parameters.crbegin(); i != parent.parameters.crend(); - ++i) { - if (i->init || i->originalDefaultValue.isEmpty()) - break; - ++defaultAtEndCount; - } - const int numberOfParameters = static_cast(parent.parameters.size()) - - defaultAtEndCount; - constructorBody += parent.className + "("; - int counter = 0; - for (const auto ¶m : parent.parameters) { - if (++counter > numberOfParameters) - break; - if (param.init) { - if (param.customValueType) - constructorBody += "std::move(" + param.parameterName + ')'; - else - constructorBody += param.parameterName; - } else if (!param.originalDefaultValue.isEmpty()) - constructorBody += param.originalDefaultValue; - else - constructorBody += "/* insert value */"; - constructorBody += ", "; - } - constructorBody.resize(constructorBody.length() - 2); - constructorBody += "),\n"; - } - } - for (auto &member : members) { - if (member->parentClassConstructor) - continue; - QString param = member->parameterName; - if (member->customValueType) - param = "std::move(" + member->parameterName + ')'; - constructorBody += member->memberVariableName + '(' + param + "),\n"; - } - if (!members.empty()) { - inClassDeclaration.resize(inClassDeclaration.length() - 2); - constructorBody.remove(constructorBody.length() - 2, 1); // ..),\n => ..)\n - constructorBody += "{}"; - if (!implCode.isEmpty()) - implCode.resize(implCode.length() - 2); - } - implCode += constructorBody; - - if (constructorLocation == CppQuickFixSettings::FunctionLocation::InsideClass) - inClassDeclaration += constructorBody; - else - inClassDeclaration += QLatin1String(");"); - - TranslationUnit *tu = m_headerFile->cppDocument()->translationUnit(); - insertAndIndent(m_headerFile, - m_locator.constructorDeclarationInClass(tu, - m_classAST, - m_accessSpec, - int(members.size())), - inClassDeclaration); - - if (constructorLocation == CppQuickFixSettings::FunctionLocation::CppFile) { - addSourceFileCode(implCode); - } else if (constructorLocation - == CppQuickFixSettings::FunctionLocation::OutsideClass) { - if (m_isHeaderHeaderFile) - implCode.prepend("inline "); - insertAndIndent(m_headerFile, implLoc, implCode); - } - } - }; - GenerateConstructorRefactoringHelper helper(this, - currentFile()->filePath(), - m_classAST->symbol, - m_classAST, - accessSpec); - - auto members = Utils::filtered(infos, [](const auto mi) { - return mi->init || mi->parentClassConstructor; - }); - helper.generateConstructor(std::move(members), parentClassConstructors); - helper.applyChanges(); - } - - ConstructorParams parameterModel; - ParentClassConstructors parentClassConstructors; - const ClassSpecifierAST *m_classAST = nullptr; - bool m_test = false; -}; } // namespace -void GenerateConstructor::doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) -{ - const auto op = QSharedPointer::create(interface); - if (!op->isApplicable()) - return; - op->setTest(m_test); - result << op; -} namespace { class ConvertCommentStyleOp : public CppQuickFixOperation @@ -10009,15 +7501,12 @@ void createCppQuickFixes() new ReformatPointerDeclaration; new CompleteSwitchCaseStatement; - new InsertQtPropertyMembers; new ConvertQt4Connect; new ApplyDeclDefLinkChanges; new ConvertFromAndToPointer; new ExtractFunction; new ExtractLiteralAsParameter; - new GenerateGetterSetter; - new GenerateGettersSettersForClass; new InsertDeclFromDef; new InsertDefFromDecl; new AddDeclarationForUndeclaredIdentifier; @@ -10032,6 +7521,7 @@ void createCppQuickFixes() registerInsertVirtualMethodsQuickfix(); registerMoveClassToOwnFileQuickfix(); + registerCodeGenerationQuickfixes(); new OptimizeForLoop; @@ -10040,7 +7530,6 @@ void createCppQuickFixes() new ExtraRefactoringOperations; new RemoveUsingNamespace; - new GenerateConstructor; new ConvertCommentStyle; new MoveFunctionComments; new ConvertToMetaMethodCall; @@ -10054,5 +7543,3 @@ void destroyCppQuickFixes() } // namespace Internal } // namespace CppEditor - -#include "cppquickfixes.moc" diff --git a/src/plugins/cppeditor/quickfixes/cppquickfixes.h b/src/plugins/cppeditor/quickfixes/cppquickfixes.h index bd47b318c72..cd820f9466d 100644 --- a/src/plugins/cppeditor/quickfixes/cppquickfixes.h +++ b/src/plugins/cppeditor/quickfixes/cppquickfixes.h @@ -453,39 +453,6 @@ public: void doMatch(const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result) override; }; -/*! - Adds getter and setter functions for a member variable - */ -class GenerateGetterSetter : public CppQuickFixFactory -{ -public: - void doMatch(const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result) override; -}; - -/*! - Adds getter and setter functions for several member variables - */ -class GenerateGettersSettersForClass : public CppQuickFixFactory -{ -protected: - void setTest() { m_test = true; } - -private: - void doMatch(const CppQuickFixInterface &interface, - TextEditor::QuickFixOperations &result) override; - - bool m_test = false; -}; - -/*! - Adds missing members for a Q_PROPERTY - */ -class InsertQtPropertyMembers : public CppQuickFixFactory -{ -public: - void doMatch(const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result) override; -}; - /*! Converts a Qt 4 QObject::connect() to Qt 5 style. */ @@ -586,21 +553,6 @@ private: void doMatch(const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result) override; }; -/*! - Generate constructor - */ -class GenerateConstructor : public CppQuickFixFactory -{ -protected: - void setTest() { m_test = true; } - -private: - void doMatch(const CppQuickFixInterface &interface, - TextEditor::QuickFixOperations &result) override; - - bool m_test = false; -}; - //! Converts C-style to C++-style comments and vice versa class ConvertCommentStyle : public CppQuickFixFactory {