diff --git a/src/libs/3rdparty/cplusplus/AST.h b/src/libs/3rdparty/cplusplus/AST.h index 170072ee90a..4fd31f391c7 100644 --- a/src/libs/3rdparty/cplusplus/AST.h +++ b/src/libs/3rdparty/cplusplus/AST.h @@ -75,8 +75,51 @@ public: Tptr value; List *next; + + class ListIterator + { + List *iter; + + public: + ListIterator(List *iter) + : iter(iter) + {} + Tptr operator*() { return iter->value; } + ListIterator &operator++() + { + if (iter) + iter = iter->next; + return *this; + } + bool operator==(ListIterator other) { return iter == other.iter; } + bool operator!=(ListIterator other) { return iter != other.iter; } + }; + ListIterator begin() { return {this}; } + ListIterator end() { return {nullptr}; } + + int size() { return next ? next->size() + 1 : 1; } }; +template +typename List::ListIterator begin(List *list) +{ + return list ? list->begin() : typename List::ListIterator(nullptr); +} + +template +typename List::ListIterator end(List *list) +{ + return list ? list->end() : typename List::ListIterator(nullptr); +} + +template +int size(List *list) +{ + if (list) + return list->size(); + return 0; +} + class CPLUSPLUS_EXPORT AST: public Managed { AST(const AST &other); diff --git a/src/plugins/cppeditor/cppeditorplugin.h b/src/plugins/cppeditor/cppeditorplugin.h index 7cf5f27fbf3..f84d6828548 100644 --- a/src/plugins/cppeditor/cppeditorplugin.h +++ b/src/plugins/cppeditor/cppeditorplugin.h @@ -231,6 +231,9 @@ private slots: void test_quickfix_removeUsingNamespace_simple(); void test_quickfix_removeUsingNamespace_differentSymbols(); + void test_quickfix_generateConstructor_data(); + void test_quickfix_generateConstructor(); + void test_quickfix_InsertVirtualMethods_data(); void test_quickfix_InsertVirtualMethods(); void test_quickfix_InsertVirtualMethods_implementationFile(); diff --git a/src/plugins/cppeditor/cppquickfix_test.cpp b/src/plugins/cppeditor/cppquickfix_test.cpp index 457b020a083..1f453189cf3 100644 --- a/src/plugins/cppeditor/cppquickfix_test.cpp +++ b/src/plugins/cppeditor/cppquickfix_test.cpp @@ -7576,5 +7576,285 @@ void CppEditorPlugin::test_quickfix_removeUsingNamespace_differentSymbols() QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), 0); } +enum ConstructorLocation { Inside, Outside, CppGenNamespace, CppGenUsingDirective, CppRewriteType }; + +void CppEditorPlugin::test_quickfix_generateConstructor_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; + + 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 CppEditorPlugin::test_quickfix_generateConstructor() +{ + 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 << QuickFixTestDocument::create("file.h", original_header, expected_header); + testDocuments << QuickFixTestDocument::create("file.cpp", original_source, expected_source); + TestFactory factory; + QuickFixOperationTest(testDocuments, &factory); +} + } // namespace Internal } // namespace CppEditor diff --git a/src/plugins/cppeditor/cppquickfixes.cpp b/src/plugins/cppeditor/cppquickfixes.cpp index dc1e28328e3..354c2fb747a 100644 --- a/src/plugins/cppeditor/cppquickfixes.cpp +++ b/src/plugins/cppeditor/cppquickfixes.cpp @@ -413,10 +413,12 @@ QStringList getNamespaceNames(const Symbol *symbol) // TODO: We should use the "CreateMissing" approach everywhere. enum class NamespaceHandling { CreateMissing, Ignore }; -InsertionLocation insertLocationForMethodDefinition(Symbol *symbol, const bool useSymbolFinder, +InsertionLocation insertLocationForMethodDefinition(Symbol *symbol, + const bool useSymbolFinder, NamespaceHandling namespaceHandling, - CppRefactoringChanges& refactoring, - const QString& fileName) + const CppRefactoringChanges &refactoring, + const QString &fileName, + QStringList *insertedNamespaces = nullptr) { QTC_ASSERT(symbol, return InsertionLocation()); @@ -484,6 +486,8 @@ InsertionLocation insertLocationForMethodDefinition(Symbol *symbol, const bool u prefix += "namespace " + ns + " {\n"; suffix += "}\n"; } + if (insertedNamespaces) + *insertedNamespaces = visitor.remainingNamespaces(); //TODO watch for moc-includes @@ -3689,7 +3693,7 @@ Utils::optional getFirstTemplateParameter(FullySpecifiedType QString symbolAtDifferentLocation(const CppQuickFixInterface &interface, Symbol *symbol, - CppRefactoringFilePtr &targetFile, + const CppRefactoringFilePtr &targetFile, InsertionLocation targetLocation) { QTC_ASSERT(symbol, return QString()); @@ -3713,12 +3717,20 @@ QString symbolAtDifferentLocation(const CppQuickFixInterface &interface, FullySpecifiedType typeAtDifferentLocation(const CppQuickFixInterface &interface, FullySpecifiedType type, Scope *originalScope, - CppRefactoringFilePtr &targetFile, - InsertionLocation targetLocation) + 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) @@ -3750,21 +3762,24 @@ struct ExistingGetterSetterData class GetterSetterRefactoringHelper { public: - GetterSetterRefactoringHelper(CppQuickFixOperation *operation, const QString &fileName, Class *clazz) + GetterSetterRefactoringHelper(CppQuickFixOperation *operation, + const QString &fileName, + Class *clazz) : m_operation(operation) , m_changes(m_operation->snapshot()) , m_locator(m_changes) + , m_headerFile(m_changes.file(fileName)) + , m_sourceFile([&] { + QString cppFileName = correspondingHeaderOrSource(fileName, &m_isHeaderHeaderFile); + if (!m_isHeaderHeaderFile || !QFile::exists(cppFileName)) { + // there is no "source" file + return m_headerFile; + } else { + return m_changes.file(cppFileName); + } + }()) , m_class(clazz) - { - m_headerFile = m_changes.file(fileName); - QString cppFileName = correspondingHeaderOrSource(fileName, &m_isHeaderHeaderFile); - if (!m_isHeaderHeaderFile || !QFile::exists(cppFileName)) { - // there is no "source" file - m_sourceFile = m_headerFile; - } else { - m_sourceFile = m_changes.file(cppFileName); - } - } + {} void performGeneration(ExistingGetterSetterData data, int generationFlags); @@ -3798,7 +3813,8 @@ public: } } - bool hasSourceFile() { return m_headerFile != m_sourceFile; } + bool hasSourceFile() const { return m_headerFile != m_sourceFile; } + bool isHeaderHeaderFile() const { return m_isHeaderHeaderFile; } protected: void insertAndIndent(const RefactoringFilePtr &file, @@ -3831,17 +3847,86 @@ protected: return type; } - QString symbolAt(Symbol *symbol, CppRefactoringFilePtr &targetFile, InsertionLocation targetLocation) + QString symbolAt(Symbol *symbol, + const CppRefactoringFilePtr &targetFile, + InsertionLocation targetLocation) { return symbolAtDifferentLocation(*m_operation, symbol, targetFile, targetLocation); } FullySpecifiedType typeAt(FullySpecifiedType type, Scope *originalScope, - CppRefactoringFilePtr &targetFile, - InsertionLocation targetLocation) + const CppRefactoringFilePtr &targetFile, + InsertionLocation targetLocation, + const QStringList &newNamespaceNamesAtLoc = {}) { - return typeAtDifferentLocation(*m_operation, type, originalScope, targetFile, targetLocation); + 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->isPointerType() || t->isEnumType() || t->isIntegerType() || t->isFloatType() + || t->isReferenceType(); + }; + if (type->isNamedType()) { + // 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) mutable -> bool { + // 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()->isNamedType()) { // check if we have to search recursively + const Name *newName = i.type()->asNamedType()->name(); + Scope *newScope = i.declaration()->enclosingScope(); + if (newName == name && newScope == scope) + 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) @@ -3864,31 +3949,38 @@ protected: return loc; } - InsertionLocation sourceLocationFor(Declaration *declarationSymbol) + InsertionLocation sourceLocationFor(Symbol *symbol, QStringList *insertedNamespaces = nullptr) { if (m_sourceFileInsertionPoint.isValid()) return m_sourceFileInsertionPoint; - m_sourceFileInsertionPoint = insertLocationForMethodDefinition( - declarationSymbol, - false, - m_settings->createMissingNamespacesinCppFile() ? NamespaceHandling::CreateMissing - : NamespaceHandling::Ignore, - m_changes, - m_sourceFile->fileName()); + m_sourceFileInsertionPoint + = insertLocationForMethodDefinition(symbol, + false, + m_settings->createMissingNamespacesinCppFile() + ? NamespaceHandling::CreateMissing + : NamespaceHandling::Ignore, + m_changes, + m_sourceFile->fileName(), + insertedNamespaces); if (m_settings->addUsingNamespaceinCppFile()) { // check if we have to insert a using namespace ... - auto requiredNamespaces = getNamespaceNames(declarationSymbol->enclosingClass()); + auto requiredNamespaces = getNamespaceNames( + symbol->isClass() ? 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 '::' @@ -3911,20 +4003,22 @@ protected: m_sourceFileCode += code; } +protected: + CppQuickFixOperation *const m_operation; + const CppRefactoringChanges m_changes; + const InsertionPointLocator m_locator; + const CppRefactoringFilePtr m_headerFile; + const CppRefactoringFilePtr m_sourceFile; + CppQuickFixSettings *const m_settings = CppQuickFixProjectsSettings::getQuickFixSettings( + ProjectExplorer::ProjectTree::currentProject()); + Class *const m_class; + private: - CppQuickFixOperation *m_operation; - CppRefactoringChanges m_changes; - CppRefactoringFilePtr m_headerFile; - CppRefactoringFilePtr m_sourceFile; ChangeSet m_headerFileChangeSet; ChangeSet m_sourceFileChangeSet; - InsertionPointLocator m_locator; QMap m_headerInsertionPoints; InsertionLocation m_sourceFileInsertionPoint; - CppQuickFixSettings *m_settings = CppQuickFixProjectsSettings::getQuickFixSettings( - ProjectExplorer::ProjectTree::currentProject()); QString m_sourceFileCode; - Class *m_class; QMap m_headerFileCode; bool m_isHeaderHeaderFile; // the "header" (where the class is defined) can be a source file }; @@ -4070,47 +4164,8 @@ void GetterSetterRefactoringHelper::performGeneration(ExistingGetterSetterData d overview.showTemplateParameters = true; // Ok... - If a type is a Named type we have to search recusive for the real type - const bool isValueType = [settings = m_settings, - scope = data.declarationSymbol->enclosingScope(), - document = m_headerFile->cppDocument(), - &snapshot = m_changes.snapshot()](FullySpecifiedType type) { - // a type is a value type if it is one of the following - const auto isTypeValueType = [](const FullySpecifiedType &t) { - return t->isPointerType() || t->isEnumType() || t->isIntegerType() || t->isFloatType() - || t->isReferenceType(); - }; - if (type->isNamedType()) { - // we need a recursive search and a lookup context - LookupContext context(document, snapshot); - auto isValueType = [settings, &context, &isTypeValueType](const Name *name, - Scope *scope, - auto &isValueType) mutable -> bool { - // 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()))) - 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()->isNamedType()) { // check if we have to search recursively - const Name *newName = i.type()->asNamedType()->name(); - Scope *newScope = i.declaration()->enclosingScope(); - if (newName == name && newScope == scope) - 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(), scope, isValueType); - } - return isTypeValueType(type); - }(memberVariableType); + const bool isValueType = this->isValueType(memberVariableType, + data.declarationSymbol->enclosingScope()); const FullySpecifiedType parameterType = isValueType ? memberVariableType : makeConstRef(memberVariableType); @@ -8350,6 +8405,351 @@ void RemoveUsingNamespace::match(const CppQuickFixInterface &interface, QuickFix } } +namespace { + +class ConstructorMemberInfo +{ +public: + ConstructorMemberInfo(const QString &name, Symbol *symbol) + : memberVariableName(name) + , parameterName(memberBaseName(name)) + , symbol(symbol) + , type(symbol->type()) + {} + + QString memberVariableName; + QString parameterName; + bool init = true; + Symbol *symbol; // for the right type later + FullySpecifiedType type; +}; +using ConstructorMemberCandidates = std::vector; + +class ConstructorCandidateTreeItem : public Utils::TreeItem +{ +public: + enum Column { ShouldInitColumn, MemberNameColumn, ParameterNameColumn }; + ConstructorCandidateTreeItem(ConstructorMemberInfo *memberInfo) + : m_memberInfo(memberInfo) + {} + +private: + QVariant data(int column, int role) const override + { + if (role == Qt::CheckStateRole && column == ShouldInitColumn) + return m_memberInfo->init ? Qt::Checked : Qt::Unchecked; + if (role == Qt::DisplayRole && column == MemberNameColumn) + return m_memberInfo->memberVariableName; + if ((role == Qt::DisplayRole || role == Qt::EditRole) && column == ParameterNameColumn) + return m_memberInfo->parameterName; + return {}; + } + + bool setData(int column, const QVariant &data, int role) override + { + if (column == ShouldInitColumn && role == Qt::CheckStateRole) { + m_memberInfo->init = data.toInt() == Qt::Checked; + update(); + return true; + } + if (column == ParameterNameColumn && role == Qt::EditRole) { + m_memberInfo->parameterName = data.toString(); + return true; + } + return false; + } + + Qt::ItemFlags flags(int column) const override + { + if (column == ShouldInitColumn) + return Qt::ItemIsEnabled | Qt::ItemIsUserCheckable; + if (!m_memberInfo->init) + return {}; + if (column == MemberNameColumn) + return Qt::ItemIsEnabled; + if (column == ParameterNameColumn) + return Qt::ItemIsEnabled | Qt::ItemIsEditable; + return {}; + } + + ConstructorMemberInfo *const m_memberInfo; +}; + +class GenerateConstructorDialog : public QDialog +{ + Q_DECLARE_TR_FUNCTIONS(GenerateConstructorDialog) +public: + GenerateConstructorDialog(const ConstructorMemberCandidates &candidates) + : QDialog() + , m_candidates(candidates) + { + setWindowTitle(tr("Constructor")); + const auto model = new Utils::TreeModel(this); + model->setHeader(QStringList({ + tr("Initialize in Constructor"), + tr("Member Name"), + tr("Parameter Name"), + })); + for (auto &candidate : m_candidates) + model->rootItem()->appendChild(new ConstructorCandidateTreeItem(&candidate)); + const auto view = new QTreeView(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); + + // setup select all/none checkbox + QCheckBox *const checkBox = new QCheckBox(tr("Initialize all members")); + checkBox->setChecked(true); + connect(checkBox, &QCheckBox::stateChanged, [model](int state) { + if (state != Qt::PartiallyChecked) { + for (int i = 0; i < model->rowCount(); ++i) + model->setData(model->index(i, ConstructorCandidateTreeItem::ShouldInitColumn), + state, + Qt::CheckStateRole); + } + }); + connect(checkBox, &QCheckBox::clicked, this, [checkBox] { + if (checkBox->checkState() == Qt::PartiallyChecked) + checkBox->setCheckState(Qt::Checked); + }); + connect(model, &QAbstractItemModel::dataChanged, this, [this, checkBox] { + const auto selectedCount = Utils::count(m_candidates, + [](const ConstructorMemberInfo &mi) { + return mi.init; + }); + + const auto state = [this, selectedCount]() { + if (selectedCount == 0) + return Qt::Unchecked; + if (static_cast(m_candidates.size()) == selectedCount) + return Qt::Checked; + return Qt::PartiallyChecked; + }(); + checkBox->setCheckState(state); + }); + + using A = InsertionPointLocator::AccessSpec; + auto accessCombo = new QComboBox; + connect(accessCombo, qOverload(&QComboBox::currentIndexChanged), [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("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("Select the members " + "to be initialized in the constructor."))); + mainLayout->addLayout(row); + 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()); + } + + ConstructorMemberCandidates candidates() const { return m_candidates; } + InsertionPointLocator::AccessSpec accessSpec() const { return m_accessSpec; } + +private: + ConstructorMemberCandidates m_candidates; + InsertionPointLocator::AccessSpec m_accessSpec; +}; + +class GenerateConstructorOperation : public CppQuickFixOperation +{ +public: + GenerateConstructorOperation(const CppQuickFixInterface &interface) + : CppQuickFixOperation(interface) + { + setDescription(CppQuickFixFactory::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 + for (auto it = theClass->memberBegin(); it != theClass->memberEnd(); ++it) { + Symbol *const s = *it; + if (!s->identifier() || !s->type() || s->type().isTypedef()) + continue; + if ((s->isDeclaration() && s->type()->asFunctionType()) || s->asFunction()) + continue; + if (s->isDeclaration() && (s->isPrivate() || s->isProtected()) && !s->isStatic()) { + const auto name = QString::fromUtf8(s->identifier()->chars(), + s->identifier()->size()); + m_candidates.emplace_back(name, s); + } + } + } + + ConstructorMemberCandidates &candidates() { return m_candidates; } + bool isApplicable() const { return !m_candidates.empty(); } + + void setTest(bool isTest = true) { m_test = isTest; } + +private: + void perform() override + { + InsertionPointLocator::AccessSpec accessSpec = InsertionPointLocator::Public; + if (!m_test) { + GenerateConstructorDialog dlg(m_candidates); + if (dlg.exec() == QDialog::Rejected) + return; + m_candidates = dlg.candidates(); + accessSpec = dlg.accessSpec(); + } + if (m_candidates.empty()) + return; + struct GenerateConstructorRefactoringHelper : public GetterSetterRefactoringHelper + { + const ClassSpecifierAST *m_classAST; + InsertionPointLocator::AccessSpec m_accessSpec; + GenerateConstructorRefactoringHelper(CppQuickFixOperation *operation, + const QString &fileName, + Class *clazz, + const ClassSpecifierAST *classAST, + InsertionPointLocator::AccessSpec accessSpec) + : GetterSetterRefactoringHelper(operation, fileName, clazz) + , m_classAST(classAST) + , m_accessSpec(accessSpec) + {} + void generateConstructor(ConstructorMemberCandidates candidates) + { + ConstructorMemberCandidates members = Utils::filtered(candidates, + [](const auto &mi) { + return mi.init; + }); + auto constructorLocation = m_settings->determineSetterLocation(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; + QString clazz = overview.prettyName(m_class->name()); + 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->fileName(), + &insertedNamespaces); + implFile = m_headerFile; + implCode = symbolAt(m_class, m_headerFile, implLoc); + implCode += "::" + className + "("; + } + + QString inClassDeclaration = overview.prettyName(m_class->name()) + "("; + QString constructorBody = members.empty() ? ") {}" : ") : "; + for (auto &member : members) { + bool customValueType; + if (isValueType(member.symbol, &customValueType)) + member.type.setConst(false); + else + member.type = makeConstRef(member.type); + + inClassDeclaration += overview.prettyType(member.type, member.parameterName); + inClassDeclaration += ", "; + QString param = member.parameterName; + if (customValueType) + param = "std::move(" + member.parameterName + ')'; + constructorBody += member.memberVariableName + '(' + param + "),\n"; + if (implFile) { + FullySpecifiedType type = typeAt(member.type, + m_class, + implFile, + implLoc, + insertedNamespaces); + implCode += overview.prettyType(type, member.parameterName) + ", "; + } + } + 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, + members.size()), + inClassDeclaration); + + if (constructorLocation == CppQuickFixSettings::FunctionLocation::CppFile) { + addSourceFileCode(implCode); + } else if (constructorLocation + == CppQuickFixSettings::FunctionLocation::OutsideClass) { + if (isHeaderHeaderFile()) + implCode.prepend("inline "); + insertAndIndent(m_headerFile, implLoc, implCode); + } + } + }; + GenerateConstructorRefactoringHelper helper(this, + currentFile()->fileName(), + m_classAST->symbol, + m_classAST, + accessSpec); + helper.generateConstructor(m_candidates); + helper.applyChanges(); + } + + ConstructorMemberCandidates m_candidates; + const ClassSpecifierAST *m_classAST = nullptr; + bool m_test = false; +}; +} // namespace +void GenerateConstructor::match(const CppQuickFixInterface &interface, QuickFixOperations &result) +{ + const auto op = QSharedPointer::create(interface); + if (!op->isApplicable()) + return; + op->setTest(m_test); + result << op; +} + void createCppQuickFixes() { new AddIncludeForUndefinedIdentifier; @@ -8406,6 +8806,7 @@ void createCppQuickFixes() new ExtraRefactoringOperations; new RemoveUsingNamespace; + new GenerateConstructor; } void destroyCppQuickFixes() diff --git a/src/plugins/cppeditor/cppquickfixes.h b/src/plugins/cppeditor/cppquickfixes.h index 4a9d1ab3579..fd7e3af2b2e 100644 --- a/src/plugins/cppeditor/cppquickfixes.h +++ b/src/plugins/cppeditor/cppquickfixes.h @@ -590,5 +590,20 @@ public: void match(const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result) override; }; +/*! + Generate constructor + */ +class GenerateConstructor : public CppQuickFixFactory +{ +protected: + void setTest() { m_test = true; } + +private: + void match(const CppQuickFixInterface &interface, + TextEditor::QuickFixOperations &result) override; + + bool m_test = false; +}; + } // namespace Internal } // namespace CppEditor diff --git a/src/plugins/cpptools/insertionpointlocator.cpp b/src/plugins/cpptools/insertionpointlocator.cpp index 228d5344306..5ccfda63195 100644 --- a/src/plugins/cpptools/insertionpointlocator.cpp +++ b/src/plugins/cpptools/insertionpointlocator.cpp @@ -60,6 +60,7 @@ static int ordering(InsertionPointLocator::AccessSpec xsSpec) struct AccessRange { int start = 0; + int beforeStart = 0; unsigned end = 0; InsertionPointLocator::AccessSpec xsSpec = InsertionPointLocator::Invalid; unsigned colonToken = 0; @@ -81,16 +82,14 @@ struct AccessRange class FindInClass: public ASTVisitor { public: - FindInClass(const Document::Ptr &doc, const Class *clazz, InsertionPointLocator::AccessSpec xsSpec) - : ASTVisitor(doc->translationUnit()) - , _doc(doc) + FindInClass(TranslationUnit *tu, const Class *clazz) + : ASTVisitor(tu) , _clazz(clazz) - , _xsSpec(xsSpec) {} - InsertionLocation operator()() + ClassSpecifierAST *operator()() { - _result = InsertionLocation(); + _result = nullptr; AST *ast = translationUnit()->ast(); accept(ast); @@ -107,146 +106,120 @@ protected: return true; if (!ast->symbol || !ast->symbol->match(_clazz)) return true; - - QList ranges = collectAccessRanges( - ast->member_specifier_list, - tokenKind(ast->classkey_token) == T_CLASS ? InsertionPointLocator::Private : InsertionPointLocator::Public, - ast->lbrace_token, - ast->rbrace_token); - - unsigned beforeToken = 0; - bool needsLeadingEmptyLine = false; - bool needsPrefix = false; - bool needsSuffix = false; - findMatch(ranges, _xsSpec, beforeToken, needsLeadingEmptyLine, needsPrefix, needsSuffix); - - int line = 0, column = 0; - getTokenStartPosition(beforeToken, &line, &column); - - QString prefix; - if (needsLeadingEmptyLine) - prefix += QLatin1String("\n"); - if (needsPrefix) - prefix += InsertionPointLocator::accessSpecToString(_xsSpec) + QLatin1String(":\n"); - - QString suffix; - if (needsSuffix) - suffix = QLatin1Char('\n'); - - _result = InsertionLocation(_doc->fileName(), prefix, suffix, - line, column); + _result = ast; return false; } - static void findMatch(const QList &ranges, - InsertionPointLocator::AccessSpec xsSpec, - unsigned &beforeToken, - bool &needsLeadingEmptyLine, - bool &needsPrefix, - bool &needsSuffix) - { - QTC_ASSERT(!ranges.isEmpty(), return); - const int lastIndex = ranges.size() - 1; - - needsLeadingEmptyLine = false; - - // try an exact match, and ignore the first (default) access spec: - for (int i = lastIndex; i > 0; --i) { - const AccessRange &range = ranges.at(i); - if (range.xsSpec == xsSpec) { - beforeToken = range.end; - needsPrefix = false; - needsSuffix = (i != lastIndex); - return; - } - } - - // try to find a fitting access spec to insert XXX: - for (int i = lastIndex; i > 0; --i) { - const AccessRange ¤t = ranges.at(i); - - if (ordering(xsSpec) > ordering(current.xsSpec)) { - beforeToken = current.end; - needsPrefix = true; - needsSuffix = (i != lastIndex); - return; - } - } - - // otherwise: - beforeToken = ranges.first().end; - needsLeadingEmptyLine = !ranges.first().isEmpty(); - needsPrefix = true; - needsSuffix = (ranges.size() != 1); - } - - QList collectAccessRanges(DeclarationListAST *decls, - InsertionPointLocator::AccessSpec initialXs, - int firstRangeStart, - int lastRangeEnd) const - { - QList ranges; - ranges.append(AccessRange(firstRangeStart, lastRangeEnd, initialXs, 0)); - - for (DeclarationListAST *iter = decls; iter; iter = iter->next) { - DeclarationAST *decl = iter->value; - - if (AccessDeclarationAST *xsDecl = decl->asAccessDeclaration()) { - const unsigned token = xsDecl->access_specifier_token; - InsertionPointLocator::AccessSpec newXsSpec = initialXs; - bool isSlot = xsDecl->slots_token - && tokenKind(xsDecl->slots_token) == T_Q_SLOTS; - - switch (tokenKind(token)) { - case T_PUBLIC: - newXsSpec = isSlot ? InsertionPointLocator::PublicSlot - : InsertionPointLocator::Public; - break; - - case T_PROTECTED: - newXsSpec = isSlot ? InsertionPointLocator::ProtectedSlot - : InsertionPointLocator::Protected; - break; - - case T_PRIVATE: - newXsSpec = isSlot ? InsertionPointLocator::PrivateSlot - : InsertionPointLocator::Private; - break; - - case T_Q_SIGNALS: - newXsSpec = InsertionPointLocator::Signals; - break; - - case T_Q_SLOTS: { - newXsSpec = (InsertionPointLocator::AccessSpec) - (ranges.last().xsSpec | InsertionPointLocator::SlotBit); - break; - } - - default: - break; - } - - if (newXsSpec != ranges.last().xsSpec || ranges.size() == 1) { - ranges.last().end = token; - AccessRange r(token, lastRangeEnd, newXsSpec, xsDecl->colon_token); - ranges.append(r); - } - } - } - - ranges.last().end = lastRangeEnd; - return ranges; - } - private: - Document::Ptr _doc; const Class *_clazz; - InsertionPointLocator::AccessSpec _xsSpec; - - InsertionLocation _result; + ClassSpecifierAST *_result; }; +void findMatch(const QList &ranges, + InsertionPointLocator::AccessSpec xsSpec, + InsertionPointLocator::Position positionInAccessSpec, + unsigned &beforeToken, + bool &needsLeadingEmptyLine, + bool &needsPrefix, + bool &needsSuffix) +{ + QTC_ASSERT(!ranges.isEmpty(), return ); + const int lastIndex = ranges.size() - 1; + const bool atEnd = positionInAccessSpec == InsertionPointLocator::AccessSpecEnd; + needsLeadingEmptyLine = false; + + // try an exact match, and ignore the first (default) access spec: + for (int i = lastIndex; i > 0; --i) { + const AccessRange &range = ranges.at(i); + if (range.xsSpec == xsSpec) { + beforeToken = atEnd ? range.end : range.beforeStart; + needsLeadingEmptyLine = !atEnd; + needsPrefix = false; + needsSuffix = (i != lastIndex); + return; + } + } + + // try to find a fitting access spec to insert XXX: + for (int i = lastIndex; i > 0; --i) { + const AccessRange ¤t = ranges.at(i); + + if (ordering(xsSpec) > ordering(current.xsSpec)) { + beforeToken = atEnd ? current.end : current.end - 1; + needsLeadingEmptyLine = !atEnd; + needsPrefix = true; + needsSuffix = (i != lastIndex); + return; + } + } + + // otherwise: + beforeToken = atEnd ? ranges.first().end : ranges.first().end - 1; + needsLeadingEmptyLine = !ranges.first().isEmpty(); + needsPrefix = true; + needsSuffix = (ranges.size() != 1); +} + +QList collectAccessRanges(const CPlusPlus::TranslationUnit *tu, + DeclarationListAST *decls, + InsertionPointLocator::AccessSpec initialXs, + int firstRangeStart, + int lastRangeEnd) +{ + QList ranges; + ranges.append(AccessRange(firstRangeStart, lastRangeEnd, initialXs, 0)); + + for (DeclarationListAST *iter = decls; iter; iter = iter->next) { + DeclarationAST *decl = iter->value; + + if (AccessDeclarationAST *xsDecl = decl->asAccessDeclaration()) { + const unsigned token = xsDecl->access_specifier_token; + InsertionPointLocator::AccessSpec newXsSpec = initialXs; + bool isSlot = xsDecl->slots_token && tu->tokenKind(xsDecl->slots_token) == T_Q_SLOTS; + + switch (tu->tokenKind(token)) { + case T_PUBLIC: + newXsSpec = isSlot ? InsertionPointLocator::PublicSlot + : InsertionPointLocator::Public; + break; + + case T_PROTECTED: + newXsSpec = isSlot ? InsertionPointLocator::ProtectedSlot + : InsertionPointLocator::Protected; + break; + + case T_PRIVATE: + newXsSpec = isSlot ? InsertionPointLocator::PrivateSlot + : InsertionPointLocator::Private; + break; + + case T_Q_SIGNALS: + newXsSpec = InsertionPointLocator::Signals; + break; + + case T_Q_SLOTS: { + newXsSpec = (InsertionPointLocator::AccessSpec)(ranges.last().xsSpec + | InsertionPointLocator::SlotBit); + break; + } + + default: + break; + } + + if (newXsSpec != ranges.last().xsSpec || ranges.size() == 1) { + ranges.last().end = token; + AccessRange r(token, lastRangeEnd, newXsSpec, xsDecl->colon_token); + r.beforeStart = xsDecl->lastToken() - 1; + ranges.append(r); + } + } + } + + ranges.last().end = lastRangeEnd; + return ranges; +} + } // end of anonymous namespace InsertionLocation::InsertionLocation() = default; @@ -301,13 +274,118 @@ InsertionLocation InsertionPointLocator::methodDeclarationInClass( { const Document::Ptr doc = m_refactoringChanges.file(fileName)->cppDocument(); if (doc) { - FindInClass find(doc, clazz, xsSpec); - return find(); + FindInClass find(doc->translationUnit(), clazz); + ClassSpecifierAST *classAST = find(); + return methodDeclarationInClass(doc->translationUnit(), classAST, xsSpec); } else { return InsertionLocation(); } } +InsertionLocation InsertionPointLocator::methodDeclarationInClass( + const TranslationUnit *tu, + const ClassSpecifierAST *clazz, + InsertionPointLocator::AccessSpec xsSpec, + Position pos) const +{ + if (!clazz) + return {}; + QList ranges = collectAccessRanges(tu, + clazz->member_specifier_list, + tu->tokenKind(clazz->classkey_token) == T_CLASS + ? InsertionPointLocator::Private + : InsertionPointLocator::Public, + clazz->lbrace_token, + clazz->rbrace_token); + + unsigned beforeToken = 0; + bool needsLeadingEmptyLine = false; + bool needsPrefix = false; + bool needsSuffix = false; + findMatch(ranges, xsSpec, pos, beforeToken, needsLeadingEmptyLine, needsPrefix, needsSuffix); + + int line = 0, column = 0; + if (pos == InsertionPointLocator::AccessSpecEnd) + tu->getTokenStartPosition(beforeToken, &line, &column); + else + tu->getTokenEndPosition(beforeToken, &line, &column); + + QString prefix; + if (needsLeadingEmptyLine) + prefix += QLatin1String("\n"); + if (needsPrefix) + prefix += InsertionPointLocator::accessSpecToString(xsSpec) + QLatin1String(":\n"); + + QString suffix; + if (needsSuffix) + suffix = QLatin1Char('\n'); + const QString fileName = QString::fromUtf8(tu->fileName(), tu->fileNameLength()); + return InsertionLocation(fileName, prefix, suffix, line, column); +} + +InsertionPointLocator::AccessSpec symbolsAccessSpec(Symbol *symbol) +{ + if (symbol->isPrivate()) + return InsertionPointLocator::Private; + if (symbol->isProtected()) + return InsertionPointLocator::Protected; + if (symbol->isPublic()) + return InsertionPointLocator::Public; + return InsertionPointLocator::Invalid; +} + +InsertionLocation InsertionPointLocator::constructorDeclarationInClass( + const CPlusPlus::TranslationUnit *tu, + const ClassSpecifierAST *clazz, + InsertionPointLocator::AccessSpec xsSpec, + int constructorArgumentCount) const +{ + std::map> constructors; + for (DeclarationAST *rootDecl : clazz->member_specifier_list) { + if (SimpleDeclarationAST *ast = rootDecl->asSimpleDeclaration()) { + if (!ast->symbols) + continue; + if (symbolsAccessSpec(ast->symbols->value) != xsSpec) + continue; + if (ast->symbols->value->name() != clazz->name->name) + continue; + for (DeclaratorAST *d : ast->declarator_list) { + for (PostfixDeclaratorAST *decl : d->postfix_declarator_list) { + if (FunctionDeclaratorAST *func = decl->asFunctionDeclarator()) { + int params = 0; + if (func->parameter_declaration_clause) { + params = size( + func->parameter_declaration_clause->parameter_declaration_list); + } + auto &entry = constructors[params]; + if (!entry.first) + entry.first = rootDecl; + entry.second = rootDecl; + } + } + } + } + } + if (constructors.empty()) + return methodDeclarationInClass(tu, clazz, xsSpec, AccessSpecBegin); + + auto iter = constructors.lower_bound(constructorArgumentCount); + if (iter == constructors.end()) { + // we have a constructor with x arguments but there are only ones with < x arguments + --iter; // select greatest one (in terms of argument count) + } + const QString fileName = QString::fromUtf8(tu->fileName(), tu->fileNameLength()); + int line, column; + if (iter->first <= constructorArgumentCount) { + tu->getTokenEndPosition(iter->second.second->lastToken() - 1, &line, &column); + return InsertionLocation(fileName, "\n", "", line, column); + } + // before iter + // end pos of firstToken-1 instead of start pos of firstToken to skip leading commend + tu->getTokenEndPosition(iter->second.first->firstToken() - 1, &line, &column); + return InsertionLocation(fileName, "\n", "", line, column); +} + namespace { template class HighestValue diff --git a/src/plugins/cpptools/insertionpointlocator.h b/src/plugins/cpptools/insertionpointlocator.h index b3ce7542600..1d79a7000fb 100644 --- a/src/plugins/cpptools/insertionpointlocator.h +++ b/src/plugins/cpptools/insertionpointlocator.h @@ -86,6 +86,11 @@ public: }; static QString accessSpecToString(InsertionPointLocator::AccessSpec xsSpec); + enum Position { + AccessSpecBegin, + AccessSpecEnd, + }; + public: explicit InsertionPointLocator(const CppRefactoringChanges &refactoringChanges); @@ -93,6 +98,16 @@ public: const CPlusPlus::Class *clazz, AccessSpec xsSpec) const; + InsertionLocation methodDeclarationInClass(const CPlusPlus::TranslationUnit *tu, + const CPlusPlus::ClassSpecifierAST *clazz, + AccessSpec xsSpec, + Position positionInAccessSpec = AccessSpecEnd) const; + + InsertionLocation constructorDeclarationInClass(const CPlusPlus::TranslationUnit *tu, + const CPlusPlus::ClassSpecifierAST *clazz, + AccessSpec xsSpec, + int constructorArgumentCount) const; + const QList methodDefinition( CPlusPlus::Symbol *declaration, bool useSymbolFinder = true,