diff options
Diffstat (limited to 'src/plugins/cppeditor')
129 files changed, 23631 insertions, 21070 deletions
diff --git a/src/plugins/cppeditor/CMakeLists.txt b/src/plugins/cppeditor/CMakeLists.txt index 3a137a4d64..bdfe7b2283 100644 --- a/src/plugins/cppeditor/CMakeLists.txt +++ b/src/plugins/cppeditor/CMakeLists.txt @@ -53,7 +53,6 @@ add_qtc_plugin(CppEditor cppincludehierarchy.cpp cppincludehierarchy.h cppincludesfilter.cpp cppincludesfilter.h cppindexingsupport.cpp cppindexingsupport.h - cppinsertvirtualmethods.cpp cppinsertvirtualmethods.h cpplocalrenaming.cpp cpplocalrenaming.h cpplocalsymbols.cpp cpplocalsymbols.h cpplocatordata.cpp cpplocatordata.h @@ -71,14 +70,6 @@ add_qtc_plugin(CppEditor cppprojectpartchooser.cpp cppprojectpartchooser.h cppprojectupdater.cpp cppprojectupdater.h cppqtstyleindenter.cpp cppqtstyleindenter.h - cppquickfix.cpp cppquickfix.h - cppquickfixassistant.cpp cppquickfixassistant.h - cppquickfixes.cpp cppquickfixes.h - cppquickfixprojectsettings.cpp cppquickfixprojectsettings.h - cppquickfixprojectsettingswidget.cpp cppquickfixprojectsettingswidget.h - cppquickfixsettings.cpp cppquickfixsettings.h - cppquickfixsettingspage.cpp cppquickfixsettingspage.h - cppquickfixsettingswidget.cpp cppquickfixsettingswidget.h cpprefactoringchanges.cpp cpprefactoringchanges.h cppselectionchanger.cpp cppselectionchanger.h cppsemanticinfo.h @@ -104,6 +95,37 @@ add_qtc_plugin(CppEditor insertionpointlocator.cpp insertionpointlocator.h projectinfo.cpp projectinfo.h projectpart.cpp projectpart.h + quickfixes/assigntolocalvariable.cpp quickfixes/assigntolocalvariable.h + quickfixes/bringidentifierintoscope.cpp quickfixes/bringidentifierintoscope.h + quickfixes/completeswitchstatement.cpp quickfixes/completeswitchstatement.h + quickfixes/convertnumericliteral.cpp quickfixes/convertnumericliteral.h + quickfixes/convertqt4connect.cpp quickfixes/convertqt4connect.h + quickfixes/convertstringliteral.cpp quickfixes/convertstringliteral.h + quickfixes/converttocamelcase.cpp quickfixes/converttocamelcase.h + quickfixes/converttometamethodcall.cpp quickfixes/converttometamethodcall.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 + quickfixes/cppquickfixes.cpp quickfixes/cppquickfixes.h + quickfixes/cppquickfixhelpers.h quickfixes/cppquickfixhelpers.cpp + quickfixes/cppquickfixprojectsettings.cpp quickfixes/cppquickfixprojectsettings.h + quickfixes/cppquickfixprojectsettingswidget.cpp quickfixes/cppquickfixprojectsettingswidget.h + quickfixes/cppquickfixsettings.cpp quickfixes/cppquickfixsettings.h + quickfixes/cppquickfixsettingspage.cpp quickfixes/cppquickfixsettingspage.h + quickfixes/cppquickfixsettingswidget.cpp quickfixes/cppquickfixsettingswidget.h + quickfixes/convertfromandtopointer.cpp quickfixes/convertfromandtopointer.h + quickfixes/createdeclarationfromuse.cpp quickfixes/createdeclarationfromuse.h + quickfixes/extractfunction.cpp quickfixes/extractfunction.h + quickfixes/extractliteralasparameter.cpp quickfixes/extractliteralasparameter.h + quickfixes/insertfunctiondefinition.cpp quickfixes/insertfunctiondefinition.h + quickfixes/logicaloperationquickfixes.cpp quickfixes/logicaloperationquickfixes.h + quickfixes/moveclasstoownfile.cpp quickfixes/moveclasstoownfile.h + quickfixes/movefunctiondefinition.cpp quickfixes/movefunctiondefinition.h + quickfixes/removeusingnamespace.cpp quickfixes/removeusingnamespace.h + quickfixes/rewritecomment.cpp quickfixes/rewritecomment.cpp + quickfixes/rewritecontrolstatements.cpp quickfixes/rewritecontrolstatements.h + quickfixes/splitsimpledeclaration.cpp quickfixes/splitsimpledeclaration.h resourcepreviewhoverhandler.cpp resourcepreviewhoverhandler.h searchsymbols.cpp searchsymbols.h semantichighlighter.cpp semantichighlighter.h @@ -112,7 +134,7 @@ add_qtc_plugin(CppEditor typehierarchybuilder.cpp typehierarchybuilder.h wrappablelineedit.cpp wrappablelineedit.h EXPLICIT_MOC - cppquickfixsettingswidget.h + quickfixes/cppquickfixsettingswidget.h ) extend_qtc_plugin(CppEditor @@ -127,7 +149,6 @@ extend_qtc_plugin(CppEditor cpplocatorfilter_test.cpp cpplocatorfilter_test.h cppmodelmanager_test.cpp cppmodelmanager_test.h cpppointerdeclarationformatter_test.cpp cpppointerdeclarationformatter_test.h - cppquickfix_test.cpp cppquickfix_test.h cpprenaming_test.cpp cpprenaming_test.h cppsourceprocessertesthelper.cpp cppsourceprocessertesthelper.h cppsourceprocessor_test.cpp cppsourceprocessor_test.h @@ -137,9 +158,10 @@ extend_qtc_plugin(CppEditor followsymbol_switchmethoddecldef_test.cpp followsymbol_switchmethoddecldef_test.h modelmanagertesthelper.cpp modelmanagertesthelper.h projectinfo_test.cpp projectinfo_test.h + quickfixes/cppquickfix_test.cpp quickfixes/cppquickfix_test.h symbolsearcher_test.cpp symbolsearcher_test.h typehierarchybuilder_test.cpp typehierarchybuilder_test.h EXPLICIT_MOC cppdoxygen_test.h - cppquickfix_test.h + quickfixes/cppquickfix_test.h ) diff --git a/src/plugins/cppeditor/cppbuiltinmodelmanagersupport.cpp b/src/plugins/cppeditor/cppbuiltinmodelmanagersupport.cpp index 0133292703..3a82a41132 100644 --- a/src/plugins/cppeditor/cppbuiltinmodelmanagersupport.cpp +++ b/src/plugins/cppeditor/cppbuiltinmodelmanagersupport.cpp @@ -111,7 +111,8 @@ void BuiltinModelManagerSupport::followSymbol(const CursorInEditor &data, SymbolFinder finder; m_followSymbol->findLink(data, processLinkCallback, resolveTarget, CppModelManager::snapshot(), - data.editorWidget()->semanticInfo().doc, &finder, inNextSplit); + data.editorWidget() ? data.editorWidget()->semanticInfo().doc : data.cppDocument(), + &finder, inNextSplit); } void BuiltinModelManagerSupport::followSymbolToType(const CursorInEditor &data, diff --git a/src/plugins/cppeditor/cppcodemodelsettings.cpp b/src/plugins/cppeditor/cppcodemodelsettings.cpp index c798cd9dd3..53c009d4c3 100644 --- a/src/plugins/cppeditor/cppcodemodelsettings.cpp +++ b/src/plugins/cppeditor/cppcodemodelsettings.cpp @@ -48,6 +48,7 @@ public: CppCodeModelSettings settings() const; void setSettings(const CppCodeModelSettings &settings); + void forceCustomSettingsSettings(const CppCodeModelSettings &settings); bool useGlobalSettings() const { return m_useGlobalSettings; } void setUseGlobalSettings(bool useGlobal); @@ -64,6 +65,7 @@ private: static Key pchUsageKey() { return Constants::CPPEDITOR_MODEL_MANAGER_PCH_USAGE; } static Key interpretAmbiguousHeadersAsCHeadersKey() { return Constants::CPPEDITOR_INTERPRET_AMBIGIUOUS_HEADERS_AS_C_HEADERS; } +static Key enableIndexingKey() { return "EnableIndexing"; } static Key skipIndexingBigFilesKey() { return Constants::CPPEDITOR_SKIP_INDEXING_BIG_FILES; } static Key ignoreFilesKey() { return Constants::CPPEDITOR_IGNORE_FILES; } static Key ignorePatternKey() { return Constants::CPPEDITOR_IGNORE_PATTERN; } @@ -76,6 +78,7 @@ bool operator==(const CppEditor::CppCodeModelSettings &s1, { return s1.pchUsage == s2.pchUsage && s1.interpretAmbigiousHeadersAsC == s2.interpretAmbigiousHeadersAsC + && s1.enableIndexing == s2.enableIndexing && s1.skipIndexingBigFiles == s2.skipIndexingBigFiles && s1.useBuiltinPreprocessor == s2.useBuiltinPreprocessor && s1.indexerFileSizeLimitInMb == s2.indexerFileSizeLimitInMb @@ -88,6 +91,7 @@ Store CppCodeModelSettings::toMap() const Store store; store.insert(pchUsageKey(), pchUsage); store.insert(interpretAmbiguousHeadersAsCHeadersKey(), interpretAmbigiousHeadersAsC); + store.insert(enableIndexingKey(), enableIndexing); store.insert(skipIndexingBigFilesKey(), skipIndexingBigFiles); store.insert(ignoreFilesKey(), ignoreFiles); store.insert(ignorePatternKey(), ignorePattern); @@ -103,6 +107,7 @@ void CppCodeModelSettings::fromMap(const Utils::Store &store) interpretAmbigiousHeadersAsC = store.value(interpretAmbiguousHeadersAsCHeadersKey(), def.interpretAmbigiousHeadersAsC) .toBool(); + enableIndexing = store.value(enableIndexingKey(), def.enableIndexing).toBool(); skipIndexingBigFiles = store.value(skipIndexingBigFilesKey(), def.skipIndexingBigFiles).toBool(); ignoreFiles = store.value(ignoreFilesKey(), def.ignoreFiles).toBool(); @@ -155,8 +160,7 @@ void CppCodeModelSettings::setSettingsForProject( ProjectExplorer::Project *project, const CppCodeModelSettings &settings) { CppCodeModelProjectSettings pSettings(project); - pSettings.setUseGlobalSettings(false); - pSettings.setSettings(settings); + pSettings.forceCustomSettingsSettings(settings); } void CppCodeModelSettings::setGlobal(const CppCodeModelSettings &settings) @@ -218,6 +222,12 @@ void CppCodeModelProjectSettings::setSettings(const CppCodeModelSettings &settin CppModelManager::handleSettingsChange(m_project); } +void CppCodeModelProjectSettings::forceCustomSettingsSettings(const CppCodeModelSettings &settings) +{ + m_useGlobalSettings = false; + setSettings(settings); +} + void CppCodeModelProjectSettings::setUseGlobalSettings(bool useGlobal) { m_useGlobalSettings = useGlobal; @@ -261,6 +271,7 @@ private: QCheckBox *m_interpretAmbiguousHeadersAsCHeaders; QCheckBox *m_ignorePchCheckBox; QCheckBox *m_useBuiltinPreprocessorCheckBox; + QCheckBox *m_enableIndexingCheckBox; QCheckBox *m_skipIndexingBigFilesCheckBox; QSpinBox *m_bigFilesLimitSpinBox; QCheckBox *m_ignoreFilesCheckBox; @@ -272,6 +283,12 @@ CppCodeModelSettingsWidget::CppCodeModelSettingsWidget(const CppCodeModelSetting m_interpretAmbiguousHeadersAsCHeaders = new QCheckBox(Tr::tr("Interpret ambiguous headers as C headers")); + m_enableIndexingCheckBox = new QCheckBox(Tr::tr("Enable indexing")); + m_enableIndexingCheckBox->setChecked(settings.enableIndexing); + m_enableIndexingCheckBox->setToolTip(Tr::tr( + "Indexing should almost always be kept enabled, as disabling it will severely limit the " + "capabilities of the code model.")); + m_skipIndexingBigFilesCheckBox = new QCheckBox(Tr::tr("Do not index files greater than")); m_skipIndexingBigFilesCheckBox->setChecked(settings.skipIndexingBigFiles); @@ -320,6 +337,7 @@ CppCodeModelSettingsWidget::CppCodeModelSettingsWidget(const CppCodeModelSetting m_interpretAmbiguousHeadersAsCHeaders, m_ignorePchCheckBox, m_useBuiltinPreprocessorCheckBox, + m_enableIndexingCheckBox, Row { m_skipIndexingBigFilesCheckBox, m_bigFilesLimitSpinBox, st }, Row { Column { m_ignoreFilesCheckBox, st }, m_ignorePatternTextEdit }, } @@ -329,6 +347,7 @@ CppCodeModelSettingsWidget::CppCodeModelSettingsWidget(const CppCodeModelSetting for (const QCheckBox *const b : {m_interpretAmbiguousHeadersAsCHeaders, m_ignorePchCheckBox, + m_enableIndexingCheckBox, m_useBuiltinPreprocessorCheckBox, m_skipIndexingBigFilesCheckBox, m_ignoreFilesCheckBox}) { @@ -349,6 +368,7 @@ CppCodeModelSettings CppCodeModelSettingsWidget::settings() const { CppCodeModelSettings settings; settings.interpretAmbigiousHeadersAsC = m_interpretAmbiguousHeadersAsCHeaders->isChecked(); + settings.enableIndexing = m_enableIndexingCheckBox->isChecked(); settings.skipIndexingBigFiles = m_skipIndexingBigFilesCheckBox->isChecked(); settings.useBuiltinPreprocessor = m_useBuiltinPreprocessorCheckBox->isChecked(); settings.ignoreFiles = m_ignoreFilesCheckBox->isChecked(); diff --git a/src/plugins/cppeditor/cppcodemodelsettings.h b/src/plugins/cppeditor/cppcodemodelsettings.h index 1a465baaa4..9bf2b6862d 100644 --- a/src/plugins/cppeditor/cppcodemodelsettings.h +++ b/src/plugins/cppeditor/cppcodemodelsettings.h @@ -62,6 +62,7 @@ public: bool skipIndexingBigFiles = true; bool useBuiltinPreprocessor = true; bool ignoreFiles = false; + bool enableIndexing = true; bool m_categorizeFindReferences = false; // Ephemeral! private: diff --git a/src/plugins/cppeditor/cppcodestylesettingspage.cpp b/src/plugins/cppeditor/cppcodestylesettingspage.cpp index 04acf12880..edcca95a34 100644 --- a/src/plugins/cppeditor/cppcodestylesettingspage.cpp +++ b/src/plugins/cppeditor/cppcodestylesettingspage.cpp @@ -244,7 +244,7 @@ public: sizePolicy.setVerticalPolicy(QSizePolicy::Preferred); m_statementMacros->setToolTip( - Tr::tr("Macros that can be used as statements without a trailing semicolon")); + Tr::tr("Macros that can be used as statements without a trailing semicolon.")); m_statementMacros->setSizePolicy(sizePolicy); const Group statementMacrosGroup { title(Tr::tr("Statement macros")), diff --git a/src/plugins/cppeditor/cppcompletion_test.cpp b/src/plugins/cppeditor/cppcompletion_test.cpp index fdc30e16be..6f152c65a6 100644 --- a/src/plugins/cppeditor/cppcompletion_test.cpp +++ b/src/plugins/cppeditor/cppcompletion_test.cpp @@ -2889,6 +2889,16 @@ void CompletionTest::testCompletionMemberAccessOperator_data() ) << _("p->") << QStringList({"S", "m"}) << false << false; + QTest::newRow("dot to arrow: template + reference + double typedef") + << _("template <typename T> struct C {\n" + " using ref = T &;\n" + " ref operator[](int i);\n" + "};\n" + "struct S { int m; };\n" + "template<typename T> using CS = C<T>;\n" + "CS<S *> v;\n" + "@\n") + << _("v[0].") << QStringList({"S", "m"}) << false << true; } } // namespace CppEditor::Internal diff --git a/src/plugins/cppeditor/cppeditor.qbs b/src/plugins/cppeditor/cppeditor.qbs index c535488de9..c02ed9b8e6 100644 --- a/src/plugins/cppeditor/cppeditor.qbs +++ b/src/plugins/cppeditor/cppeditor.qbs @@ -119,8 +119,6 @@ QtcPlugin { "cppincludesfilter.h", "cppindexingsupport.cpp", "cppindexingsupport.h", - "cppinsertvirtualmethods.cpp", - "cppinsertvirtualmethods.h", "cpplocalrenaming.cpp", "cpplocalrenaming.h", "cpplocalsymbols.cpp", @@ -153,22 +151,6 @@ QtcPlugin { "cppprojectinfogenerator.h", "cppprojectupdater.cpp", "cppprojectupdater.h", - "cppquickfix.cpp", - "cppquickfix.h", - "cppquickfixassistant.cpp", - "cppquickfixassistant.h", - "cppquickfixes.cpp", - "cppquickfixes.h", - "cppquickfixprojectsettings.cpp", - "cppquickfixprojectsettings.h", - "cppquickfixprojectsettingswidget.cpp", - "cppquickfixprojectsettingswidget.h", - "cppquickfixsettings.cpp", - "cppquickfixsettings.h", - "cppquickfixsettingspage.cpp", - "cppquickfixsettingspage.h", - "cppquickfixsettingswidget.cpp", - "cppquickfixsettingswidget.h", "cppqtstyleindenter.cpp", "cppqtstyleindenter.h", "cpprefactoringchanges.cpp", @@ -234,6 +216,75 @@ QtcPlugin { ] Group { + name: "Quickfixes" + prefix: "quickfixes/" + files: [ + "assigntolocalvariable.cpp", + "assigntolocalvariable.h", + "bringidentifierintoscope.cpp", + "bringidentifierintoscope.h", + "completeswitchstatement.cpp", + "completeswitchstatement.h", + "convertfromandtopointer.cpp", + "convertfromandtopointer.h", + "convertnumericliteral.cpp", + "convertnumericliteral.h", + "convertqt4connect.cpp", + "convertqt4connect.h", + "convertstringliteral.cpp", + "convertstringliteral.h", + "converttocamelcase.cpp", + "converttocamelcase.h", + "converttometamethodcall.cpp", + "converttometamethodcall.h", + "cppcodegenerationquickfixes.cpp", + "cppcodegenerationquickfixes.h", + "cppinsertvirtualmethods.cpp", + "cppinsertvirtualmethods.h", + "cppquickfix.cpp", + "cppquickfix.h", + "cppquickfixassistant.cpp", + "cppquickfixassistant.h", + "cppquickfixes.cpp", + "cppquickfixes.h", + "cppquickfixhelpers.cpp", + "cppquickfixhelpers.h", + "cppquickfixprojectsettings.cpp", + "cppquickfixprojectsettings.h", + "cppquickfixprojectsettingswidget.cpp", + "cppquickfixprojectsettingswidget.h", + "cppquickfixsettings.cpp", + "cppquickfixsettings.h", + "cppquickfixsettingspage.cpp", + "cppquickfixsettingspage.h", + "cppquickfixsettingswidget.cpp", + "cppquickfixsettingswidget.h", + "createdeclarationfromuse.cpp", + "createdeclarationfromuse.h", + "extractfunction.cpp", + "extractfunction.h", + "extractliteralasparameter.cpp", + "extractliteralasparameter.h", + "insertfunctiondefinition.cpp", + "insertfunctiondefinition.h", + "logicaloperationquickfixes.cpp", + "logicaloperationquickfixes.h", + "moveclasstoownfile.cpp", + "moveclasstoownfile.h", + "movefunctiondefinition.cpp", + "movefunctiondefinition.h", + "removeusingnamespace.cpp", + "removeusingnamespace.h", + "rewritecomment.cpp", + "rewritecomment.h", + "rewritecontrolstatements.cpp", + "rewritecontrolstatements.h", + "splitsimpledeclaration.cpp", + "splitsimpledeclaration.h", + ] + } + + Group { name: "TestCase" condition: qtc.withPluginTests || qtc.withAutotests files: [ @@ -244,6 +295,16 @@ QtcPlugin { QtcTestFiles { cpp.defines: outer.concat(['SRCDIR="' + FileInfo.path(filePath) + '"']) + + Group { + name: "Quickfix tests" + prefix: "quickfixes/" + files: [ + "cppquickfix_test.cpp", + "cppquickfix_test.h", + ] + } + files: [ "compileroptionsbuilder_test.cpp", "compileroptionsbuilder_test.h", @@ -263,8 +324,6 @@ QtcPlugin { "cppmodelmanager_test.h", "cpppointerdeclarationformatter_test.cpp", "cpppointerdeclarationformatter_test.h", - "cppquickfix_test.cpp", - "cppquickfix_test.h", "cpprenaming_test.cpp", "cpprenaming_test.h", "cppsourceprocessor_test.cpp", diff --git a/src/plugins/cppeditor/cppeditor.qrc b/src/plugins/cppeditor/cppeditor.qrc index c28111d21e..e90b587dc2 100644 --- a/src/plugins/cppeditor/cppeditor.qrc +++ b/src/plugins/cppeditor/cppeditor.qrc @@ -4,5 +4,42 @@ <file>images/dark_qt_h.png</file> <file>images/dark_qt_c.png</file> <file>testcases/highlightingtestcase.cpp</file> + <file>testcases/move-class/complex/complex.pro</file> + <file>testcases/move-class/complex/main.cpp</file> + <file>testcases/move-class/complex/main.cpp_expected</file> + <file>testcases/move-class/complex/theclass.cpp_expected</file> + <file>testcases/move-class/complex/theheader.h</file> + <file>testcases/move-class/complex/theheader.h_expected</file> + <file>testcases/move-class/complex/thesource.cpp</file> + <file>testcases/move-class/complex/thesource.cpp_expected</file> + <file>testcases/move-class/complex/complex.pro_expected</file> + <file>testcases/move-class/nested/main.cpp</file> + <file>testcases/move-class/nested/nested.pro</file> + <file>testcases/move-class/match1/match1.pro</file> + <file>testcases/move-class/match1/TheClass.h</file> + <file>testcases/move-class/match2/match2.pro</file> + <file>testcases/move-class/match2/theclass.h</file> + <file>testcases/move-class/match3/match3.pro</file> + <file>testcases/move-class/match3/the_class.h</file> + <file>testcases/move-class/single/single.pro</file> + <file>testcases/move-class/single/theheader.h</file> + <file>testcases/move-class/header-only/header-only.pro</file> + <file>testcases/move-class/header-only/header-only.pro_expected</file> + <file>testcases/move-class/header-only/theclass.h_expected</file> + <file>testcases/move-class/header-only/theheader.h</file> + <file>testcases/move-class/header-only/thesource.cpp</file> + <file>testcases/move-class/header-only/theheader.h_expected</file> + <file>testcases/move-class/header-only/thesource.cpp_expected</file> + <file>testcases/move-class/decl-in-source/decl-in-source.pro</file> + <file>testcases/move-class/decl-in-source/decl-in-source.pro_expected</file> + <file>testcases/move-class/decl-in-source/theclass.h_expected</file> + <file>testcases/move-class/decl-in-source/thesource.cpp</file> + <file>testcases/move-class/decl-in-source/thesource.cpp_expected</file> + <file>testcases/move-class/template/template.pro</file> + <file>testcases/move-class/template/template.pro_expected</file> + <file>testcases/move-class/template/theclass.h_expected</file> + <file>testcases/move-class/template/theheader.h</file> + <file>testcases/move-class/template/theheader.h_expected</file> + <file>testcases/move-class/complex/theclass.h_expected</file> </qresource> </RCC> diff --git a/src/plugins/cppeditor/cppeditordocument.cpp b/src/plugins/cppeditor/cppeditordocument.cpp index 6f2a67fede..1f5adc4baa 100644 --- a/src/plugins/cppeditor/cppeditordocument.cpp +++ b/src/plugins/cppeditor/cppeditordocument.cpp @@ -11,7 +11,7 @@ #include "cppeditorconstants.h" #include "cppeditortr.h" #include "cpphighlighter.h" -#include "cppquickfixassistant.h" +#include "quickfixes/cppquickfixassistant.h" #include <coreplugin/editormanager/editormanager.h> #include <coreplugin/session.h> diff --git a/src/plugins/cppeditor/cppeditorplugin.cpp b/src/plugins/cppeditor/cppeditorplugin.cpp index 6718d4cbc8..9d0b161583 100644 --- a/src/plugins/cppeditor/cppeditorplugin.cpp +++ b/src/plugins/cppeditor/cppeditorplugin.cpp @@ -16,12 +16,12 @@ #include "cppmodelmanager.h" #include "cppoutline.h" #include "cppprojectupdater.h" -#include "cppquickfixes.h" -#include "cppquickfixprojectsettingswidget.h" -#include "cppquickfixsettingspage.h" #include "cpptoolsreuse.h" #include "cpptoolssettings.h" #include "cpptypehierarchy.h" +#include "quickfixes/cppquickfixes.h" +#include "quickfixes/cppquickfixprojectsettingswidget.h" +#include "quickfixes/cppquickfixsettingspage.h" #include "resourcepreviewhoverhandler.h" #ifdef WITH_TESTS @@ -31,12 +31,10 @@ #include "cppdoxygen_test.h" #include "cpphighlighter.h" #include "cppincludehierarchy_test.h" -#include "cppinsertvirtualmethods.h" #include "cpplocalsymbols_test.h" #include "cpplocatorfilter_test.h" #include "cppmodelmanager_test.h" #include "cpppointerdeclarationformatter_test.h" -#include "cppquickfix_test.h" #include "cpprenaming_test.h" #include "cppsourceprocessor_test.h" #include "cppuseselections_test.h" @@ -45,6 +43,7 @@ #include "functionutils.h" #include "includeutils.h" #include "projectinfo_test.h" +#include "quickfixes/cppquickfix_test.h" #include "symbolsearcher_test.h" #include "typehierarchybuilder_test.h" #endif @@ -514,7 +513,6 @@ void CppEditorPlugin::registerTests() addTest<Tests::FileAndTokenActionsTest>(); addTest<Tests::FollowSymbolTest>(); addTest<Tests::IncludeHierarchyTest>(); - addTest<Tests::InsertVirtualMethodsTest>(); addTest<Tests::QuickfixTest>(); addTest<Tests::GlobalRenamingTest>(); addTest<Tests::SelectionsTest>(); diff --git a/src/plugins/cppeditor/cppeditorwidget.cpp b/src/plugins/cppeditor/cppeditorwidget.cpp index 5882c8d164..d5d64597b9 100644 --- a/src/plugins/cppeditor/cppeditorwidget.cpp +++ b/src/plugins/cppeditor/cppeditorwidget.cpp @@ -14,11 +14,11 @@ #include "cpplocalrenaming.h" #include "cppmodelmanager.h" #include "cpppreprocessordialog.h" -#include "cppquickfixassistant.h" #include "cppselectionchanger.h" #include "cppsemanticinfo.h" #include "cppuseselectionsupdater.h" #include "doxygengenerator.h" +#include "quickfixes/cppquickfixassistant.h" #include <coreplugin/actionmanager/actioncontainer.h> #include <coreplugin/actionmanager/actionmanager.h> diff --git a/src/plugins/cppeditor/cppfunctiondecldeflink.cpp b/src/plugins/cppeditor/cppfunctiondecldeflink.cpp index e489045f35..4cd17ae52d 100644 --- a/src/plugins/cppeditor/cppfunctiondecldeflink.cpp +++ b/src/plugins/cppeditor/cppfunctiondecldeflink.cpp @@ -8,8 +8,8 @@ #include "cppeditortr.h" #include "cppeditorwidget.h" #include "cpplocalsymbols.h" -#include "cppquickfixassistant.h" #include "cpptoolsreuse.h" +#include "quickfixes/cppquickfixassistant.h" #include "symbolfinder.h" #include <coreplugin/actionmanager/actionmanager.h> diff --git a/src/plugins/cppeditor/cppindexingsupport.cpp b/src/plugins/cppeditor/cppindexingsupport.cpp index 1d637f6e8a..73e3fafff3 100644 --- a/src/plugins/cppeditor/cppindexingsupport.cpp +++ b/src/plugins/cppeditor/cppindexingsupport.cpp @@ -219,21 +219,22 @@ static void index(QPromise<void> &promise, const ParseParams params) qCDebug(indexerLog) << "Indexing finished."; } -static void parse(QPromise<void> &promise, const ParseParams ¶ms) +static void parse( + QPromise<void> &promise, + const std::function<QSet<QString>()> &sourceFiles, + const ProjectExplorer::HeaderPaths &headerPaths, + const WorkingCopy &workingCopy) { - const QSet<QString> &files = params.sourceFiles; - if (files.isEmpty()) - return; - - promise.setProgressRange(0, files.size()); + ParseParams params{headerPaths, workingCopy, sourceFiles()}; + promise.setProgressRange(0, params.sourceFiles.size()); if (CppIndexingSupport::isFindErrorsIndexingActive()) indexFindErrors(promise, params); else index(promise, params); - promise.setProgressValue(files.size()); - CppModelManager::finishedRefreshingSourceFiles(files); + promise.setProgressValue(params.sourceFiles.size()); + CppModelManager::finishedRefreshingSourceFiles(params.sourceFiles); } } // anonymous namespace @@ -300,18 +301,19 @@ bool CppIndexingSupport::isFindErrorsIndexingActive() return Utils::qtcEnvironmentVariable("QTC_FIND_ERRORS_INDEXING") == "1"; } -QFuture<void> CppIndexingSupport::refreshSourceFiles(const QSet<QString> &sourceFiles, - CppModelManager::ProgressNotificationMode mode) +QFuture<void> CppIndexingSupport::refreshSourceFiles( + const std::function<QSet<QString>()> &sourceFiles, + CppModelManager::ProgressNotificationMode mode) { - ParseParams params; - params.headerPaths = CppModelManager::headerPaths(); - params.workingCopy = CppModelManager::workingCopy(); - params.sourceFiles = sourceFiles; - - QFuture<void> result = Utils::asyncRun(CppModelManager::sharedThreadPool(), parse, params); + QFuture<void> result = Utils::asyncRun( + CppModelManager::sharedThreadPool(), + parse, + sourceFiles, + CppModelManager::headerPaths(), + CppModelManager::workingCopy()); m_synchronizer.addFuture(result); - if (mode == CppModelManager::ForcedProgressNotification || sourceFiles.count() > 1) { + if (mode == CppModelManager::ForcedProgressNotification) { Core::ProgressManager::addTask(result, Tr::tr("Parsing C/C++ Files"), CppEditor::Constants::TASK_INDEX); } diff --git a/src/plugins/cppeditor/cppindexingsupport.h b/src/plugins/cppeditor/cppindexingsupport.h index c04eb8ba76..f32498fa5e 100644 --- a/src/plugins/cppeditor/cppindexingsupport.h +++ b/src/plugins/cppeditor/cppindexingsupport.h @@ -11,6 +11,8 @@ #include <QFuture> +#include <functional> + namespace Utils { class SearchResultItem; } namespace CppEditor { @@ -59,8 +61,10 @@ class CPPEDITOR_EXPORT CppIndexingSupport public: static bool isFindErrorsIndexingActive(); - QFuture<void> refreshSourceFiles(const QSet<QString> &sourceFiles, - CppModelManager::ProgressNotificationMode mode); + QFuture<void> refreshSourceFiles( + const std::function<QSet<QString>()> &sourceFiles, + CppModelManager::ProgressNotificationMode mode); + private: Utils::FutureSynchronizer m_synchronizer; }; diff --git a/src/plugins/cppeditor/cppmodelmanager.cpp b/src/plugins/cppeditor/cppmodelmanager.cpp index ac7fe32b91..fbb727eb72 100644 --- a/src/plugins/cppeditor/cppmodelmanager.cpp +++ b/src/plugins/cppeditor/cppmodelmanager.cpp @@ -90,6 +90,7 @@ #include <QWriteLocker> #include <memory> +#include <unordered_map> #if defined(QTCREATOR_WITH_DUMP_AST) && defined(Q_CC_GNU) #define WITH_AST_DUMP @@ -1302,16 +1303,19 @@ CppLocatorData *CppModelManager::locatorData() return &d->m_locatorData; } -static QSet<QString> filteredFilesRemoved(const QSet<QString> &files, int fileSizeLimitInMb, - bool ignoreFiles, - const QString& ignorePattern) +static QSet<QString> filteredFilesRemoved(const QSet<QString> &files, + const CppCodeModelSettings &settings) { - if (fileSizeLimitInMb <= 0 && !ignoreFiles) + if (!settings.enableIndexing) + return {}; + + const int fileSizeLimitInMb = settings.effectiveIndexerFileSizeLimitInMb(); + if (fileSizeLimitInMb <= 0 && !settings.ignoreFiles) return files; QSet<QString> result; QList<QRegularExpression> regexes; - const QStringList wildcards = ignorePattern.split('\n'); + const QStringList wildcards = settings.ignorePattern.split('\n'); for (const QString &wildcard : wildcards) regexes.append(QRegularExpression::fromWildcard(wildcard, Qt::CaseInsensitive, @@ -1322,7 +1326,7 @@ static QSet<QString> filteredFilesRemoved(const QSet<QString> &files, int fileSi if (fileSizeLimitInMb > 0 && fileSizeExceedsLimit(filePath, fileSizeLimitInMb)) continue; bool skip = false; - if (ignoreFiles) { + if (settings.ignoreFiles) { for (const QRegularExpression &rx: std::as_const(regexes)) { QRegularExpressionMatch match = rx.match(filePath.absoluteFilePath().path()); if (match.hasMatch()) { @@ -1350,18 +1354,25 @@ QFuture<void> CppModelManager::updateSourceFiles(const QSet<FilePath> &sourceFil if (sourceFiles.isEmpty() || !d->m_indexerEnabled) return QFuture<void>(); - QHash<Project *, QSet<QString>> sourcesPerProject; // TODO: Work with QList from here on? + std::unordered_map<Project *, QSet<QString>> sourcesPerProject; for (const FilePath &fp : sourceFiles) sourcesPerProject[ProjectManager::projectForFile(fp)] << fp.toString(); - QSet<QString> filteredFiles; - for (auto it = sourcesPerProject.cbegin(); it != sourcesPerProject.cend(); ++it) { - const CppCodeModelSettings settings = CppCodeModelSettings::settingsForProject(it.key()); - filteredFiles.unite(filteredFilesRemoved(it.value(), - settings.effectiveIndexerFileSizeLimitInMb(), - settings.ignoreFiles, - settings.ignorePattern)); + std::vector<std::pair<QSet<QString>, CppCodeModelSettings>> sourcesAndSettings; + for (const auto &it : sourcesPerProject) { + sourcesAndSettings + .emplace_back(it.second, CppCodeModelSettings::settingsForProject(it.first)); } + const auto filteredFiles = [sourcesAndSettings = std::move(sourcesAndSettings)] { + QSet<QString> result; + for (const auto &it : sourcesAndSettings) + result.unite(filteredFilesRemoved(it.first, it.second)); + return result; + }; + + // "ReservedProgressNotification" should be shown if there is more than one source file. + if (sourceFiles.size() > 1) + mode = ForcedProgressNotification; return d->m_internalIndexingSupport->refreshSourceFiles(filteredFiles, mode); } @@ -1386,14 +1397,20 @@ ProjectInfo::ConstPtr CppModelManager::projectInfo(Project *project) void CppModelManager::removeProjectInfoFilesAndIncludesFromSnapshot(const ProjectInfo &projectInfo) { QMutexLocker snapshotLocker(&d->m_snapshotMutex); + QStringList removedFiles; for (const ProjectPart::ConstPtr &projectPart : projectInfo.projectParts()) { for (const ProjectFile &cxxFile : std::as_const(projectPart->files)) { const QSet<FilePath> filePaths = d->m_snapshot.allIncludesForDocument(cxxFile.path); - for (const FilePath &filePath : filePaths) + for (const FilePath &filePath : filePaths) { d->m_snapshot.remove(filePath); + removedFiles << filePath.toString(); + } d->m_snapshot.remove(cxxFile.path); + removedFiles << cxxFile.path.toString(); } } + + emit m_instance->aboutToRemoveFiles(removedFiles); } const QList<CppEditorDocumentHandle *> CppModelManager::cppEditorDocuments() diff --git a/src/plugins/cppeditor/cppmodelmanager_test.cpp b/src/plugins/cppeditor/cppmodelmanager_test.cpp index eda55f09ff..21351659a9 100644 --- a/src/plugins/cppeditor/cppmodelmanager_test.cpp +++ b/src/plugins/cppeditor/cppmodelmanager_test.cpp @@ -29,6 +29,8 @@ #include <QScopeGuard> #include <QtTest> +#include <memory> + #define VERIFY_DOCUMENT_REVISION(document, expectedRevision) \ QVERIFY(document); \ QCOMPARE(document->revision(), expectedRevision); @@ -41,6 +43,7 @@ using CPlusPlus::Document; // FIXME: Clean up the namespaces using CppEditor::Tests::ModelManagerTestHelper; using CppEditor::Tests::ProjectOpenerAndCloser; +using CppEditor::Tests::SourceFilesRefreshGuard; using CppEditor::Tests::TemporaryCopiedDir; using CppEditor::Tests::TemporaryDir; using CppEditor::Tests::TestCase; @@ -985,30 +988,6 @@ void ModelManagerTest::testRenameIncludes_data() << "subdir2/header2.h" << "subdir1/header2_moved.h" << false; } -class SourceFilesRefreshGuard : public QObject -{ -public: - SourceFilesRefreshGuard() - { - connect(CppModelManager::instance(), &CppModelManager::sourceFilesRefreshed, this, [this] { - m_refreshed = true; - }); - } - - void reset() { m_refreshed = false; } - bool wait() - { - for (int i = 0; i < 10 && !m_refreshed; ++i) { - CppEditor::Tests::waitForSignalOrTimeout( - CppModelManager::instance(), &CppModelManager::sourceFilesRefreshed, 1000); - } - return m_refreshed; - } - -private: - bool m_refreshed = false; -}; - void ModelManagerTest::testRenameIncludes() { // Set up project. @@ -1392,4 +1371,160 @@ void ModelManagerTest::testSettingsChanges() QCOMPARE(refreshedFiles, filteredP1Sources); } +static bool True = true; +static bool False = false; + +void ModelManagerTest::testOptionalIndexing_data() +{ + QTest::addColumn<bool>("enableGlobally"); + QTest::addColumn<bool *>("enableForP1"); + QTest::addColumn<bool *>("enableForP2"); + QTest::addColumn<bool>("foo1Present"); + QTest::addColumn<bool>("foo2Present"); + + QTest::addRow("globally disabled, no custom settings") + << false << (bool *) nullptr << (bool *) nullptr << false << false; + QTest::addRow("globally disabled, redundantly disabled for project 2") + << false << (bool *) nullptr << &False << false << false; + QTest::addRow("globally disabled, enabled for project 2") + << false << (bool *) nullptr << &True << false << true; + QTest::addRow("globally disabled, redundantly disabled for project 1") + << false << &False << (bool *) nullptr << false << false; + QTest::addRow("globally disabled, redundantly disabled for both projects") + << false << &False << &False << false << false; + QTest::addRow("globally disabled, redundantly disabled for project 1, enabled for project 2") + << false << &False << &True << false << true; + QTest::addRow("globally disabled, enabled for project 1") + << false << &True << (bool *) nullptr << true << false; + QTest::addRow("globally disabled, enabled for project 1, redundantly disabled for project 2") + << false << &True << &False << true << false; + QTest::addRow("globally disabled, enabled for both project") + << false << &True << &True << true << true; + QTest::addRow("globally enabled, no custom settings") + << true << (bool *) nullptr << (bool *) nullptr << true << true ; + QTest::addRow("globally enabled, disabled for project 2") + << true << (bool *) nullptr << &False << true << false; + QTest::addRow("globally enabled, redundantly enabled for project 2") + << true << (bool *) nullptr << &True << true << true; + QTest::addRow("globally enabled, disabled for project 1") + << true << &False << (bool *) nullptr << false << true; + QTest::addRow("globally enabled, disabled for both projects") + << true << &False << &False << false << false; + QTest::addRow("globally enabled, disabled for project 1, redundantly enabled for project 2") + << true << &False << &True << false << true; + QTest::addRow("globally enabled, redundantly enabled for project 1") + << true << &True << (bool *) nullptr << true << true; + QTest::addRow("globally enabled, redundantly enabled for project 1, disabled for project 2") + << true << &True << &False << true << false; + QTest::addRow("globally enabled, redundantly enabled for both projects") + << true << &True << &True << true << true; +} + +void ModelManagerTest::testOptionalIndexing() +{ + if (CppModelManager::isClangCodeModelActive()) + QSKIP("Test only makes sense with built-in locators"); + + QFETCH(bool, enableGlobally); + QFETCH(bool *, enableForP1); + QFETCH(bool *, enableForP2); + QFETCH(bool, foo1Present); + QFETCH(bool, foo2Present); + + // Apply global setting, if necessary. Needs to be reverted in the end. + class TempIndexingDisabler { + public: + TempIndexingDisabler(bool enable) + { + if (!enable) + reset(false); + } + ~TempIndexingDisabler() { reset(true); } + private: + void reset(bool enable) + { + CppCodeModelSettings settings = CppCodeModelSettings::global(); + settings.enableIndexing = enable; + CppCodeModelSettings::setGlobal(settings); + } + }; + const TempIndexingDisabler disabler(enableGlobally); + + // Set up projects. + TemporaryDir tmpDir; + QVERIFY(tmpDir.isValid()); + const MyTestDataDir sourceDir("testdata_optionalindexing"); + const FilePath srcFilePath = FilePath::fromString(sourceDir.path()); + const FilePath projectDir = tmpDir.filePath().pathAppended(srcFilePath.fileName()); + const auto copyResult = srcFilePath.copyRecursively(projectDir); + if (!copyResult) + qDebug() << copyResult.error(); + QVERIFY(copyResult); + Kit * const kit = Utils::findOr(KitManager::kits(), nullptr, [](const Kit *k) { + return k->isValid() && !k->hasWarning() && k->value("QtSupport.QtInformation").isValid(); + }); + if (!kit) + QSKIP("The test requires at least one valid kit with a valid Qt"); + const FilePath p1ProjectFile = projectDir.pathAppended("lib1.pro"); + auto projectMgr = std::make_unique<ProjectOpenerAndCloser>(); + SourceFilesRefreshGuard refreshGuard; + QVERIFY(projectMgr->open(p1ProjectFile, true, kit)); + QVERIFY(refreshGuard.wait()); + refreshGuard.reset(); + Project *p1 = projectMgr->projects().first(); + const FilePath p2ProjectFile = projectDir.pathAppended("lib2.pro"); + QVERIFY(projectMgr->open(p2ProjectFile, true, kit)); + QVERIFY(refreshGuard.wait()); + refreshGuard.reset(); + Project *p2 = projectMgr->projects().last(); + + const auto applyProjectSpecificSettings = [&](Project *p, bool *enable) { + if (!enable) + return; + refreshGuard.reset(); + CppCodeModelSettings settings = CppCodeModelSettings::settingsForProject(p); + settings.enableIndexing = *enable; + CppCodeModelSettings::setSettingsForProject(p, settings); + if (*enable != enableGlobally) + QVERIFY(refreshGuard.wait()); + }; + applyProjectSpecificSettings(p1, enableForP1); + applyProjectSpecificSettings(p2, enableForP2); + + // Compare locator results to expectations. + Core::LocatorFilterEntries entries = Core::LocatorMatcher::runBlocking( + Core::LocatorMatcher::matchers(Core::MatcherType::Functions), "foo"); + const auto hasEntry = [&](const QString &name) { + return Utils::contains(entries, [&](const Core::LocatorFilterEntry &e) { + return e.displayName == name + "()"; + }); + }; + QCOMPARE(hasEntry("foo1"), foo1Present); + QCOMPARE(hasEntry("foo2"), foo2Present); + + // Close and re-open projects, then check again, to see whether the settings persisted + // and are taking effect. + projectMgr.reset(nullptr); + projectMgr.reset(new ProjectOpenerAndCloser); + refreshGuard.reset(); + QVERIFY(projectMgr->open(p1ProjectFile, true, kit)); + p1 = projectMgr->projects().first(); + QCOMPARE( + CppCodeModelSettings::settingsForProject(p1).enableIndexing, + enableForP1 ? *enableForP1 : enableGlobally); + QVERIFY(refreshGuard.wait()); + refreshGuard.reset(); + QVERIFY(projectMgr->open(p2ProjectFile, true, kit)); + p2 = projectMgr->projects().last(); + QCOMPARE( + CppCodeModelSettings::settingsForProject(p2).enableIndexing, + enableForP2 ? *enableForP2 : enableGlobally); + QVERIFY(refreshGuard.wait()); + + entries = Core::LocatorMatcher::runBlocking( + Core::LocatorMatcher::matchers(Core::MatcherType::Functions), "foo"); + QCOMPARE(hasEntry("foo1"), foo1Present); + QCOMPARE(hasEntry("foo2"), foo2Present); +} + } // CppEditor::Internal diff --git a/src/plugins/cppeditor/cppmodelmanager_test.h b/src/plugins/cppeditor/cppmodelmanager_test.h index 6bcf25bc8e..bca0753fa9 100644 --- a/src/plugins/cppeditor/cppmodelmanager_test.h +++ b/src/plugins/cppeditor/cppmodelmanager_test.h @@ -35,6 +35,8 @@ private slots: void testRenameIncludesInEditor(); void testDocumentsAndRevisions(); void testSettingsChanges(); + void testOptionalIndexing_data(); + void testOptionalIndexing(); }; } // namespace CppEditor::Internal diff --git a/src/plugins/cppeditor/cppquickfix_test.cpp b/src/plugins/cppeditor/cppquickfix_test.cpp deleted file mode 100644 index 0a5b71c814..0000000000 --- a/src/plugins/cppeditor/cppquickfix_test.cpp +++ /dev/null @@ -1,9951 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include "cppquickfix_test.h" - -#include "cppcodestylepreferences.h" -#include "cppeditorwidget.h" -#include "cppmodelmanager.h" -#include "cppquickfixassistant.h" -#include "cppquickfixes.h" -#include "cppquickfixsettings.h" -#include "cppsourceprocessertesthelper.h" -#include "cpptoolssettings.h" - -#include <utils/fileutils.h> - -#include <QDebug> -#include <QDir> -#include <QtTest> - -/*! - Tests for quick-fixes. - */ -using namespace Core; -using namespace CPlusPlus; -using namespace TextEditor; -using namespace Utils; - -using CppEditor::Tests::TemporaryDir; -using CppEditor::Tests::Internal::TestIncludePaths; - -typedef QByteArray _; - -namespace CppEditor::Internal::Tests { -typedef QList<TestDocumentPtr> QuickFixTestDocuments; -} -Q_DECLARE_METATYPE(CppEditor::Internal::Tests::QuickFixTestDocuments) - -namespace CppEditor { -namespace Internal { -namespace Tests { - -/// Tests the offered operations provided by a given CppQuickFixFactory -class QuickFixOfferedOperationsTest : public BaseQuickFixTestCase -{ -public: - QuickFixOfferedOperationsTest(const QList<TestDocumentPtr> &testDocuments, - CppQuickFixFactory *factory, - const ProjectExplorer::HeaderPaths &headerPaths - = ProjectExplorer::HeaderPaths(), - const QStringList &expectedOperations = QStringList()); -}; - -QList<TestDocumentPtr> singleDocument(const QByteArray &original, - const QByteArray &expected) -{ - return {CppTestDocument::create("file.cpp", original, expected)}; -} - -BaseQuickFixTestCase::BaseQuickFixTestCase(const QList<TestDocumentPtr> &testDocuments, - const ProjectExplorer::HeaderPaths &headerPaths, - const QByteArray &clangFormatSettings) - : m_testDocuments(testDocuments) - , m_cppCodeStylePreferences(0) - , m_restoreHeaderPaths(false) -{ - QVERIFY(succeededSoFar()); - m_succeededSoFar = false; - - // Check if there is exactly one cursor marker - unsigned cursorMarkersCount = 0; - for (const TestDocumentPtr &document : std::as_const(m_testDocuments)) { - if (document->hasCursorMarker()) - ++cursorMarkersCount; - } - QVERIFY2(cursorMarkersCount == 1, "Exactly one cursor marker is allowed."); - - // Write documents to disk - m_temporaryDirectory.reset(new TemporaryDir); - QVERIFY(m_temporaryDirectory->isValid()); - for (const TestDocumentPtr &document : std::as_const(m_testDocuments)) { - if (QFileInfo(document->m_fileName).isRelative()) - document->setBaseDirectory(m_temporaryDirectory->path()); - document->writeToDisk(); - } - - // Create .clang-format file - if (!clangFormatSettings.isEmpty()) - m_temporaryDirectory->createFile(".clang-format", clangFormatSettings); - - // Set appropriate include paths - if (!headerPaths.isEmpty()) { - m_restoreHeaderPaths = true; - m_headerPathsToRestore = CppModelManager::headerPaths(); - CppModelManager::setHeaderPaths(headerPaths); - } - - // Update Code Model - QSet<FilePath> filePaths; - for (const TestDocumentPtr &document : std::as_const(m_testDocuments)) - filePaths << document->filePath(); - QVERIFY(parseFiles(filePaths)); - - // Open Files - for (const TestDocumentPtr &document : std::as_const(m_testDocuments)) { - QVERIFY(openCppEditor(document->filePath(), &document->m_editor, - &document->m_editorWidget)); - closeEditorAtEndOfTestCase(document->m_editor); - - // Set cursor position - if (document->hasCursorMarker()) { - if (document->hasAnchorMarker()) { - document->m_editor->setCursorPosition(document->m_anchorPosition); - document->m_editor->select(document->m_cursorPosition); - } else { - document->m_editor->setCursorPosition(document->m_cursorPosition); - } - } else { - document->m_editor->setCursorPosition(0); - } - - // Rehighlight - waitForRehighlightedSemanticDocument(document->m_editorWidget); - } - - // Enforce the default cpp code style, so we are independent of config file settings. - // This is needed by e.g. the GenerateGetterSetter quick fix. - m_cppCodeStylePreferences = CppToolsSettings::cppCodeStyle(); - QVERIFY(m_cppCodeStylePreferences); - m_cppCodeStylePreferencesOriginalDelegateId = m_cppCodeStylePreferences->currentDelegateId(); - m_cppCodeStylePreferences->setCurrentDelegate("qt"); - - // Find the document having the cursor marker - for (const TestDocumentPtr &document : std::as_const(m_testDocuments)) { - if (document->hasCursorMarker()){ - m_documentWithMarker = document; - break; - } - } - - QVERIFY(m_documentWithMarker); - m_succeededSoFar = true; -} - -BaseQuickFixTestCase::~BaseQuickFixTestCase() -{ - // Restore default cpp code style - if (m_cppCodeStylePreferences) - m_cppCodeStylePreferences->setCurrentDelegate(m_cppCodeStylePreferencesOriginalDelegateId); - - // Restore include paths - if (m_restoreHeaderPaths) - CppModelManager::setHeaderPaths(m_headerPathsToRestore); - - // Remove created files from file system - for (const TestDocumentPtr &testDocument : std::as_const(m_testDocuments)) - QVERIFY(testDocument->filePath().removeFile()); -} - -/// 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) -{ - const QStringList lines = input.split(QLatin1Char('\n')); - input.resize(0); - for (int i = 0, total = lines.size(); i < total; ++i) { - QString line = lines.at(i); - while (line.length() > 0) { - QChar lastChar = line[line.length() - 1]; - if (lastChar == QLatin1Char(' ') || lastChar == QLatin1Char('\t')) - line.chop(1); - else - break; - } - input.append(line); - - const bool isLastLine = i == lines.size() - 1; - if (!isLastLine) - input.append(QLatin1Char('\n')); - } - return input; -} - -QuickFixOperationTest::QuickFixOperationTest(const QList<TestDocumentPtr> &testDocuments, - CppQuickFixFactory *factory, - const ProjectExplorer::HeaderPaths &headerPaths, - int operationIndex, - const QByteArray &expectedFailMessage, - const QByteArray &clangFormatSettings) - : BaseQuickFixTestCase(testDocuments, headerPaths, clangFormatSettings) -{ - if (factory->clangdReplacement() && CppModelManager::isClangCodeModelActive()) - return; - - QVERIFY(succeededSoFar()); - - // Perform operation if there is one - CppQuickFixInterface quickFixInterface(m_documentWithMarker->m_editorWidget, ExplicitlyInvoked); - QuickFixOperations operations; - factory->match(quickFixInterface, operations); - if (operations.isEmpty()) { - QEXPECT_FAIL("CompleteSwitchCaseStatement_QTCREATORBUG-25998", "FIXME", Abort); - QVERIFY(testDocuments.first()->m_expectedSource.isEmpty()); - return; - } - - QVERIFY(operationIndex < operations.size()); - const QuickFixOperation::Ptr operation = operations.at(operationIndex); - operation->perform(); - - // Compare all files - for (const TestDocumentPtr &testDocument : std::as_const(m_testDocuments)) { - // Check - QString result = testDocument->m_editorWidget->document()->toPlainText(); - removeTrailingWhitespace(result); - QEXPECT_FAIL("escape string literal: raw string literal", "FIXME", Continue); - QEXPECT_FAIL("escape string literal: unescape adjacent literals", "FIXME", Continue); - if (!expectedFailMessage.isEmpty()) - QEXPECT_FAIL("", expectedFailMessage.data(), Continue); - else if (result != testDocument->m_expectedSource) { - qDebug() << "---" << testDocument->m_expectedSource; - qDebug() << "+++" << result; - } - QCOMPARE(result, testDocument->m_expectedSource); - - // Undo the change - for (int i = 0; i < 100; ++i) - testDocument->m_editorWidget->undo(); - result = testDocument->m_editorWidget->document()->toPlainText(); - QCOMPARE(result, testDocument->m_source); - } -} - -void QuickFixOperationTest::run(const QList<TestDocumentPtr> &testDocuments, - CppQuickFixFactory *factory, - const QString &headerPath, - int operationIndex) -{ - ProjectExplorer::HeaderPaths headerPaths; - headerPaths.push_back(ProjectExplorer::HeaderPath::makeUser(headerPath)); - QuickFixOperationTest(testDocuments, factory, headerPaths, operationIndex); -} - -QuickFixOfferedOperationsTest::QuickFixOfferedOperationsTest( - const QList<TestDocumentPtr> &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 -{ -public: - AddIncludeForUndefinedIdentifierTestFactory(const QString &include) - : m_include(include) {} - - void doMatch(const CppQuickFixInterface &cppQuickFixInterface, QuickFixOperations &result) override - { - result << new AddIncludeForUndefinedIdentifierOp(cppQuickFixInterface, 0, m_include); - } - -private: - const QString m_include; -}; - -class AddForwardDeclForUndefinedIdentifierTestFactory : public CppQuickFixFactory -{ -public: - AddForwardDeclForUndefinedIdentifierTestFactory(const QString &className, int symbolPos) - : m_className(className), m_symbolPos(symbolPos) {} - - void doMatch(const CppQuickFixInterface &cppQuickFixInterface, QuickFixOperations &result) override - { - result << new AddForwardDeclForUndefinedIdentifierOp(cppQuickFixInterface, 0, - m_className, m_symbolPos); - } - -private: - const QString m_className; - const int m_symbolPos; -}; - -} // namespace Tests -} // namespace Internal - -typedef QSharedPointer<CppQuickFixFactory> CppQuickFixFactoryPtr; - -} // namespace CppEditor - -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<CppQuickFixFactoryPtr>("factory"); - QTest::addColumn<QByteArray>("original"); - QTest::addColumn<QByteArray>("expected"); - - // Checks: All enum values are added as case statements for a blank switch. - QTest::newRow("CompleteSwitchCaseStatement_basic1") - << CppQuickFixFactoryPtr(new CompleteSwitchCaseStatement) << _( - "enum EnumType { V1, V2 };\n" - "\n" - "void f()\n" - "{\n" - " EnumType t;\n" - " @switch (t) {\n" - " }\n" - "}\n" - ) << _( - "enum EnumType { V1, V2 };\n" - "\n" - "void f()\n" - "{\n" - " EnumType t;\n" - " switch (t) {\n" - " case V1:\n" - " break;\n" - " case V2:\n" - " break;\n" - " }\n" - "}\n" - ); - - // Same as above for enum class. - QTest::newRow("CompleteSwitchCaseStatement_basic1_enum class") - << CppQuickFixFactoryPtr(new CompleteSwitchCaseStatement) << _( - "enum class EnumType { V1, V2 };\n" - "\n" - "void f()\n" - "{\n" - " EnumType t;\n" - " @switch (t) {\n" - " }\n" - "}\n" - ) << _( - "enum class EnumType { V1, V2 };\n" - "\n" - "void f()\n" - "{\n" - " EnumType t;\n" - " switch (t) {\n" - " case EnumType::V1:\n" - " break;\n" - " case EnumType::V2:\n" - " break;\n" - " }\n" - "}\n" - ); - - // Same as above with the cursor somewhere in the body. - QTest::newRow("CompleteSwitchCaseStatement_basic1_enum class, cursor in the body") - << CppQuickFixFactoryPtr(new CompleteSwitchCaseStatement) << _( - "enum class EnumType { V1, V2 };\n" - "\n" - "void f()\n" - "{\n" - " EnumType t;\n" - " switch (t) {\n" - " @}\n" - "}\n" - ) << _( - "enum class EnumType { V1, V2 };\n" - "\n" - "void f()\n" - "{\n" - " EnumType t;\n" - " switch (t) {\n" - " case EnumType::V1:\n" - " break;\n" - " case EnumType::V2:\n" - " break;\n" - " }\n" - "}\n" - ); - - // Checks: All enum values are added as case statements for a blank switch when - // the variable is declared alongside the enum definition. - QTest::newRow("CompleteSwitchCaseStatement_basic1_enum_with_declaration") - << CppQuickFixFactoryPtr(new CompleteSwitchCaseStatement) << _( - "enum EnumType { V1, V2 } t;\n" - "\n" - "void f()\n" - "{\n" - " @switch (t) {\n" - " }\n" - "}\n" - ) << _( - "enum EnumType { V1, V2 } t;\n" - "\n" - "void f()\n" - "{\n" - " switch (t) {\n" - " case V1:\n" - " break;\n" - " case V2:\n" - " break;\n" - " }\n" - "}\n" - ); - - // Same as above for enum class. - QTest::newRow("CompleteSwitchCaseStatement_basic1_enum_with_declaration_enumClass") - << CppQuickFixFactoryPtr(new CompleteSwitchCaseStatement) << _( - "enum class EnumType { V1, V2 } t;\n" - "\n" - "void f()\n" - "{\n" - " @switch (t) {\n" - " }\n" - "}\n" - ) << _( - "enum class EnumType { V1, V2 } t;\n" - "\n" - "void f()\n" - "{\n" - " switch (t) {\n" - " case EnumType::V1:\n" - " break;\n" - " case EnumType::V2:\n" - " break;\n" - " }\n" - "}\n" - ); - - // Checks: All enum values are added as case statements for a blank switch - // for anonymous enums. - QTest::newRow("CompleteSwitchCaseStatement_basic1_anonymous_enum") - << CppQuickFixFactoryPtr(new CompleteSwitchCaseStatement) << _( - "enum { V1, V2 } t;\n" - "\n" - "void f()\n" - "{\n" - " @switch (t) {\n" - " }\n" - "}\n" - ) << _( - "enum { V1, V2 } t;\n" - "\n" - "void f()\n" - "{\n" - " switch (t) {\n" - " case V1:\n" - " break;\n" - " case V2:\n" - " break;\n" - " }\n" - "}\n" - ); - - // Checks: All enum values are added as case statements for a blank switch with a default case. - QTest::newRow("CompleteSwitchCaseStatement_basic2") - << CppQuickFixFactoryPtr(new CompleteSwitchCaseStatement) << _( - "enum EnumType { V1, V2 };\n" - "\n" - "void f()\n" - "{\n" - " EnumType t;\n" - " @switch (t) {\n" - " default:\n" - " break;\n" - " }\n" - "}\n" - ) << _( - "enum EnumType { V1, V2 };\n" - "\n" - "void f()\n" - "{\n" - " EnumType t;\n" - " switch (t) {\n" - " case V1:\n" - " break;\n" - " case V2:\n" - " break;\n" - " default:\n" - " break;\n" - " }\n" - "}\n" - ); - - // Same as above for enum class. - QTest::newRow("CompleteSwitchCaseStatement_basic2_enumClass") - << CppQuickFixFactoryPtr(new CompleteSwitchCaseStatement) << _( - "enum class EnumType { V1, V2 };\n" - "\n" - "void f()\n" - "{\n" - " EnumType t;\n" - " @switch (t) {\n" - " default:\n" - " break;\n" - " }\n" - "}\n" - ) << _( - "enum class EnumType { V1, V2 };\n" - "\n" - "void f()\n" - "{\n" - " EnumType t;\n" - " switch (t) {\n" - " case EnumType::V1:\n" - " break;\n" - " case EnumType::V2:\n" - " break;\n" - " default:\n" - " break;\n" - " }\n" - "}\n" - ); - - // Checks: Enum type in class is found. - QTest::newRow("CompleteSwitchCaseStatement_enumTypeInClass") - << CppQuickFixFactoryPtr(new CompleteSwitchCaseStatement) << _( - "struct C { enum EnumType { V1, V2 }; };\n" - "\n" - "void f(C::EnumType t) {\n" - " @switch (t) {\n" - " }\n" - "}\n" - ) << _( - "struct C { enum EnumType { V1, V2 }; };\n" - "\n" - "void f(C::EnumType t) {\n" - " switch (t) {\n" - " case C::V1:\n" - " break;\n" - " case C::V2:\n" - " break;\n" - " }\n" - "}\n" - ); - - // Same as above for enum class. - QTest::newRow("CompleteSwitchCaseStatement_enumClassInClass") - << CppQuickFixFactoryPtr(new CompleteSwitchCaseStatement) << _( - "struct C { enum class EnumType { V1, V2 }; };\n" - "\n" - "void f(C::EnumType t) {\n" - " @switch (t) {\n" - " }\n" - "}\n" - ) << _( - "struct C { enum class EnumType { V1, V2 }; };\n" - "\n" - "void f(C::EnumType t) {\n" - " switch (t) {\n" - " case C::EnumType::V1:\n" - " break;\n" - " case C::EnumType::V2:\n" - " break;\n" - " }\n" - "}\n" - ); - - // Checks: Enum type in namespace is found. - QTest::newRow("CompleteSwitchCaseStatement_enumTypeInNamespace") - << CppQuickFixFactoryPtr(new CompleteSwitchCaseStatement) << _( - "namespace N { enum EnumType { V1, V2 }; };\n" - "\n" - "void f(N::EnumType t) {\n" - " @switch (t) {\n" - " }\n" - "}\n" - ) << _( - "namespace N { enum EnumType { V1, V2 }; };\n" - "\n" - "void f(N::EnumType t) {\n" - " switch (t) {\n" - " case N::V1:\n" - " break;\n" - " case N::V2:\n" - " break;\n" - " }\n" - "}\n" - ); - - // Same as above for enum class. - QTest::newRow("CompleteSwitchCaseStatement_enumClassInNamespace") - << CppQuickFixFactoryPtr(new CompleteSwitchCaseStatement) << _( - "namespace N { enum class EnumType { V1, V2 }; };\n" - "\n" - "void f(N::EnumType t) {\n" - " @switch (t) {\n" - " }\n" - "}\n" - ) << _( - "namespace N { enum class EnumType { V1, V2 }; };\n" - "\n" - "void f(N::EnumType t) {\n" - " switch (t) {\n" - " case N::EnumType::V1:\n" - " break;\n" - " case N::EnumType::V2:\n" - " break;\n" - " }\n" - "}\n" - ); - - // Checks: The missing enum value is added. - QTest::newRow("CompleteSwitchCaseStatement_oneValueMissing") - << CppQuickFixFactoryPtr(new CompleteSwitchCaseStatement) << _( - "enum EnumType { V1, V2 };\n" - "\n" - "void f()\n" - "{\n" - " EnumType t;\n" - " @switch (t) {\n" - " case V2:\n" - " break;\n" - " default:\n" - " break;\n" - " }\n" - "}\n" - ) << _( - "enum EnumType { V1, V2 };\n" - "\n" - "void f()\n" - "{\n" - " EnumType t;\n" - " switch (t) {\n" - " case V1:\n" - " break;\n" - " case V2:\n" - " break;\n" - " default:\n" - " break;\n" - " }\n" - "}\n" - ); - - // Checks: Same as above for enum class. - QTest::newRow("CompleteSwitchCaseStatement_oneValueMissing_enumClass") - << CppQuickFixFactoryPtr(new CompleteSwitchCaseStatement) << _( - "enum class EnumType { V1, V2 };\n" - "\n" - "void f()\n" - "{\n" - " EnumType t;\n" - " @switch (t) {\n" - " case EnumType::V2:\n" - " break;\n" - " default:\n" - " break;\n" - " }\n" - "}\n" - ) << _( - "enum class EnumType { V1, V2 };\n" - "\n" - "void f()\n" - "{\n" - " EnumType t;\n" - " switch (t) {\n" - " case EnumType::V1:\n" - " break;\n" - " case EnumType::V2:\n" - " break;\n" - " default:\n" - " break;\n" - " }\n" - "}\n" - ); - - // Checks: Find the correct enum type despite there being a declaration with the same name. - QTest::newRow("CompleteSwitchCaseStatement_QTCREATORBUG10366_1") - << CppQuickFixFactoryPtr(new CompleteSwitchCaseStatement) << _( - "enum test { TEST_1, TEST_2 };\n" - "\n" - "void f() {\n" - " enum test test;\n" - " @switch (test) {\n" - " }\n" - "}\n" - ) << _( - "enum test { TEST_1, TEST_2 };\n" - "\n" - "void f() {\n" - " enum test test;\n" - " switch (test) {\n" - " case TEST_1:\n" - " break;\n" - " case TEST_2:\n" - " break;\n" - " }\n" - "}\n" - ); - - // Same as above for enum class. - QTest::newRow("CompleteSwitchCaseStatement_QTCREATORBUG10366_1_enumClass") - << CppQuickFixFactoryPtr(new CompleteSwitchCaseStatement) << _( - "enum class test { TEST_1, TEST_2 };\n" - "\n" - "void f() {\n" - " enum test test;\n" - " @switch (test) {\n" - " }\n" - "}\n" - ) << _( - "enum class test { TEST_1, TEST_2 };\n" - "\n" - "void f() {\n" - " enum test test;\n" - " switch (test) {\n" - " case test::TEST_1:\n" - " break;\n" - " case test::TEST_2:\n" - " break;\n" - " }\n" - "}\n" - ); - - // Checks: Find the correct enum type despite there being a declaration with the same name. - QTest::newRow("CompleteSwitchCaseStatement_QTCREATORBUG10366_2") - << CppQuickFixFactoryPtr(new CompleteSwitchCaseStatement) << _( - "enum test1 { Wrong11, Wrong12 };\n" - "enum test { Right1, Right2 };\n" - "enum test2 { Wrong21, Wrong22 };\n" - "\n" - "int main() {\n" - " enum test test;\n" - " @switch (test) {\n" - " }\n" - "}\n" - ) << _( - "enum test1 { Wrong11, Wrong12 };\n" - "enum test { Right1, Right2 };\n" - "enum test2 { Wrong21, Wrong22 };\n" - "\n" - "int main() {\n" - " enum test test;\n" - " switch (test) {\n" - " case Right1:\n" - " break;\n" - " case Right2:\n" - " break;\n" - " }\n" - "}\n" - ); - - // Same as above for enum class. - QTest::newRow("CompleteSwitchCaseStatement_QTCREATORBUG10366_2_enumClass") - << CppQuickFixFactoryPtr(new CompleteSwitchCaseStatement) << _( - "enum class test1 { Wrong11, Wrong12 };\n" - "enum class test { Right1, Right2 };\n" - "enum class test2 { Wrong21, Wrong22 };\n" - "\n" - "int main() {\n" - " enum test test;\n" - " @switch (test) {\n" - " }\n" - "}\n" - ) << _( - "enum class test1 { Wrong11, Wrong12 };\n" - "enum class test { Right1, Right2 };\n" - "enum class test2 { Wrong21, Wrong22 };\n" - "\n" - "int main() {\n" - " enum test test;\n" - " switch (test) {\n" - " case test::Right1:\n" - " break;\n" - " case test::Right2:\n" - " break;\n" - " }\n" - "}\n" - ); - - // Checks: Do not crash on incomplete case statetement. - QTest::newRow("CompleteSwitchCaseStatement_doNotCrashOnIncompleteCase") - << CppQuickFixFactoryPtr(new CompleteSwitchCaseStatement) << _( - "enum E {};\n" - "void f(E o)\n" - "{\n" - " @switch (o)\n" - " {\n" - " case\n" - " }\n" - "}\n" - ) << _( - "" - ); - - // Same as above for enum class. - QTest::newRow("CompleteSwitchCaseStatement_doNotCrashOnIncompleteCase_enumClass") - << CppQuickFixFactoryPtr(new CompleteSwitchCaseStatement) << _( - "enum class E {};\n" - "void f(E o)\n" - "{\n" - " @switch (o)\n" - " {\n" - " case\n" - " }\n" - "}\n" - ) << _( - "" - ); - - // Checks: complete switch statement where enum is goes via a template type parameter - QTest::newRow("CompleteSwitchCaseStatement_QTCREATORBUG-24752") - << CppQuickFixFactoryPtr(new CompleteSwitchCaseStatement) << _( - "enum E {EA, EB};\n" - "template<typename T> struct S {\n" - " static T theType() { return T(); }\n" - "};\n" - "int main() {\n" - " @switch (S<E>::theType()) {\n" - " }\n" - "}\n" - ) << _( - "enum E {EA, EB};\n" - "template<typename T> struct S {\n" - " static T theType() { return T(); }\n" - "};\n" - "int main() {\n" - " switch (S<E>::theType()) {\n" - " case EA:\n" - " break;\n" - " case EB:\n" - " break;\n" - " }\n" - "}\n" - ); - - // Same as above for enum class. - QTest::newRow("CompleteSwitchCaseStatement_QTCREATORBUG-24752_enumClass") - << CppQuickFixFactoryPtr(new CompleteSwitchCaseStatement) << _( - "enum class E {A, B};\n" - "template<typename T> struct S {\n" - " static T theType() { return T(); }\n" - "};\n" - "int main() {\n" - " @switch (S<E>::theType()) {\n" - " }\n" - "}\n" - ) << _( - "enum class E {A, B};\n" - "template<typename T> struct S {\n" - " static T theType() { return T(); }\n" - "};\n" - "int main() {\n" - " switch (S<E>::theType()) {\n" - " case E::A:\n" - " break;\n" - " case E::B:\n" - " break;\n" - " }\n" - "}\n" - ); - - // Checks: Complete switch statement where enum is return type of a template function - // which is outside the scope of the return value. - // TODO: Type minimization. - QTest::newRow("CompleteSwitchCaseStatement_QTCREATORBUG-25998") - << CppQuickFixFactoryPtr(new CompleteSwitchCaseStatement) << _( - "template <typename T> T enumCast(int value) { return static_cast<T>(value); }\n" - "class Test {\n" - " enum class E { V1, V2 };" - " void func(int i) {\n" - " @switch (enumCast<E>(i)) {\n" - " }\n" - " }\n" - "};\n" - ) << _( - "template <typename T> T enumCast(int value) { return static_cast<T>(value); }\n" - "class Test {\n" - " enum class E { V1, V2 };" - " void func(int i) {\n" - " switch (enumCast<E>(i)) {\n" - " case Test::E::V1:\n" - " break;\n" - " case Test::E::V2:\n" - " break;\n" - " }\n" - " }\n" - "};\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" - "{\n" - " if (Foo *@foo = g())\n" - " h();\n" - "}\n" - ) << _( - "void f()\n" - "{\n" - " Foo *foo = g();\n" - " if (foo)\n" - " h();\n" - "}\n" - ); - - QTest::newRow("MoveDeclarationOutOfIf_ifElse") - << CppQuickFixFactoryPtr(new MoveDeclarationOutOfIf) << _( - "void f()\n" - "{\n" - " if (Foo *@foo = g())\n" - " h();\n" - " else\n" - " i();\n" - "}\n" - ) << _( - "void f()\n" - "{\n" - " Foo *foo = g();\n" - " if (foo)\n" - " h();\n" - " else\n" - " i();\n" - "}\n" - ); - - QTest::newRow("MoveDeclarationOutOfIf_ifElseIf") - << CppQuickFixFactoryPtr(new MoveDeclarationOutOfIf) << _( - "void f()\n" - "{\n" - " if (Foo *foo = g()) {\n" - " if (Bar *@bar = x()) {\n" - " h();\n" - " j();\n" - " }\n" - " } else {\n" - " i();\n" - " }\n" - "}\n" - ) << _( - "void f()\n" - "{\n" - " if (Foo *foo = g()) {\n" - " Bar *bar = x();\n" - " if (bar) {\n" - " h();\n" - " j();\n" - " }\n" - " } else {\n" - " i();\n" - " }\n" - "}\n" - ); - - QTest::newRow("MoveDeclarationOutOfWhile_singleWhile") - << CppQuickFixFactoryPtr(new MoveDeclarationOutOfWhile) << _( - "void f()\n" - "{\n" - " while (Foo *@foo = g())\n" - " j();\n" - "}\n" - ) << _( - "void f()\n" - "{\n" - " Foo *foo;\n" - " while ((foo = g()) != 0)\n" - " j();\n" - "}\n" - ); - - QTest::newRow("MoveDeclarationOutOfWhile_whileInWhile") - << CppQuickFixFactoryPtr(new MoveDeclarationOutOfWhile) << _( - "void f()\n" - "{\n" - " while (Foo *foo = g()) {\n" - " while (Bar *@bar = h()) {\n" - " i();\n" - " j();\n" - " }\n" - " }\n" - "}\n" - ) << _( - "void f()\n" - "{\n" - " while (Foo *foo = g()) {\n" - " Bar *bar;\n" - " while ((bar = h()) != 0) {\n" - " i();\n" - " j();\n" - " }\n" - " }\n" - "}\n" - ); - - // Check: Just a basic test since the main functionality is tested in - // cpppointerdeclarationformatter_test.cpp - QTest::newRow("ReformatPointerDeclaration") - << CppQuickFixFactoryPtr(new ReformatPointerDeclaration) - << _("char@*s;") - << _("char *s;"); - - // Check from source file: If there is no header file, insert the definition after the class. - QByteArray original = - "struct Foo\n" - "{\n" - " Foo();@\n" - "};\n"; - - QTest::newRow("InsertDefFromDecl_basic") - << CppQuickFixFactoryPtr(new InsertDefFromDecl) << original - << original + _( - "\n" - "Foo::Foo()\n" - "{\n\n" - "}\n" - ); - - QTest::newRow("InsertDefFromDecl_freeFunction") - << CppQuickFixFactoryPtr(new InsertDefFromDecl) - << _("void free()@;\n") - << _( - "void free()\n" - "{\n\n" - "}\n" - ); - - // Check not triggering when it is a statement - QTest::newRow("InsertDefFromDecl_notTriggeringStatement") - << CppQuickFixFactoryPtr(new InsertDefFromDecl) << _( - "class Foo {\n" - "public:\n" - " Foo() {}\n" - "};\n" - "void freeFunc() {\n" - " Foo @f();" - "}\n" - ) << _(); - - // Check: Add local variable for a free function. - QTest::newRow("AssignToLocalVariable_freeFunction") - << CppQuickFixFactoryPtr(new AssignToLocalVariable) << _( - "int foo() {return 1;}\n" - "void bar() {fo@o();}\n" - ) << _( - "int foo() {return 1;}\n" - "void bar() {auto localFoo = foo();}\n" - ); - - // Check: Add local variable for a member function. - QTest::newRow("AssignToLocalVariable_memberFunction") - << CppQuickFixFactoryPtr(new AssignToLocalVariable) << _( - "class Foo {public: int* fooFunc();}\n" - "void bar() {\n" - " Foo *f = new Foo;\n" - " @f->fooFunc();\n" - "}\n" - ) << _( - "class Foo {public: int* fooFunc();}\n" - "void bar() {\n" - " Foo *f = new Foo;\n" - " auto localFooFunc = f->fooFunc();\n" - "}\n" - ); - - // Check: Add local variable for a member function, cursor in the middle (QTCREATORBUG-10355) - QTest::newRow("AssignToLocalVariable_memberFunction2ndGrade1") - << CppQuickFixFactoryPtr(new AssignToLocalVariable) << _( - "struct Foo {int* func();};\n" - "struct Baz {Foo* foo();};\n" - "void bar() {\n" - " Baz *b = new Baz;\n" - " b->foo@()->func();\n" - "}" - ) << _( - "struct Foo {int* func();};\n" - "struct Baz {Foo* foo();};\n" - "void bar() {\n" - " Baz *b = new Baz;\n" - " auto localFunc = b->foo()->func();\n" - "}" - ); - - // Check: Add local variable for a member function, cursor on function call (QTCREATORBUG-10355) - QTest::newRow("AssignToLocalVariable_memberFunction2ndGrade2") - << CppQuickFixFactoryPtr(new AssignToLocalVariable) << _( - "struct Foo {int* func();};\n" - "struct Baz {Foo* foo();};\n" - "void bar() {\n" - " Baz *b = new Baz;\n" - " b->foo()->f@unc();\n" - "}" - ) << _( - "struct Foo {int* func();};\n" - "struct Baz {Foo* foo();};\n" - "void bar() {\n" - " Baz *b = new Baz;\n" - " auto localFunc = b->foo()->func();\n" - "}" - ); - - // Check: Add local variable for a static member function. - QTest::newRow("AssignToLocalVariable_staticMemberFunction") - << CppQuickFixFactoryPtr(new AssignToLocalVariable) << _( - "class Foo {public: static int* fooFunc();}\n" - "void bar() {\n" - " Foo::fooF@unc();\n" - "}" - ) << _( - "class Foo {public: static int* fooFunc();}\n" - "void bar() {\n" - " auto localFooFunc = Foo::fooFunc();\n" - "}" - ); - - // Check: Add local variable for a new Expression. - QTest::newRow("AssignToLocalVariable_newExpression") - << CppQuickFixFactoryPtr(new AssignToLocalVariable) << _( - "class Foo {}\n" - "void bar() {\n" - " new Fo@o;\n" - "}" - ) << _( - "class Foo {}\n" - "void bar() {\n" - " auto localFoo = new Foo;\n" - "}" - ); - - // Check: No trigger for function inside member initialization list. - QTest::newRow("AssignToLocalVariable_noInitializationList") - << CppQuickFixFactoryPtr(new AssignToLocalVariable) << _( - "class Foo\n" - "{\n" - " public: Foo : m_i(fooF@unc()) {}\n" - " int fooFunc() {return 2;}\n" - " int m_i;\n" - "};\n" - ) << _(); - - // Check: No trigger for void functions. - QTest::newRow("AssignToLocalVariable_noVoidFunction") - << CppQuickFixFactoryPtr(new AssignToLocalVariable) << _( - "void foo() {}\n" - "void bar() {fo@o();}" - ) << _(); - - // Check: No trigger for void member functions. - QTest::newRow("AssignToLocalVariable_noVoidMemberFunction") - << CppQuickFixFactoryPtr(new AssignToLocalVariable) << _( - "class Foo {public: void fooFunc();}\n" - "void bar() {\n" - " Foo *f = new Foo;\n" - " @f->fooFunc();\n" - "}" - ) << _(); - - // Check: No trigger for void static member functions. - QTest::newRow("AssignToLocalVariable_noVoidStaticMemberFunction") - << CppQuickFixFactoryPtr(new AssignToLocalVariable) << _( - "class Foo {public: static void fooFunc();}\n" - "void bar() {\n" - " Foo::fo@oFunc();\n" - "}" - ) << _(); - - // Check: No trigger for functions in expressions. - QTest::newRow("AssignToLocalVariable_noFunctionInExpression") - << CppQuickFixFactoryPtr(new AssignToLocalVariable) << _( - "int foo(int a) {return a;}\n" - "int bar() {return 1;}" - "void baz() {foo(@bar() + bar());}" - ) << _(); - - // Check: No trigger for functions in functions. (QTCREATORBUG-9510) - QTest::newRow("AssignToLocalVariable_noFunctionInFunction") - << CppQuickFixFactoryPtr(new AssignToLocalVariable) << _( - "int foo(int a, int b) {return a + b;}\n" - "int bar(int a) {return a;}\n" - "void baz() {\n" - " int a = foo(ba@r(), bar());\n" - "}\n" - ) << _(); - - // Check: No trigger for functions in return statements (classes). - QTest::newRow("AssignToLocalVariable_noReturnClass1") - << CppQuickFixFactoryPtr(new AssignToLocalVariable) << _( - "class Foo {public: static void fooFunc();}\n" - "Foo* bar() {\n" - " return new Fo@o;\n" - "}" - ) << _(); - - // Check: No trigger for functions in return statements (classes). (QTCREATORBUG-9525) - QTest::newRow("AssignToLocalVariable_noReturnClass2") - << CppQuickFixFactoryPtr(new AssignToLocalVariable) << _( - "class Foo {public: int fooFunc();}\n" - "int bar() {\n" - " return (new Fo@o)->fooFunc();\n" - "}" - ) << _(); - - // Check: No trigger for functions in return statements (functions). - QTest::newRow("AssignToLocalVariable_noReturnFunc1") - << CppQuickFixFactoryPtr(new AssignToLocalVariable) << _( - "class Foo {public: int fooFunc();}\n" - "int bar() {\n" - " return Foo::fooFu@nc();\n" - "}" - ) << _(); - - // Check: No trigger for functions in return statements (functions). (QTCREATORBUG-9525) - QTest::newRow("AssignToLocalVariable_noReturnFunc2") - << CppQuickFixFactoryPtr(new AssignToLocalVariable) << _( - "int bar() {\n" - " return list.firs@t().foo;\n" - "}\n" - ) << _(); - - // Check: No trigger for functions which does not match in signature. - QTest::newRow("AssignToLocalVariable_noSignatureMatch") - << CppQuickFixFactoryPtr(new AssignToLocalVariable) << _( - "int someFunc(int);\n" - "\n" - "void f()\n" - "{\n" - " some@Func();\n" - "}" - ) << _(); - - QTest::newRow("ExtractLiteralAsParameter_freeFunction") - << CppQuickFixFactoryPtr(new ExtractLiteralAsParameter) << _( - "void foo(const char *a, long b = 1)\n" - "{return 1@56 + 123 + 156;}\n" - ) << _( - "void foo(const char *a, long b = 1, int newParameter = 156)\n" - "{return newParameter + 123 + newParameter;}\n" - ); - - QTest::newRow("ExtractLiteralAsParameter_memberFunction") - << CppQuickFixFactoryPtr(new ExtractLiteralAsParameter) << _( - "class Narf {\n" - "public:\n" - " int zort();\n" - "};\n\n" - "int Narf::zort()\n" - "{ return 15@5 + 1; }\n" - ) << _( - "class Narf {\n" - "public:\n" - " int zort(int newParameter = 155);\n" - "};\n\n" - "int Narf::zort(int newParameter)\n" - "{ return newParameter + 1; }\n" - ); - - QTest::newRow("ExtractLiteralAsParameter_memberFunctionInline") - << CppQuickFixFactoryPtr(new ExtractLiteralAsParameter) << _( - "class Narf {\n" - "public:\n" - " int zort()\n" - " { return 15@5 + 1; }\n" - "};\n" - ) << _( - "class Narf {\n" - "public:\n" - " int zort(int newParameter = 155)\n" - " { return newParameter + 1; }\n" - "};\n" - ); - - // Check: optimize postcrement - QTest::newRow("OptimizeForLoop_postcrement") - << CppQuickFixFactoryPtr(new OptimizeForLoop) - << _("void foo() {f@or (int i = 0; i < 3; i++) {}}\n") - << _("void foo() {for (int i = 0; i < 3; ++i) {}}\n"); - - // Check: optimize condition - QTest::newRow("OptimizeForLoop_condition") - << CppQuickFixFactoryPtr(new OptimizeForLoop) - << _("void foo() {f@or (int i = 0; i < 3 + 5; ++i) {}}\n") - << _("void foo() {for (int i = 0, total = 3 + 5; i < total; ++i) {}}\n"); - - // Check: optimize fliped condition - QTest::newRow("OptimizeForLoop_flipedCondition") - << CppQuickFixFactoryPtr(new OptimizeForLoop) - << _("void foo() {f@or (int i = 0; 3 + 5 > i; ++i) {}}\n") - << _("void foo() {for (int i = 0, total = 3 + 5; total > i; ++i) {}}\n"); - - // Check: if "total" used, create other name. - QTest::newRow("OptimizeForLoop_alterVariableName") - << CppQuickFixFactoryPtr(new OptimizeForLoop) - << _("void foo() {f@or (int i = 0, total = 0; i < 3 + 5; ++i) {}}\n") - << _("void foo() {for (int i = 0, total = 0, totalX = 3 + 5; i < totalX; ++i) {}}\n"); - - // Check: optimize postcrement and condition - QTest::newRow("OptimizeForLoop_optimizeBoth") - << CppQuickFixFactoryPtr(new OptimizeForLoop) - << _("void foo() {f@or (int i = 0; i < 3 + 5; i++) {}}\n") - << _("void foo() {for (int i = 0, total = 3 + 5; i < total; ++i) {}}\n"); - - // Check: empty initializier - QTest::newRow("OptimizeForLoop_emptyInitializer") - << CppQuickFixFactoryPtr(new OptimizeForLoop) - << _("int i; void foo() {f@or (; i < 3 + 5; ++i) {}}\n") - << _("int i; void foo() {for (int total = 3 + 5; i < total; ++i) {}}\n"); - - // Check: wrong initializier type -> no trigger - QTest::newRow("OptimizeForLoop_wrongInitializer") - << CppQuickFixFactoryPtr(new OptimizeForLoop) - << _("int i; void foo() {f@or (double a = 0; i < 3 + 5; ++i) {}}\n") - << _(); - - // Check: No trigger when numeric - QTest::newRow("OptimizeForLoop_noTriggerNumeric1") - << CppQuickFixFactoryPtr(new OptimizeForLoop) - << _("void foo() {fo@r (int i = 0; i < 3; ++i) {}}\n") - << _(); - - // Check: No trigger when numeric - QTest::newRow("OptimizeForLoop_noTriggerNumeric2") - << CppQuickFixFactoryPtr(new OptimizeForLoop) - << _("void foo() {fo@r (int i = 0; i < -3; ++i) {}}\n") - << _(); - - // Escape String Literal as UTF-8 (no-trigger) - QTest::newRow("EscapeStringLiteral_notrigger") - << CppQuickFixFactoryPtr(new EscapeStringLiteral) - << _("const char *notrigger = \"@abcdef \\a\\n\\\\\";\n") - << _(); - - // Escape String Literal as UTF-8 - QTest::newRow("EscapeStringLiteral") - << CppQuickFixFactoryPtr(new EscapeStringLiteral) - << _("const char *utf8 = \"@\xe3\x81\x82\xe3\x81\x84\";\n") - << _("const char *utf8 = \"\\xe3\\x81\\x82\\xe3\\x81\\x84\";\n"); - - // Unescape String Literal as UTF-8 (from hexdecimal escape sequences) - QTest::newRow("UnescapeStringLiteral_hex") - << CppQuickFixFactoryPtr(new EscapeStringLiteral) - << _("const char *hex_escaped = \"@\\xe3\\x81\\x82\\xe3\\x81\\x84\";\n") - << _("const char *hex_escaped = \"\xe3\x81\x82\xe3\x81\x84\";\n"); - - // Unescape String Literal as UTF-8 (from octal escape sequences) - QTest::newRow("UnescapeStringLiteral_oct") - << CppQuickFixFactoryPtr(new EscapeStringLiteral) - << _("const char *oct_escaped = \"@\\343\\201\\202\\343\\201\\204\";\n") - << _("const char *oct_escaped = \"\xe3\x81\x82\xe3\x81\x84\";\n"); - - // Unescape String Literal as UTF-8 (triggered but no change) - QTest::newRow("UnescapeStringLiteral_noconv") - << CppQuickFixFactoryPtr(new EscapeStringLiteral) - << _("const char *escaped_ascii = \"@\\x1b\";\n") - << _("const char *escaped_ascii = \"\\x1b\";\n"); - - // Unescape String Literal as UTF-8 (no conversion because of invalid utf-8) - QTest::newRow("UnescapeStringLiteral_invalid") - << CppQuickFixFactoryPtr(new EscapeStringLiteral) - << _("const char *escaped = \"@\\xe3\\x81\";\n") - << _("const char *escaped = \"\\xe3\\x81\";\n"); - - QTest::newRow("ConvertFromPointer") - << CppQuickFixFactoryPtr(new ConvertFromAndToPointer) - << _("void foo() {\n" - " QString *@str;\n" - " if (!str->isEmpty())\n" - " str->clear();\n" - " f1(*str);\n" - " f2(str);\n" - "}\n") - << _("void foo() {\n" - " QString str;\n" - " if (!str.isEmpty())\n" - " str.clear();\n" - " f1(str);\n" - " f2(&str);\n" - "}\n"); - - QTest::newRow("ConvertToPointer") - << CppQuickFixFactoryPtr(new ConvertFromAndToPointer) - << _("void foo() {\n" - " QString @str;\n" - " if (!str.isEmpty())\n" - " str.clear();\n" - " f1(str);\n" - " f2(&str);\n" - "}\n") - << _("void foo() {\n" - " QString *str = new QString;\n" - " if (!str->isEmpty())\n" - " str->clear();\n" - " f1(*str);\n" - " f2(str);\n" - "}\n"); - - QTest::newRow("ConvertReferenceToPointer") - << CppQuickFixFactoryPtr(new ConvertFromAndToPointer) - << _("void foo() {\n" - " QString narf;" - " QString &@str = narf;\n" - " if (!str.isEmpty())\n" - " str.clear();\n" - " f1(str);\n" - " f2(&str);\n" - "}\n") - << _("void foo() {\n" - " QString narf;" - " QString *str = &narf;\n" - " if (!str->isEmpty())\n" - " str->clear();\n" - " f1(*str);\n" - " f2(str);\n" - "}\n"); - - QTest::newRow("ConvertFromPointer_withInitializer") - << CppQuickFixFactoryPtr(new ConvertFromAndToPointer) - << _("void foo() {\n" - " QString *@str = new QString(QLatin1String(\"schnurz\"));\n" - " if (!str->isEmpty())\n" - " str->clear();\n" - "}\n") - << _("void foo() {\n" - " QString str(QLatin1String(\"schnurz\"));\n" - " if (!str.isEmpty())\n" - " str.clear();\n" - "}\n"); - - QTest::newRow("ConvertFromPointer_withBareInitializer") - << CppQuickFixFactoryPtr(new ConvertFromAndToPointer) - << _("void foo() {\n" - " QString *@str = new QString;\n" - " if (!str->isEmpty())\n" - " str->clear();\n" - "}\n") - << _("void foo() {\n" - " QString str;\n" - " if (!str.isEmpty())\n" - " str.clear();\n" - "}\n"); - - QTest::newRow("ConvertFromPointer_withEmptyInitializer") - << CppQuickFixFactoryPtr(new ConvertFromAndToPointer) - << _("void foo() {\n" - " QString *@str = new QString();\n" - " if (!str->isEmpty())\n" - " str->clear();\n" - "}\n") - << _("void foo() {\n" - " QString str;\n" - " if (!str.isEmpty())\n" - " str.clear();\n" - "}\n"); - - QTest::newRow("ConvertFromPointer_structWithPointer") - << CppQuickFixFactoryPtr(new ConvertFromAndToPointer) - << _("struct Bar{ QString *str; };\n" - "void foo() {\n" - " Bar *@bar = new Bar;\n" - " bar->str = new QString;\n" - " delete bar->str;\n" - " delete bar;\n" - "}\n") - << _("struct Bar{ QString *str; };\n" - "void foo() {\n" - " Bar bar;\n" - " bar.str = new QString;\n" - " delete bar.str;\n" - " // delete bar;\n" - "}\n"); - - QTest::newRow("ConvertToPointer_withInitializer") - << CppQuickFixFactoryPtr(new ConvertFromAndToPointer) - << _("void foo() {\n" - " QString @str = QLatin1String(\"narf\");\n" - " if (!str.isEmpty())\n" - " str.clear();\n" - "}\n") - << _("void foo() {\n" - " QString *str = new QString(QLatin1String(\"narf\"));\n" - " if (!str->isEmpty())\n" - " str->clear();\n" - "}\n"); - - QTest::newRow("ConvertToPointer_withParenInitializer") - << CppQuickFixFactoryPtr(new ConvertFromAndToPointer) - << _("void foo() {\n" - " QString @str(QLatin1String(\"narf\"));\n" - " if (!str.isEmpty())\n" - " str.clear();\n" - "}\n") - << _("void foo() {\n" - " QString *str = new QString(QLatin1String(\"narf\"));\n" - " if (!str->isEmpty())\n" - " str->clear();\n" - "}\n"); - - QTest::newRow("ConvertToPointer_noTriggerRValueRefs") - << CppQuickFixFactoryPtr(new ConvertFromAndToPointer) - << _("void foo(Narf &&@narf) {}\n") - << _(); - - QTest::newRow("ConvertToPointer_noTriggerGlobal") - << CppQuickFixFactoryPtr(new ConvertFromAndToPointer) - << _("int @global;\n") - << _(); - - QTest::newRow("ConvertToPointer_noTriggerClassMember") - << CppQuickFixFactoryPtr(new ConvertFromAndToPointer) - << _("struct C { int @member; };\n") - << _(); - - QTest::newRow("ConvertToPointer_noTriggerClassMember2") - << CppQuickFixFactoryPtr(new ConvertFromAndToPointer) - << _("void f() { struct C { int @member; }; }\n") - << _(); - - QTest::newRow("ConvertToPointer_functionOfFunctionLocalClass") - << CppQuickFixFactoryPtr(new ConvertFromAndToPointer) - << _("void f() {\n" - " struct C {\n" - " void g() { int @member; }\n" - " };\n" - "}\n") - << _("void f() {\n" - " struct C {\n" - " void g() { int *member; }\n" - " };\n" - "}\n"); - - QTest::newRow("ConvertToPointer_redeclaredVariable_block") - << CppQuickFixFactoryPtr(new ConvertFromAndToPointer) - << _("void foo() {\n" - " QString @str;\n" - " str.clear();\n" - " {\n" - " QString str;\n" - " str.clear();\n" - " }\n" - " f1(str);\n" - "}\n") - << _("void foo() {\n" - " QString *str = new QString;\n" - " str->clear();\n" - " {\n" - " QString str;\n" - " str.clear();\n" - " }\n" - " f1(*str);\n" - "}\n"); - - QTest::newRow("ConvertAutoFromPointer") - << CppQuickFixFactoryPtr(new ConvertFromAndToPointer) - << _("void foo() {\n" - " auto @str = new QString(QLatin1String(\"foo\"));\n" - " if (!str->isEmpty())\n" - " str->clear();\n" - " f1(*str);\n" - " f2(str);\n" - "}\n") - << _("void foo() {\n" - " auto str = QString(QLatin1String(\"foo\"));\n" - " if (!str.isEmpty())\n" - " str.clear();\n" - " f1(str);\n" - " f2(&str);\n" - "}\n"); - - QTest::newRow("ConvertAutoFromPointer2") - << CppQuickFixFactoryPtr(new ConvertFromAndToPointer) - << _("void foo() {\n" - " auto *@str = new QString;\n" - " if (!str->isEmpty())\n" - " str->clear();\n" - " f1(*str);\n" - " f2(str);\n" - "}\n") - << _("void foo() {\n" - " auto str = QString();\n" - " if (!str.isEmpty())\n" - " str.clear();\n" - " f1(str);\n" - " f2(&str);\n" - "}\n"); - - QTest::newRow("ConvertAutoToPointer") - << CppQuickFixFactoryPtr(new ConvertFromAndToPointer) - << _("void foo() {\n" - " auto @str = QString(QLatin1String(\"foo\"));\n" - " if (!str.isEmpty())\n" - " str.clear();\n" - " f1(str);\n" - " f2(&str);\n" - "}\n") - << _("void foo() {\n" - " auto @str = new QString(QLatin1String(\"foo\"));\n" - " if (!str->isEmpty())\n" - " str->clear();\n" - " f1(*str);\n" - " f2(str);\n" - "}\n"); - - QTest::newRow("ConvertToPointerWithMacro") - << CppQuickFixFactoryPtr(new ConvertFromAndToPointer) - << _("#define BAR bar\n" - "void func()\n" - "{\n" - " int @foo = 42;\n" - " int bar;\n" - " BAR = foo;\n" - "}\n") - << _("#define BAR bar\n" - "void func()\n" - "{\n" - " int *foo = 42;\n" - " int bar;\n" - " BAR = *foo;\n" - "}\n"); - - QString testObjAndFunc = "struct Object\n" - "{\n" - " Object(%1){}\n" - "};\n" - "void func()\n" - "{\n" - " %2\n" - "}\n"; - - QTest::newRow("ConvertToStack1_QTCREATORBUG23181") - << CppQuickFixFactoryPtr(new ConvertFromAndToPointer) - << _(testObjAndFunc.arg("int").arg("Object *@obj = new Object(0);").toUtf8()) - << _(testObjAndFunc.arg("int").arg("Object obj(0);").toUtf8()); - - QTest::newRow("ConvertToStack2_QTCREATORBUG23181") - << CppQuickFixFactoryPtr(new ConvertFromAndToPointer) - << _(testObjAndFunc.arg("int").arg("Object *@obj = new Object{0};").toUtf8()) - << _(testObjAndFunc.arg("int").arg("Object obj{0};").toUtf8()); - - QTest::newRow("ConvertToPointer1_QTCREATORBUG23181") - << CppQuickFixFactoryPtr(new ConvertFromAndToPointer) - << _(testObjAndFunc.arg("").arg("Object @obj;").toUtf8()) - << _(testObjAndFunc.arg("").arg("Object *obj = new Object;").toUtf8()); - - QTest::newRow("ConvertToPointer2_QTCREATORBUG23181") - << CppQuickFixFactoryPtr(new ConvertFromAndToPointer) - << _(testObjAndFunc.arg("").arg("Object @obj();").toUtf8()) - << _(testObjAndFunc.arg("").arg("Object *obj = new Object();").toUtf8()); - - QTest::newRow("ConvertToPointer3_QTCREATORBUG23181") - << CppQuickFixFactoryPtr(new ConvertFromAndToPointer) - << _(testObjAndFunc.arg("").arg("Object @obj{};").toUtf8()) - << _(testObjAndFunc.arg("").arg("Object *obj = new Object{};").toUtf8()); - - QTest::newRow("ConvertToPointer4_QTCREATORBUG23181") - << CppQuickFixFactoryPtr(new ConvertFromAndToPointer) - << _(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") - << _("void lowerCaseFunction();\n"); - QTest::newRow("convert to camel case: already camel case") - << CppQuickFixFactoryPtr(new ConvertToCamelCase(true)) - << _("void @camelCaseFunction();\n") - << _(); - QTest::newRow("convert to camel case: no underscores (lower case)") - << CppQuickFixFactoryPtr(new ConvertToCamelCase(true)) - << _("void @lowercasefunction();\n") - << _(); - QTest::newRow("convert to camel case: no underscores (upper case)") - << CppQuickFixFactoryPtr(new ConvertToCamelCase(true)) - << _("void @UPPERCASEFUNCTION();\n") - << _(); - QTest::newRow("convert to camel case: non-applicable underscore") - << CppQuickFixFactoryPtr(new ConvertToCamelCase(true)) - << _("void @m_a_member;\n") - << _("void m_aMember;\n"); - QTest::newRow("convert to camel case: upper case") - << CppQuickFixFactoryPtr(new ConvertToCamelCase(true)) - << _("void @UPPER_CASE_FUNCTION();\n") - << _("void upperCaseFunction();\n"); - QTest::newRow("convert to camel case: partially camel case already") - << CppQuickFixFactoryPtr(new ConvertToCamelCase(true)) - << _("void mixed@_andCamelCase();\n") - << _("void mixedAndCamelCase();\n"); - QTest::newRow("convert to camel case: wild mix") - << CppQuickFixFactoryPtr(new ConvertToCamelCase(true)) - << _("void @WhAt_TODO_hErE();\n") - << _("void WhAtTODOHErE();\n"); - QTest::newRow("escape string literal: simple case") - << CppQuickFixFactoryPtr(new EscapeStringLiteral) - << _(R"(const char *str = @"à xyz";)") - << _(R"(const char *str = "\xc3\xa0xyz";)"); - QTest::newRow("escape string literal: simple case reverse") - << CppQuickFixFactoryPtr(new EscapeStringLiteral) - << _(R"(const char *str = @"\xc3\xa0xyz";)") - << _(R"(const char *str = "à xyz";)"); - QTest::newRow("escape string literal: raw string literal") - << CppQuickFixFactoryPtr(new EscapeStringLiteral) - << _(R"x(const char *str = @R"(à xyz)";)x") - << _(R"x(const char *str = R"(\xc3\xa0xyz)";)x"); - QTest::newRow("escape string literal: splitting required") - << CppQuickFixFactoryPtr(new EscapeStringLiteral) - << _(R"(const char *str = @"à f23бgб1";)") - << _(R"(const char *str = "\xc3\xa0""f23\xd0\xb1g\xd0\xb1""1";)"); - QTest::newRow("escape string literal: unescape adjacent literals") - << CppQuickFixFactoryPtr(new EscapeStringLiteral) - << _(R"(const char *str = @"\xc3\xa0""f23\xd0\xb1g\xd0\xb1""1";)") - << _(R"(const char *str = "à f23бgб1";)"); - QTest::newRow("AddLocalDeclaration_QTCREATORBUG-26004") - << CppQuickFixFactoryPtr(new AddDeclarationForUndeclaredIdentifier) - << _("void func() {\n" - " QStringList list;\n" - " @it = list.cbegin();\n" - "}\n") - << _("void func() {\n" - " QStringList list;\n" - " auto it = list.cbegin();\n" - "}\n"); -} - -void QuickfixTest::testGeneric() -{ - QFETCH(CppQuickFixFactoryPtr, factory); - QFETCH(QByteArray, original); - QFETCH(QByteArray, expected); - - QuickFixOperationTest(singleDocument(original, expected), factory.data()); -} - -void QuickfixTest::testGenerateGetterSetterNamespaceHandlingCreate_data() -{ - QTest::addColumn<QByteArrayList>("headers"); - QTest::addColumn<QByteArrayList>("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<TestDocumentPtr> 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<Name>"; - s->setterInCppFileFrom = 1; - s->getterInCppFileFrom = 1; - GenerateGetterSetter factory; - QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), 2); -} - -void QuickfixTest::testGenerateGetterSetterNamespaceHandlingAddUsing_data() -{ - QTest::addColumn<QByteArrayList>("headers"); - QTest::addColumn<QByteArrayList>("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<TestDocumentPtr> 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<QByteArrayList>("headers"); - QTest::addColumn<QByteArrayList>("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<TestDocumentPtr> 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<QByteArrayList>("headers"); - QTest::addColumn<int>("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<TestDocumentPtr> testDocuments( - {CppTestDocument::create("file.h", headers.at(0), headers.at(1))}); - - QuickFixSettings s; - s->setterInCppFileFrom = 0; - s->getterInCppFileFrom = 0; - s->setterNameTemplate = "Seet_<Name>"; - s->getterNameTemplate = "give_me_<snake>"; - s->signalNameTemplate = "new<Camel>Value"; - s->setterParameterNameTemplate = "New_<Snake>"; - s->resetNameTemplate = "set_<camel>_toDefault"; - s->memberVariableNameTemplate = "mem_<name>"; - 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<QByteArrayList>("headers"); - QTest::addColumn<int>("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<typename T> -class Value{}; -class Test { - Value<int> v@; -}; -)-"; - expectedSource = R"-( -template<typename T> -class Value{}; -class Test { - Value<int> v; - -public: - Value<int> getV() const - { - return v; - } -}; -)-"; - QTest::addRow("value template class") << QByteArrayList{originalSource, expectedSource} << 1; -} - -void QuickfixTest::testGenerateGetterSetterValueTypes() -{ - QFETCH(QByteArrayList, headers); - QFETCH(int, operation); - - QList<TestDocumentPtr> testDocuments( - {CppTestDocument::create("file.h", headers.at(0), headers.at(1))}); - - QuickFixSettings s; - s->setterInCppFileFrom = 0; - s->getterInCppFileFrom = 0; - s->getterNameTemplate = "get<Name>"; - 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<TestDocumentPtr> testDocuments; - QByteArray original; - QByteArray expected; - - const _ customTypeDecl = R"--( -namespace N1 { -namespace N2 { -struct test{}; -} -template<typename T> -struct custom { - void assign(const custom<T>&); - bool equals(const custom<T>&); - T* get(); -}; -)--"; - // Header File - original = customTypeDecl + R"--( -class Foo -{ -public: - custom<N2::test> bar@; -}; -})--"; - expected = customTypeDecl + R"--( -class Foo -{ -public: - custom<N2::test> bar@; - N2::test *getBar() const; - void setBar(const custom<N2::test> &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<N2::test> &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<Name>"; - s->getterInCppFileFrom = 1; - s->signalWithNewValue = true; - CppQuickFixSettings::CustomTemplate t; - t.types.append("custom"); - t.equalComparison = "<cur>.equals(<new>)"; - t.returnExpression = "<cur>.get()"; - t.returnType = "<T> *"; - t.assignment = "<cur>.assign(<new>)"; - 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<TestDocumentPtr> 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 = "<name>"; - s->setterInCppFileFrom = 0; - - GenerateGetterSetter factory; - QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), 0); -} - -void QuickfixTest::testGenerateGetterSetterOfferedFixes_data() -{ - QTest::addColumn<QByteArray>("header"); - QTest::addColumn<QStringList>("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<TestDocumentPtr> testDocuments( - {CppTestDocument::create("file.h", header, header)}); - - GenerateGetterSetter factory; - QuickFixOfferedOperationsTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), offered); -} - -void QuickfixTest::testGenerateGetterSetterGeneralTests_data() -{ - QTest::addColumn<int>("operation"); - QTest::addColumn<QByteArray>("original"); - QTest::addColumn<QByteArray>("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 = "<name>"; - s->getterInCppFileFrom = 1; - s->setterInCppFileFrom = 1; - - GenerateGetterSetter factory; - QuickFixOperationTest(singleDocument(original, expected), - &factory, - ProjectExplorer::HeaderPaths(), - operation); -} -/// Checks: Only generate getter -void QuickfixTest::testGenerateGetterSetterOnlyGetter() -{ - QList<TestDocumentPtr> 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<Name>"; - GenerateGetterSetter factory; - QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), 1); -} - -/// Checks: Only generate setter -void QuickfixTest::testGenerateGetterSetterOnlySetter() -{ - QList<TestDocumentPtr> 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<TestDocumentPtr> 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<TestDocumentPtr> 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<Name>"; - - GenerateGetterSetter factory; - QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), 4); -} - -void QuickfixTest::testGenerateGetterSetterOnlySetterHeaderFileWithIncludeGuard() -{ - QList<TestDocumentPtr> 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<TestDocumentPtr> testDocuments; - const QByteArray original = R"( -template<typename T> class TS {}; -template<typename T, typename U> class TS<T(U)> {}; - -class S2 { - TS<int(int)> @member; -}; -)"; - const QByteArray expected = R"( -template<typename T> class TS {}; -template<typename T, typename U> class TS<T(U)> {}; - -class S2 { - TS<int(int)> member; - -public: - const TS<int (int)> &getMember() const - { - return member; - } -}; -)"; - - testDocuments << CppTestDocument::create("file.h", original, expected); - - QuickFixSettings s; - s->getterOutsideClassFrom = 0; - s->getterInCppFileFrom = 0; - s->getterNameTemplate = "get<Name>"; - s->returnByConstRef = true; - - GenerateGetterSetter factory; - QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), 1); -} - -class CppCodeStyleSettingsChanger { -public: - CppCodeStyleSettingsChanger(const CppCodeStyleSettings &settings); - ~CppCodeStyleSettingsChanger(); // Restore original - - static CppCodeStyleSettings currentSettings(); - -private: - void setSettings(const CppCodeStyleSettings &settings); - - CppCodeStyleSettings m_originalSettings; -}; - -CppCodeStyleSettingsChanger::CppCodeStyleSettingsChanger(const CppCodeStyleSettings &settings) -{ - m_originalSettings = currentSettings(); - setSettings(settings); -} - -CppCodeStyleSettingsChanger::~CppCodeStyleSettingsChanger() -{ - setSettings(m_originalSettings); -} - -void CppCodeStyleSettingsChanger::setSettings(const CppCodeStyleSettings &settings) -{ - QVariant variant; - variant.setValue(settings); - - CppToolsSettings::cppCodeStyle()->currentDelegate()->setValue(variant); -} - -CppCodeStyleSettings CppCodeStyleSettingsChanger::currentSettings() -{ - return CppToolsSettings::cppCodeStyle()->currentDelegate()->value().value<CppCodeStyleSettings>(); -} - -void QuickfixTest::testGenerateGettersSetters_data() -{ - QTest::addColumn<QByteArray>("original"); - QTest::addColumn<QByteArray>("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<Name>"; - 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<QByteArray>("original"); - QTest::addColumn<QByteArray>("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 = "<name>"; - s->signalWithNewValue = true; - - InsertQtPropertyMembers factory; - QuickFixOperationTest({CppTestDocument::create("file.cpp", original, expected)}, &factory); -} - -void QuickfixTest::testInsertMemberFromUse_data() -{ - QTest::addColumn<QByteArray>("original"); - QTest::addColumn<QByteArray>("expected"); - - QByteArray original; - QByteArray expected; - - original = - "class C {\n" - "public:\n" - " C(int x) : @m_x(x) {}\n" - "private:\n" - " int m_y;\n" - "};\n"; - expected = - "class C {\n" - "public:\n" - " C(int x) : m_x(x) {}\n" - "private:\n" - " int m_y;\n" - " int m_x;\n" - "};\n"; - QTest::addRow("inline constructor") << original << expected; - - original = - "class C {\n" - "public:\n" - " C(int x, double d);\n" - "private:\n" - " int m_x;\n" - "};\n" - "C::C(int x, double d) : m_x(x), @m_d(d)\n"; - expected = - "class C {\n" - "public:\n" - " C(int x, double d);\n" - "private:\n" - " int m_x;\n" - " double m_d;\n" - "};\n" - "C::C(int x, double d) : m_x(x), m_d(d)\n"; - QTest::addRow("out-of-line constructor") << original << expected; - - original = - "class C {\n" - "public:\n" - " C(int x) : @m_x(x) {}\n" - "private:\n" - " int m_x;\n" - "};\n"; - expected = ""; - QTest::addRow("member already present") << original << expected; - - original = - "int func() { return 0; }\n" - "class C {\n" - "public:\n" - " C() : @m_x(func()) {}\n" - "private:\n" - " int m_y;\n" - "};\n"; - expected = - "int func() { return 0; }\n" - "class C {\n" - "public:\n" - " C() : m_x(func()) {}\n" - "private:\n" - " int m_y;\n" - " int m_x;\n" - "};\n"; - QTest::addRow("initialization via function call") << original << expected; - - original = - "struct S {\n\n};\n" - "class C {\n" - "public:\n" - " void setValue(int v) { m_s.@value = v; }\n" - "private:\n" - " S m_s;\n" - "};\n"; - expected = - "struct S {\n\n" - " int value;\n" - "};\n" - "class C {\n" - "public:\n" - " void setValue(int v) { m_s.value = v; }\n" - "private:\n" - " S m_s;\n" - "};\n"; - QTest::addRow("add member to other struct") << original << expected; - - original = - "struct S {\n\n};\n" - "class C {\n" - "public:\n" - " void setValue(int v) { S::@value = v; }\n" - "};\n"; - expected = - "struct S {\n\n" - " static int value;\n" - "};\n" - "class C {\n" - "public:\n" - " void setValue(int v) { S::value = v; }\n" - "};\n"; - QTest::addRow("add static member to other struct (explicit)") << original << expected; - - original = - "struct S {\n\n};\n" - "class C {\n" - "public:\n" - " void setValue(int v) { m_s.@value = v; }\n" - "private:\n" - " static S m_s;\n" - "};\n"; - expected = - "struct S {\n\n" - " static int value;\n" - "};\n" - "class C {\n" - "public:\n" - " void setValue(int v) { m_s.value = v; }\n" - "private:\n" - " static S m_s;\n" - "};\n"; - QTest::addRow("add static member to other struct (implicit)") << original << expected; - - original = - "class C {\n" - "public:\n" - " void setValue(int v);\n" - "};\n" - "void C::setValue(int v) { this->@m_value = v; }\n"; - expected = - "class C {\n" - "public:\n" - " void setValue(int v);\n" - "private:\n" - " int m_value;\n" - "};\n" - "void C::setValue(int v) { this->@m_value = v; }\n"; - QTest::addRow("add member to this (explicit)") << original << expected; - - original = - "class C {\n" - "public:\n" - " void setValue(int v) { @m_value = v; }\n" - "};\n"; - expected = - "class C {\n" - "public:\n" - " void setValue(int v) { m_value = v; }\n" - "private:\n" - " int m_value;\n" - "};\n"; - QTest::addRow("add member to this (implicit)") << original << expected; - - original = - "class C {\n" - "public:\n" - " static void setValue(int v) { @m_value = v; }\n" - "};\n"; - expected = - "class C {\n" - "public:\n" - " static void setValue(int v) { m_value = v; }\n" - "private:\n" - " static int m_value;\n" - "};\n"; - QTest::addRow("add static member to this (inline)") << original << expected; - - original = - "class C {\n" - "public:\n" - " static void setValue(int v);\n" - "};\n" - "void C::setValue(int v) { @m_value = v; }\n"; - expected = - "class C {\n" - "public:\n" - " static void setValue(int v);\n" - "private:\n" - " static int m_value;\n" - "};\n" - "void C::setValue(int v) { @m_value = v; }\n"; - QTest::addRow("add static member to this (non-inline)") << original << expected; - - original = - "struct S {\n\n};\n" - "class C {\n" - "public:\n" - " void setValue(int v) { m_s.@setValue(v); }\n" - "private:\n" - " S m_s;\n" - "};\n"; - expected = - "struct S {\n\n" - " void setValue(int);\n" - "};\n" - "class C {\n" - "public:\n" - " void setValue(int v) { m_s.setValue(v); }\n" - "private:\n" - " S m_s;\n" - "};\n"; - QTest::addRow("add member function to other struct") << original << expected; - - original = - "struct S {\n\n};\n" - "class C {\n" - "public:\n" - " void setValue(int v) { S::@setValue(v); }\n" - "};\n"; - expected = - "struct S {\n\n" - " static void setValue(int);\n" - "};\n" - "class C {\n" - "public:\n" - " void setValue(int v) { S::setValue(v); }\n" - "};\n"; - QTest::addRow("add static member function to other struct (explicit)") << original << expected; - - original = - "struct S {\n\n};\n" - "class C {\n" - "public:\n" - " void setValue(int v) { m_s.@setValue(v); }\n" - "private:\n" - " static S m_s;\n" - "};\n"; - expected = - "struct S {\n\n" - " static void setValue(int);\n" - "};\n" - "class C {\n" - "public:\n" - " void setValue(int v) { m_s.setValue(v); }\n" - "private:\n" - " static S m_s;\n" - "};\n"; - QTest::addRow("add static member function to other struct (implicit)") << original << expected; - - original = - "class C {\n" - "public:\n" - " void setValue(int v);\n" - "};\n" - "void C::setValue(int v) { this->@setValueInternal(v); }\n"; - expected = - "class C {\n" - "public:\n" - " void setValue(int v);\n" - "private:\n" - " void setValueInternal(int);\n" - "};\n" - "void C::setValue(int v) { this->setValueInternal(v); }\n"; - QTest::addRow("add member function to this (explicit)") << original << expected; - - original = - "class C {\n" - "public:\n" - " void setValue(int v) { @setValueInternal(v); }\n" - "};\n"; - expected = - "class C {\n" - "public:\n" - " void setValue(int v) { setValueInternal(v); }\n" - "private:\n" - " void setValueInternal(int);\n" - "};\n"; - QTest::addRow("add member function to this (implicit)") << original << expected; - - original = - "class C {\n" - "public:\n" - " int value() const { return @valueInternal(); }\n" - "};\n"; - expected = - "class C {\n" - "public:\n" - " int value() const { return valueInternal(); }\n" - "private:\n" - " int valueInternal() const;\n" - "};\n"; - QTest::addRow("add const member function to this (implicit)") << original << expected; - - original = - "class C {\n" - "public:\n" - " static int value() { int i = @valueInternal(); return i; }\n" - "};\n"; - expected = - "class C {\n" - "public:\n" - " static int value() { int i = @valueInternal(); return i; }\n" - "private:\n" - " static int valueInternal();\n" - "};\n"; - QTest::addRow("add static member function to this (inline)") << original << expected; - - original = - "class C {\n" - "public:\n" - " static int value();\n" - "};\n" - "int C::value() { return @valueInternal(); }\n"; - expected = - "class C {\n" - "public:\n" - " static int value();\n" - "private:\n" - " static int valueInternal();\n" - "};\n" - "int C::value() { return valueInternal(); }\n"; - QTest::addRow("add static member function to this (non-inline)") << original << expected; -} - -void QuickfixTest::testInsertMemberFromUse() -{ - QFETCH(QByteArray, original); - QFETCH(QByteArray, expected); - - QList<TestDocumentPtr> testDocuments({ - CppTestDocument::create("file.h", original, expected) - }); - - AddDeclarationForUndeclaredIdentifier factory; - factory.setMembersOnly(); - QuickFixOperationTest(testDocuments, &factory); -} - -/// Check if definition is inserted right after class for insert definition outside -void QuickfixTest::testInsertDefFromDeclAfterClass() -{ - QList<TestDocumentPtr> testDocuments; - QByteArray original; - QByteArray expected; - - // Header File - original = - "class Foo\n" - "{\n" - " Foo();\n" - " void a@();\n" - "};\n" - "\n" - "class Bar {};\n"; - expected = - "class Foo\n" - "{\n" - " Foo();\n" - " void a();\n" - "};\n" - "\n" - "inline void Foo::a()\n" - "{\n\n}\n" - "\n" - "class Bar {};\n"; - testDocuments << CppTestDocument::create("file.h", original, expected); - - // Source File - original = - "#include \"file.h\"\n" - "\n" - "Foo::Foo()\n" - "{\n\n" - "}\n"; - expected = original; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - - InsertDefFromDecl factory; - QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), 1); -} - -/// Check from header file: If there is a source file, insert the definition in the source file. -/// Case: Source file is empty. -void QuickfixTest::testInsertDefFromDeclHeaderSourceBasic1() -{ - QList<TestDocumentPtr> testDocuments; - - QByteArray original; - QByteArray expected; - - // Header File - original = - "struct Foo\n" - "{\n" - " Foo()@;\n" - "};\n"; - expected = original; - testDocuments << CppTestDocument::create("file.h", original, expected); - - // Source File - original.resize(0); - expected = - "\n" - "Foo::Foo()\n" - "{\n\n" - "}\n" - ; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - - InsertDefFromDecl factory; - QuickFixOperationTest(testDocuments, &factory); -} - -/// Check from header file: If there is a source file, insert the definition in the source file. -/// Case: Source file is not empty. -void QuickfixTest::testInsertDefFromDeclHeaderSourceBasic2() -{ - QList<TestDocumentPtr> testDocuments; - - QByteArray original; - QByteArray expected; - - // Header File - original = "void f(const std::vector<int> &v)@;\n"; - expected = original; - testDocuments << CppTestDocument::create("file.h", original, expected); - - // Source File - original = - "#include \"file.h\"\n" - "\n" - "int x;\n" - ; - expected = - "#include \"file.h\"\n" - "\n" - "int x;\n" - "\n" - "void f(const std::vector<int> &v)\n" - "{\n" - "\n" - "}\n" - ; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - - InsertDefFromDecl factory; - QuickFixOperationTest(testDocuments, &factory); -} - -/// Check from source file: Insert in source file, not header file. -void QuickfixTest::testInsertDefFromDeclHeaderSourceBasic3() -{ - QList<TestDocumentPtr> testDocuments; - - QByteArray original; - QByteArray expected; - - // Empty Header File - testDocuments << CppTestDocument::create("file.h", "", ""); - - // Source File - original = - "struct Foo\n" - "{\n" - " Foo()@;\n" - "};\n"; - expected = original + - "\n" - "Foo::Foo()\n" - "{\n\n" - "}\n" - ; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - - InsertDefFromDecl factory; - QuickFixOperationTest(testDocuments, &factory); -} - -/// Check from header file: If the class is in a namespace, the added function definition -/// name must be qualified accordingly. -void QuickfixTest::testInsertDefFromDeclHeaderSourceNamespace1() -{ - QList<TestDocumentPtr> testDocuments; - - QByteArray original; - QByteArray expected; - - // Header File - original = - "namespace N {\n" - "struct Foo\n" - "{\n" - " Foo()@;\n" - "};\n" - "}\n"; - expected = original; - testDocuments << CppTestDocument::create("file.h", original, expected); - - // Source File - original.resize(0); - expected = - "\n" - "N::Foo::Foo()\n" - "{\n\n" - "}\n" - ; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - - InsertDefFromDecl factory; - QuickFixOperationTest(testDocuments, &factory); -} - -/// Check from header file: If the class is in namespace N and the source file has a -/// "using namespace N" line, the function definition name must be qualified accordingly. -void QuickfixTest::testInsertDefFromDeclHeaderSourceNamespace2() -{ - QList<TestDocumentPtr> testDocuments; - - QByteArray original; - QByteArray expected; - - // Header File - original = - "namespace N {\n" - "struct Foo\n" - "{\n" - " Foo()@;\n" - "};\n" - "}\n"; - expected = original; - testDocuments << CppTestDocument::create("file.h", original, expected); - - // Source File - original = - "#include \"file.h\"\n" - "using namespace N;\n" - ; - expected = original + - "\n" - "Foo::Foo()\n" - "{\n\n" - "}\n" - ; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - - InsertDefFromDecl factory; - QuickFixOperationTest(testDocuments, &factory); -} - -/// Check definition insert inside class -void QuickfixTest::testInsertDefFromDeclInsideClass() -{ - const QByteArray original = - "class Foo {\n" - " void b@ar();\n" - "};"; - const QByteArray expected = - "class Foo {\n" - " void bar()\n" - " {\n\n" - " }\n" - "};"; - - InsertDefFromDecl factory; - QuickFixOperationTest(singleDocument(original, expected), &factory, ProjectExplorer::HeaderPaths(), - 1); -} - -/// Check not triggering when definition exists -void QuickfixTest::testInsertDefFromDeclNotTriggeringWhenDefinitionExists() -{ - const QByteArray original = - "class Foo {\n" - " void b@ar();\n" - "};\n" - "void Foo::bar() {}\n"; - - InsertDefFromDecl factory; - QuickFixOperationTest(singleDocument(original, ""), &factory, ProjectExplorer::HeaderPaths(), 1); -} - -/// Find right implementation file. -void QuickfixTest::testInsertDefFromDeclFindRightImplementationFile() -{ - QList<TestDocumentPtr> testDocuments; - - QByteArray original; - QByteArray expected; - - // Header File - original = - "struct Foo\n" - "{\n" - " Foo();\n" - " void a();\n" - " void b@();\n" - "};\n" - "}\n"; - expected = original; - testDocuments << CppTestDocument::create("file.h", original, expected); - - // Source File #1 - original = - "#include \"file.h\"\n" - "\n" - "Foo::Foo()\n" - "{\n\n" - "}\n"; - expected = original; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - - - // Source File #2 - original = - "#include \"file.h\"\n" - "\n" - "void Foo::a()\n" - "{\n\n" - "}\n"; - expected = original + - "\n" - "void Foo::b()\n" - "{\n\n" - "}\n"; - testDocuments << CppTestDocument::create("file2.cpp", original, expected); - - InsertDefFromDecl factory; - QuickFixOperationTest(testDocuments, &factory); -} - -/// Ignore generated functions declarations when looking at the surrounding -/// functions declarations in order to find the right implementation file. -void QuickfixTest::testInsertDefFromDeclIgnoreSurroundingGeneratedDeclarations() -{ - QList<TestDocumentPtr> testDocuments; - - QByteArray original; - QByteArray expected; - - // Header File - original = - "#define DECLARE_HIDDEN_FUNCTION void hidden();\n" - "struct Foo\n" - "{\n" - " void a();\n" - " DECLARE_HIDDEN_FUNCTION\n" - " void b@();\n" - "};\n" - "}\n"; - expected = original; - testDocuments << CppTestDocument::create("file.h", original, expected); - - // Source File #1 - original = - "#include \"file.h\"\n" - "\n" - "void Foo::a()\n" - "{\n\n" - "}\n"; - expected = - "#include \"file.h\"\n" - "\n" - "void Foo::a()\n" - "{\n\n" - "}\n" - "\n" - "void Foo::b()\n" - "{\n\n" - "}\n"; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - - // Source File #2 - original = - "#include \"file.h\"\n" - "\n" - "void Foo::hidden()\n" - "{\n\n" - "}\n"; - expected = original; - testDocuments << CppTestDocument::create("file2.cpp", original, expected); - - InsertDefFromDecl factory; - QuickFixOperationTest(testDocuments, &factory); -} - -/// Check if whitespace is respected for operator functions -void QuickfixTest::testInsertDefFromDeclRespectWsInOperatorNames1() -{ - QByteArray original = - "class Foo\n" - "{\n" - " Foo &opera@tor =();\n" - "};\n"; - QByteArray expected = - "class Foo\n" - "{\n" - " Foo &operator =();\n" - "};\n" - "\n" - "Foo &Foo::operator =()\n" - "{\n" - "\n" - "}\n"; - - InsertDefFromDecl factory; - QuickFixOperationTest(singleDocument(original, expected), &factory); -} - -/// Check if whitespace is respected for operator functions -void QuickfixTest::testInsertDefFromDeclRespectWsInOperatorNames2() -{ - QByteArray original = - "class Foo\n" - "{\n" - " Foo &opera@tor=();\n" - "};\n"; - QByteArray expected = - "class Foo\n" - "{\n" - " Foo &operator=();\n" - "};\n" - "\n" - "Foo &Foo::operator=()\n" - "{\n" - "\n" - "}\n"; - - InsertDefFromDecl factory; - QuickFixOperationTest(singleDocument(original, expected), &factory); -} - -/// Check that the noexcept exception specifier is transferred -void QuickfixTest::testInsertDefFromDeclNoexceptSpecifier() -{ - QByteArray original = - "class Foo\n" - "{\n" - " void @foo() noexcept(false);\n" - "};\n"; - QByteArray expected = - "class Foo\n" - "{\n" - " void foo() noexcept(false);\n" - "};\n" - "\n" - "void Foo::foo() noexcept(false)\n" - "{\n" - "\n" - "}\n"; - - InsertDefFromDecl factory; - QuickFixOperationTest(singleDocument(original, expected), &factory); -} - -/// Check if a function like macro use is not separated by the function to insert -/// Case: Macro preceded by preproceesor directives and declaration. -void QuickfixTest::testInsertDefFromDeclMacroUsesAtEndOfFile1() -{ - QList<TestDocumentPtr> testDocuments; - - QByteArray original; - QByteArray expected; - - // Header File - original = "void f()@;\n"; - expected = original; - testDocuments << CppTestDocument::create("file.h", original, expected); - - // Source File - original = - "#include \"file.h\"\n" - "#define MACRO(X) X x;\n" - "int lala;\n" - "\n" - "MACRO(int)\n" - ; - expected = - "#include \"file.h\"\n" - "#define MACRO(X) X x;\n" - "int lala;\n" - "\n" - "\n" - "\n" - "void f()\n" - "{\n" - "\n" - "}\n" - "\n" - "MACRO(int)\n" - ; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - - InsertDefFromDecl factory; - QuickFixOperationTest(testDocuments, &factory); -} - -/// Check if a function like macro use is not separated by the function to insert -/// Case: Marco preceded only by preprocessor directives. -void QuickfixTest::testInsertDefFromDeclMacroUsesAtEndOfFile2() -{ - QList<TestDocumentPtr> testDocuments; - - QByteArray original; - QByteArray expected; - - // Header File - original = "void f()@;\n"; - expected = original; - testDocuments << CppTestDocument::create("file.h", original, expected); - - // Source File - original = - "#include \"file.h\"\n" - "#define MACRO(X) X x;\n" - "\n" - "MACRO(int)\n" - ; - expected = - "#include \"file.h\"\n" - "#define MACRO(X) X x;\n" - "\n" - "\n" - "\n" - "void f()\n" - "{\n" - "\n" - "}\n" - "\n" - "MACRO(int)\n" - ; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - - InsertDefFromDecl factory; - QuickFixOperationTest(testDocuments, &factory); -} - -/// Check if insertion happens before syntactically erroneous statements at end of file. -void QuickfixTest::testInsertDefFromDeclErroneousStatementAtEndOfFile() -{ - QList<TestDocumentPtr> testDocuments; - - QByteArray original; - QByteArray expected; - - // Header File - original = "void f()@;\n"; - expected = original; - testDocuments << CppTestDocument::create("file.h", original, expected); - - // Source File - original = - "#include \"file.h\"\n" - "\n" - "MissingSemicolon(int)\n" - ; - expected = - "#include \"file.h\"\n" - "\n" - "\n" - "\n" - "void f()\n" - "{\n" - "\n" - "}\n" - "\n" - "MissingSemicolon(int)\n" - ; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - - InsertDefFromDecl factory; - QuickFixOperationTest(testDocuments, &factory); -} - -/// Check: Respect rvalue references -void QuickfixTest::testInsertDefFromDeclRvalueReference() -{ - QList<TestDocumentPtr> testDocuments; - - QByteArray original; - QByteArray expected; - - // Header File - original = "void f(Foo &&)@;\n"; - expected = original; - testDocuments << CppTestDocument::create("file.h", original, expected); - - // Source File - original = ""; - expected = - "\n" - "void f(Foo &&)\n" - "{\n" - "\n" - "}\n" - ; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - - InsertDefFromDecl factory; - QuickFixOperationTest(testDocuments, &factory); -} - -void QuickfixTest::testInsertDefFromDeclFunctionTryBlock() -{ - QList<TestDocumentPtr> testDocuments; - - QByteArray original; - QByteArray expected; - - // Header File - original = R"( -struct Foo { - void tryCatchFunc(); - void @otherFunc(); -}; -)"; - expected = original; - testDocuments << CppTestDocument::create("file.h", original, expected); - - // Source File - original = R"( -#include "file.h" - -void Foo::tryCatchFunc() try {} catch (...) {} -)"; - expected = R"( -#include "file.h" - -void Foo::tryCatchFunc() try {} catch (...) {} - -void Foo::otherFunc() -{ - -} -)"; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - - InsertDefFromDecl factory; - QuickFixOperationTest(testDocuments, &factory); -} - -void QuickfixTest::testInsertDefFromDeclUsingDecl() -{ - QList<TestDocumentPtr> testDocuments; - - QByteArray original; - QByteArray expected; - - // Header File - original = R"( -namespace N { struct S; } -using N::S; - -void @func(const S &s); -)"; - expected = original; - testDocuments << CppTestDocument::create("file.h", original, expected); - - // Source File - original = R"( -#include "file.h" -)"; - expected = R"( -#include "file.h" - -void func(const S &s) -{ - -} -)"; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - - InsertDefFromDecl factory; - QuickFixOperationTest(testDocuments, &factory); - - testDocuments.clear(); - original = R"( -namespace N1 { -namespace N2 { struct S; } -using N2::S; -} - -void @func(const N1::S &s); -)"; - expected = original; - testDocuments << CppTestDocument::create("file.h", original, expected); - - // Source File - original = R"( -#include "file.h" -)"; - expected = R"( -#include "file.h" - -void func(const N1::S &s) -{ - -} -)"; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - QuickFixOperationTest(testDocuments, &factory); - - // No using declarations here, but the code model has one. No idea why. - testDocuments.clear(); - original = R"( -class B {}; -class D : public B { - @D(); -}; -)"; - expected = original; - testDocuments << CppTestDocument::create("file.h", original, expected); - - // Source File - original = R"( -#include "file.h" -)"; - expected = R"( -#include "file.h" - -D::D() -{ - -} -)"; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - QuickFixOperationTest(testDocuments, &factory); - - testDocuments.clear(); - original = R"( -namespace ns1 { template<typename T> class span {}; } - -namespace ns { -using ns1::span; -class foo -{ - void @bar(ns::span<int>); -}; -} -)"; - expected = R"( -namespace ns1 { template<typename T> class span {}; } - -namespace ns { -using ns1::span; -class foo -{ - void bar(ns::span<int>); -}; - -void foo::bar(ns::span<int>) -{ - -} - -} -)"; - // TODO: Unneeded namespace gets inserted in RewriteName::visit(const QualifiedNameId *) - testDocuments << CppTestDocument::create("file.cpp", original, expected); - QuickFixOperationTest(testDocuments, &factory); -} - -/// Find right implementation file. (QTCREATORBUG-10728) -void QuickfixTest::testInsertDefFromDeclFindImplementationFile() -{ - QList<TestDocumentPtr> testDocuments; - - QByteArray original; - QByteArray expected; - - // Header File - original = - "class Foo {\n" - " void bar();\n" - " void ba@z();\n" - "};\n" - "\n" - "void Foo::bar()\n" - "{}\n"; - expected = original; - testDocuments << CppTestDocument::create("file.h", original, expected); - - // Source File - original = - "#include \"file.h\"\n" - ; - expected = - "#include \"file.h\"\n" - "\n" - "void Foo::baz()\n" - "{\n" - "\n" - "}\n" - ; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - - InsertDefFromDecl factory; - QuickFixOperationTest(testDocuments, &factory); -} - -void QuickfixTest::testInsertDefFromDeclUnicodeIdentifier() -{ - QList<TestDocumentPtr> testDocuments; - - QByteArray original; - QByteArray expected; - - // - // The following "non-latin1" code points are used in the tests: - // - // U+00FC - 2 code units in UTF8, 1 in UTF16 - LATIN SMALL LETTER U WITH DIAERESIS - // U+4E8C - 3 code units in UTF8, 1 in UTF16 - CJK UNIFIED IDEOGRAPH-4E8C - // U+10302 - 4 code units in UTF8, 2 in UTF16 - OLD ITALIC LETTER KE - // - -#define UNICODE_U00FC "\xc3\xbc" -#define UNICODE_U4E8C "\xe4\xba\x8c" -#define UNICODE_U10302 "\xf0\x90\x8c\x82" -#define TEST_UNICODE_IDENTIFIER UNICODE_U00FC UNICODE_U4E8C UNICODE_U10302 - - original = - "class Foo {\n" - " void @" TEST_UNICODE_IDENTIFIER "();\n" - "};\n"; - ; - expected = original; - expected += - "\n" - "void Foo::" TEST_UNICODE_IDENTIFIER "()\n" - "{\n" - "\n" - "}\n"; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - -#undef UNICODE_U00FC -#undef UNICODE_U4E8C -#undef UNICODE_U10302 -#undef TEST_UNICODE_IDENTIFIER - - InsertDefFromDecl factory; - QuickFixOperationTest(testDocuments, &factory); -} - -void QuickfixTest::testInsertDefFromDeclTemplateClass() -{ - QByteArray original = - "template<class T>\n" - "class Foo\n" - "{\n" - " void fun@c1();\n" - " void func2();\n" - "};\n\n" - "template<class T>\n" - "void Foo<T>::func2() {}\n"; - QByteArray expected = - "template<class T>\n" - "class Foo\n" - "{\n" - " void func1();\n" - " void func2();\n" - "};\n\n" - "template<class T>\n" - "void Foo<T>::func1()\n" - "{\n" - "\n" - "}\n\n" - "template<class T>\n" - "void Foo<T>::func2() {}\n"; - - InsertDefFromDecl factory; - QuickFixOperationTest(singleDocument(original, expected), &factory); -} - -void QuickfixTest::testInsertDefFromDeclTemplateClassWithValueParam() -{ - QList<TestDocumentPtr> testDocuments; - QByteArray original = - "template<typename T, int size> struct MyArray {};\n" - "MyArray<int, 1> @foo();"; - QByteArray expected = original; - testDocuments << CppTestDocument::create("file.h", original, expected); - - original = "#include \"file.h\"\n"; - expected = - "#include \"file.h\"\n\n" - "MyArray<int, 1> foo()\n" - "{\n\n" - "}\n"; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - - InsertDefFromDecl factory; - QuickFixOperationTest(testDocuments, &factory); -} - -void QuickfixTest::testInsertDefFromDeclTemplateFunction() -{ - QByteArray original = - "class Foo\n" - "{\n" - " template<class T>\n" - " void fun@c();\n" - "};\n"; - QByteArray expected = - "class Foo\n" - "{\n" - " template<class T>\n" - " void fun@c();\n" - "};\n" - "\n" - "template<class T>\n" - "void Foo::func()\n" - "{\n" - "\n" - "}\n"; - - InsertDefFromDecl factory; - QuickFixOperationTest(singleDocument(original, expected), &factory); -} - -void QuickfixTest::testInsertDefFromDeclTemplateClassAndTemplateFunction() -{ - QByteArray original = - "template<class T>" - "class Foo\n" - "{\n" - " template<class U>\n" - " T fun@c(U u);\n" - "};\n"; - QByteArray expected = - "template<class T>" - "class Foo\n" - "{\n" - " template<class U>\n" - " T fun@c(U u);\n" - "};\n" - "\n" - "template<class T>\n" - "template<class U>\n" - "T Foo<T>::func(U u)\n" - "{\n" - "\n" - "}\n"; - - InsertDefFromDecl factory; - QuickFixOperationTest(singleDocument(original, expected), &factory); -} - -void QuickfixTest::testInsertDefFromDeclTemplateClassAndFunctionInsideNamespace() -{ - QByteArray original = - "namespace N {\n" - "template<class T>" - "class Foo\n" - "{\n" - " template<class U>\n" - " T fun@c(U u);\n" - "};\n" - "}\n"; - QByteArray expected = - "namespace N {\n" - "template<class T>" - "class Foo\n" - "{\n" - " template<class U>\n" - " T fun@c(U u);\n" - "};\n" - "\n" - "template<class T>\n" - "template<class U>\n" - "T Foo<T>::func(U u)\n" - "{\n" - "\n" - "}\n" - "\n" - "}\n"; - - InsertDefFromDecl factory; - QuickFixOperationTest(singleDocument(original, expected), &factory); -} - -void QuickfixTest::testInsertDefFromDeclFunctionWithSignedUnsignedArgument() -{ - QByteArray original; - QByteArray expected; - InsertDefFromDecl factory; - - original =R"--( -class myclass -{ - myc@lass(QVector<signed> g); - myclass(QVector<unsigned> g); -} -)--"; - expected =R"--( -class myclass -{ - myclass(QVector<signed> g); - myclass(QVector<unsigned> g); -} - -myclass::myclass(QVector<signed int> g) -{ - -} -)--"; - - QuickFixOperationTest(singleDocument(original, expected), &factory); - - original =R"--( -class myclass -{ - myclass(QVector<signed> g); - myc@lass(QVector<unsigned> g); -} -)--"; - expected =R"--( -class myclass -{ - myclass(QVector<signed> g); - myclass(QVector<unsigned> g); -} - -myclass::myclass(QVector<unsigned int> g) -{ - -} -)--"; - - QuickFixOperationTest(singleDocument(original, expected), &factory); - - original =R"--( -class myclass -{ - unsigned f@oo(unsigned); -} -)--"; - expected =R"--( -class myclass -{ - unsigned foo(unsigned); -} - -unsigned int myclass::foo(unsigned int) -{ - -} -)--"; - QuickFixOperationTest(singleDocument(original, expected), &factory); - - original =R"--( -class myclass -{ - signed f@oo(signed); -} -)--"; - expected =R"--( -class myclass -{ - signed foo(signed); -} - -signed int myclass::foo(signed int) -{ - -} -)--"; - QuickFixOperationTest(singleDocument(original, expected), &factory); -} - -void QuickfixTest::testInsertDefFromDeclNotTriggeredForFriendFunc() -{ - const QByteArray contents = - "class Foo\n" - "{\n" - " friend void f@unc();\n" - "};\n" - "\n"; - - InsertDefFromDecl factory; - QuickFixOperationTest(singleDocument(contents, ""), &factory); -} - -void QuickfixTest::testInsertDefFromDeclMinimalFunctionParameterType() -{ - QList<TestDocumentPtr> testDocuments; - - QByteArray original; - QByteArray expected; - - // Header File - original = R"( -class C { - typedef int A; - A @foo(A); -}; -)"; - expected = original; - testDocuments << CppTestDocument::create("file.h", original, expected); - - // Source File - original = R"( -#include "file.h" -)"; - expected = R"( -#include "file.h" - -C::A C::foo(A) -{ - -} -)"; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - - InsertDefFromDecl factory; - QuickFixOperationTest(testDocuments, &factory); - - testDocuments.clear(); - // Header File - original = R"( -namespace N { - struct S; - S @foo(const S &s); -}; -)"; - expected = original; - testDocuments << CppTestDocument::create("file.h", original, expected); - - // Source File - original = R"( -#include "file.h" -)"; - expected = R"( -#include "file.h" - -N::S N::foo(const S &s) -{ - -} -)"; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - - QuickFixOperationTest(testDocuments, &factory); -} - -void QuickfixTest::testInsertDefFromDeclAliasTemplateAsReturnType() -{ - QList<TestDocumentPtr> testDocuments; - - QByteArray original; - QByteArray expected; - - // Header File - original = R"( -struct foo { - struct foo2 { - template <typename T> using MyType = T; - MyType<int> @bar(); - }; -}; -)"; - expected = original; - testDocuments << CppTestDocument::create("file.h", original, expected); - - // Source File - original = R"( -#include "file.h" -)"; - expected = R"( -#include "file.h" - -foo::foo2::MyType<int> foo::foo2::bar() -{ - -} -)"; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - - InsertDefFromDecl factory; - QuickFixOperationTest(testDocuments, &factory); -} - -void QuickfixTest::testInsertDefsFromDecls_data() -{ - QTest::addColumn<QByteArrayList>("headers"); - QTest::addColumn<QByteArrayList>("sources"); - QTest::addColumn<int>("mode"); - - QByteArray origHeader = R"( -namespace N { -class @C -{ -public: - friend void ignoredFriend(); - void ignoredImplemented() {}; - void ignoredImplemented2(); // Below - void ignoredImplemented3(); // In cpp file - void funcNotSelected(); - void funcInline(); - void funcBelow(); - void funcCppFile(); - -signals: - void ignoredSignal(); -}; - -inline void C::ignoredImplemented2() {} - -} // namespace N)"; - QByteArray origSource = R"( -#include "file.h" - -namespace N { - -void C::ignoredImplemented3() {} - -} // namespace N)"; - - QByteArray expectedHeader = R"( -namespace N { -class C -{ -public: - friend void ignoredFriend(); - void ignoredImplemented() {}; - void ignoredImplemented2(); // Below - void ignoredImplemented3(); // In cpp file - void funcNotSelected(); - void funcInline() - { - - } - void funcBelow(); - void funcCppFile(); - -signals: - void ignoredSignal(); -}; - -inline void C::ignoredImplemented2() {} - -inline void C::funcBelow() -{ - -} - -} // namespace N)"; - QByteArray expectedSource = R"( -#include "file.h" - -namespace N { - -void C::ignoredImplemented3() {} - -void C::funcCppFile() -{ - -} - -} // namespace N)"; - QTest::addRow("normal case") - << QByteArrayList{origHeader, expectedHeader} - << QByteArrayList{origSource, expectedSource} - << int(InsertDefsFromDecls::Mode::Alternating); - QTest::addRow("aborted dialog") - << QByteArrayList{origHeader, origHeader} - << QByteArrayList{origSource, origSource} - << int(InsertDefsFromDecls::Mode::Off); - - origHeader = R"( - namespace N { - class @C - { - public: - friend void ignoredFriend(); - void ignoredImplemented() {}; - void ignoredImplemented2(); // Below - void ignoredImplemented3(); // In cpp file - - signals: - void ignoredSignal(); - }; - - inline void C::ignoredImplemented2() {} - - } // namespace N)"; - QTest::addRow("no candidates") - << QByteArrayList{origHeader, origHeader} - << QByteArrayList{origSource, origSource} - << int(InsertDefsFromDecls::Mode::Alternating); - - origHeader = R"( - namespace N { - class @C - { - public: - friend void ignoredFriend(); - void ignoredImplemented() {}; - - signals: - void ignoredSignal(); - }; - } // namespace N)"; - QTest::addRow("no member functions") - << QByteArrayList{origHeader, ""} - << QByteArrayList{origSource, ""} - << int(InsertDefsFromDecls::Mode::Alternating); -} - -void QuickfixTest::testInsertDefsFromDecls() -{ - QFETCH(QByteArrayList, headers); - QFETCH(QByteArrayList, sources); - QFETCH(int, mode); - - QList<TestDocumentPtr> testDocuments({ - CppTestDocument::create("file.h", headers.at(0), headers.at(1)), - CppTestDocument::create("file.cpp", sources.at(0), sources.at(1))}); - InsertDefsFromDecls factory; - factory.setMode(static_cast<InsertDefsFromDecls::Mode>(mode)); - QuickFixOperationTest(testDocuments, &factory); -} - -void QuickfixTest::testInsertAndFormatDefsFromDecls() -{ - if (!isClangFormatPresent()) - QSKIP("This test reqires ClangFormat"); - - const QByteArray origHeader = R"( -class @C -{ -public: - void func1 (int const &i); - void func2 (double const d); -}; -)"; - const QByteArray origSource = R"( -#include "file.h" -)"; - - const QByteArray expectedSource = R"( -#include "file.h" - -void C::func1 (int const &i) -{ - -} - -void C::func2 (double const d) -{ - -} -)"; - - const QByteArray clangFormatSettings = R"( -BreakBeforeBraces: Allman -QualifierAlignment: Right -SpaceBeforeParens: Always -)"; - - const QList<TestDocumentPtr> testDocuments({ - CppTestDocument::create("file.h", origHeader, origHeader), - CppTestDocument::create("file.cpp", origSource, expectedSource)}); - InsertDefsFromDecls factory; - factory.setMode(InsertDefsFromDecls::Mode::Impl); - CppCodeStylePreferences * const prefs = CppToolsSettings::cppCodeStyle(); - const CppCodeStyleSettings settings = prefs->codeStyleSettings(); - CppCodeStyleSettings tempSettings = settings; - tempSettings.forceFormatting = true; - prefs->setCodeStyleSettings(tempSettings); - QuickFixOperationTest(testDocuments, &factory, {}, {}, {}, clangFormatSettings); - prefs->setCodeStyleSettings(settings); -} - -QList<TestDocumentPtr> singleHeader(const QByteArray &original, const QByteArray &expected) -{ - return {CppTestDocument::create("file.h", original, expected)}; -} - -void QuickfixTest::testInsertDefOutsideFromDeclTemplateClassAndTemplateFunction() -{ - QByteArray original = - "template<class T>" - "class Foo\n" - "{\n" - " template<class U>\n" - " void fun@c();\n" - "};\n"; - QByteArray expected = - "template<class T>" - "class Foo\n" - "{\n" - " template<class U>\n" - " void fun@c();\n" - "};\n" - "\n" - "template<class T>\n" - "template<class U>\n" - "inline void Foo<T>::func()\n" - "{\n" - "\n" - "}\n"; - - InsertDefFromDecl factory; - factory.m_defPosOutsideClass = true; - QuickFixOperationTest(singleHeader(original, expected), &factory); -} - -void QuickfixTest::testInsertDefOutsideFromDeclTemplateClass() -{ - QByteArray original = - "template<class T>" - "class Foo\n" - "{\n" - " void fun@c();\n" - "};\n"; - QByteArray expected = - "template<class T>" - "class Foo\n" - "{\n" - " void fun@c();\n" - "};\n" - "\n" - "template<class T>\n" - "inline void Foo<T>::func()\n" - "{\n" - "\n" - "}\n"; - - InsertDefFromDecl factory; - factory.m_defPosOutsideClass = true; - QuickFixOperationTest(singleHeader(original, expected), &factory); -} - -void QuickfixTest::testInsertDefOutsideFromDeclTemplateFunction() -{ - QByteArray original = - "class Foo\n" - "{\n" - " template<class U>\n" - " void fun@c();\n" - "};\n"; - QByteArray expected = - "class Foo\n" - "{\n" - " template<class U>\n" - " void fun@c();\n" - "};\n" - "\n" - "template<class U>\n" - "inline void Foo::func()\n" - "{\n" - "\n" - "}\n"; - - InsertDefFromDecl factory; - factory.m_defPosOutsideClass = true; - QuickFixOperationTest(singleHeader(original, expected), &factory); -} - -void QuickfixTest::testInsertDefOutsideFromDeclFunction() -{ - QByteArray original = - "class Foo\n" - "{\n" - " void fun@c();\n" - "};\n"; - QByteArray expected = - "class Foo\n" - "{\n" - " void fun@c();\n" - "};\n" - "\n" - "inline void Foo::func()\n" - "{\n" - "\n" - "}\n"; - - InsertDefFromDecl factory; - factory.m_defPosOutsideClass = true; - QuickFixOperationTest(singleHeader(original, expected), &factory); -} - -// Function for one of InsertDeclDef section cases -void insertToSectionDeclFromDef(const QByteArray §ion, int sectionIndex) -{ - QList<TestDocumentPtr> testDocuments; - - QByteArray original; - QByteArray expected; - QByteArray sectionString = section + ":\n"; - if (sectionIndex == 4) - sectionString.clear(); - - // Header File - original = - "class Foo\n" - "{\n" - "};\n"; - expected = - "class Foo\n" - "{\n" - + sectionString + - " Foo();\n" - "@};\n"; - testDocuments << CppTestDocument::create("file.h", original, expected); - - // Source File - original = - "#include \"file.h\"\n" - "\n" - "Foo::Foo@()\n" - "{\n" - "}\n" - ; - expected = original; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - - InsertDeclFromDef factory; - QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), sectionIndex); -} - -/// Check from source file: Insert in header file. -void QuickfixTest::testInsertDeclFromDef() -{ - insertToSectionDeclFromDef("public", 0); - insertToSectionDeclFromDef("public slots", 1); - insertToSectionDeclFromDef("protected", 2); - insertToSectionDeclFromDef("protected slots", 3); - insertToSectionDeclFromDef("private", 4); - insertToSectionDeclFromDef("private slots", 5); -} - -void QuickfixTest::testInsertDeclFromDefTemplateFuncTypename() -{ - QByteArray original = - "class Foo\n" - "{\n" - "};\n" - "\n" - "template<class T>\n" - "void Foo::fu@nc() {}\n"; - - QByteArray expected = - "class Foo\n" - "{\n" - "public:\n" - " template<class T>\n" - " void func();\n" - "};\n" - "\n" - "template<class T>\n" - "void Foo::fu@nc() {}\n"; - - InsertDeclFromDef factory; - QuickFixOperationTest(singleDocument(original, expected), &factory, {}, 0); -} - -void QuickfixTest::testInsertDeclFromDefTemplateFuncInt() -{ - QByteArray original = - "class Foo\n" - "{\n" - "};\n" - "\n" - "template<int N>\n" - "void Foo::fu@nc() {}\n"; - - QByteArray expected = - "class Foo\n" - "{\n" - "public:\n" - " template<int N>\n" - " void func();\n" - "};\n" - "\n" - "template<int N>\n" - "void Foo::fu@nc() {}\n"; - - InsertDeclFromDef factory; - QuickFixOperationTest(singleDocument(original, expected), &factory, {}, 0); -} - -void QuickfixTest::testInsertDeclFromDefTemplateReturnType() -{ - QByteArray original = - "class Foo\n" - "{\n" - "};\n" - "\n" - "std::vector<int> Foo::fu@nc() const {}\n"; - - QByteArray expected = - "class Foo\n" - "{\n" - "public:\n" - " std::vector<int> func() const;\n" - "};\n" - "\n" - "std::vector<int> Foo::func() const {}\n"; - - InsertDeclFromDef factory; - QuickFixOperationTest(singleDocument(original, expected), &factory, {}, 0); -} - -void QuickfixTest::testInsertDeclFromDefNotTriggeredForTemplateFunc() -{ - QByteArray contents = - "class Foo\n" - "{\n" - " template<class T>\n" - " void func();\n" - "};\n" - "\n" - "template<class T>\n" - "void Foo::fu@nc() {}\n"; - - InsertDeclFromDef factory; - QuickFixOperationTest(singleDocument(contents, ""), &factory); -} - -void QuickfixTest::testAddIncludeForUndefinedIdentifier_data() -{ - QTest::addColumn<QString>("headerPath"); - QTest::addColumn<QuickFixTestDocuments>("testDocuments"); - QTest::addColumn<int>("refactoringOperationIndex"); - QTest::addColumn<QString>("includeForTestFactory"); - - const int firstRefactoringOperation = 0; - const int secondRefactoringOperation = 1; - - QList<TestDocumentPtr> testDocuments; - QByteArray original; - QByteArray expected; - - // ------------------------------------------------------------------------------------------- - - // Header File - original = "class Foo {};\n"; - expected = original; - testDocuments << CppTestDocument::create("afile.h", original, expected); - - // Source File - original = - "#include \"header.h\"\n" - "\n" - "void f()\n" - "{\n" - " Fo@o foo;\n" - "}\n" - ; - expected = - "#include \"afile.h\"\n" - "#include \"header.h\"\n" - "\n" - "void f()\n" - "{\n" - " Foo foo;\n" - "}\n" - ; - testDocuments << CppTestDocument::create("afile.cpp", original, expected); - QTest::newRow("onSimpleName") - << TestIncludePaths::globalIncludePath() - << testDocuments << firstRefactoringOperation << ""; - testDocuments.clear(); - - // ------------------------------------------------------------------------------------------- - - // Header File - original = "namespace N { class Foo {}; }\n"; - expected = original; - testDocuments << CppTestDocument::create("afile.h", original, expected); - - // Source File - original = - "#include \"header.h\"\n" - "\n" - "void f()\n" - "{\n" - " N::Fo@o foo;\n" - "}\n" - ; - expected = - "#include \"afile.h\"\n" - "#include \"header.h\"\n" - "\n" - "void f()\n" - "{\n" - " N::Foo foo;\n" - "}\n" - ; - testDocuments << CppTestDocument::create("afile.cpp", original, expected); - QTest::newRow("onNameOfQualifiedName") - << TestIncludePaths::globalIncludePath() - << testDocuments << firstRefactoringOperation << ""; - testDocuments.clear(); - - // ------------------------------------------------------------------------------------------- - - // Header File - original = "namespace N { class Foo {}; }\n"; - expected = original; - testDocuments << CppTestDocument::create("afile.h", original, expected); - - // Source File - original = - "#include \"header.h\"\n" - "\n" - "void f()\n" - "{\n" - " @N::Foo foo;\n" - "}\n" - ; - expected = - "#include \"afile.h\"\n" - "#include \"header.h\"\n" - "\n" - "void f()\n" - "{\n" - " N::Foo foo;\n" - "}\n" - ; - testDocuments << CppTestDocument::create("afile.cpp", original, expected); - QTest::newRow("onBaseOfQualifiedName") - << TestIncludePaths::globalIncludePath() - << testDocuments << firstRefactoringOperation << ""; - testDocuments.clear(); - - // ------------------------------------------------------------------------------------------- - - // Header File - original = "class Foo { static void bar() {} };\n"; - expected = original; - testDocuments << CppTestDocument::create("afile.h", original, expected); - - // Source File - original = - "#include \"header.h\"\n" - "\n" - "void f()\n" - "{\n" - " @Foo::bar();\n" - "}\n" - ; - expected = - "#include \"afile.h\"\n" - "#include \"header.h\"\n" - "\n" - "void f()\n" - "{\n" - " Foo::bar();\n" - "}\n" - ; - testDocuments << CppTestDocument::create("afile.cpp", original, expected); - QTest::newRow("onBaseOfQualifiedClassName") - << TestIncludePaths::globalIncludePath() - << testDocuments << firstRefactoringOperation << ""; - testDocuments.clear(); - - // ------------------------------------------------------------------------------------------- - - // Header File - original = "template <typename T> class Foo { static void bar() {} };\n"; - expected = original; - testDocuments << CppTestDocument::create("afile.h", original, expected); - - // Source File - original = - "#include \"header.h\"\n" - "\n" - "void f()\n" - "{\n" - " @Foo<int>::bar();\n" - "}\n" - ; - expected = - "#include \"afile.h\"\n" - "#include \"header.h\"\n" - "\n" - "void f()\n" - "{\n" - " Foo<int>::bar();\n" - "}\n" - ; - testDocuments << CppTestDocument::create("afile.cpp", original, expected); - QTest::newRow("onBaseOfQualifiedTemplateClassName") - << TestIncludePaths::globalIncludePath() - << testDocuments << firstRefactoringOperation << ""; - testDocuments.clear(); - - // ------------------------------------------------------------------------------------------- - - // Header File - original = "namespace N { template <typename T> class Foo {}; }\n"; - expected = original; - testDocuments << CppTestDocument::create("afile.h", original, expected); - - // Source File - original = - "#include \"header.h\"\n" - "\n" - "void f()\n" - "{\n" - " @N::Foo<Bar> foo;\n" - "}\n" - ; - expected = - "#include \"afile.h\"\n" - "#include \"header.h\"\n" - "\n" - "void f()\n" - "{\n" - " N::Foo<Bar> foo;\n" - "}\n" - ; - testDocuments << CppTestDocument::create("afile.cpp", original, expected); - QTest::newRow("onTemplateName") - << TestIncludePaths::globalIncludePath() - << testDocuments << firstRefactoringOperation << ""; - testDocuments.clear(); - - // ------------------------------------------------------------------------------------------- - - // Header File - original = "namespace N { template <typename T> class Foo {}; }\n"; - expected = original; - testDocuments << CppTestDocument::create("afile.h", original, expected); - - // Source File - original = - "#include \"header.h\"\n" - "\n" - "void f()\n" - "{\n" - " N::Bar<@Foo> foo;\n" - "}\n" - ; - expected = - "#include \"afile.h\"\n" - "#include \"header.h\"\n" - "\n" - "void f()\n" - "{\n" - " N::Bar<Foo> foo;\n" - "}\n" - ; - testDocuments << CppTestDocument::create("afile.cpp", original, expected); - QTest::newRow("onTemplateNameInsideArguments") - << TestIncludePaths::globalIncludePath() - << testDocuments << firstRefactoringOperation << ""; - testDocuments.clear(); - - // ------------------------------------------------------------------------------------------- - - // Header File - original = "class Foo {};\n"; - expected = original; - testDocuments << CppTestDocument::create("afile.h", original, expected); - - // Source File - original = - "#include \"header.h\"\n" - "\n" - "class Foo;\n" - "\n" - "void f()\n" - "{\n" - " @Foo foo;\n" - "}\n" - ; - expected = - "#include \"afile.h\"\n" - "#include \"header.h\"\n" - "\n" - "class Foo;\n" - "\n" - "void f()\n" - "{\n" - " Foo foo;\n" - "}\n" - ; - testDocuments << CppTestDocument::create("afile.cpp", original, expected); - QTest::newRow("withForwardDeclaration") - << TestIncludePaths::globalIncludePath() - << testDocuments << firstRefactoringOperation << ""; - testDocuments.clear(); - - // ------------------------------------------------------------------------------------------- - - // Header File - original = "template<class T> class Foo {};\n"; - expected = original; - testDocuments << CppTestDocument::create("afile.h", original, expected); - - // Source File - original = - "#include \"header.h\"\n" - "\n" - "template<class T> class Foo;\n" - "\n" - "void f()\n" - "{\n" - " @Foo foo;\n" - "}\n" - ; - expected = - "#include \"afile.h\"\n" - "#include \"header.h\"\n" - "\n" - "template<class T> class Foo;\n" - "\n" - "void f()\n" - "{\n" - " Foo foo;\n" - "}\n" - ; - testDocuments << CppTestDocument::create("afile.cpp", original, expected); - QTest::newRow("withForwardDeclaration2") - << TestIncludePaths::globalIncludePath() - << testDocuments << firstRefactoringOperation << ""; - testDocuments.clear(); - - // ------------------------------------------------------------------------------------------- - - // Header File - original = "template<class T> class QMyClass {};\n"; - expected = original; - testDocuments << CppTestDocument::create("qmyclass.h", original, expected); - - // Forward Header File - original = "#include \"qmyclass.h\"\n"; - expected = original; - testDocuments << CppTestDocument::create("QMyClass", original, expected); - - // Source File - original = - "#include \"header.h\"\n" - "\n" - "void f()\n" - "{\n" - " @QMyClass c;\n" - "}\n" - ; - expected = - "#include \"QMyClass\"\n" - "#include \"header.h\"\n" - "\n" - "void f()\n" - "{\n" - " QMyClass c;\n" - "}\n" - ; - testDocuments << CppTestDocument::create("afile.cpp", original, expected); - QTest::newRow("withForwardHeader") - << TestIncludePaths::globalIncludePath() - << testDocuments << secondRefactoringOperation << ""; - testDocuments.clear(); - - // ------------------------------------------------------------------------------------------- - - original = - "void @f();\n" - "#include \"file.moc\";\n" - ; - expected = - "#include \"file.h\"\n" - "\n" - "void f();\n" - "#include \"file.moc\";\n" - ; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - QTest::newRow("insertingIgnoreMoc") - << TestIncludePaths::globalIncludePath() - << testDocuments << firstRefactoringOperation << "\"file.h\""; - testDocuments.clear(); - - // ------------------------------------------------------------------------------------------- - - original = - "#include \"y.h\"\n" - "#include \"z.h\"\n" - "\n@" - ; - expected = - "#include \"file.h\"\n" - "#include \"y.h\"\n" - "#include \"z.h\"\n" - "\n" - ; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - QTest::newRow("insertingSortingTop") - << TestIncludePaths::globalIncludePath() - << testDocuments << firstRefactoringOperation << "\"file.h\""; - testDocuments.clear(); - - // ------------------------------------------------------------------------------------------- - - original = - "#include \"a.h\"\n" - "#include \"z.h\"\n" - "\n@" - ; - expected = - "#include \"a.h\"\n" - "#include \"file.h\"\n" - "#include \"z.h\"\n" - "\n" - ; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - QTest::newRow("insertingSortingMiddle") - << TestIncludePaths::globalIncludePath() - << testDocuments << firstRefactoringOperation << "\"file.h\""; - testDocuments.clear(); - - // ------------------------------------------------------------------------------------------- - - original = - "#include \"a.h\"\n" - "#include \"b.h\"\n" - "\n@" - ; - expected = - "#include \"a.h\"\n" - "#include \"b.h\"\n" - "#include \"file.h\"\n" - "\n" - ; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - QTest::newRow("insertingSortingBottom") - << TestIncludePaths::globalIncludePath() - << testDocuments << firstRefactoringOperation << "\"file.h\""; - testDocuments.clear(); - - // ------------------------------------------------------------------------------------------- - - original = - "#include \"b.h\"\n" - "#include \"a.h\"\n" - "\n@" - ; - expected = - "#include \"b.h\"\n" - "#include \"a.h\"\n" - "#include \"file.h\"\n" - "\n" - ; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - QTest::newRow("inserting_appendToUnsorted") - << TestIncludePaths::globalIncludePath() - << testDocuments << firstRefactoringOperation << "\"file.h\""; - testDocuments.clear(); - - // ------------------------------------------------------------------------------------------- - - original = - "#include <a.h>\n" - "#include <b.h>\n" - "\n@" - ; - expected = - "#include \"file.h\"\n" - "\n" - "#include <a.h>\n" - "#include <b.h>\n" - "\n" - ; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - QTest::newRow("inserting_firstLocalIncludeAtFront") - << TestIncludePaths::globalIncludePath() - << testDocuments << firstRefactoringOperation << "\"file.h\""; - testDocuments.clear(); - - // ------------------------------------------------------------------------------------------- - - original = - "#include \"a.h\"\n" - "#include \"b.h\"\n" - "\n" - "void @f();\n" - ; - expected = - "#include \"a.h\"\n" - "#include \"b.h\"\n" - "\n" - "#include <file.h>\n" - "\n" - "void f();\n" - ; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - QTest::newRow("firstGlobalIncludeAtBack") - << TestIncludePaths::globalIncludePath() - << testDocuments << firstRefactoringOperation << "<file.h>"; - testDocuments.clear(); - - // ------------------------------------------------------------------------------------------- - - original = - "#include \"prefixa.h\"\n" - "#include \"prefixb.h\"\n" - "\n" - "#include \"foo.h\"\n" - "\n@" - ; - expected = - "#include \"prefixa.h\"\n" - "#include \"prefixb.h\"\n" - "#include \"prefixc.h\"\n" - "\n" - "#include \"foo.h\"\n" - "\n" - ; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - QTest::newRow("inserting_preferGroupWithLongerMatchingPrefix") - << TestIncludePaths::globalIncludePath() - << testDocuments << firstRefactoringOperation << "\"prefixc.h\""; - testDocuments.clear(); - - // ------------------------------------------------------------------------------------------- - - original = - "#include \"lib/file.h\"\n" - "#include \"lib/fileother.h\"\n" - "\n@" - ; - expected = - "#include \"lib/file.h\"\n" - "#include \"lib/fileother.h\"\n" - "\n" - "#include \"file.h\"\n" - "\n" - ; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - QTest::newRow("inserting_newGroupIfOnlyDifferentIncludeDirs") - << TestIncludePaths::globalIncludePath() - << testDocuments << firstRefactoringOperation << "\"file.h\""; - testDocuments.clear(); - - // ------------------------------------------------------------------------------------------- - - original = - "#include <lib/file.h>\n" - "#include <otherlib/file.h>\n" - "#include <utils/file.h>\n" - "\n@" - ; - expected = - "#include <firstlib/file.h>\n" - "#include <lib/file.h>\n" - "#include <otherlib/file.h>\n" - "#include <utils/file.h>\n" - "\n" - ; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - QTest::newRow("inserting_mixedDirsSorted") - << TestIncludePaths::globalIncludePath() - << testDocuments << firstRefactoringOperation << "<firstlib/file.h>"; - testDocuments.clear(); - - // ------------------------------------------------------------------------------------------- - - original = - "#include <otherlib/file.h>\n" - "#include <lib/file.h>\n" - "#include <utils/file.h>\n" - "\n@" - ; - expected = - "#include <otherlib/file.h>\n" - "#include <lib/file.h>\n" - "#include <utils/file.h>\n" - "#include <lastlib/file.h>\n" - "\n" - ; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - QTest::newRow("inserting_mixedDirsUnsorted") - << TestIncludePaths::globalIncludePath() - << testDocuments << firstRefactoringOperation << "<lastlib/file.h>"; - testDocuments.clear(); - - // ------------------------------------------------------------------------------------------- - - original = - "#include \"a.h\"\n" - "#include <global.h>\n" - "\n@" - ; - expected = - "#include \"a.h\"\n" - "#include \"z.h\"\n" - "#include <global.h>\n" - "\n" - ; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - QTest::newRow("inserting_mixedIncludeTypes1") - << TestIncludePaths::globalIncludePath() - << testDocuments << firstRefactoringOperation << "\"z.h\""; - testDocuments.clear(); - - // ------------------------------------------------------------------------------------------- - - original = - "#include \"z.h\"\n" - "#include <global.h>\n" - "\n@" - ; - expected = - "#include \"a.h\"\n" - "#include \"z.h\"\n" - "#include <global.h>\n" - "\n" - ; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - QTest::newRow("inserting_mixedIncludeTypes2") - << TestIncludePaths::globalIncludePath() - << testDocuments << firstRefactoringOperation << "\"a.h\""; - testDocuments.clear(); - - // ------------------------------------------------------------------------------------------- - - original = - "#include \"z.h\"\n" - "#include <global.h>\n" - "\n@" - ; - expected = - "#include \"z.h\"\n" - "#include \"lib/file.h\"\n" - "#include <global.h>\n" - "\n" - ; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - QTest::newRow("inserting_mixedIncludeTypes3") - << TestIncludePaths::globalIncludePath() - << testDocuments << firstRefactoringOperation << "\"lib/file.h\""; - testDocuments.clear(); - - // ------------------------------------------------------------------------------------------- - - original = - "#include \"z.h\"\n" - "#include <global.h>\n" - "\n@" - ; - expected = - "#include \"z.h\"\n" - "#include <global.h>\n" - "#include <lib/file.h>\n" - "\n" - ; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - QTest::newRow("inserting_mixedIncludeTypes4") - << TestIncludePaths::globalIncludePath() - << testDocuments << firstRefactoringOperation << "<lib/file.h>"; - testDocuments.clear(); - - // ------------------------------------------------------------------------------------------- - - original = - "void @f();\n" - ; - expected = - "#include \"file.h\"\n" - "\n" - "void f();\n" - ; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - QTest::newRow("inserting_noinclude") - << TestIncludePaths::globalIncludePath() - << testDocuments << firstRefactoringOperation << "\"file.h\""; - testDocuments.clear(); - - // ------------------------------------------------------------------------------------------- - - original = - "#ifndef FOO_H\n" - "#define FOO_H\n" - "void @f();\n" - "#endif\n" - ; - expected = - "#ifndef FOO_H\n" - "#define FOO_H\n" - "\n" - "#include \"file.h\"\n" - "\n" - "void f();\n" - "#endif\n" - ; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - QTest::newRow("inserting_onlyIncludeGuard") - << TestIncludePaths::globalIncludePath() - << testDocuments << firstRefactoringOperation << "\"file.h\""; - testDocuments.clear(); - - // ------------------------------------------------------------------------------------------- - - original = - "\n" - "// comment\n" - "\n" - "void @f();\n" - ; - expected = - "\n" - "// comment\n" - "\n" - "#include \"file.h\"\n" - "\n" - "void @f();\n" - ; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - QTest::newRow("inserting_veryFirstIncludeCppStyleCommentOnTop") - << TestIncludePaths::globalIncludePath() - << testDocuments << firstRefactoringOperation << "\"file.h\""; - testDocuments.clear(); - - // ------------------------------------------------------------------------------------------- - - original = - "\n" - "/*\n" - " comment\n" - " */\n" - "\n" - "void @f();\n" - ; - expected = - "\n" - "/*\n" - " comment\n" - " */\n" - "\n" - "#include \"file.h\"\n" - "\n" - "void @f();\n" - ; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - QTest::newRow("inserting_veryFirstIncludeCStyleCommentOnTop") - << TestIncludePaths::globalIncludePath() - << testDocuments << firstRefactoringOperation << "\"file.h\""; - testDocuments.clear(); - - // ------------------------------------------------------------------------------------------- - - original = - "@QDir dir;\n" - ; - expected = - "#include <QDir>\n" - "\n" - "QDir dir;\n" - ; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - QTest::newRow("inserting_checkQSomethingInQtIncludePaths") - << TestIncludePaths::globalQtCoreIncludePath() - << testDocuments << firstRefactoringOperation << ""; - testDocuments.clear(); - - original = - "std::s@tring s;\n" - ; - expected = - "#include <string>\n" - "\n" - "std::string s;\n" - ; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - QTest::newRow("inserting_std::string") - << TestIncludePaths::globalIncludePath() - << testDocuments << firstRefactoringOperation << ""; - testDocuments.clear(); - - original = "class A{};"; - testDocuments << CppTestDocument::create("a.h", original, original); - original = "class B{};"; - testDocuments << CppTestDocument::create("b.h", original, original); - original = - "#include \"b.h\"\n" - "@A a;\n" - "B b;"; - expected = - "#include \"b.h\"\n\n" - "#include \"a.h\"\n" - "A a;\n" - "B b;"; - testDocuments << CppTestDocument::create("b.cpp", original, expected); - QTest::newRow("preserve first header") - << TestIncludePaths::globalIncludePath() - << testDocuments << firstRefactoringOperation << ""; - testDocuments.clear(); - - original = "class C{};"; - testDocuments << CppTestDocument::create("c.h", original, original); - original = "class B{};"; - testDocuments << CppTestDocument::create("b.h", original, original); - original = - "#include \"c.h\"\n" - "C c;\n" - "@B b;"; - expected = - "#include \"b.h\"\n" - "#include \"c.h\"\n" - "C c;\n" - "B b;"; - testDocuments << CppTestDocument::create("x.cpp", original, expected); - QTest::newRow("do not preserve first header") - << TestIncludePaths::globalIncludePath() - << testDocuments << firstRefactoringOperation << ""; - testDocuments.clear(); -} - -void QuickfixTest::testAddIncludeForUndefinedIdentifier() -{ - QFETCH(QString, headerPath); - QFETCH(QuickFixTestDocuments, testDocuments); - QFETCH(int, refactoringOperationIndex); - QFETCH(QString, includeForTestFactory); - - TemporaryDir temporaryDir; - QVERIFY(temporaryDir.isValid()); - for (const TestDocumentPtr &testDocument : std::as_const(testDocuments)) - testDocument->setBaseDirectory(temporaryDir.path()); - - QScopedPointer<CppQuickFixFactory> factory; - if (includeForTestFactory.isEmpty()) - factory.reset(new AddIncludeForUndefinedIdentifier); - else - factory.reset(new AddIncludeForUndefinedIdentifierTestFactory(includeForTestFactory)); - - QuickFixOperationTest::run(testDocuments, factory.data(), headerPath, - refactoringOperationIndex); -} - -void QuickfixTest::testAddIncludeForUndefinedIdentifierNoDoubleQtHeaderInclude() -{ - TemporaryDir temporaryDir; - QVERIFY(temporaryDir.isValid()); - - QList<TestDocumentPtr> testDocuments; - QByteArray original; - QByteArray expected; - - const QByteArray base = temporaryDir.path().toUtf8(); - - // This file makes the QDir definition available so that locator finds it. - original = expected = "#include <QDir>\n" - "void avoidBeingRecognizedAsForwardingHeader();"; - testDocuments << CppTestDocument::create(base + "/fileUsingQDir.cpp", original, expected); - - original = expected = "@QDir dir;\n"; - testDocuments << CppTestDocument::create(base + "/fileWantsToUseQDir.cpp", original, expected); - - AddIncludeForUndefinedIdentifier factory; - const QStringList expectedOperations = QStringList("Add #include <QDir>"); - QuickFixOfferedOperationsTest(testDocuments, &factory, ProjectExplorer::toUserHeaderPaths( - QStringList{TestIncludePaths::globalQtCoreIncludePath()}), expectedOperations); -} - -void QuickfixTest::testAddForwardDeclForUndefinedIdentifier_data() -{ - QTest::addColumn<QuickFixTestDocuments>("testDocuments"); - QTest::addColumn<QString>("symbol"); - QTest::addColumn<int>("symbolPos"); - - QByteArray original; - QByteArray expected; - - original = - "#pragma once\n" - "\n" - "void f(const Blu@bb &b)\n" - "{\n" - "}\n" - ; - expected = - "#pragma once\n" - "\n" - "\n" - "class Blubb;\n" - "void f(const Blubb &b)\n" - "{\n" - "}\n" - ; - QTest::newRow("unqualified symbol") - << QuickFixTestDocuments{CppTestDocument::create("theheader.h", original, expected)} - << "Blubb" << original.indexOf('@'); - - original = - "#pragma once\n" - "\n" - "namespace NS {\n" - "class C;\n" - "}\n" - "void f(const NS::Blu@bb &b)\n" - "{\n" - "}\n" - ; - expected = - "#pragma once\n" - "\n" - "namespace NS {\n" - "\n" - "class Blubb;\n" - "class C;\n" - "}\n" - "void f(const NS::Blubb &b)\n" - "{\n" - "}\n" - ; - QTest::newRow("qualified symbol, full namespace present") - << QuickFixTestDocuments{CppTestDocument::create("theheader.h", original, expected)} - << "NS::Blubb" << original.indexOf('@'); - - original = - "#pragma once\n" - "\n" - "namespace NS {\n" - "class C;\n" - "}\n" - "void f(const NS::NS2::Blu@bb &b)\n" - "{\n" - "}\n" - ; - expected = - "#pragma once\n" - "\n" - "namespace NS {\n" - "\n" - "namespace NS2 { class Blubb; }\n" - "class C;\n" - "}\n" - "void f(const NS::NS2::Blubb &b)\n" - "{\n" - "}\n" - ; - QTest::newRow("qualified symbol, partial namespace present") - << QuickFixTestDocuments{CppTestDocument::create("theheader.h", original, expected)} - << "NS::NS2::Blubb" << original.indexOf('@'); - - original = - "#pragma once\n" - "\n" - "namespace NS {\n" - "class C;\n" - "}\n" - "void f(const NS2::Blu@bb &b)\n" - "{\n" - "}\n" - ; - expected = - "#pragma once\n" - "\n" - "\n" - "namespace NS2 { class Blubb; }\n" - "namespace NS {\n" - "class C;\n" - "}\n" - "void f(const NS2::Blubb &b)\n" - "{\n" - "}\n" - ; - QTest::newRow("qualified symbol, other namespace present") - << QuickFixTestDocuments{CppTestDocument::create("theheader.h", original, expected)} - << "NS2::Blubb" << original.indexOf('@'); - - original = - "#pragma once\n" - "\n" - "void f(const NS2::Blu@bb &b)\n" - "{\n" - "}\n" - ; - expected = - "#pragma once\n" - "\n" - "\n" - "namespace NS2 { class Blubb; }\n" - "void f(const NS2::Blubb &b)\n" - "{\n" - "}\n" - ; - QTest::newRow("qualified symbol, no namespace present") - << QuickFixTestDocuments{CppTestDocument::create("theheader.h", original, expected)} - << "NS2::Blubb" << original.indexOf('@'); - - original = - "#pragma once\n" - "\n" - "void f(const NS2::Blu@bb &b)\n" - "{\n" - "}\n" - "namespace NS2 {}\n" - ; - expected = - "#pragma once\n" - "\n" - "\n" - "namespace NS2 { class Blubb; }\n" - "void f(const NS2::Blubb &b)\n" - "{\n" - "}\n" - "namespace NS2 {}\n" - ; - QTest::newRow("qualified symbol, existing namespace after symbol") - << QuickFixTestDocuments{CppTestDocument::create("theheader.h", original, expected)} - << "NS2::Blubb" << original.indexOf('@'); -} - -void QuickfixTest::testAddForwardDeclForUndefinedIdentifier() -{ - QFETCH(QuickFixTestDocuments, testDocuments); - QFETCH(QString, symbol); - QFETCH(int, symbolPos); - - TemporaryDir temporaryDir; - QVERIFY(temporaryDir.isValid()); - testDocuments.first()->setBaseDirectory(temporaryDir.path()); - - QScopedPointer<CppQuickFixFactory> factory( - new AddForwardDeclForUndefinedIdentifierTestFactory(symbol, symbolPos)); - QuickFixOperationTest::run({testDocuments}, factory.data(), ".", 0); -} - -/// Check: Move definition from header to cpp. -void QuickfixTest::testMoveFuncDefOutsideMemberFuncToCpp() -{ - QList<TestDocumentPtr> testDocuments; - QByteArray original; - QByteArray expected; - - // Header File - original = - "class Foo {\n" - " inline int numbe@r() const\n" - " {\n" - " return 5;\n" - " }\n" - "\n" - " void bar();\n" - "};\n"; - expected = - "class Foo {\n" - " int number() const;\n" - "\n" - " void bar();\n" - "};\n"; - testDocuments << CppTestDocument::create("file.h", original, expected); - - // Source File - original = - "#include \"file.h\"\n"; - expected = - "#include \"file.h\"\n" - "\n" - "int Foo::number() const\n" - "{\n" - " return 5;\n" - "}\n" - ; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - - MoveFuncDefOutside factory; - QuickFixOperationTest(testDocuments, &factory); -} - -void QuickfixTest::testMoveFuncDefOutsideMemberFuncToCppStatic() -{ - QList<TestDocumentPtr> testDocuments; - QByteArray original; - QByteArray expected; - - // Header File - original = - "class Foo {\n" - " static inline int numbe@r() const\n" - " {\n" - " return 5;\n" - " }\n" - "\n" - " void bar();\n" - "};\n"; - expected = - "class Foo {\n" - " static int number() const;\n" - "\n" - " void bar();\n" - "};\n"; - testDocuments << CppTestDocument::create("file.h", original, expected); - - // Source File - original = - "#include \"file.h\"\n"; - expected = - "#include \"file.h\"\n" - "\n" - "int Foo::number() const\n" - "{\n" - " return 5;\n" - "}\n"; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - - MoveFuncDefOutside factory; - QuickFixOperationTest(testDocuments, &factory); -} - -void QuickfixTest::testMoveFuncDefOutsideMemberFuncToCppWithInlinePartOfName() -{ - QList<TestDocumentPtr> testDocuments; - QByteArray original; - QByteArray expected; - - // Header File - original = - "class Foo {\n" - " static inline int numbe@r_inline () const\n" - " {\n" - " return 5;\n" - " }\n" - "\n" - " void bar();\n" - "};\n"; - expected = - "class Foo {\n" - " static int number_inline () const;\n" - "\n" - " void bar();\n" - "};\n"; - testDocuments << CppTestDocument::create("file.h", original, expected); - - // Source File - original = - "#include \"file.h\"\n"; - expected = - "#include \"file.h\"\n" - "\n" - "int Foo::number_inline() const\n" - "{\n" - " return 5;\n" - "}\n"; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - - MoveFuncDefOutside factory; - QuickFixOperationTest(testDocuments, &factory); -} - -void QuickfixTest::testMoveFuncDefOutsideMixedQualifiers() -{ - QList<TestDocumentPtr> testDocuments; - QByteArray original; - QByteArray expected; - - // Header File - original = R"( -struct Base { - virtual auto func() const && noexcept -> void = 0; -}; -struct Derived : public Base { - auto @func() const && noexcept -> void override {} -};)"; - expected = R"( -struct Base { - virtual auto func() const && noexcept -> void = 0; -}; -struct Derived : public Base { - auto func() const && noexcept -> void override; -};)"; - testDocuments << CppTestDocument::create("file.h", original, expected); - - // Source File - original = "#include \"file.h\"\n"; - expected = R"DELIM(#include "file.h" - -auto Derived::func() const && noexcept -> void {} -)DELIM"; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - - MoveFuncDefOutside factory; - QuickFixOperationTest(testDocuments, &factory); -} - -void QuickfixTest::testMoveFuncDefOutsideMemberFuncToCppInsideNS() -{ - QList<TestDocumentPtr> testDocuments; - QByteArray original; - QByteArray expected; - - // Header File - original = - "namespace SomeNamespace {\n" - "class Foo {\n" - " int ba@r()\n" - " {\n" - " return 5;\n" - " }\n" - "};\n" - "}\n"; - expected = - "namespace SomeNamespace {\n" - "class Foo {\n" - " int ba@r();\n" - "};\n" - "}\n"; - testDocuments << CppTestDocument::create("file.h", original, expected); - - // Source File - original = - "#include \"file.h\"\n" - "namespace SomeNamespace {\n" - "\n" - "}\n"; - expected = - "#include \"file.h\"\n" - "namespace SomeNamespace {\n" - "\n" - "int Foo::bar()\n" - "{\n" - " return 5;\n" - "}\n" - "\n" - "}\n"; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - - MoveFuncDefOutside factory; - QuickFixOperationTest(testDocuments, &factory); -} - -/// Check: Move definition outside class -void QuickfixTest::testMoveFuncDefOutsideMemberFuncOutside1() -{ - QByteArray original = - "class Foo {\n" - " void f1();\n" - " inline int f2@() const\n" - " {\n" - " return 1;\n" - " }\n" - " void f3();\n" - " void f4();\n" - "};\n" - "\n" - "void Foo::f4() {}\n"; - QByteArray expected = - "class Foo {\n" - " void f1();\n" - " int f2@() const;\n" - " void f3();\n" - " void f4();\n" - "};\n" - "\n" - "int Foo::f2() const\n" - "{\n" - " return 1;\n" - "}\n" - "\n" - "void Foo::f4() {}\n"; - - MoveFuncDefOutside factory; - QuickFixOperationTest(singleDocument(original, expected), &factory); -} - -/// Check: Move definition outside class -void QuickfixTest::testMoveFuncDefOutsideMemberFuncOutside2() -{ - QList<TestDocumentPtr> testDocuments; - QByteArray original; - QByteArray expected; - - // Header File - original = - "class Foo {\n" - " void f1();\n" - " int f2@()\n" - " {\n" - " return 1;\n" - " }\n" - " void f3();\n" - "};\n"; - expected = - "class Foo {\n" - " void f1();\n" - " int f2();\n" - " void f3();\n" - "};\n" - "\n" - "inline int Foo::f2()\n" - "{\n" - " return 1;\n" - "}\n"; - testDocuments << CppTestDocument::create("file.h", original, expected); - - // Source File - original = - "#include \"file.h\"\n" - "void Foo::f1() {}\n" - "void Foo::f3() {}\n"; - expected = original; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - - MoveFuncDefOutside factory; - QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), 1); -} - -/// Check: Move definition from header to cpp (with namespace). -void QuickfixTest::testMoveFuncDefOutsideMemberFuncToCppNS() -{ - QList<TestDocumentPtr> testDocuments; - QByteArray original; - QByteArray expected; - - // Header File - original = - "namespace MyNs {\n" - "class Foo {\n" - " inline int numbe@r() const\n" - " {\n" - " return 5;\n" - " }\n" - "};\n" - "}\n"; - expected = - "namespace MyNs {\n" - "class Foo {\n" - " int number() const;\n" - "};\n" - "}\n"; - testDocuments << CppTestDocument::create("file.h", original, expected); - - // Source File - original = - "#include \"file.h\"\n"; - expected = - "#include \"file.h\"\n" - "\n" - "int MyNs::Foo::number() const\n" - "{\n" - " return 5;\n" - "}\n"; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - - MoveFuncDefOutside factory; - QuickFixOperationTest(testDocuments, &factory); -} - -/// Check: Move definition from header to cpp (with namespace + using). -void QuickfixTest::testMoveFuncDefOutsideMemberFuncToCppNSUsing() -{ - QList<TestDocumentPtr> testDocuments; - QByteArray original; - QByteArray expected; - - // Header File - original = - "namespace MyNs {\n" - "class Foo {\n" - " inline int numbe@r() const\n" - " {\n" - " return 5;\n" - " }\n" - "};\n" - "}\n"; - expected = - "namespace MyNs {\n" - "class Foo {\n" - " int number() const;\n" - "};\n" - "}\n"; - testDocuments << CppTestDocument::create("file.h", original, expected); - - // Source File - original = - "#include \"file.h\"\n" - "using namespace MyNs;\n"; - expected = - "#include \"file.h\"\n" - "using namespace MyNs;\n" - "\n" - "int Foo::number() const\n" - "{\n" - " return 5;\n" - "}\n"; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - - MoveFuncDefOutside factory; - QuickFixOperationTest(testDocuments, &factory); -} - -/// Check: Move definition outside class with Namespace -void QuickfixTest::testMoveFuncDefOutsideMemberFuncOutsideWithNs() -{ - QByteArray original = - "namespace MyNs {\n" - "class Foo {\n" - " inline int numbe@r() const\n" - " {\n" - " return 5;\n" - " }\n" - "};}\n"; - QByteArray expected = - "namespace MyNs {\n" - "class Foo {\n" - " int number() const;\n" - "};\n" - "\n" - "int Foo::number() const\n" - "{\n" - " return 5;\n" - "}\n" - "\n}\n"; - - MoveFuncDefOutside factory; - QuickFixOperationTest(singleDocument(original, expected), &factory); -} - -/// Check: Move free function from header to cpp. -void QuickfixTest::testMoveFuncDefOutsideFreeFuncToCpp() -{ - QList<TestDocumentPtr> testDocuments; - QByteArray original; - QByteArray expected; - - // Header File - original = - "int numbe@r() const\n" - "{\n" - " return 5;\n" - "}\n"; - expected = - "int number() const;\n" - ; - testDocuments << CppTestDocument::create("file.h", original, expected); - - // Source File - original = - "#include \"file.h\"\n"; - expected = - "#include \"file.h\"\n" - "\n" - "int number() const\n" - "{\n" - " return 5;\n" - "}\n"; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - - MoveFuncDefOutside factory; - QuickFixOperationTest(testDocuments, &factory); -} - -/// Check: Move free function from header to cpp (with namespace). -void QuickfixTest::testMoveFuncDefOutsideFreeFuncToCppNS() -{ - QList<TestDocumentPtr> testDocuments; - QByteArray original; - QByteArray expected; - - // Header File - original = - "namespace MyNamespace {\n" - "int numbe@r() const\n" - "{\n" - " return 5;\n" - "}\n" - "}\n"; - expected = - "namespace MyNamespace {\n" - "int number() const;\n" - "}\n"; - testDocuments << CppTestDocument::create("file.h", original, expected); - - // Source File - original = - "#include \"file.h\"\n"; - expected = - "#include \"file.h\"\n" - "\n" - "int MyNamespace::number() const\n" - "{\n" - " return 5;\n" - "}\n"; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - - MoveFuncDefOutside factory; - QuickFixOperationTest(testDocuments, &factory); -} - -/// Check: Move Ctor with member initialization list (QTCREATORBUG-9157). -void QuickfixTest::testMoveFuncDefOutsideCtorWithInitialization1() -{ - QList<TestDocumentPtr> testDocuments; - QByteArray original; - QByteArray expected; - - // Header File - original = - "class Foo {\n" - "public:\n" - " Fo@o() : a(42), b(3.141) {}\n" - "private:\n" - " int a;\n" - " float b;\n" - "};\n"; - expected = - "class Foo {\n" - "public:\n" - " Foo();\n" - "private:\n" - " int a;\n" - " float b;\n" - "};\n"; - testDocuments << CppTestDocument::create("file.h", original, expected); - - // Source File - original ="#include \"file.h\"\n"; - expected = - "#include \"file.h\"\n" - "\n" - "Foo::Foo() : a(42), b(3.141) {}\n" - ; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - - MoveFuncDefOutside factory; - QuickFixOperationTest(testDocuments, &factory); -} - -/// Check: Move Ctor with member initialization list (QTCREATORBUG-9462). -void QuickfixTest::testMoveFuncDefOutsideCtorWithInitialization2() -{ - QList<TestDocumentPtr> testDocuments; - QByteArray original; - QByteArray expected; - - // Header File - original = - "class Foo\n" - "{\n" - "public:\n" - " Fo@o() : member(2)\n" - " {\n" - " }\n" - "\n" - " int member;\n" - "};\n"; - - expected = - "class Foo\n" - "{\n" - "public:\n" - " Foo();\n" - "\n" - " int member;\n" - "};\n"; - testDocuments << CppTestDocument::create("file.h", original, expected); - - // Source File - original ="#include \"file.h\"\n"; - expected = - "#include \"file.h\"\n" - "\n" - "Foo::Foo() : member(2)\n" - "{\n" - "}\n" - ; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - - MoveFuncDefOutside factory; - QuickFixOperationTest(testDocuments, &factory); -} - -/// Check if definition is inserted right after class for move definition outside -void QuickfixTest::testMoveFuncDefOutsideAfterClass() -{ - QList<TestDocumentPtr> testDocuments; - QByteArray original; - QByteArray expected; - - // Header File - original = - "class Foo\n" - "{\n" - " Foo();\n" - " void a@() {}\n" - "};\n" - "\n" - "class Bar {};\n"; - expected = - "class Foo\n" - "{\n" - " Foo();\n" - " void a();\n" - "};\n" - "\n" - "inline void Foo::a() {}\n" - "\n" - "class Bar {};\n"; - testDocuments << CppTestDocument::create("file.h", original, expected); - - // Source File - original = - "#include \"file.h\"\n" - "\n" - "Foo::Foo()\n" - "{\n\n" - "}\n"; - expected = original; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - - MoveFuncDefOutside factory; - QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), 1); -} - -/// Check if whitespace is respected for operator functions -void QuickfixTest::testMoveFuncDefOutsideRespectWsInOperatorNames1() -{ - QByteArray original = - "class Foo\n" - "{\n" - " Foo &opera@tor =() {}\n" - "};\n"; - QByteArray expected = - "class Foo\n" - "{\n" - " Foo &operator =();\n" - "};\n" - "\n" - "Foo &Foo::operator =() {}\n" - ; - - MoveFuncDefOutside factory; - QuickFixOperationTest(singleDocument(original, expected), &factory); -} - -/// Check if whitespace is respected for operator functions -void QuickfixTest::testMoveFuncDefOutsideRespectWsInOperatorNames2() -{ - QByteArray original = - "class Foo\n" - "{\n" - " Foo &opera@tor=() {}\n" - "};\n"; - QByteArray expected = - "class Foo\n" - "{\n" - " Foo &operator=();\n" - "};\n" - "\n" - "Foo &Foo::operator=() {}\n" - ; - - MoveFuncDefOutside factory; - QuickFixOperationTest(singleDocument(original, expected), &factory); -} - -void QuickfixTest::testMoveFuncDefOutsideMacroUses() -{ - QByteArray original = - "#define CONST const\n" - "#define VOLATILE volatile\n" - "class Foo\n" - "{\n" - " int fu@nc(int a, int b) CONST VOLATILE\n" - " {\n" - " return 42;\n" - " }\n" - "};\n"; - QByteArray expected = - "#define CONST const\n" - "#define VOLATILE volatile\n" - "class Foo\n" - "{\n" - " int func(int a, int b) CONST VOLATILE;\n" - "};\n" - "\n" - "\n" - // const volatile become lowercase: QTCREATORBUG-12620 - "int Foo::func(int a, int b) const volatile\n" - "{\n" - " return 42;\n" - "}\n" - ; - - MoveFuncDefOutside factory; - QuickFixOperationTest(singleDocument(original, expected), &factory, - ProjectExplorer::HeaderPaths(), 0, "QTCREATORBUG-12314"); -} - -void QuickfixTest::testMoveFuncDefOutsideTemplate() -{ - QByteArray original = - "template<class T>\n" - "class Foo { void fu@nc() {} };\n"; - QByteArray expected = - "template<class T>\n" - "class Foo { void fu@nc(); };\n" - "\n" - "template<class T>\n" - "void Foo<T>::func() {}\n"; - ; - - MoveFuncDefOutside factory; - QuickFixOperationTest(singleDocument(original, expected), &factory); -} - -void QuickfixTest::testMoveFuncDefOutsideMemberFunctionTemplate() -{ - const QByteArray original = R"( -struct S { - template<typename In> - void @foo(In in) { (void)in; } -}; -)"; - const QByteArray expected = R"( -struct S { - template<typename In> - void foo(In in); -}; - -template<typename In> -void S::foo(In in) { (void)in; } -)"; - - MoveFuncDefOutside factory; - QuickFixOperationTest(singleDocument(original, expected), &factory); -} - -void QuickfixTest::testMoveFuncDefOutsideTemplateSpecializedClass() -{ - QByteArray original = R"( -template<typename T> class base {}; -template<> -class base<int> -{ -public: - void @bar() {} -}; -)"; - QByteArray expected = R"( -template<typename T> class base {}; -template<> -class base<int> -{ -public: - void bar(); -}; - -void base<int>::bar() {} -)"; - - MoveFuncDefOutside factory; - QuickFixOperationTest(singleDocument(original, expected), &factory); -} - -void QuickfixTest::testMoveFuncDefOutsideUnnamedTemplate() -{ - QByteArray original = - "template<typename T, typename>\n" - "class Foo { void fu@nc() {} };\n"; - QByteArray expected = - "template<typename T, typename>\n" - "class Foo { void fu@nc(); };\n" - "\n" - "template<typename T, typename T2>\n" - "void Foo<T, T2>::func() {}\n"; - ; - - MoveFuncDefOutside factory; - QuickFixOperationTest(singleDocument(original, expected), &factory); -} - -void QuickfixTest::testMoveFuncDefToDecl_data() -{ - QTest::addColumn<QByteArrayList>("headers"); - QTest::addColumn<QByteArrayList>("sources"); - - QByteArray originalHeader; - QByteArray expectedHeader; - QByteArray originalSource; - QByteArray expectedSource; - - originalHeader = - "class Foo {\n" - " inline int @number() const;\n" - "};\n"; - expectedHeader = - "class Foo {\n" - " inline int number() const {return 5;}\n" - "};\n"; - originalSource = - "#include \"file.h\"\n" - "\n" - "int Foo::num@ber() const {return 5;}\n"; - expectedSource = - "#include \"file.h\"\n" - "\n\n"; - QTest::newRow("member function, two files") << QByteArrayList{originalHeader, expectedHeader} - << QByteArrayList{originalSource, expectedSource}; - - originalSource = - "class Foo {\n" - " inline int @number() const;\n" - "};\n" - "\n" - "int Foo::num@ber() const\n" - "{\n" - " return 5;\n" - "}\n"; - - expectedSource = - "class Foo {\n" - " inline int number() const\n" - " {\n" - " return 5;\n" - " }\n" - "};\n\n\n"; - QTest::newRow("member function, one file") << QByteArrayList() - << QByteArrayList{originalSource, expectedSource}; - - originalHeader = - "namespace MyNs {\n" - "class Foo {\n" - " inline int @number() const;\n" - "};\n" - "}\n"; - expectedHeader = - "namespace MyNs {\n" - "class Foo {\n" - " inline int number() const\n" - " {\n" - " return 5;\n" - " }\n" - "};\n" - "}\n"; - originalSource = - "#include \"file.h\"\n" - "\n" - "int MyNs::Foo::num@ber() const\n" - "{\n" - " return 5;\n" - "}\n"; - expectedSource = "#include \"file.h\"\n\n\n"; - QTest::newRow("member function, two files, namespace") - << QByteArrayList{originalHeader, expectedHeader} - << QByteArrayList{originalSource, expectedSource}; - - originalHeader = - "namespace MyNs {\n" - "class Foo {\n" - " inline int numbe@r() const;\n" - "};\n" - "}\n"; - expectedHeader = - "namespace MyNs {\n" - "class Foo {\n" - " inline int number() const\n" - " {\n" - " return 5;\n" - " }\n" - "};\n" - "}\n"; - originalSource = - "#include \"file.h\"\n" - "using namespace MyNs;\n" - "\n" - "int Foo::num@ber() const\n" - "{\n" - " return 5;\n" - "}\n"; - expectedSource = - "#include \"file.h\"\n" - "using namespace MyNs;\n" - "\n\n"; - QTest::newRow("member function, two files, namespace with using-directive") - << QByteArrayList{originalHeader, expectedHeader} - << QByteArrayList{originalSource, expectedSource}; - - originalSource = - "namespace MyNs {\n" - "class Foo {\n" - " inline int @number() const;\n" - "};\n" - "\n" - "int Foo::numb@er() const\n" - "{\n" - " return 5;\n" - "}" - "\n}\n"; - expectedSource = - "namespace MyNs {\n" - "class Foo {\n" - " inline int number() const\n" - " {\n" - " return 5;\n" - " }\n" - "};\n\n\n}\n"; - - QTest::newRow("member function, one file, namespace") - << QByteArrayList() << QByteArrayList{originalSource, expectedSource}; - - originalHeader = "int nu@mber() const;\n"; - expectedHeader = - "inline int number() const\n" - "{\n" - " return 5;\n" - "}\n"; - originalSource = - "#include \"file.h\"\n" - "\n" - "\n" - "int numb@er() const\n" - "{\n" - " return 5;\n" - "}\n"; - expectedSource = "#include \"file.h\"\n\n\n\n"; - QTest::newRow("free function") << QByteArrayList{originalHeader, expectedHeader} - << QByteArrayList{originalSource, expectedSource}; - - originalHeader = - "namespace MyNamespace {\n" - "int n@umber() const;\n" - "}\n"; - expectedHeader = - "namespace MyNamespace {\n" - "inline int number() const\n" - "{\n" - " return 5;\n" - "}\n" - "}\n"; - originalSource = - "#include \"file.h\"\n" - "\n" - "int MyNamespace::nu@mber() const\n" - "{\n" - " return 5;\n" - "}\n"; - expectedSource = - "#include \"file.h\"\n" - "\n\n"; - QTest::newRow("free function, namespace") << QByteArrayList{originalHeader, expectedHeader} - << QByteArrayList{originalSource, expectedSource}; - - originalHeader = - "class Foo {\n" - "public:\n" - " Fo@o();\n" - "private:\n" - " int a;\n" - " float b;\n" - "};\n"; - expectedHeader = - "class Foo {\n" - "public:\n" - " Foo() : a(42), b(3.141) {}\n" - "private:\n" - " int a;\n" - " float b;\n" - "};\n"; - originalSource = - "#include \"file.h\"\n" - "\n" - "Foo::F@oo() : a(42), b(3.141) {}" - ; - expectedSource ="#include \"file.h\"\n\n"; - QTest::newRow("constructor") << QByteArrayList{originalHeader, expectedHeader} - << QByteArrayList{originalSource, expectedSource}; - - originalSource = - "struct Foo\n" - "{\n" - " void f@oo();\n" - "} bar;\n" - "void Foo::fo@o()\n" - "{\n" - " return;\n" - "}"; - expectedSource = - "struct Foo\n" - "{\n" - " void foo()\n" - " {\n" - " return;\n" - " }\n" - "} bar;\n"; - QTest::newRow("QTCREATORBUG-10303") << QByteArrayList() - << QByteArrayList{originalSource, expectedSource}; - - originalSource = - "struct Base {\n" - " virtual int foo() = 0;\n" - "};\n" - "struct Derived : Base {\n" - " int @foo() override;\n" - "};\n" - "\n" - "int Derived::fo@o()\n" - "{\n" - " return 5;\n" - "}\n"; - expectedSource = - "struct Base {\n" - " virtual int foo() = 0;\n" - "};\n" - "struct Derived : Base {\n" - " int foo() override\n" - " {\n" - " return 5;\n" - " }\n" - "};\n\n\n"; - QTest::newRow("overridden virtual") << QByteArrayList() - << QByteArrayList{originalSource, expectedSource}; - - originalSource = - "template<class T>\n" - "class Foo { void @func(); };\n" - "\n" - "template<class T>\n" - "void Foo<T>::fu@nc() {}\n"; - expectedSource = - "template<class T>\n" - "class Foo { void fu@nc() {} };\n\n\n"; - QTest::newRow("class template") << QByteArrayList() - << QByteArrayList{originalSource, expectedSource}; - - originalSource = - "class Foo\n" - "{\n" - " template<class T>\n" - " void @func();\n" - "};\n" - "\n" - "template<class T>\n" - "void Foo::fu@nc() {}\n"; - expectedSource = - "class Foo\n" - "{\n" - " template<class T>\n" - " void func() {}\n" - "};\n\n\n"; - QTest::newRow("function template") << QByteArrayList() - << QByteArrayList{originalSource, expectedSource}; -} - -void QuickfixTest::testMoveFuncDefToDecl() -{ - QFETCH(QByteArrayList, headers); - QFETCH(QByteArrayList, sources); - - QVERIFY(headers.isEmpty() || headers.size() == 2); - QVERIFY(sources.size() == 2); - - QByteArray &declDoc = !headers.empty() ? headers.first() : sources.first(); - const int declCursorPos = declDoc.indexOf('@'); - QVERIFY(declCursorPos != -1); - const int defCursorPos = sources.first().lastIndexOf('@'); - QVERIFY(defCursorPos != -1); - QVERIFY(declCursorPos != defCursorPos); - - declDoc.remove(declCursorPos, 1); - QList<TestDocumentPtr> testDocuments; - if (!headers.isEmpty()) - testDocuments << CppTestDocument::create("file.h", headers.first(), headers.last()); - testDocuments << CppTestDocument::create("file.cpp", sources.first(), sources.last()); - - MoveFuncDefToDeclPush pushFactory; - QuickFixOperationTest(testDocuments, &pushFactory); - - declDoc.insert(declCursorPos, '@'); - sources.first().remove(defCursorPos, 1); - testDocuments.clear(); - if (!headers.isEmpty()) - testDocuments << CppTestDocument::create("file.h", headers.first(), headers.last()); - testDocuments << CppTestDocument::create("file.cpp", sources.first(), sources.last()); - - MoveFuncDefToDeclPull pullFactory; - QuickFixOperationTest(testDocuments, &pullFactory); -} - -void QuickfixTest::testMoveFuncDefToDeclMacroUses() -{ - QByteArray original = - "#define CONST const\n" - "#define VOLATILE volatile\n" - "class Foo\n" - "{\n" - " int func(int a, int b) CONST VOLATILE;\n" - "};\n" - "\n" - "\n" - "int Foo::fu@nc(int a, int b) CONST VOLATILE" - "{\n" - " return 42;\n" - "}\n"; - QByteArray expected = - "#define CONST const\n" - "#define VOLATILE volatile\n" - "class Foo\n" - "{\n" - " int func(int a, int b) CONST VOLATILE\n" - " {\n" - " return 42;\n" - " }\n" - "};\n\n\n\n"; - - MoveFuncDefToDeclPush factory; - QuickFixOperationTest(singleDocument(original, expected), &factory, - ProjectExplorer::HeaderPaths(), 0, "QTCREATORBUG-12314"); -} - -/// Check: Move all definitions from header to cpp. -void QuickfixTest::testMoveAllFuncDefOutsideMemberFuncToCpp() -{ - QList<TestDocumentPtr> testDocuments; - QByteArray original; - QByteArray expected; - - // Header File - original = - "class Foo {@\n" - " int numberA() const\n" - " {\n" - " return 5;\n" - " }\n" - " int numberB() const\n" - " {\n" - " return 5;\n" - " }\n" - "};\n"; - expected = - "class Foo {\n" - " int numberA() const;\n" - " int numberB() const;\n" - "};\n"; - testDocuments << CppTestDocument::create("file.h", original, expected); - - // Source File - original = - "#include \"file.h\"\n"; - expected = - "#include \"file.h\"\n" - "\n" - "int Foo::numberA() const\n" - "{\n" - " return 5;\n" - "}\n" - "\n" - "int Foo::numberB() const\n" - "{\n" - " return 5;\n" - "}\n" - ; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - - MoveAllFuncDefOutside factory; - QuickFixOperationTest(testDocuments, &factory); -} - -/// Check: Move all definition outside class -void QuickfixTest::testMoveAllFuncDefOutsideMemberFuncOutside() -{ - QByteArray original = - "class F@oo {\n" - " int f1()\n" - " {\n" - " return 1;\n" - " }\n" - " int f2() const\n" - " {\n" - " return 2;\n" - " }\n" - "};\n"; - QByteArray expected = - "class Foo {\n" - " int f1();\n" - " int f2() const;\n" - "};\n" - "\n" - "int Foo::f1()\n" - "{\n" - " return 1;\n" - "}\n" - "\n" - "int Foo::f2() const\n" - "{\n" - " return 2;\n" - "}\n"; - - MoveAllFuncDefOutside factory; - QuickFixOperationTest(singleDocument(original, expected), &factory); -} - -/// Check: Move all definition outside class -void QuickfixTest::testMoveAllFuncDefOutsideDoNotTriggerOnBaseClass() -{ - QByteArray original = - "class Bar;\n" - "class Foo : public Ba@r {\n" - " int f1()\n" - " {\n" - " return 1;\n" - " }\n" - "};\n"; - - MoveAllFuncDefOutside factory; - QuickFixOperationTest(singleDocument(original, ""), &factory); -} - -/// Check: Move all definition outside class -void QuickfixTest::testMoveAllFuncDefOutsideClassWithBaseClass() -{ - QByteArray original = - "class Bar;\n" - "class Fo@o : public Bar {\n" - " int f1()\n" - " {\n" - " return 1;\n" - " }\n" - "};\n"; - QByteArray expected = - "class Bar;\n" - "class Foo : public Bar {\n" - " int f1();\n" - "};\n" - "\n" - "int Foo::f1()\n" - "{\n" - " return 1;\n" - "}\n"; - - MoveAllFuncDefOutside factory; - QuickFixOperationTest(singleDocument(original, expected), &factory); -} - -/// Check: Do not take macro expanded code into account (QTCREATORBUG-13900) -void QuickfixTest::testMoveAllFuncDefOutsideIgnoreMacroCode() -{ - QByteArray original = - "#define FAKE_Q_OBJECT int bar() {return 5;}\n" - "class Fo@o {\n" - " FAKE_Q_OBJECT\n" - " int f1()\n" - " {\n" - " return 1;\n" - " }\n" - "};\n"; - QByteArray expected = - "#define FAKE_Q_OBJECT int bar() {return 5;}\n" - "class Foo {\n" - " FAKE_Q_OBJECT\n" - " int f1();\n" - "};\n" - "\n" - "int Foo::f1()\n" - "{\n" - " return 1;\n" - "}\n"; - - MoveAllFuncDefOutside factory; - QuickFixOperationTest(singleDocument(original, expected), &factory); -} - -void QuickfixTest::testAssignToLocalVariableTemplates() -{ - - QList<TestDocumentPtr> testDocuments; - QByteArray original; - QByteArray expected; - - // Header File - original = - "template <typename T>\n" - "class List {\n" - "public:\n" - " T first();" - "};\n" - ; - expected = original; - testDocuments << CppTestDocument::create("file.h", original, expected); - - // Source File - original = - "#include \"file.h\"\n" - "void foo() {\n" - " List<int> list;\n" - " li@st.first();\n" - "}\n"; - expected = - "#include \"file.h\"\n" - "void foo() {\n" - " List<int> list;\n" - " auto localFirst = list.first();\n" - "}\n"; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - - AssignToLocalVariable factory; - QuickFixOperationTest(testDocuments, &factory); -} - -void QuickfixTest::testExtractFunction_data() -{ - QTest::addColumn<QByteArray>("original"); - QTest::addColumn<QByteArray>("expected"); - - QTest::newRow("basic") - << _("// Documentation for f\n" - "void f()\n" - "{\n" - " @{start}g();@{end}\n" - "}\n") - << _("inline void extracted()\n" - "{\n" - " g();\n" - "}\n" - "\n" - "// Documentation for f\n" - "void f()\n" - "{\n" - " extracted();\n" - "}\n"); - - QTest::newRow("class function") - << _("class Foo\n" - "{\n" - "private:\n" - " void bar();\n" - "};\n\n" - "void Foo::bar()\n" - "{\n" - " @{start}g();@{end}\n" - "}\n") - << _("class Foo\n" - "{\n" - "public:\n" - " void extracted();\n\n" - "private:\n" - " void bar();\n" - "};\n\n" - "inline void Foo::extracted()\n" - "{\n" - " g();\n" - "}\n\n" - "void Foo::bar()\n" - "{\n" - " extracted();\n" - "}\n"); - - QTest::newRow("class in namespace") - << _("namespace NS {\n" - "class C {\n" - " void f(C &c);\n" - "};\n" - "}\n" - "void NS::C::f(NS::C &c)\n" - "{\n" - " @{start}C *c2 = &c;@{end}\n" - "}\n") - << _("namespace NS {\n" - "class C {\n" - " void f(C &c);\n" - "\n" - "public:\n" - " void extracted(NS::C &c);\n" // TODO: Remove non-required qualification - "};\n" - "}\n" - "inline void NS::C::extracted(NS::C &c)\n" - "{\n" - " C *c2 = &c;\n" - "}\n" - "\n" - "void NS::C::f(NS::C &c)\n" - "{\n" - " extracted(c);\n" - "}\n"); - - QTest::newRow("if-block") - << _("inline void func()\n" - "{\n" - " int dummy = 0;\n" - " @{start}if@{end} (dummy < 10) {\n" - " ++dummy;\n" - " }\n" - "}\n") - << _("inline void extracted(int dummy)\n" - "{\n" - " if (dummy < 10) {\n" - " ++dummy;\n" - " }\n" - "}\n\n" - "inline void func()\n" - "{\n" - " int dummy = 0;\n" - " extracted(dummy);\n" - "}\n"); -} - -void QuickfixTest::testExtractFunction() -{ - QFETCH(QByteArray, original); - QFETCH(QByteArray, expected); - - QList<TestDocumentPtr> testDocuments; - testDocuments << CppTestDocument::create("file.h", original, expected); - - ExtractFunction factory([]() { return QLatin1String("extracted"); }); - QuickFixOperationTest(testDocuments, &factory); -} - -void QuickfixTest::testExtractLiteralAsParameterTypeDeduction_data() -{ - QTest::addColumn<QByteArray>("typeString"); - QTest::addColumn<QByteArray>("literal"); - QTest::newRow("int") - << QByteArray("int ") << QByteArray("156"); - QTest::newRow("unsigned int") - << QByteArray("unsigned int ") << QByteArray("156u"); - QTest::newRow("long") - << QByteArray("long ") << QByteArray("156l"); - QTest::newRow("unsigned long") - << QByteArray("unsigned long ") << QByteArray("156ul"); - QTest::newRow("long long") - << QByteArray("long long ") << QByteArray("156ll"); - QTest::newRow("unsigned long long") - << QByteArray("unsigned long long ") << QByteArray("156ull"); - QTest::newRow("float") - << QByteArray("float ") << QByteArray("3.14159f"); - QTest::newRow("double") - << QByteArray("double ") << QByteArray("3.14159"); - QTest::newRow("long double") - << QByteArray("long double ") << QByteArray("3.14159L"); - QTest::newRow("bool") - << QByteArray("bool ") << QByteArray("true"); - QTest::newRow("bool") - << QByteArray("bool ") << QByteArray("false"); - QTest::newRow("char") - << QByteArray("char ") << QByteArray("'X'"); - QTest::newRow("wchar_t") - << QByteArray("wchar_t ") << QByteArray("L'X'"); - QTest::newRow("char16_t") - << QByteArray("char16_t ") << QByteArray("u'X'"); - QTest::newRow("char32_t") - << QByteArray("char32_t ") << QByteArray("U'X'"); - QTest::newRow("const char *") - << QByteArray("const char *") << QByteArray("\"narf\""); - QTest::newRow("const wchar_t *") - << QByteArray("const wchar_t *") << QByteArray("L\"narf\""); - QTest::newRow("const char16_t *") - << QByteArray("const char16_t *") << QByteArray("u\"narf\""); - QTest::newRow("const char32_t *") - << QByteArray("const char32_t *") << QByteArray("U\"narf\""); -} - -void QuickfixTest::testExtractLiteralAsParameterTypeDeduction() -{ - QFETCH(QByteArray, typeString); - QFETCH(QByteArray, literal); - const QByteArray original = QByteArray("void foo() {return @") + literal + QByteArray(";}\n"); - const QByteArray expected = QByteArray("void foo(") + typeString + QByteArray("newParameter = ") - + literal + QByteArray(") {return newParameter;}\n"); - - if (literal == "3.14159") { - qWarning("Literal 3.14159 is wrongly reported as int. Skipping."); - return; - } else if (literal == "3.14159L") { - qWarning("Literal 3.14159L is wrongly reported as long. Skipping."); - return; - } - - ExtractLiteralAsParameter factory; - QuickFixOperationTest(singleDocument(original, expected), &factory); -} - -void QuickfixTest::testExtractLiteralAsParameterFreeFunctionSeparateFiles() -{ - QList<TestDocumentPtr> testDocuments; - QByteArray original; - QByteArray expected; - - // Header File - original = - "void foo(const char *a, long b = 1);\n"; - expected = - "void foo(const char *a, long b = 1, int newParameter = 156);\n"; - testDocuments << CppTestDocument::create("file.h", original, expected); - - // Source File - original = - "void foo(const char *a, long b)\n" - "{return 1@56 + 123 + 156;}\n"; - expected = - "void foo(const char *a, long b, int newParameter)\n" - "{return newParameter + 123 + newParameter;}\n"; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - - ExtractLiteralAsParameter factory; - QuickFixOperationTest(testDocuments, &factory); -} - -void QuickfixTest::testExtractLiteralAsParameterMemberFunctionSeparateFiles() -{ - QList<TestDocumentPtr> testDocuments; - QByteArray original; - QByteArray expected; - - // Header File - original = - "class Narf {\n" - "public:\n" - " int zort();\n" - "};\n"; - expected = - "class Narf {\n" - "public:\n" - " int zort(int newParameter = 155);\n" - "};\n"; - testDocuments << CppTestDocument::create("file.h", original, expected); - - // Source File - original = - "#include \"file.h\"\n\n" - "int Narf::zort()\n" - "{ return 15@5 + 1; }\n"; - expected = - "#include \"file.h\"\n\n" - "int Narf::zort(int newParameter)\n" - "{ return newParameter + 1; }\n"; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - - ExtractLiteralAsParameter factory; - QuickFixOperationTest(testDocuments, &factory); -} - -void QuickfixTest::testExtractLiteralAsParameterNotTriggeringForInvalidCode() -{ - QList<TestDocumentPtr> testDocuments; - QByteArray original; - original = - "T(\"test\")\n" - "{\n" - " const int i = @14;\n" - "}\n"; - testDocuments << CppTestDocument::create("file.cpp", original, ""); - - ExtractLiteralAsParameter factory; - QuickFixOperationTest(testDocuments, &factory); -} - -void QuickfixTest::testAddCurlyBraces_data() -{ - QTest::addColumn<QByteArray>("original"); - QTest::addColumn<QByteArray>("expected"); - - QByteArray original = R"delim( -void MyObject::f() -{ - @if (true) - emit mySig(); -})delim"; - QByteArray expected = R"delim( -void MyObject::f() -{ - if (true) { - emit mySig(); - } -})delim"; - QTest::newRow("if") << original << expected; - - original = R"delim( -void MyObject::f() -{ - @if (true) - emit mySig(); - else - emit otherSig(); -})delim"; - expected = R"delim( -void MyObject::f() -{ - @if (true) { - emit mySig(); - } else { - emit otherSig(); - } -})delim"; - QTest::newRow("if with one else, unbraced") << original << expected; - - original = R"delim( -void MyObject::f() -{ - @if (true) { - emit mySig(); - } else - emit otherSig(); -})delim"; - expected = R"delim( -void MyObject::f() -{ - @if (true) { - emit mySig(); - } else { - emit otherSig(); - } -})delim"; - QTest::newRow("if with one else, if braced") << original << expected; - - original = R"delim( -void MyObject::f() -{ - @if (true) - emit mySig(); - else { - emit otherSig(); - } -})delim"; - expected = R"delim( -void MyObject::f() -{ - @if (true) { - emit mySig(); - } else { - emit otherSig(); - } -})delim"; - QTest::newRow("if with one else, else braced") << original << expected; - - original = R"delim( -void MyObject::f() -{ - @if (true) { - emit mySig(); - } else { - emit otherSig(); - } -})delim"; - expected.clear(); - QTest::newRow("if with one else, both braced") << original << expected; - - original = R"delim( -void MyObject::f() -{ - @if (x == 1) - emit sig1(); - else if (x == 2) - emit sig2(); -})delim"; - expected = R"delim( -void MyObject::f() -{ - if (x == 1) { - emit sig1(); - } else if (x == 2) { - emit sig2(); - } -})delim"; - QTest::newRow("if-else chain without final else, unbraced") << original << expected; - - original = R"delim( -void MyObject::f() -{ - @if (x == 1) { - emit sig1(); - } else if (x == 2) - emit sig2(); -})delim"; - expected = R"delim( -void MyObject::f() -{ - if (x == 1) { - emit sig1(); - } else if (x == 2) { - emit sig2(); - } -})delim"; - QTest::newRow("if-else chain without final else, partially braced 1") << original << expected; - - original = R"delim( -void MyObject::f() -{ - @if (x == 1) - emit sig1(); - else if (x == 2) { - emit sig2(); - } -})delim"; - expected = R"delim( -void MyObject::f() -{ - if (x == 1) { - emit sig1(); - } else if (x == 2) { - emit sig2(); - } -})delim"; - QTest::newRow("if-else chain without final else, partially braced 2") << original << expected; - - original = R"delim( -void MyObject::f() -{ - @if (x == 1) { - emit sig1(); - } else if (x == 2) { - emit sig2(); - } -})delim"; - expected.clear(); - QTest::newRow("if-else chain without final else, fully braced") << original << expected; - - original = R"delim( -void MyObject::f() -{ - @if (x == 1) - emit sig1(); - else if (x == 2) - emit sig2(); - else if (x == 3) - emit sig3(); - else - emit otherSig(); -})delim"; - expected = R"delim( -void MyObject::f() -{ - if (x == 1) { - emit sig1(); - } else if (x == 2) { - emit sig2(); - } else if (x == 3) { - emit sig3(); - } else { - emit otherSig(); - } -})delim"; - QTest::newRow("if-else chain, unbraced") << original << expected; - - original = R"delim( -void MyObject::f() -{ - @if (x == 1) { - emit sig1(); - } else if (x == 2) - emit sig2(); - else if (x == 3) - emit sig3(); - else - emit otherSig(); -})delim"; - expected = R"delim( -void MyObject::f() -{ - if (x == 1) { - emit sig1(); - } else if (x == 2) { - emit sig2(); - } else if (x == 3) { - emit sig3(); - } else { - emit otherSig(); - } -})delim"; - QTest::newRow("if-else chain, partially braced 1") << original << expected; - - original = R"delim( -void MyObject::f() -{ - @if (x == 1) - emit sig1(); - else if (x == 2) { - emit sig2(); - } else if (x == 3) - emit sig3(); - else - emit otherSig(); -})delim"; - expected = R"delim( -void MyObject::f() -{ - if (x == 1) { - emit sig1(); - } else if (x == 2) { - emit sig2(); - } else if (x == 3) { - emit sig3(); - } else { - emit otherSig(); - } -})delim"; - QTest::newRow("if-else chain, partially braced 2") << original << expected; - - original = R"delim( -void MyObject::f() -{ - @if (x == 1) - emit sig1(); - else if (x == 2) - emit sig2(); - else if (x == 3) { - emit sig3(); - } else - emit otherSig(); -})delim"; - expected = R"delim( -void MyObject::f() -{ - if (x == 1) { - emit sig1(); - } else if (x == 2) { - emit sig2(); - } else if (x == 3) { - emit sig3(); - } else { - emit otherSig(); - } -})delim"; - QTest::newRow("if-else chain, partially braced 3") << original << expected; - - original = R"delim( -void MyObject::f() -{ - @if (x == 1) - emit sig1(); - else if (x == 2) - emit sig2(); - else if (x == 3) - emit sig3(); - else { - emit otherSig(); - } -})delim"; - expected = R"delim( -void MyObject::f() -{ - if (x == 1) { - emit sig1(); - } else if (x == 2) { - emit sig2(); - } else if (x == 3) { - emit sig3(); - } else { - emit otherSig(); - } -})delim"; - QTest::newRow("if-else chain, partially braced 4") << original << expected; - - original = R"delim( -void MyObject::f() -{ - @if (x == 1) { - emit sig1(); - } else if (x == 2) { - emit sig2(); - } else if (x == 3) { - emit sig3(); - } else { - emit otherSig(); - } -})delim"; - expected.clear(); - QTest::newRow("if-else chain, fully braced") << original << expected; - - original = R"delim( -void MyObject::f() -{ - @while (true) - emit mySig(); -})delim"; - expected = R"delim( -void MyObject::f() -{ - while (true) { - emit mySig(); - } -})delim"; - QTest::newRow("while") << original << expected; - - original = R"delim( -void MyObject::f() -{ - @for (int i = 0; i < 10; ++i) - emit mySig(); -})delim"; - expected = R"delim( -void MyObject::f() -{ - for (int i = 0; i < 10; ++i) { - emit mySig(); - } -})delim"; - QTest::newRow("for") << original << expected; - - original = R"delim( -void MyObject::f() -{ - @for (int i : list) - emit mySig(); -})delim"; - expected = R"delim( -void MyObject::f() -{ - for (int i : list) { - emit mySig(); - } -})delim"; - QTest::newRow("range-based for") << original << expected; - - original = R"delim( -void MyObject::f() -{ - @do - emit mySig(); - while (true); -})delim"; - expected = R"delim( -void MyObject::f() -{ - do { - emit mySig(); - } while (true); -})delim"; - QTest::newRow("do") << original << expected; - - original = R"delim( -void MyObject::f() -{ - @do { - emit mySig(); - } while (true); -})delim"; - expected.clear(); - QTest::newRow("already has braces") << original << expected; -} - -void QuickfixTest::testAddCurlyBraces() -{ - QFETCH(QByteArray, original); - QFETCH(QByteArray, expected); - - AddBracesToControlStatement factory; - QuickFixOperationTest({CppTestDocument::create("file.cpp", original, expected)}, &factory); -} - -void QuickfixTest::testConvertQt4ConnectConnectOutOfClass() -{ - QByteArray prefix = - "class QObject {};\n" - "class TestClass : public QObject\n" - "{\n" - "public:\n" - " void setProp(int) {}\n" - " void sigFoo(int) {}\n" - "};\n" - "\n" - "int foo()\n" - "{\n"; - - QByteArray suffix = "\n}\n"; - - QByteArray original = prefix - + " TestClass obj;\n" - " conne@ct(&obj, SIGNAL(sigFoo(int)), &obj, SLOT(setProp(int)));" - + suffix; - - QByteArray expected = prefix - + " TestClass obj;\n" - " connect(&obj, &TestClass::sigFoo, &obj, &TestClass::setProp);" - + suffix; - - QList<TestDocumentPtr> testDocuments; - testDocuments << CppTestDocument::create("file.cpp", original, expected); - - ConvertQt4Connect factory; - QuickFixOperationTest(testDocuments, &factory); -} - -void QuickfixTest::testConvertQt4ConnectConnectWithinClass_data() -{ - QTest::addColumn<QByteArray>("original"); - QTest::addColumn<QByteArray>("expected"); - - QTest::newRow("four-args-connect") - << QByteArray("conne@ct(this, SIGNAL(sigFoo(int)), this, SLOT(setProp(int)));") - << QByteArray("connect(this, &TestClass::sigFoo, this, &TestClass::setProp);"); - - QTest::newRow("four-args-disconnect") - << QByteArray("disconne@ct(this, SIGNAL(sigFoo(int)), this, SLOT(setProp(int)));") - << QByteArray("disconnect(this, &TestClass::sigFoo, this, &TestClass::setProp);"); - - QTest::newRow("three-args-connect") - << QByteArray("conne@ct(this, SIGNAL(sigFoo(int)), SLOT(setProp(int)));") - << QByteArray("connect(this, &TestClass::sigFoo, this, &TestClass::setProp);"); - - QTest::newRow("template-value") - << QByteArray("Pointer<TestClass> p;\n" - "conne@ct(p.t, SIGNAL(sigFoo(int)), p.t, SLOT(setProp(int)));") - << QByteArray("Pointer<TestClass> p;\n" - "connect(p.t, &TestClass::sigFoo, p.t, &TestClass::setProp);"); - - QTest::newRow("implicit-pointer") - << QByteArray("Pointer<TestClass> p;\n" - "conne@ct(p, SIGNAL(sigFoo(int)), p, SLOT(setProp(int)));") - << QByteArray("Pointer<TestClass> p;\n" - "connect(p.data(), &TestClass::sigFoo, p.data(), &TestClass::setProp);"); -} - -void QuickfixTest::testConvertQt4ConnectConnectWithinClass() -{ - QFETCH(QByteArray, original); - QFETCH(QByteArray, expected); - - QByteArray prefix = - "template<class T>\n" - "struct Pointer\n" - "{\n" - " T *t;\n" - " operator T*() const { return t; }\n" - " T *data() const { return t; }\n" - "};\n" - "class QObject {};\n" - "class TestClass : public QObject\n" - "{\n" - "public:\n" - " void setProp(int) {}\n" - " void sigFoo(int) {}\n" - " void setupSignals();\n" - "};\n" - "\n" - "int TestClass::setupSignals()\n" - "{\n"; - - QByteArray suffix = "\n}\n"; - - QList<TestDocumentPtr> testDocuments; - testDocuments << CppTestDocument::create("file.cpp", - prefix + original + suffix, - prefix + expected + suffix); - - ConvertQt4Connect factory; - QuickFixOperationTest(testDocuments, &factory); -} - -void QuickfixTest::testConvertQt4ConnectDifferentNamespace() -{ - const QByteArray prefix = - "namespace NsA {\n" - "class ClassA : public QObject\n" - "{\n" - " static ClassA *instance();\n" - "signals:\n" - " void sig();\n" - "};\n" - "}\n" - "\n" - "namespace NsB {\n" - "class ClassB : public QObject\n" - "{\n" - " void slot();\n" - " void connector() {\n"; - - const QByteArray suffix = " }\n};\n}"; - - const QByteArray original = "co@nnect(NsA::ClassA::instance(), SIGNAL(sig()),\n" - " this, SLOT(slot()));\n"; - const QByteArray expected = "connect(NsA::ClassA::instance(), &NsA::ClassA::sig,\n" - " this, &ClassB::slot);\n"; - QList<TestDocumentPtr> testDocuments; - testDocuments << CppTestDocument::create("file.cpp", - prefix + original + suffix, - prefix + expected + suffix); - - ConvertQt4Connect factory; - QuickFixOperationTest(testDocuments, &factory); -} - -void QuickfixTest::testRemoveUsingNamespace_data() -{ - QTest::addColumn<QByteArray>("header1"); - QTest::addColumn<QByteArray>("header2"); - QTest::addColumn<QByteArray>("header3"); - QTest::addColumn<QByteArray>("expected1"); - QTest::addColumn<QByteArray>("expected2"); - QTest::addColumn<QByteArray>("expected3"); - QTest::addColumn<int>("operation"); - - const QByteArray header1 = "namespace std{\n" - " template<typename T>\n" - " class vector{};\n" - " namespace chrono{\n" - " using seconds = int;\n" - " }\n" - "}\n" - "using namespace std;\n" - "namespace test{\n" - " class vector{\n" - " std::vector<int> ints;\n" - " };\n" - "}\n"; - const QByteArray header2 = "#include \"header1.h\"\n" - "using foo = test::vector;\n" - "using namespace std;\n" - "using namespace test;\n" - "vector<int> others;\n"; - - const QByteArray header3 = "#include \"header2.h\"\n" - "using namespace std;\n" - "using namespace chrono;\n" - "namespace test{\n" - " vector vec;\n" - " seconds t;\n" - "}\n" - "void scope(){\n" - " for (;;) {\n" - " using namespace std;\n" - " vector<int> fori;\n" - " }\n" - " vector<int> no;\n" - " using namespace std;\n" - " vector<int> _no_change;\n" - "}\n" - "foo foos;\n"; - - QByteArray h3 = "#include \"header2.h\"\n" - "using namespace s@td;\n" - "using namespace chrono;\n" - "namespace test{\n" - " vector vec;\n" - " seconds t;\n" - "}\n" - "void scope(){\n" - " for (;;) {\n" - " using namespace std;\n" - " vector<int> fori;\n" - " }\n" - " vector<int> no;\n" - " using namespace std;\n" - " vector<int> _no_change;\n" - "}\n" - "foo foos;\n"; - - // like header1 but without "using namespace std;\n" - QByteArray expected1 = "namespace std{\n" - " template<typename T>\n" - " class vector{};\n" - " namespace chrono{\n" - " using seconds = int;\n" - " }\n" - "}\n" - "namespace test{\n" - " class vector{\n" - " std::vector<int> ints;\n" - " };\n" - "}\n"; - - // like header2 but without "using namespace std;\n" and with std::vector - QByteArray expected2 = "#include \"header1.h\"\n" - "using foo = test::vector;\n" - "using namespace test;\n" - "std::vector<int> others;\n"; - - QByteArray expected3 = "#include \"header2.h\"\n" - "using namespace std::chrono;\n" - "namespace test{\n" - " vector vec;\n" - " seconds t;\n" - "}\n" - "void scope(){\n" - " for (;;) {\n" - " using namespace std;\n" - " vector<int> fori;\n" - " }\n" - " std::vector<int> no;\n" - " using namespace std;\n" - " vector<int> _no_change;\n" - "}\n" - "foo foos;\n"; - - QTest::newRow("remove only in one file local") - << header1 << header2 << h3 << header1 << header2 << expected3 << 0; - QTest::newRow("remove only in one file globally") - << header1 << header2 << h3 << expected1 << expected2 << expected3 << 1; - - QByteArray h2 = "#include \"header1.h\"\n" - "using foo = test::vector;\n" - "using namespace s@td;\n" - "using namespace test;\n" - "vector<int> others;\n"; - - QTest::newRow("remove across two files only this") - << header1 << h2 << header3 << header1 << expected2 << header3 << 0; - QTest::newRow("remove across two files globally1") - << header1 << h2 << header3 << expected1 << expected2 << expected3 << 1; - - QByteArray h1 = "namespace std{\n" - " template<typename T>\n" - " class vector{};\n" - " namespace chrono{\n" - " using seconds = int;\n" - " }\n" - "}\n" - "using namespace s@td;\n" - "namespace test{\n" - " class vector{\n" - " std::vector<int> ints;\n" - " };\n" - "}\n"; - - QTest::newRow("remove across tree files only this") - << h1 << header2 << header3 << expected1 << header2 << header3 << 0; - QTest::newRow("remove across tree files globally") - << h1 << header2 << header3 << expected1 << expected2 << expected3 << 1; - - expected3 = "#include \"header2.h\"\n" - "using namespace std::chrono;\n" - "namespace test{\n" - " vector vec;\n" - " seconds t;\n" - "}\n" - "void scope(){\n" - " for (;;) {\n" - " using namespace s@td;\n" - " vector<int> fori;\n" - " }\n" - " std::vector<int> no;\n" - " using namespace std;\n" - " vector<int> _no_change;\n" - "}\n" - "foo foos;\n"; - - QByteArray expected3_new = "#include \"header2.h\"\n" - "using namespace std::chrono;\n" - "namespace test{\n" - " vector vec;\n" - " seconds t;\n" - "}\n" - "void scope(){\n" - " for (;;) {\n" - " std::vector<int> fori;\n" - " }\n" - " std::vector<int> no;\n" - " using namespace std;\n" - " vector<int> _no_change;\n" - "}\n" - "foo foos;\n"; - - QTest::newRow("scoped remove") - << expected1 << expected2 << expected3 << expected1 << expected2 << expected3_new << 0; - - h2 = "#include \"header1.h\"\n" - "using foo = test::vector;\n" - "using namespace std;\n" - "using namespace t@est;\n" - "vector<int> others;\n"; - expected2 = "#include \"header1.h\"\n" - "using foo = test::vector;\n" - "using namespace std;\n" - "vector<int> others;\n"; - - QTest::newRow("existing namespace") - << header1 << h2 << header3 << header1 << expected2 << header3 << 1; - - // test: remove using directive at global scope in every file - h1 = "using namespace tes@t;"; - h2 = "using namespace test;"; - h3 = "using namespace test;"; - - expected1 = expected2 = expected3 = ""; - QTest::newRow("global scope remove in every file") - << h1 << h2 << h3 << expected1 << expected2 << expected3 << 1; - - // test: dont print inline namespaces - h1 = R"--( -namespace test { - inline namespace test { - class Foo{ - void foo1(); - void foo2(); - }; - inline int TEST = 42; - } -} -)--"; - h2 = R"--( -#include "header1.h" -using namespace tes@t; -)--"; - h3 = R"--( -#include "header2.h" -Foo f1; -test::Foo f2; -using T1 = Foo; -using T2 = test::Foo; -int i1 = TEST; -int i2 = test::TEST; -void Foo::foo1(){}; -void test::Foo::foo2(){}; -)--"; - - expected1 = h1; - expected2 = R"--( -#include "header1.h" -)--"; - expected3 = R"--( -#include "header2.h" -test::Foo f1; -test::Foo f2; -using T1 = test::Foo; -using T2 = test::Foo; -int i1 = test::TEST; -int i2 = test::TEST; -void test::Foo::foo1(){}; -void test::Foo::foo2(){}; -)--"; - QTest::newRow("don't insert inline namespaces") - << h1 << h2 << h3 << expected1 << expected2 << expected3 << 0; -} - -void QuickfixTest::testRemoveUsingNamespace() -{ - QFETCH(QByteArray, header1); - QFETCH(QByteArray, header2); - QFETCH(QByteArray, header3); - QFETCH(QByteArray, expected1); - QFETCH(QByteArray, expected2); - QFETCH(QByteArray, expected3); - QFETCH(int, operation); - - QList<TestDocumentPtr> testDocuments; - testDocuments << CppTestDocument::create("header1.h", header1, expected1); - testDocuments << CppTestDocument::create("header2.h", header2, expected2); - testDocuments << CppTestDocument::create("header3.h", header3, expected3); - - RemoveUsingNamespace factory; - QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), operation); -} - -void QuickfixTest::testRemoveUsingNamespaceSimple_data() -{ - QTest::addColumn<QByteArray>("header"); - QTest::addColumn<QByteArray>("expected"); - - const QByteArray common = R"--( -namespace N{ - template<typename T> - struct vector{ - using iterator = T*; - }; - using int_vector = vector<int>; -} -)--"; - const QByteArray header = common + R"--( -using namespace N@; -int_vector ints; -int_vector::iterator intIter; -using vec = vector<int>; -vec::iterator it; -)--"; - const QByteArray expected = common + R"--( -N::int_vector ints; -N::int_vector::iterator intIter; -using vec = N::vector<int>; -vec::iterator it; -)--"; - - QTest::newRow("nested typedefs with Namespace") << header << expected; -} - -void QuickfixTest::testRemoveUsingNamespaceSimple() -{ - QFETCH(QByteArray, header); - QFETCH(QByteArray, expected); - - QList<TestDocumentPtr> testDocuments; - testDocuments << CppTestDocument::create("header.h", header, expected); - - RemoveUsingNamespace factory; - QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths()); -} - -void QuickfixTest::testRemoveUsingNamespaceDifferentSymbols() -{ - QByteArray header = "namespace test{\n" - " struct foo{\n" - " foo();\n" - " void bar();\n" - " };\n" - " void func();\n" - " enum E {E1, E2};\n" - " int bar;\n" - "}\n" - "using namespace t@est;\n" - "foo::foo(){}\n" - "void foo::bar(){}\n" - "void test(){\n" - " int i = bar * 4;\n" - " E val = E1;\n" - " auto p = &foo::bar;\n" - " func()\n" - "}\n"; - QByteArray expected = "namespace test{\n" - " struct foo{\n" - " foo();\n" - " void bar();\n" - " };\n" - " void func();\n" - " enum E {E1, E2};\n" - " int bar;\n" - "}\n" - "test::foo::foo(){}\n" - "void test::foo::bar(){}\n" - "void test(){\n" - " int i = test::bar * 4;\n" - " test::E val = test::E1;\n" - " auto p = &test::foo::bar;\n" - " test::func()\n" - "}\n"; - - QList<TestDocumentPtr> testDocuments; - testDocuments << CppTestDocument::create("file.h", header, expected); - RemoveUsingNamespace factory; - QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), 0); -} - -enum ConstructorLocation { Inside, Outside, CppGenNamespace, CppGenUsingDirective, CppRewriteType }; - -void QuickfixTest::testGenerateConstructor_data() -{ - QTest::addColumn<QByteArray>("original_header"); - QTest::addColumn<QByteArray>("expected_header"); - QTest::addColumn<QByteArray>("original_source"); - QTest::addColumn<QByteArray>("expected_source"); - QTest::addColumn<int>("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<typename T> - struct vector{ - }; -} -)--"; - header = common + R"--( -namespace M{ -enum G{g}; -class@ Foo{ - N::vector<G> g; - enum E{e}e; -public: -}; -} -)--"; - - expected = common + R"--( -namespace M{ -enum G{g}; -class@ Foo{ - N::vector<G> g; - enum E{e}e; -public: - Foo(const N::vector<G> &g, E e); -}; - -Foo::Foo(const N::vector<G> &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> g; - enum E{e}e; -public: - Foo(const N::vector<G> &g, E e); -}; -} - - -inline M::Foo::Foo(const N::vector<M::G> &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> g; - enum E{e}e; -public: - Foo(const N::vector<G> &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> &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> &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<M::G> &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<TestDocumentPtr> 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<QString>("input"); - QTest::addColumn<QString>("expectedOutput"); - - QTest::newRow("C -> C++ / no selection / single line") << R"( -int var1; -/* Other comment, unaffected */ -/* Our @comment */ -/* Another unaffected comment */ -int var2;)" << R"( -int var1; -/* Other comment, unaffected */ -// Our comment -/* Another unaffected comment */ -int var2;)"; - - QTest::newRow("C -> C++ / no selection / multi-line / preserved header and footer") << R"( -/**************************************************** - * some info - * more @info - ***************************************************/)" << R"( -///////////////////////////////////////////////////// -// some info -// more info -/////////////////////////////////////////////////////)"; - - QTest::newRow("C -> C++ / no selection / multi-line / non-preserved header and footer") << R"( -/* - * some info - * more @info - */)" << R"( -// some info -// more info -)"; - - QTest::newRow("C -> C++ / no selection / qdoc") << R"( -/*! - \qmlproperty string Type::element.name - \qmlproperty int Type::element.id - - \brief Holds the @element name and id. -*/)" << R"( -//! \qmlproperty string Type::element.name -//! \qmlproperty @int Type::element.id -//! -//! \brief Holds the element name and id. -)"; - - QTest::newRow("C -> C++ / no selection / doxygen") << R"( -/*! \class Test - \brief A test class. - - A more detailed @class description. -*/)" << R"( -//! \class Test -//! \brief A test class. -//! -//! A more detailed class description. -)"; - - QTest::newRow("C -> C++ / selection / single line") << R"( -int var1; -/* Other comment, unaffected */ -@{start}/* Our comment */@{end} -/* Another unaffected comment */ -int var2;)" << R"( -int var1; -/* Other comment, unaffected */ -// Our comment -/* Another unaffected comment */ -int var2;)"; - - QTest::newRow("C -> C++ / selection / multi-line / preserved header and footer") << R"( -/**************************************************** - * @{start}some info - * more info@{end} - ***************************************************/)" << R"( -///////////////////////////////////////////////////// -// some info -// more info -/////////////////////////////////////////////////////)"; - - QTest::newRow("C -> C++ / selection / multi-line / non-preserved header and footer") << R"( -/*@{start} - * some in@{end}fo - * more info - */)" << R"( -// some info -// more info -)"; - - QTest::newRow("C -> C++ / selection / qdoc") << R"( -/*!@{start} - \qmlproperty string Type::element.name - \qmlproperty int Type::element.id - - \brief Holds the element name and id. -*/@{end})" << R"( -//! \qmlproperty string Type::element.name -//! \qmlproperty int Type::element.id -//! -//! \brief Holds the element name and id. -)"; - - QTest::newRow("C -> C++ / selection / doxygen") << R"( -/** Expand envi@{start}ronment variables in a string. - * - * Environment variables are accepted in the @{end}following forms: - * $SOMEVAR, ${SOMEVAR} on Unix and %SOMEVAR% on Windows. - * No escapes and quoting are supported. - * If a variable is not found, it is not substituted. - */)" << R"( -//! Expand environment variables in a string. -//! -//! Environment variables are accepted in the following forms: -//! $SOMEVAR, ${SOMEVAR} on Unix and %SOMEVAR% on Windows. -//! No escapes and quoting are supported. -//! If a variable is not found, it is not substituted. -)"; - - QTest::newRow("C -> C++ / selection / multiple comments") << R"( -@{start}/* Affected comment */ -/* Another affected comment */ -/* A third affected comment */@{end} -/* An unaffected comment */)" << R"( -// Affected comment -// Another affected comment -// A third affected comment -/* An unaffected comment */)"; - - // FIXME: Remove adjacent newline along with last block - // FIXME: Use CppRefactoringFile to auto-indent continuation lines? - QTest::newRow("C -> C++, indented") << R"( -struct S { - /* - * @This is an - * indented comment. - */ - void func(); -)" << R"( -struct S { - // This is an -// indented comment. - - void func(); -)"; - - QTest::newRow("C++ -> C / no selection / single line") << R"( -// Other comment, unaffected -// Our @comment -// Another unaffected comment)" << R"( -// Other comment, unaffected -/* Our comment */ -// Another unaffected comment)"; - - QTest::newRow("C++ -> C / selection / single line") << R"( -// Other comment, unaffected -@{start}// Our comment@{end} -// Another unaffected comment)" << R"( -// Other comment, unaffected -/* Our comment */ -// Another unaffected comment)"; - - QTest::newRow("C++ -> C / selection / multi-line / preserved header and footer") << R"( -@{start}///////////////////////////////////////////////////// -// some info -// more info -/////////////////////////////////////////////////////@{end})" << R"( -/****************************************************/ -/* some info */ -/* more info */ -/****************************************************/)"; - - QTest::newRow("C++ -> C / selection / qdoc") << R"( -@{start}//! \qmlproperty string Type::element.name -//! \qmlproperty int Type::element.id -//! -//! \brief Holds the element name and id.@{end} -)" << R"( -/*! - \qmlproperty string Type::element.name - \qmlproperty int Type::element.id - - \brief Holds the element name and id. -*/ -)"; - - QTest::newRow("C++ -> C / selection / doxygen") << R"( -@{start}//! \class Test -//! \brief A test class. -//! -//! A more detailed class description.@{end} -)" << R"( -/*! - \class Test - \brief A test class. - - A more detailed class description. -*/ -)"; -} - -void QuickfixTest::testChangeCommentType() -{ - QFETCH(QString, input); - QFETCH(QString, expectedOutput); - - ConvertCommentStyle factory; - QuickFixOperationTest( - {CppTestDocument::create("file.h", input.toUtf8(), expectedOutput.toUtf8())}, - &factory); -} - -void QuickfixTest::testMoveComments_data() -{ - QTest::addColumn<QByteArrayList>("headers"); - QTest::addColumn<QByteArrayList>("sources"); - - const QByteArrayList headersFuncDecl2Def{R"( -// Function comment -void @aFunction(); -)", R"( -void aFunction(); -)"}; - const QByteArrayList sourcesFuncDecl2Def{R"( -#include "file.h" - -void aFunction() {} -)", R"( -#include "file.h" - -// Function comment -void aFunction() {} -)"}; - QTest::newRow("function: from decl to def") << headersFuncDecl2Def << sourcesFuncDecl2Def; - - const QByteArrayList headersFuncDef2Decl{R"( -void aFunction(); -)", R"( -/* function */ -/* comment */ -void aFunction(); -)"}; - const QByteArrayList sourcesFuncDef2Decl{R"( -#include "file.h" - -/* function */ -/* comment */ -void a@Function() {} -)", R"( -#include "file.h" - -void aFunction() {} -)"}; - QTest::newRow("function: from def to decl") << headersFuncDef2Decl << sourcesFuncDef2Decl; - - const QByteArrayList headersFuncNoDef{R"( -// Function comment -void @aFunction(); -)", R"( -// Function comment -void aFunction(); -)"}; - QTest::newRow("function: no def") << headersFuncNoDef << QByteArrayList(); - - const QByteArrayList headersFuncNoDecl{R"( -// Function comment -inline void @aFunction() {} -)", R"( -// Function comment -inline void aFunction() {} -)"}; - QTest::newRow("function: no decl") << headersFuncNoDecl << QByteArrayList(); - - const QByteArrayList headersFuncTemplateDecl2Def{R"( -// Function comment -template<typename T> T @aFunction(); - -template<typename T> inline T aFunction() { return T(); } -)", R"( -template<typename T> T aFunction(); - -// Function comment -template<typename T> inline T aFunction() { return T(); } -)"}; - QTest::newRow("function template: from decl to def") << headersFuncTemplateDecl2Def - << QByteArrayList(); - - const QByteArrayList headersFuncTemplateDef2Decl{R"( -template<typename T> T aFunction(); - -// Function comment -template<typename T> inline T @aFunction() { return T(); } -)", R"( -// Function comment -template<typename T> T aFunction(); - -template<typename T> inline T aFunction() { return T(); } -)"}; - QTest::newRow("function template: from def to decl") << headersFuncTemplateDef2Decl - << QByteArrayList(); - - const QByteArrayList headersMemberDecl2Def{R"( -class C { - /** - * \brief Foo::aMember - */ - void @aMember(); -)", R"( -class C { - void aMember(); -)"}; - const QByteArrayList sourcesMemberDecl2Def{R"( -#include "file.h" - -void C::aMember() {} -)", R"( -#include "file.h" - -/** - * \brief Foo::aMember - */ -void C::aMember() {} -)"}; - QTest::newRow("member function: from decl to def") << headersMemberDecl2Def - << sourcesMemberDecl2Def; - - const QByteArrayList headersMemberDef2Decl{R"( -class C { - void aMember(); -)", R"( -class C { - /** - * \brief Foo::aMember - */ - void aMember(); -)"}; - const QByteArrayList sourcesMemberDef2Decl{R"( -#include "file.h" - -/** - * \brief Foo::aMember - */ -void C::aMember() {@} -)", R"( -#include "file.h" - -void C::aMember() {} -)"}; - QTest::newRow("member function: from def to decl") << headersMemberDef2Decl - << sourcesMemberDef2Decl; -} - -void QuickfixTest::testMoveComments() -{ - QFETCH(QByteArrayList, headers); - QFETCH(QByteArrayList, sources); - - QList<TestDocumentPtr> documents; - QCOMPARE(headers.size(), 2); - documents << CppTestDocument::create("file.h", headers.at(0), headers.at(1)); - if (!sources.isEmpty()) { - QCOMPARE(sources.size(), 2); - documents << CppTestDocument::create("file.cpp", sources.at(0), sources.at(1)); - } - MoveFunctionComments factory; - QByteArray failMessage; - if (QByteArray(QTest::currentDataTag()) == "function template: from def to decl") - failMessage = "decl/def switch doesn't work for templates"; - QuickFixOperationTest(documents, &factory, {}, {}, failMessage); -} - -void QuickfixTest::testConvertToMetaMethodInvocation_data() -{ - QTest::addColumn<QByteArray>("input"); - QTest::addColumn<QByteArray>("expected"); - - // ^ marks the cursor locations. - // $ marks the replacement regions. - // The quoted string in the comment is the data tag. - // The rest of the comment is the replacement string. - const QByteArray allCases = R"( -class C { -public: - C() { - $this->^aSignal()$; // "signal from region on pointer to object" QMetaObject::invokeMethod(this, "aSignal") - C c; - $c.^aSignal()$; // "signal from region on object value" QMetaObject::invokeMethod(&c, "aSignal") - $(new C)->^aSignal()$; // "signal from region on expression" QMetaObject::invokeMethod((new C), "aSignal") - $emit this->^aSignal()$; // "signal from region, with emit" QMetaObject::invokeMethod(this, "aSignal") - $Q_EMIT this->^aSignal()$; // "signal from region, with Q_EMIT" QMetaObject::invokeMethod(this, "aSignal") - $this->^aSlot()$; // "slot from region" QMetaObject::invokeMethod(this, "aSlot") - $this->^noArgs()$; // "Q_SIGNAL, no arguments" QMetaObject::invokeMethod(this, "noArgs") - $this->^oneArg(0)$; // "Q_SLOT, one argument" QMetaObject::invokeMethod(this, "oneArg", Q_ARG(int, 0)) - $this->^twoArgs(0, c)$; // "Q_INVOKABLE, two arguments" QMetaObject::invokeMethod(this, "twoArgs", Q_ARG(int, 0), Q_ARG(C, c)) - this->^notInvokable(); // "not invokable" - } - -signals: - void aSignal(); - -private slots: - void aSlot(); - -private: - Q_SIGNAL void noArgs(); - Q_SLOT void oneArg(int index); - Q_INVOKABLE void twoArgs(int index, const C &value); - void notInvokable(); -}; -)"; - - qsizetype nextCursor = allCases.indexOf('^'); - while (nextCursor != -1) { - const int commentStart = allCases.indexOf("//", nextCursor); - QVERIFY(commentStart != -1); - const int tagStart = allCases.indexOf('"', commentStart + 2); - QVERIFY(tagStart != -1); - const int tagEnd = allCases.indexOf('"', tagStart + 1); - QVERIFY(tagEnd != -1); - QByteArray input = allCases; - QByteArray output = allCases; - input.replace(nextCursor, 1, "@"); - const QByteArray tag = allCases.mid(tagStart + 1, tagEnd - tagStart - 1); - const int prevNewline = allCases.lastIndexOf('\n', nextCursor); - const int regionStart = allCases.lastIndexOf('$', nextCursor); - bool hasReplacement = false; - if (regionStart != -1 && regionStart > prevNewline) { - const int regionEnd = allCases.indexOf('$', regionStart + 1); - QVERIFY(regionEnd != -1); - const int nextNewline = allCases.indexOf('\n', tagEnd); - QVERIFY(nextNewline != -1); - const QByteArray replacement - = allCases.mid(tagEnd + 1, nextNewline - tagEnd - 1).trimmed(); - output.replace(regionStart, regionEnd - regionStart, replacement); - hasReplacement = true; - } - static const auto matcher = [](char c) { return c == '^' || c == '$'; }; - input.removeIf(matcher); - if (hasReplacement) { - output.removeIf(matcher); - output.prepend("#include <QMetaObject>\n\n"); - } else { - output.clear(); - } - QTest::newRow(tag.data()) << input << output; - nextCursor = allCases.indexOf('^', nextCursor + 1); - } -} - -void QuickfixTest::testConvertToMetaMethodInvocation() -{ - QFETCH(QByteArray, input); - QFETCH(QByteArray, expected); - - ConvertToMetaMethodCall factory; - QuickFixOperationTest({CppTestDocument::create("file.cpp", input, expected)}, &factory); -} - -} // namespace CppEditor::Internal::Tests diff --git a/src/plugins/cppeditor/cppquickfix_test.h b/src/plugins/cppeditor/cppquickfix_test.h deleted file mode 100644 index 917abe19c7..0000000000 --- a/src/plugins/cppeditor/cppquickfix_test.h +++ /dev/null @@ -1,236 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#pragma once - -#include "cppquickfix.h" -#include "cpptoolstestcase.h" - -#include <projectexplorer/headerpath.h> - -#include <QByteArray> -#include <QList> -#include <QObject> -#include <QSharedPointer> -#include <QStringList> - -namespace TextEditor { class QuickFixOperation; } - -namespace CppEditor { -class CppCodeStylePreferences; - -namespace Internal { -namespace Tests { - -class BaseQuickFixTestCase : public CppEditor::Tests::TestCase -{ -public: - /// Exactly one QuickFixTestDocument must contain the cursor position marker '@' - /// or "@{start}" and "@{end}" - BaseQuickFixTestCase(const QList<TestDocumentPtr> &testDocuments, - const ProjectExplorer::HeaderPaths &headerPaths, - const QByteArray &clangFormatSettings = {}); - - ~BaseQuickFixTestCase(); - -protected: - TestDocumentPtr m_documentWithMarker; - QList<TestDocumentPtr> m_testDocuments; - -private: - QScopedPointer<CppEditor::Tests::TemporaryDir> m_temporaryDirectory; - - CppCodeStylePreferences *m_cppCodeStylePreferences; - QByteArray m_cppCodeStylePreferencesOriginalDelegateId; - - ProjectExplorer::HeaderPaths m_headerPathsToRestore; - bool m_restoreHeaderPaths; -}; - -/// Tests a concrete QuickFixOperation of a given CppQuickFixFactory -class QuickFixOperationTest : public BaseQuickFixTestCase -{ -public: - QuickFixOperationTest(const QList<TestDocumentPtr> &testDocuments, - CppQuickFixFactory *factory, - const ProjectExplorer::HeaderPaths &headerPaths - = ProjectExplorer::HeaderPaths(), - int operationIndex = 0, - const QByteArray &expectedFailMessage = {}, - const QByteArray &clangFormatSettings = {}); - - static void run(const QList<TestDocumentPtr> &testDocuments, - CppQuickFixFactory *factory, - const QString &headerPath, - int operationIndex = 0); -}; - -QList<TestDocumentPtr> singleDocument(const QByteArray &original, - const QByteArray &expected); - -class QuickfixTest : public QObject -{ - Q_OBJECT - -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(); - - void testConvertQt4ConnectConnectOutOfClass(); - void testConvertQt4ConnectConnectWithinClass_data(); - void testConvertQt4ConnectConnectWithinClass(); - void testConvertQt4ConnectDifferentNamespace(); - - void testInsertDefFromDeclAfterClass(); - void testInsertDefFromDeclHeaderSourceBasic1(); - void testInsertDefFromDeclHeaderSourceBasic2(); - void testInsertDefFromDeclHeaderSourceBasic3(); - void testInsertDefFromDeclHeaderSourceNamespace1(); - void testInsertDefFromDeclHeaderSourceNamespace2(); - void testInsertDefFromDeclInsideClass(); - void testInsertDefFromDeclNotTriggeringWhenDefinitionExists(); - void testInsertDefFromDeclFindRightImplementationFile(); - void testInsertDefFromDeclIgnoreSurroundingGeneratedDeclarations(); - void testInsertDefFromDeclRespectWsInOperatorNames1(); - void testInsertDefFromDeclRespectWsInOperatorNames2(); - void testInsertDefFromDeclNoexceptSpecifier(); - void testInsertDefFromDeclMacroUsesAtEndOfFile1(); - void testInsertDefFromDeclMacroUsesAtEndOfFile2(); - void testInsertDefFromDeclErroneousStatementAtEndOfFile(); - void testInsertDefFromDeclRvalueReference(); - void testInsertDefFromDeclFunctionTryBlock(); - void testInsertDefFromDeclUsingDecl(); - void testInsertDefFromDeclFindImplementationFile(); - void testInsertDefFromDeclUnicodeIdentifier(); - void testInsertDefFromDeclTemplateClass(); - void testInsertDefFromDeclTemplateClassWithValueParam(); - void testInsertDefFromDeclTemplateFunction(); - void testInsertDefFromDeclTemplateClassAndTemplateFunction(); - void testInsertDefFromDeclTemplateClassAndFunctionInsideNamespace(); - void testInsertDefFromDeclFunctionWithSignedUnsignedArgument(); - void testInsertDefFromDeclNotTriggeredForFriendFunc(); - void testInsertDefFromDeclMinimalFunctionParameterType(); - void testInsertDefFromDeclAliasTemplateAsReturnType(); - void testInsertDefsFromDecls_data(); - void testInsertDefsFromDecls(); - void testInsertAndFormatDefsFromDecls(); - - void testInsertDefOutsideFromDeclTemplateClassAndTemplateFunction(); - void testInsertDefOutsideFromDeclTemplateClass(); - void testInsertDefOutsideFromDeclTemplateFunction(); - void testInsertDefOutsideFromDeclFunction(); - - void testInsertDeclFromDef(); - void testInsertDeclFromDefTemplateFuncTypename(); - void testInsertDeclFromDefTemplateFuncInt(); - void testInsertDeclFromDefTemplateReturnType(); - void testInsertDeclFromDefNotTriggeredForTemplateFunc(); - - void testAddIncludeForUndefinedIdentifier_data(); - void testAddIncludeForUndefinedIdentifier(); - void testAddIncludeForUndefinedIdentifierNoDoubleQtHeaderInclude(); - - void testAddForwardDeclForUndefinedIdentifier_data(); - void testAddForwardDeclForUndefinedIdentifier(); - - void testMoveFuncDefOutsideMemberFuncToCpp(); - void testMoveFuncDefOutsideMemberFuncToCppInsideNS(); - void testMoveFuncDefOutsideMemberFuncOutside1(); - void testMoveFuncDefOutsideMemberFuncOutside2(); - void testMoveFuncDefOutsideMemberFuncToCppNS(); - void testMoveFuncDefOutsideMemberFuncToCppNSUsing(); - void testMoveFuncDefOutsideMemberFuncOutsideWithNs(); - void testMoveFuncDefOutsideFreeFuncToCpp(); - void testMoveFuncDefOutsideFreeFuncToCppNS(); - void testMoveFuncDefOutsideCtorWithInitialization1(); - void testMoveFuncDefOutsideCtorWithInitialization2(); - void testMoveFuncDefOutsideAfterClass(); - void testMoveFuncDefOutsideRespectWsInOperatorNames1(); - void testMoveFuncDefOutsideRespectWsInOperatorNames2(); - void testMoveFuncDefOutsideMacroUses(); - void testMoveFuncDefOutsideTemplate(); - void testMoveFuncDefOutsideMemberFunctionTemplate(); - void testMoveFuncDefOutsideTemplateSpecializedClass(); - void testMoveFuncDefOutsideUnnamedTemplate(); - void testMoveFuncDefOutsideMemberFuncToCppStatic(); - void testMoveFuncDefOutsideMemberFuncToCppWithInlinePartOfName(); - void testMoveFuncDefOutsideMixedQualifiers(); - - void testMoveAllFuncDefOutsideMemberFuncToCpp(); - void testMoveAllFuncDefOutsideMemberFuncOutside(); - void testMoveAllFuncDefOutsideDoNotTriggerOnBaseClass(); - void testMoveAllFuncDefOutsideClassWithBaseClass(); - void testMoveAllFuncDefOutsideIgnoreMacroCode(); - - void testMoveFuncDefToDecl_data(); - void testMoveFuncDefToDecl(); - - void testMoveFuncDefToDeclMacroUses(); - - void testAssignToLocalVariableTemplates(); - - void testExtractFunction_data(); - void testExtractFunction(); - - void testExtractLiteralAsParameterTypeDeduction_data(); - void testExtractLiteralAsParameterTypeDeduction(); - void testExtractLiteralAsParameterFreeFunctionSeparateFiles(); - void testExtractLiteralAsParameterMemberFunctionSeparateFiles(); - void testExtractLiteralAsParameterNotTriggeringForInvalidCode(); - - void testAddCurlyBraces_data(); - void testAddCurlyBraces(); - - void testRemoveUsingNamespace_data(); - void testRemoveUsingNamespace(); - void testRemoveUsingNamespaceSimple_data(); - void testRemoveUsingNamespaceSimple(); - void testRemoveUsingNamespaceDifferentSymbols(); - - void testGenerateConstructor_data(); - void testGenerateConstructor(); - - void testChangeCommentType_data(); - void testChangeCommentType(); - - void testMoveComments_data(); - void testMoveComments(); - - void testConvertToMetaMethodInvocation_data(); - void testConvertToMetaMethodInvocation(); -}; - -} // namespace Tests -} // namespace Internal -} // namespace CppEditor diff --git a/src/plugins/cppeditor/cppquickfixes.cpp b/src/plugins/cppeditor/cppquickfixes.cpp deleted file mode 100644 index 9ac67e1e29..0000000000 --- a/src/plugins/cppeditor/cppquickfixes.cpp +++ /dev/null @@ -1,10104 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include "cppquickfixes.h" - -#include "baseeditordocumentprocessor.h" -#include "cppcodestylesettings.h" -#include "cppeditordocument.h" -#include "cppeditortr.h" -#include "cppeditorwidget.h" -#include "cppfunctiondecldeflink.h" -#include "cppinsertvirtualmethods.h" -#include "cpplocatordata.h" -#include "cpppointerdeclarationformatter.h" -#include "cppquickfixassistant.h" -#include "cppquickfixprojectsettings.h" -#include "cpprefactoringchanges.h" -#include "cpptoolsreuse.h" -#include "includeutils.h" -#include "indexitem.h" -#include "insertionpointlocator.h" -#include "symbolfinder.h" - -#include <coreplugin/icore.h> -#include <coreplugin/messagebox.h> - -#include <cplusplus/ASTPath.h> -#include <cplusplus/CPlusPlusForwardDeclarations.h> -#include <cplusplus/CppRewriter.h> -#include <cplusplus/declarationcomments.h> -#include <cplusplus/NamePrettyPrinter.h> -#include <cplusplus/TypeOfExpression.h> -#include <cplusplus/TypePrettyPrinter.h> - -#include <extensionsystem/pluginmanager.h> - -#include <projectexplorer/editorconfiguration.h> -#include <projectexplorer/projectnodes.h> -#include <projectexplorer/projecttree.h> -#include <projectexplorer/projectmanager.h> - -#include <texteditor/tabsettings.h> - -#include <utils/algorithm.h> -#include <utils/basetreeview.h> -#include <utils/layoutbuilder.h> -#include <utils/fancylineedit.h> -#include <utils/fileutils.h> -#include <utils/qtcassert.h> -#include <utils/treemodel.h> - -#ifdef WITH_TESTS -#include <QAbstractItemModelTester> -#endif -#include <QApplication> -#include <QCheckBox> -#include <QComboBox> -#include <QDialog> -#include <QDialogButtonBox> -#include <QDir> -#include <QFileInfo> -#include <QFormLayout> -#include <QGridLayout> -#include <QHash> -#include <QHeaderView> -#include <QInputDialog> -#include <QMimeData> -#include <QPair> -#include <QProxyStyle> -#include <QPushButton> -#include <QRegularExpression> -#include <QSharedPointer> -#include <QStack> -#include <QStyledItemDelegate> -#include <QTableView> -#include <QTextCodec> -#include <QTextCursor> -#include <QVBoxLayout> - -#include <bitset> -#include <cctype> -#include <functional> -#include <limits> -#include <vector> - -using namespace CPlusPlus; -using namespace TextEditor; -using namespace Utils; - -namespace CppEditor { - -static QList<CppQuickFixFactory *> g_cppQuickFixFactories; - -CppQuickFixFactory::CppQuickFixFactory() -{ - g_cppQuickFixFactories.append(this); -} - -CppQuickFixFactory::~CppQuickFixFactory() -{ - g_cppQuickFixFactories.removeOne(this); -} - -void CppQuickFixFactory::match(const Internal::CppQuickFixInterface &interface, - QuickFixOperations &result) -{ - if (m_clangdReplacement) { - if (const auto clangdVersion = CppModelManager::usesClangd( - interface.currentFile()->editor()->textDocument()); - clangdVersion && clangdVersion >= m_clangdReplacement) { - return; - } - } - - doMatch(interface, result); -} - -const QList<CppQuickFixFactory *> &CppQuickFixFactory::cppQuickFixFactories() -{ - return g_cppQuickFixFactories; -} - -namespace Internal { - -QString inlinePrefix(const FilePath &targetFile, const std::function<bool()> &extraCondition = {}) -{ - if (ProjectFile::isHeader(ProjectFile::classify(targetFile.path())) - && (!extraCondition || extraCondition())) { - return "inline "; - } - return {}; -} - -// In the following anonymous namespace all functions are collected, which could be of interest for -// different quick fixes. -namespace { - -enum DefPos { - DefPosInsideClass, - DefPosOutsideClass, - DefPosImplementationFile -}; - - -inline bool isQtStringLiteral(const QByteArray &id) -{ - return id == "QLatin1String" || id == "QLatin1Literal" || id == "QStringLiteral" - || id == "QByteArrayLiteral"; -} - -inline bool isQtStringTranslation(const QByteArray &id) -{ - return id == "tr" || id == "trUtf8" || id == "translate" || id == "QT_TRANSLATE_NOOP"; -} - -Class *isMemberFunction(const LookupContext &context, Function *function) -{ - QTC_ASSERT(function, return nullptr); - - Scope *enclosingScope = function->enclosingScope(); - while (!(enclosingScope->asNamespace() || enclosingScope->asClass())) - enclosingScope = enclosingScope->enclosingScope(); - QTC_ASSERT(enclosingScope != nullptr, return nullptr); - - const Name *functionName = function->name(); - if (!functionName) - return nullptr; - - if (!functionName->asQualifiedNameId()) - return nullptr; // trying to add a declaration for a global function - - const QualifiedNameId *q = functionName->asQualifiedNameId(); - if (!q->base()) - return nullptr; - - if (ClassOrNamespace *binding = context.lookupType(q->base(), enclosingScope)) { - const QList<Symbol *> symbols = binding->symbols(); - for (Symbol *s : symbols) { - if (Class *matchingClass = s->asClass()) - return matchingClass; - } - } - - return nullptr; -} - -Namespace *isNamespaceFunction(const LookupContext &context, Function *function) -{ - QTC_ASSERT(function, return nullptr); - if (isMemberFunction(context, function)) - return nullptr; - - Scope *enclosingScope = function->enclosingScope(); - while (!(enclosingScope->asNamespace() || enclosingScope->asClass())) - enclosingScope = enclosingScope->enclosingScope(); - QTC_ASSERT(enclosingScope != nullptr, return nullptr); - - const Name *functionName = function->name(); - if (!functionName) - return nullptr; - - // global namespace - if (!functionName->asQualifiedNameId()) { - const QList<Symbol *> symbols = context.globalNamespace()->symbols(); - for (Symbol *s : symbols) { - if (Namespace *matchingNamespace = s->asNamespace()) - return matchingNamespace; - } - return nullptr; - } - - const QualifiedNameId *q = functionName->asQualifiedNameId(); - if (!q->base()) - return nullptr; - - if (ClassOrNamespace *binding = context.lookupType(q->base(), enclosingScope)) { - const QList<Symbol *> symbols = binding->symbols(); - for (Symbol *s : symbols) { - if (Namespace *matchingNamespace = s->asNamespace()) - return matchingNamespace; - } - } - - return nullptr; -} - -// Given include is e.g. "afile.h" or <afile.h> (quotes/angle brackets included!). -static void insertNewIncludeDirective(const QString &include, - CppRefactoringFilePtr file, - const Document::Ptr &cppDocument, - ChangeSet &changes) -{ - // Find optimal position - unsigned newLinesToPrepend = 0; - unsigned newLinesToAppend = 0; - const int insertLine = lineForNewIncludeDirective(file->filePath(), file->document(), - cppDocument, IgnoreMocIncludes, AutoDetect, - include, - &newLinesToPrepend, &newLinesToAppend); - QTC_ASSERT(insertLine >= 1, return); - const int insertPosition = file->position(insertLine, 1); - QTC_ASSERT(insertPosition >= 0, return); - - // Construct text to insert - const QString includeLine = QLatin1String("#include ") + include + QLatin1Char('\n'); - QString prependedNewLines, appendedNewLines; - while (newLinesToAppend--) - appendedNewLines += QLatin1String("\n"); - while (newLinesToPrepend--) - prependedNewLines += QLatin1String("\n"); - const QString textToInsert = prependedNewLines + includeLine + appendedNewLines; - - // Insert - changes.insert(insertPosition, textToInsert); -} - -bool nameIncludesOperatorName(const Name *name) -{ - return name->asOperatorNameId() - || (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; -} - -// Returns a non-null value if and only if the cursor is on the name of a (proper) class -// declaration or at some place inside the body of a class declaration that does not -// correspond to an AST of its own, i.e. on "empty space". -ClassSpecifierAST *astForClassOperations(const CppQuickFixInterface &interface) -{ - const QList<AST *> &path = interface.path(); - if (path.isEmpty()) - return nullptr; - if (const auto classSpec = path.last()->asClassSpecifier()) // Cursor inside class decl? - return classSpec; - - // Cursor on a class name? - if (path.size() < 2) - return nullptr; - const SimpleNameAST * const nameAST = path.at(path.size() - 1)->asSimpleName(); - if (!nameAST || !interface.isCursorOn(nameAST)) - return nullptr; - if (const auto classSpec = path.at(path.size() - 2)->asClassSpecifier()) - return classSpec; - return nullptr; -} - -QString nameString(const NameAST *name) -{ - return CppCodeStyleSettings::currentProjectCodeStyleOverview().prettyName(name->name); -} - -static FullySpecifiedType typeOfExpr(const ExpressionAST *expr, - const CppRefactoringFilePtr &file, - const Snapshot &snapshot, - const LookupContext &context) -{ - TypeOfExpression typeOfExpression; - typeOfExpression.init(file->cppDocument(), snapshot, context.bindings()); - Scope *scope = file->scopeAt(expr->firstToken()); - const QList<LookupItem> result = typeOfExpression( - file->textOf(expr).toUtf8(), scope, TypeOfExpression::Preprocess); - if (result.isEmpty()) - return {}; - - SubstitutionEnvironment env; - env.setContext(context); - env.switchScope(result.first().scope()); - ClassOrNamespace *con = typeOfExpression.context().lookupType(scope); - if (!con) - con = typeOfExpression.context().globalNamespace(); - UseMinimalNames q(con); - env.enter(&q); - - Control *control = context.bindings()->control().get(); - return rewriteType(result.first().type(), &env, control); -} - -// FIXME: Needs to consider the scope at the insertion site. -QString declFromExpr(const TypeOrExpr &typeOrExpr, const CallAST *call, const NameAST *varName, - const Snapshot &snapshot, const LookupContext &context, - const CppRefactoringFilePtr &file, bool makeConst) -{ - const auto getTypeFromUser = [varName, call]() -> QString { - if (call) - return {}; - const QString typeFromUser = QInputDialog::getText(Core::ICore::dialogParent(), - Tr::tr("Provide the type"), - Tr::tr("Data type:"), QLineEdit::Normal); - if (!typeFromUser.isEmpty()) - return typeFromUser + ' ' + nameString(varName); - return {}; - }; - const auto getTypeOfExpr = [&](const ExpressionAST *expr) -> FullySpecifiedType { - return typeOfExpr(expr, file, snapshot, context); - }; - - const Overview oo = CppCodeStyleSettings::currentProjectCodeStyleOverview(); - const FullySpecifiedType type = std::holds_alternative<FullySpecifiedType>(typeOrExpr) - ? std::get<FullySpecifiedType>(typeOrExpr) - : getTypeOfExpr(std::get<const ExpressionAST *>(typeOrExpr)); - if (!call) - return type.isValid() ? oo.prettyType(type, varName->name) : getTypeFromUser(); - - Function func(file->cppDocument()->translationUnit(), 0, varName->name); - func.setConst(makeConst); - for (ExpressionListAST *it = call->expression_list; it; it = it->next) { - Argument * const arg = new Argument(nullptr, 0, nullptr); - arg->setType(getTypeOfExpr(it->value)); - func.addMember(arg); - } - return oo.prettyType(type) + ' ' + oo.prettyType(func.type(), varName->name); -} - -} // anonymous namespace - -namespace { - -class InverseLogicalComparisonOp: public CppQuickFixOperation -{ -public: - InverseLogicalComparisonOp(const CppQuickFixInterface &interface, - int priority, - BinaryExpressionAST *binary, - Kind invertToken) - : CppQuickFixOperation(interface, priority) - , binary(binary) - { - Token tok; - tok.f.kind = invertToken; - replacement = QLatin1String(tok.spell()); - - // check for enclosing nested expression - if (priority - 1 >= 0) - nested = interface.path()[priority - 1]->asNestedExpression(); - - // check for ! before parentheses - if (nested && priority - 2 >= 0) { - negation = interface.path()[priority - 2]->asUnaryExpression(); - if (negation && !interface.currentFile()->tokenAt(negation->unary_op_token).is(T_EXCLAIM)) - negation = nullptr; - } - } - - QString description() const override - { - return Tr::tr("Rewrite Using %1").arg(replacement); - } - - void perform() override - { - CppRefactoringChanges refactoring(snapshot()); - CppRefactoringFilePtr currentFile = refactoring.cppFile(filePath()); - - ChangeSet changes; - if (negation) { - // can't remove parentheses since that might break precedence - changes.remove(currentFile->range(negation->unary_op_token)); - } else if (nested) { - changes.insert(currentFile->startOf(nested), QLatin1String("!")); - } else { - changes.insert(currentFile->startOf(binary), QLatin1String("!(")); - changes.insert(currentFile->endOf(binary), QLatin1String(")")); - } - changes.replace(currentFile->range(binary->binary_op_token), replacement); - currentFile->setChangeSet(changes); - currentFile->apply(); - } - -private: - BinaryExpressionAST *binary = nullptr; - NestedExpressionAST *nested = nullptr; - UnaryExpressionAST *negation = nullptr; - - QString replacement; -}; - -} // anonymous namespace - -void InverseLogicalComparison::doMatch(const CppQuickFixInterface &interface, - QuickFixOperations &result) -{ - CppRefactoringFilePtr file = interface.currentFile(); - - const QList<AST *> &path = interface.path(); - if (path.isEmpty()) - return; - int index = path.size() - 1; - BinaryExpressionAST *binary = path.at(index)->asBinaryExpression(); - if (!binary) - return; - if (!interface.isCursorOn(binary->binary_op_token)) - return; - - Kind invertToken; - switch (file->tokenAt(binary->binary_op_token).kind()) { - case T_LESS_EQUAL: - invertToken = T_GREATER; - break; - case T_LESS: - invertToken = T_GREATER_EQUAL; - break; - case T_GREATER: - invertToken = T_LESS_EQUAL; - break; - case T_GREATER_EQUAL: - invertToken = T_LESS; - break; - case T_EQUAL_EQUAL: - invertToken = T_EXCLAIM_EQUAL; - break; - case T_EXCLAIM_EQUAL: - invertToken = T_EQUAL_EQUAL; - break; - default: - return; - } - - result << new InverseLogicalComparisonOp(interface, index, binary, invertToken); -} - -namespace { - -class FlipLogicalOperandsOp: public CppQuickFixOperation -{ -public: - FlipLogicalOperandsOp(const CppQuickFixInterface &interface, int priority, - BinaryExpressionAST *binary, QString replacement) - : CppQuickFixOperation(interface) - , binary(binary) - , replacement(replacement) - { - setPriority(priority); - } - - QString description() const override - { - if (replacement.isEmpty()) - return Tr::tr("Swap Operands"); - else - return Tr::tr("Rewrite Using %1").arg(replacement); - } - - void perform() override - { - CppRefactoringChanges refactoring(snapshot()); - CppRefactoringFilePtr currentFile = refactoring.cppFile(filePath()); - - ChangeSet changes; - changes.flip(currentFile->range(binary->left_expression), - currentFile->range(binary->right_expression)); - if (!replacement.isEmpty()) - changes.replace(currentFile->range(binary->binary_op_token), replacement); - - currentFile->setChangeSet(changes); - currentFile->apply(); - } - -private: - BinaryExpressionAST *binary; - QString replacement; -}; - -} // anonymous namespace - -void FlipLogicalOperands::doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) -{ - const QList<AST *> &path = interface.path(); - if (path.isEmpty()) - return; - CppRefactoringFilePtr file = interface.currentFile(); - - int index = path.size() - 1; - BinaryExpressionAST *binary = path.at(index)->asBinaryExpression(); - if (!binary) - return; - if (!interface.isCursorOn(binary->binary_op_token)) - return; - - Kind flipToken; - switch (file->tokenAt(binary->binary_op_token).kind()) { - case T_LESS_EQUAL: - flipToken = T_GREATER_EQUAL; - break; - case T_LESS: - flipToken = T_GREATER; - break; - case T_GREATER: - flipToken = T_LESS; - break; - case T_GREATER_EQUAL: - flipToken = T_LESS_EQUAL; - break; - case T_EQUAL_EQUAL: - case T_EXCLAIM_EQUAL: - case T_AMPER_AMPER: - case T_PIPE_PIPE: - flipToken = T_EOF_SYMBOL; - break; - default: - return; - } - - QString replacement; - if (flipToken != T_EOF_SYMBOL) { - Token tok; - tok.f.kind = flipToken; - replacement = QLatin1String(tok.spell()); - } - - result << new FlipLogicalOperandsOp(interface, index, binary, replacement); -} - -namespace { - -class RewriteLogicalAndOp: public CppQuickFixOperation -{ -public: - std::shared_ptr<ASTPatternBuilder> mk; - UnaryExpressionAST *left; - UnaryExpressionAST *right; - BinaryExpressionAST *pattern; - - RewriteLogicalAndOp(const CppQuickFixInterface &interface) - : CppQuickFixOperation(interface) - , mk(new ASTPatternBuilder) - { - left = mk->UnaryExpression(); - right = mk->UnaryExpression(); - pattern = mk->BinaryExpression(left, right); - } - - void perform() override - { - CppRefactoringChanges refactoring(snapshot()); - CppRefactoringFilePtr currentFile = refactoring.cppFile(filePath()); - - ChangeSet changes; - changes.replace(currentFile->range(pattern->binary_op_token), QLatin1String("||")); - changes.remove(currentFile->range(left->unary_op_token)); - changes.remove(currentFile->range(right->unary_op_token)); - const int start = currentFile->startOf(pattern); - const int end = currentFile->endOf(pattern); - changes.insert(start, QLatin1String("!(")); - changes.insert(end, QLatin1String(")")); - - currentFile->setChangeSet(changes); - currentFile->apply(); - } -}; - -} // anonymous namespace - -void RewriteLogicalAnd::doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) -{ - BinaryExpressionAST *expression = nullptr; - const QList<AST *> &path = interface.path(); - CppRefactoringFilePtr file = interface.currentFile(); - - int index = path.size() - 1; - for (; index != -1; --index) { - expression = path.at(index)->asBinaryExpression(); - if (expression) - break; - } - - if (!expression) - return; - - if (!interface.isCursorOn(expression->binary_op_token)) - return; - - QSharedPointer<RewriteLogicalAndOp> op(new RewriteLogicalAndOp(interface)); - - ASTMatcher matcher; - - if (expression->match(op->pattern, &matcher) && - file->tokenAt(op->pattern->binary_op_token).is(T_AMPER_AMPER) && - file->tokenAt(op->left->unary_op_token).is(T_EXCLAIM) && - file->tokenAt(op->right->unary_op_token).is(T_EXCLAIM)) { - op->setDescription(Tr::tr("Rewrite Condition Using ||")); - op->setPriority(index); - result.append(op); - } -} - -static bool checkDeclarationForSplit(SimpleDeclarationAST *declaration) -{ - if (!declaration->semicolon_token) - return false; - - if (!declaration->decl_specifier_list) - return false; - - for (SpecifierListAST *it = declaration->decl_specifier_list; it; it = it->next) { - SpecifierAST *specifier = it->value; - if (specifier->asEnumSpecifier() || specifier->asClassSpecifier()) - return false; - } - - return declaration->declarator_list && declaration->declarator_list->next; -} - -namespace { - -class SplitSimpleDeclarationOp: public CppQuickFixOperation -{ -public: - SplitSimpleDeclarationOp(const CppQuickFixInterface &interface, int priority, - SimpleDeclarationAST *decl) - : CppQuickFixOperation(interface, priority) - , declaration(decl) - { - setDescription(Tr::tr("Split Declaration")); - } - - void perform() override - { - CppRefactoringChanges refactoring(snapshot()); - CppRefactoringFilePtr currentFile = refactoring.cppFile(filePath()); - - ChangeSet changes; - - SpecifierListAST *specifiers = declaration->decl_specifier_list; - int declSpecifiersStart = currentFile->startOf(specifiers->firstToken()); - int declSpecifiersEnd = currentFile->endOf(specifiers->lastToken() - 1); - int insertPos = currentFile->endOf(declaration->semicolon_token); - - DeclaratorAST *prevDeclarator = declaration->declarator_list->value; - - for (DeclaratorListAST *it = declaration->declarator_list->next; it; it = it->next) { - DeclaratorAST *declarator = it->value; - - changes.insert(insertPos, QLatin1String("\n")); - changes.copy(declSpecifiersStart, declSpecifiersEnd, insertPos); - changes.insert(insertPos, QLatin1String(" ")); - changes.move(currentFile->range(declarator), insertPos); - changes.insert(insertPos, QLatin1String(";")); - - const int prevDeclEnd = currentFile->endOf(prevDeclarator); - changes.remove(prevDeclEnd, currentFile->startOf(declarator)); - - prevDeclarator = declarator; - } - - currentFile->setChangeSet(changes); - currentFile->apply(); - } - -private: - SimpleDeclarationAST *declaration; -}; - -} // anonymous namespace - -void SplitSimpleDeclaration::doMatch(const CppQuickFixInterface &interface, - QuickFixOperations &result) -{ - CoreDeclaratorAST *core_declarator = nullptr; - const QList<AST *> &path = interface.path(); - CppRefactoringFilePtr file = interface.currentFile(); - const int cursorPosition = file->cursor().selectionStart(); - - for (int index = path.size() - 1; index != -1; --index) { - AST *node = path.at(index); - - if (CoreDeclaratorAST *coreDecl = node->asCoreDeclarator()) { - core_declarator = coreDecl; - } else if (SimpleDeclarationAST *simpleDecl = node->asSimpleDeclaration()) { - if (checkDeclarationForSplit(simpleDecl)) { - SimpleDeclarationAST *declaration = simpleDecl; - - const int startOfDeclSpecifier = file->startOf(declaration->decl_specifier_list->firstToken()); - const int endOfDeclSpecifier = file->endOf(declaration->decl_specifier_list->lastToken() - 1); - - if (cursorPosition >= startOfDeclSpecifier && cursorPosition <= endOfDeclSpecifier) { - // the AST node under cursor is a specifier. - result << new SplitSimpleDeclarationOp(interface, index, declaration); - return; - } - - if (core_declarator && interface.isCursorOn(core_declarator)) { - // got a core-declarator under the text cursor. - result << new SplitSimpleDeclarationOp(interface, index, declaration); - return; - } - } - - return; - } - } -} - -namespace { -template<typename Statement> Statement *asControlStatement(AST *node) -{ - if constexpr (std::is_same_v<Statement, IfStatementAST>) - return node->asIfStatement(); - if constexpr (std::is_same_v<Statement, WhileStatementAST>) - return node->asWhileStatement(); - if constexpr (std::is_same_v<Statement, ForStatementAST>) - return node->asForStatement(); - if constexpr (std::is_same_v<Statement, RangeBasedForStatementAST>) - return node->asRangeBasedForStatement(); - if constexpr (std::is_same_v<Statement, DoStatementAST>) - return node->asDoStatement(); - return nullptr; -} - -template<typename Statement> -int triggerToken(const Statement *statement) -{ - if constexpr (std::is_same_v<Statement, IfStatementAST>) - return statement->if_token; - if constexpr (std::is_same_v<Statement, WhileStatementAST>) - return statement->while_token; - if constexpr (std::is_same_v<Statement, DoStatementAST>) - return statement->do_token; - if constexpr (std::is_same_v<Statement, ForStatementAST> - || std::is_same_v<Statement, RangeBasedForStatementAST>) { - return statement->for_token; - } -} - -template<typename Statement> -int tokenToInsertOpeningBraceAfter(const Statement *statement) -{ - if constexpr (std::is_same_v<Statement, DoStatementAST>) - return statement->do_token; - return statement->rparen_token; -} - -template<typename Statement> class AddBracesToControlStatementOp : public CppQuickFixOperation -{ -public: - AddBracesToControlStatementOp(const CppQuickFixInterface &interface, - const QList<Statement *> &statements, - StatementAST *elseStatement, - int elseToken) - : CppQuickFixOperation(interface, 0) - , m_statements(statements), m_elseStatement(elseStatement), m_elseToken(elseToken) - { - setDescription(Tr::tr("Add Curly Braces")); - } - - void perform() override - { - CppRefactoringChanges refactoring(snapshot()); - CppRefactoringFilePtr currentFile = refactoring.cppFile(filePath()); - - ChangeSet changes; - for (Statement * const statement : m_statements) { - const int start = currentFile->endOf(tokenToInsertOpeningBraceAfter(statement)); - changes.insert(start, QLatin1String(" {")); - if constexpr (std::is_same_v<Statement, DoStatementAST>) { - const int end = currentFile->startOf(statement->while_token); - changes.insert(end, QLatin1String("} ")); - } else if constexpr (std::is_same_v<Statement, IfStatementAST>) { - if (statement->else_statement) { - changes.insert(currentFile->startOf(statement->else_token), "} "); - } else { - changes.insert(currentFile->endOf(statement->statement->lastToken() - 1), - "\n}"); - } - - } else { - const int end = currentFile->endOf(statement->statement->lastToken() - 1); - changes.insert(end, QLatin1String("\n}")); - } - } - if (m_elseStatement) { - changes.insert(currentFile->endOf(m_elseToken), " {"); - changes.insert(currentFile->endOf(m_elseStatement->lastToken() - 1), "\n}"); - } - - currentFile->setChangeSet(changes); - currentFile->apply(); - } - -private: - const QList<Statement *> m_statements; - StatementAST * const m_elseStatement; - const int m_elseToken; -}; - -} // anonymous namespace - -template<typename Statement> -bool checkControlStatementsHelper(const CppQuickFixInterface &interface, QuickFixOperations &result) -{ - Statement * const statement = asControlStatement<Statement>(interface.path().last()); - if (!statement) - return false; - - QList<Statement *> statements; - if (interface.isCursorOn(triggerToken(statement)) && statement->statement - && !statement->statement->asCompoundStatement()) { - statements << statement; - } - - StatementAST *elseStmt = nullptr; - int elseToken = 0; - if constexpr (std::is_same_v<Statement, IfStatementAST>) { - IfStatementAST *currentIfStmt = statement; - for (elseStmt = currentIfStmt->else_statement, elseToken = currentIfStmt->else_token; - elseStmt && (currentIfStmt = elseStmt->asIfStatement()); - elseStmt = currentIfStmt->else_statement, elseToken = currentIfStmt->else_token) { - if (currentIfStmt->statement && !currentIfStmt->statement->asCompoundStatement()) - statements << currentIfStmt; - } - if (elseStmt && (elseStmt->asIfStatement() || elseStmt->asCompoundStatement())) { - elseStmt = nullptr; - elseToken = 0; - } - } - - if (!statements.isEmpty() || elseStmt) - result << new AddBracesToControlStatementOp(interface, statements, elseStmt, elseToken); - return true; -} - -template<typename ...Statements> -void checkControlStatements(const CppQuickFixInterface &interface, QuickFixOperations &result) -{ - (... || checkControlStatementsHelper<Statements>(interface, result)); -} - -void AddBracesToControlStatement::doMatch(const CppQuickFixInterface &interface, - QuickFixOperations &result) -{ - if (interface.path().isEmpty()) - return; - checkControlStatements<IfStatementAST, - WhileStatementAST, - ForStatementAST, - RangeBasedForStatementAST, - DoStatementAST>(interface, result); -} - -namespace { - -class MoveDeclarationOutOfIfOp: public CppQuickFixOperation -{ -public: - MoveDeclarationOutOfIfOp(const CppQuickFixInterface &interface) - : CppQuickFixOperation(interface) - { - setDescription(Tr::tr("Move Declaration out of Condition")); - - reset(); - } - - void reset() - { - condition = mk.Condition(); - pattern = mk.IfStatement(condition); - } - - void perform() override - { - CppRefactoringChanges refactoring(snapshot()); - CppRefactoringFilePtr currentFile = refactoring.cppFile(filePath()); - - ChangeSet changes; - - changes.copy(currentFile->range(core), currentFile->startOf(condition)); - - int insertPos = currentFile->startOf(pattern); - changes.move(currentFile->range(condition), insertPos); - changes.insert(insertPos, QLatin1String(";\n")); - - currentFile->setChangeSet(changes); - currentFile->apply(); - } - - ASTMatcher matcher; - ASTPatternBuilder mk; - ConditionAST *condition = nullptr; - IfStatementAST *pattern = nullptr; - CoreDeclaratorAST *core = nullptr; -}; - -} // anonymous namespace - -void MoveDeclarationOutOfIf::doMatch(const CppQuickFixInterface &interface, - QuickFixOperations &result) -{ - const QList<AST *> &path = interface.path(); - using Ptr = QSharedPointer<MoveDeclarationOutOfIfOp>; - Ptr op(new MoveDeclarationOutOfIfOp(interface)); - - int index = path.size() - 1; - for (; index != -1; --index) { - if (IfStatementAST *statement = path.at(index)->asIfStatement()) { - if (statement->match(op->pattern, &op->matcher) && op->condition->declarator) { - DeclaratorAST *declarator = op->condition->declarator; - op->core = declarator->core_declarator; - if (!op->core) - return; - - if (interface.isCursorOn(op->core)) { - op->setPriority(index); - result.append(op); - return; - } - - op->reset(); - } - } - } -} - -namespace { - -class MoveDeclarationOutOfWhileOp: public CppQuickFixOperation -{ -public: - MoveDeclarationOutOfWhileOp(const CppQuickFixInterface &interface) - : CppQuickFixOperation(interface) - { - setDescription(Tr::tr("Move Declaration out of Condition")); - reset(); - } - - void reset() - { - condition = mk.Condition(); - pattern = mk.WhileStatement(condition); - } - - void perform() override - { - CppRefactoringChanges refactoring(snapshot()); - CppRefactoringFilePtr currentFile = refactoring.cppFile(filePath()); - - ChangeSet changes; - - changes.insert(currentFile->startOf(condition), QLatin1String("(")); - changes.insert(currentFile->endOf(condition), QLatin1String(") != 0")); - - int insertPos = currentFile->startOf(pattern); - const int conditionStart = currentFile->startOf(condition); - changes.move(conditionStart, currentFile->startOf(core), insertPos); - changes.copy(currentFile->range(core), insertPos); - changes.insert(insertPos, QLatin1String(";\n")); - - currentFile->setChangeSet(changes); - currentFile->apply(); - } - - ASTMatcher matcher; - ASTPatternBuilder mk; - ConditionAST *condition = nullptr; - WhileStatementAST *pattern = nullptr; - CoreDeclaratorAST *core = nullptr; -}; - -} // anonymous namespace - -void MoveDeclarationOutOfWhile::doMatch(const CppQuickFixInterface &interface, - QuickFixOperations &result) -{ - const QList<AST *> &path = interface.path(); - QSharedPointer<MoveDeclarationOutOfWhileOp> op(new MoveDeclarationOutOfWhileOp(interface)); - - int index = path.size() - 1; - for (; index != -1; --index) { - if (WhileStatementAST *statement = path.at(index)->asWhileStatement()) { - if (statement->match(op->pattern, &op->matcher) && op->condition->declarator) { - DeclaratorAST *declarator = op->condition->declarator; - op->core = declarator->core_declarator; - - if (!op->core) - return; - - if (!declarator->equal_token) - return; - - if (!declarator->initializer) - return; - - if (interface.isCursorOn(op->core)) { - op->setPriority(index); - result.append(op); - return; - } - - op->reset(); - } - } - } -} - -namespace { - -class SplitIfStatementOp: public CppQuickFixOperation -{ -public: - SplitIfStatementOp(const CppQuickFixInterface &interface, int priority, - IfStatementAST *pattern, BinaryExpressionAST *condition) - : CppQuickFixOperation(interface, priority) - , pattern(pattern) - , condition(condition) - { - setDescription(Tr::tr("Split if Statement")); - } - - void perform() override - { - CppRefactoringChanges refactoring(snapshot()); - CppRefactoringFilePtr currentFile = refactoring.cppFile(filePath()); - - const Token binaryToken = currentFile->tokenAt(condition->binary_op_token); - - if (binaryToken.is(T_AMPER_AMPER)) - splitAndCondition(currentFile); - else - splitOrCondition(currentFile); - } - - void splitAndCondition(CppRefactoringFilePtr currentFile) const - { - ChangeSet changes; - - int startPos = currentFile->startOf(pattern); - changes.insert(startPos, QLatin1String("if (")); - changes.move(currentFile->range(condition->left_expression), startPos); - changes.insert(startPos, QLatin1String(") {\n")); - - const int lExprEnd = currentFile->endOf(condition->left_expression); - changes.remove(lExprEnd, currentFile->startOf(condition->right_expression)); - changes.insert(currentFile->endOf(pattern), QLatin1String("\n}")); - - currentFile->setChangeSet(changes); - currentFile->apply(); - } - - void splitOrCondition(CppRefactoringFilePtr currentFile) const - { - ChangeSet changes; - - StatementAST *ifTrueStatement = pattern->statement; - CompoundStatementAST *compoundStatement = ifTrueStatement->asCompoundStatement(); - - int insertPos = currentFile->endOf(ifTrueStatement); - if (compoundStatement) - changes.insert(insertPos, QLatin1String(" ")); - else - changes.insert(insertPos, QLatin1String("\n")); - changes.insert(insertPos, QLatin1String("else if (")); - - const int rExprStart = currentFile->startOf(condition->right_expression); - changes.move(rExprStart, currentFile->startOf(pattern->rparen_token), insertPos); - changes.insert(insertPos, QLatin1String(")")); - - const int rParenEnd = currentFile->endOf(pattern->rparen_token); - changes.copy(rParenEnd, currentFile->endOf(pattern->statement), insertPos); - - const int lExprEnd = currentFile->endOf(condition->left_expression); - changes.remove(lExprEnd, currentFile->startOf(condition->right_expression)); - - currentFile->setChangeSet(changes); - currentFile->apply(); - } - -private: - IfStatementAST *pattern; - BinaryExpressionAST *condition; -}; - -} // anonymous namespace - -void SplitIfStatement::doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) -{ - IfStatementAST *pattern = nullptr; - const QList<AST *> &path = interface.path(); - - int index = path.size() - 1; - for (; index != -1; --index) { - AST *node = path.at(index); - if (IfStatementAST *stmt = node->asIfStatement()) { - pattern = stmt; - break; - } - } - - if (!pattern || !pattern->statement) - return; - - unsigned splitKind = 0; - for (++index; index < path.size(); ++index) { - AST *node = path.at(index); - BinaryExpressionAST *condition = node->asBinaryExpression(); - if (!condition) - return; - - Token binaryToken = interface.currentFile()->tokenAt(condition->binary_op_token); - - // only accept a chain of ||s or &&s - no mixing - if (!splitKind) { - splitKind = binaryToken.kind(); - if (splitKind != T_AMPER_AMPER && splitKind != T_PIPE_PIPE) - return; - // we can't reliably split &&s in ifs with an else branch - if (splitKind == T_AMPER_AMPER && pattern->else_statement) - return; - } else if (splitKind != binaryToken.kind()) { - return; - } - - if (interface.isCursorOn(condition->binary_op_token)) { - result << new SplitIfStatementOp(interface, index, pattern, condition); - return; - } - } -} - -/* Analze a string/character literal like "x", QLatin1String("x") and return the literal - * (StringLiteral or NumericLiteral for characters) and its type - * and the enclosing function (QLatin1String, tr...) */ - -enum StringLiteralType { TypeString, TypeObjCString, TypeChar, TypeNone }; - -enum ActionFlags { - EncloseInQLatin1CharAction = 0x1, - EncloseInQLatin1StringAction = 0x2, - EncloseInQStringLiteralAction = 0x4, - EncloseInQByteArrayLiteralAction = 0x8, - EncloseActionMask = EncloseInQLatin1CharAction | EncloseInQLatin1StringAction - | EncloseInQStringLiteralAction | EncloseInQByteArrayLiteralAction, - TranslateTrAction = 0x10, - TranslateQCoreApplicationAction = 0x20, - TranslateNoopAction = 0x40, - TranslationMask = TranslateTrAction | TranslateQCoreApplicationAction | TranslateNoopAction, - RemoveObjectiveCAction = 0x100, - ConvertEscapeSequencesToCharAction = 0x200, - ConvertEscapeSequencesToStringAction = 0x400, - SingleQuoteAction = 0x800, - DoubleQuoteAction = 0x1000 -}; - -/* Convert single-character string literals into character literals with some - * special cases "a" --> 'a', "'" --> '\'', "\n" --> '\n', "\"" --> '"'. */ -static QByteArray stringToCharEscapeSequences(const QByteArray &content) -{ - if (content.size() == 1) - return content.at(0) == '\'' ? QByteArray("\\'") : content; - if (content.size() == 2 && content.at(0) == '\\') - return content == "\\\"" ? QByteArray(1, '"') : content; - return QByteArray(); -} - -/* Convert character literal into a string literal with some special cases - * 'a' -> "a", '\n' -> "\n", '\'' --> "'", '"' --> "\"". */ -static QByteArray charToStringEscapeSequences(const QByteArray &content) -{ - if (content.size() == 1) - return content.at(0) == '"' ? QByteArray("\\\"") : content; - if (content.size() == 2) - return content == "\\'" ? QByteArray("'") : content; - return QByteArray(); -} - -static QString msgQtStringLiteralDescription(const QString &replacement) -{ - return Tr::tr("Enclose in %1(...)").arg(replacement); -} - -static QString stringLiteralReplacement(unsigned actions) -{ - if (actions & EncloseInQLatin1CharAction) - return QLatin1String("QLatin1Char"); - if (actions & EncloseInQLatin1StringAction) - return QLatin1String("QLatin1String"); - if (actions & EncloseInQStringLiteralAction) - return QLatin1String("QStringLiteral"); - if (actions & EncloseInQByteArrayLiteralAction) - return QLatin1String("QByteArrayLiteral"); - if (actions & TranslateTrAction) - return QLatin1String("tr"); - if (actions & TranslateQCoreApplicationAction) - return QLatin1String("QCoreApplication::translate"); - if (actions & TranslateNoopAction) - return QLatin1String("QT_TRANSLATE_NOOP"); - return QString(); -} - -static ExpressionAST *analyzeStringLiteral(const QList<AST *> &path, - const CppRefactoringFilePtr &file, StringLiteralType *type, - QByteArray *enclosingFunction = nullptr, - CallAST **enclosingFunctionCall = nullptr) -{ - *type = TypeNone; - if (enclosingFunction) - enclosingFunction->clear(); - if (enclosingFunctionCall) - *enclosingFunctionCall = nullptr; - - if (path.isEmpty()) - return nullptr; - - ExpressionAST *literal = path.last()->asExpression(); - if (literal) { - if (literal->asStringLiteral()) { - // Check for Objective C string (@"bla") - const QChar firstChar = file->charAt(file->startOf(literal)); - *type = firstChar == QLatin1Char('@') ? TypeObjCString : TypeString; - } else if (NumericLiteralAST *numericLiteral = literal->asNumericLiteral()) { - // character ('c') constants are numeric. - if (file->tokenAt(numericLiteral->literal_token).is(T_CHAR_LITERAL)) - *type = TypeChar; - } - } - - if (*type != TypeNone && enclosingFunction && path.size() > 1) { - if (CallAST *call = path.at(path.size() - 2)->asCall()) { - if (call->base_expression) { - if (IdExpressionAST *idExpr = call->base_expression->asIdExpression()) { - if (SimpleNameAST *functionName = idExpr->name->asSimpleName()) { - *enclosingFunction = file->tokenAt(functionName->identifier_token).identifier->chars(); - if (enclosingFunctionCall) - *enclosingFunctionCall = call; - } - } - } - } - } - return literal; -} - -namespace { - -/// Operation performs the operations of type ActionFlags passed in as actions. -class WrapStringLiteralOp : public CppQuickFixOperation -{ -public: - WrapStringLiteralOp(const CppQuickFixInterface &interface, int priority, - unsigned actions, const QString &description, ExpressionAST *literal, - const QString &translationContext = QString()) - : CppQuickFixOperation(interface, priority), m_actions(actions), m_literal(literal), - m_translationContext(translationContext) - { - setDescription(description); - } - - void perform() override - { - CppRefactoringChanges refactoring(snapshot()); - CppRefactoringFilePtr currentFile = refactoring.cppFile(filePath()); - - ChangeSet changes; - - const int startPos = currentFile->startOf(m_literal); - const int endPos = currentFile->endOf(m_literal); - - // kill leading '@'. No need to adapt endPos, that is done by ChangeSet - if (m_actions & RemoveObjectiveCAction) - changes.remove(startPos, startPos + 1); - - // Fix quotes - if (m_actions & (SingleQuoteAction | DoubleQuoteAction)) { - const QString newQuote((m_actions & SingleQuoteAction) - ? QLatin1Char('\'') : QLatin1Char('"')); - changes.replace(startPos, startPos + 1, newQuote); - changes.replace(endPos - 1, endPos, newQuote); - } - - // Convert single character strings into character constants - if (m_actions & ConvertEscapeSequencesToCharAction) { - StringLiteralAST *stringLiteral = m_literal->asStringLiteral(); - QTC_ASSERT(stringLiteral, return ;); - const QByteArray oldContents(currentFile->tokenAt(stringLiteral->literal_token).identifier->chars()); - const QByteArray newContents = stringToCharEscapeSequences(oldContents); - QTC_ASSERT(!newContents.isEmpty(), return ;); - if (oldContents != newContents) - changes.replace(startPos + 1, endPos -1, QString::fromLatin1(newContents)); - } - - // Convert character constants into strings constants - if (m_actions & ConvertEscapeSequencesToStringAction) { - NumericLiteralAST *charLiteral = m_literal->asNumericLiteral(); // char 'c' constants are numerical. - QTC_ASSERT(charLiteral, return ;); - const QByteArray oldContents(currentFile->tokenAt(charLiteral->literal_token).identifier->chars()); - const QByteArray newContents = charToStringEscapeSequences(oldContents); - QTC_ASSERT(!newContents.isEmpty(), return ;); - if (oldContents != newContents) - changes.replace(startPos + 1, endPos -1, QString::fromLatin1(newContents)); - } - - // Enclose in literal or translation function, macro. - if (m_actions & (EncloseActionMask | TranslationMask)) { - changes.insert(endPos, QString(QLatin1Char(')'))); - QString leading = stringLiteralReplacement(m_actions); - leading += QLatin1Char('('); - if (m_actions - & (TranslateQCoreApplicationAction | TranslateNoopAction)) { - leading += QLatin1Char('"'); - leading += m_translationContext; - leading += QLatin1String("\", "); - } - changes.insert(startPos, leading); - } - - currentFile->setChangeSet(changes); - currentFile->apply(); - } - -private: - const unsigned m_actions; - ExpressionAST *m_literal; - const QString m_translationContext; -}; - -} // anonymous namespace - -void WrapStringLiteral::doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) -{ - StringLiteralType type = TypeNone; - QByteArray enclosingFunction; - const QList<AST *> &path = interface.path(); - CppRefactoringFilePtr file = interface.currentFile(); - ExpressionAST *literal = analyzeStringLiteral(path, file, &type, &enclosingFunction); - if (!literal || type == TypeNone) - return; - if ((type == TypeChar && enclosingFunction == "QLatin1Char") - || isQtStringLiteral(enclosingFunction) - || isQtStringTranslation(enclosingFunction)) - return; - - const int priority = path.size() - 1; // very high priority - if (type == TypeChar) { - unsigned actions = EncloseInQLatin1CharAction; - QString description = msgQtStringLiteralDescription(stringLiteralReplacement(actions)); - result << new WrapStringLiteralOp(interface, priority, actions, description, literal); - if (NumericLiteralAST *charLiteral = literal->asNumericLiteral()) { - const QByteArray contents(file->tokenAt(charLiteral->literal_token).identifier->chars()); - if (!charToStringEscapeSequences(contents).isEmpty()) { - actions = DoubleQuoteAction | ConvertEscapeSequencesToStringAction; - description = Tr::tr("Convert to String Literal"); - result << new WrapStringLiteralOp(interface, priority, actions, - description, literal); - } - } - } else { - const unsigned objectiveCActions = type == TypeObjCString ? - unsigned(RemoveObjectiveCAction) : 0u; - unsigned actions = 0; - if (StringLiteralAST *stringLiteral = literal->asStringLiteral()) { - const QByteArray contents(file->tokenAt(stringLiteral->literal_token).identifier->chars()); - if (!stringToCharEscapeSequences(contents).isEmpty()) { - actions = EncloseInQLatin1CharAction | SingleQuoteAction - | ConvertEscapeSequencesToCharAction | objectiveCActions; - QString description = - Tr::tr("Convert to Character Literal and Enclose in QLatin1Char(...)"); - result << new WrapStringLiteralOp(interface, priority, actions, - description, literal); - actions &= ~EncloseInQLatin1CharAction; - description = Tr::tr("Convert to Character Literal"); - result << new WrapStringLiteralOp(interface, priority, actions, - description, literal); - } - } - actions = EncloseInQLatin1StringAction | objectiveCActions; - result << new WrapStringLiteralOp(interface, priority, actions, - msgQtStringLiteralDescription(stringLiteralReplacement(actions)), literal); - actions = EncloseInQStringLiteralAction | objectiveCActions; - result << new WrapStringLiteralOp(interface, priority, actions, - msgQtStringLiteralDescription(stringLiteralReplacement(actions)), literal); - actions = EncloseInQByteArrayLiteralAction | objectiveCActions; - result << new WrapStringLiteralOp(interface, priority, actions, - msgQtStringLiteralDescription(stringLiteralReplacement(actions)), literal); - } -} - -void TranslateStringLiteral::doMatch(const CppQuickFixInterface &interface, - QuickFixOperations &result) -{ - // Initialize - StringLiteralType type = TypeNone; - QByteArray enclosingFunction; - const QList<AST *> &path = interface.path(); - CppRefactoringFilePtr file = interface.currentFile(); - ExpressionAST *literal = analyzeStringLiteral(path, file, &type, &enclosingFunction); - if (!literal || type != TypeString - || isQtStringLiteral(enclosingFunction) || isQtStringTranslation(enclosingFunction)) - return; - - QString trContext; - - std::shared_ptr<Control> control = interface.context().bindings()->control(); - const Name *trName = control->identifier("tr"); - - // Check whether we are in a function: - const QString description = Tr::tr("Mark as Translatable"); - for (int i = path.size() - 1; i >= 0; --i) { - if (FunctionDefinitionAST *definition = path.at(i)->asFunctionDefinition()) { - Function *function = definition->symbol; - ClassOrNamespace *b = interface.context().lookupType(function); - if (b) { - // Do we have a tr function? - const QList<LookupItem> items = b->find(trName); - for (const LookupItem &r : items) { - Symbol *s = r.declaration(); - if (s->type()->asFunctionType()) { - // no context required for tr - result << new WrapStringLiteralOp(interface, path.size() - 1, - TranslateTrAction, - description, literal); - return; - } - } - } - // We need to do a QCA::translate, so we need a context. - // Use fully qualified class name: - Overview oo; - const QList<const Name *> names = LookupContext::path(function); - for (const Name *n : names) { - if (!trContext.isEmpty()) - trContext.append(QLatin1String("::")); - trContext.append(oo.prettyName(n)); - } - // ... or global if none available! - if (trContext.isEmpty()) - trContext = QLatin1String("GLOBAL"); - result << new WrapStringLiteralOp(interface, path.size() - 1, - TranslateQCoreApplicationAction, - description, literal, trContext); - return; - } - } - - // We need to use Q_TRANSLATE_NOOP - result << new WrapStringLiteralOp(interface, path.size() - 1, - TranslateNoopAction, - description, literal, trContext); -} - -namespace { - -class ConvertCStringToNSStringOp: public CppQuickFixOperation -{ -public: - ConvertCStringToNSStringOp(const CppQuickFixInterface &interface, int priority, - StringLiteralAST *stringLiteral, CallAST *qlatin1Call) - : CppQuickFixOperation(interface, priority) - , stringLiteral(stringLiteral) - , qlatin1Call(qlatin1Call) - { - setDescription(Tr::tr("Convert to Objective-C String Literal")); - } - - void perform() override - { - CppRefactoringChanges refactoring(snapshot()); - CppRefactoringFilePtr currentFile = refactoring.cppFile(filePath()); - - ChangeSet changes; - - if (qlatin1Call) { - changes.replace(currentFile->startOf(qlatin1Call), currentFile->startOf(stringLiteral), - QLatin1String("@")); - changes.remove(currentFile->endOf(stringLiteral), currentFile->endOf(qlatin1Call)); - } else { - changes.insert(currentFile->startOf(stringLiteral), QLatin1String("@")); - } - - currentFile->setChangeSet(changes); - currentFile->apply(); - } - -private: - StringLiteralAST *stringLiteral; - CallAST *qlatin1Call; -}; - -} // anonymous namespace - -void ConvertCStringToNSString::doMatch(const CppQuickFixInterface &interface, - QuickFixOperations &result) -{ - CppRefactoringFilePtr file = interface.currentFile(); - - if (!interface.editor()->cppEditorDocument()->isObjCEnabled()) - return; - - StringLiteralType type = TypeNone; - QByteArray enclosingFunction; - CallAST *qlatin1Call; - const QList<AST *> &path = interface.path(); - ExpressionAST *literal = analyzeStringLiteral(path, file, &type, &enclosingFunction, - &qlatin1Call); - if (!literal || type != TypeString) - return; - if (!isQtStringLiteral(enclosingFunction)) - qlatin1Call = nullptr; - - result << new ConvertCStringToNSStringOp(interface, path.size() - 1, literal->asStringLiteral(), - qlatin1Call); -} - -namespace { - -class ConvertNumericLiteralOp: public CppQuickFixOperation -{ -public: - ConvertNumericLiteralOp(const CppQuickFixInterface &interface, int start, int end, - const QString &replacement) - : CppQuickFixOperation(interface) - , start(start) - , end(end) - , replacement(replacement) - {} - - void perform() override - { - CppRefactoringChanges refactoring(snapshot()); - CppRefactoringFilePtr currentFile = refactoring.cppFile(filePath()); - - ChangeSet changes; - changes.replace(start, end, replacement); - currentFile->setChangeSet(changes); - currentFile->apply(); - } - -private: - int start, end; - QString replacement; -}; - -} // anonymous namespace - -void ConvertNumericLiteral::doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) -{ - const QList<AST *> &path = interface.path(); - CppRefactoringFilePtr file = interface.currentFile(); - - if (path.isEmpty()) - return; - - NumericLiteralAST *literal = path.last()->asNumericLiteral(); - - if (!literal) - return; - - Token token = file->tokenAt(literal->asNumericLiteral()->literal_token); - if (!token.is(T_NUMERIC_LITERAL)) - return; - const NumericLiteral *numeric = token.number; - if (numeric->isDouble() || numeric->isFloat()) - return; - - // remove trailing L or U and stuff - const char * const spell = numeric->chars(); - int numberLength = numeric->size(); - while (numberLength > 0 && !std::isxdigit(spell[numberLength - 1])) - --numberLength; - if (numberLength < 1) - return; - - // convert to number - bool valid; - ulong value = 0; - const QString x = QString::fromUtf8(spell).left(numberLength); - if (x.startsWith("0b", Qt::CaseInsensitive)) - value = x.mid(2).toULong(&valid, 2); - else - value = x.toULong(&valid, 0); - - if (!valid) - return; - - const int priority = path.size() - 1; // very high priority - const int start = file->startOf(literal); - const char * const str = numeric->chars(); - - const bool isBinary = numberLength > 2 && str[0] == '0' && (str[1] == 'b' || str[1] == 'B'); - const bool isOctal = numberLength >= 2 && str[0] == '0' && str[1] >= '0' && str[1] <= '7'; - const bool isDecimal = !(isBinary || isOctal || numeric->isHex()); - - if (!numeric->isHex()) { - /* - Convert integer literal to hex representation. - Replace - 0b100000 - 32 - 040 - With - 0x20 - - */ - const QString replacement = QString::asprintf("0x%lX", value); - auto op = new ConvertNumericLiteralOp(interface, start, start + numberLength, replacement); - op->setDescription(Tr::tr("Convert to Hexadecimal")); - op->setPriority(priority); - result << op; - } - - if (!isOctal) { - /* - Convert integer literal to octal representation. - Replace - 0b100000 - 32 - 0x20 - With - 040 - */ - const QString replacement = QString::asprintf("0%lo", value); - auto op = new ConvertNumericLiteralOp(interface, start, start + numberLength, replacement); - op->setDescription(Tr::tr("Convert to Octal")); - op->setPriority(priority); - result << op; - } - - if (!isDecimal) { - /* - Convert integer literal to decimal representation. - Replace - 0b100000 - 0x20 - 040 - With - 32 - */ - const QString replacement = QString::asprintf("%lu", value); - auto op = new ConvertNumericLiteralOp(interface, start, start + numberLength, replacement); - op->setDescription(Tr::tr("Convert to Decimal")); - op->setPriority(priority); - result << op; - } - - if (!isBinary) { - /* - Convert integer literal to binary representation. - Replace - 32 - 0x20 - 040 - With - 0b100000 - */ - QString replacement = "0b"; - if (value == 0) { - replacement.append('0'); - } else { - std::bitset<std::numeric_limits<decltype (value)>::digits> b(value); - QRegularExpression re("^[0]*"); - replacement.append(QString::fromStdString(b.to_string()).remove(re)); - } - auto op = new ConvertNumericLiteralOp(interface, start, start + numberLength, replacement); - op->setDescription(Tr::tr("Convert to Binary")); - op->setPriority(priority); - result << op; - } -} - -namespace { - -class AddLocalDeclarationOp: public CppQuickFixOperation -{ -public: - AddLocalDeclarationOp(const CppQuickFixInterface &interface, - int priority, - const BinaryExpressionAST *binaryAST, - const SimpleNameAST *simpleNameAST) - : CppQuickFixOperation(interface, priority) - , binaryAST(binaryAST) - , simpleNameAST(simpleNameAST) - { - setDescription(Tr::tr("Add Local Declaration")); - } - - void perform() override - { - CppRefactoringChanges refactoring(snapshot()); - CppRefactoringFilePtr currentFile = refactoring.cppFile(filePath()); - QString declaration = getDeclaration(); - - if (!declaration.isEmpty()) { - ChangeSet changes; - changes.replace(currentFile->startOf(binaryAST), - currentFile->endOf(simpleNameAST), - declaration); - currentFile->setChangeSet(changes); - currentFile->apply(); - } - } - -private: - QString getDeclaration() - { - CppRefactoringChanges refactoring(snapshot()); - CppRefactoringFilePtr currentFile = refactoring.cppFile(filePath()); - Overview oo = CppCodeStyleSettings::currentProjectCodeStyleOverview(); - const auto settings = CppQuickFixProjectsSettings::getQuickFixSettings( - ProjectExplorer::ProjectTree::currentProject()); - - if (currentFile->cppDocument()->languageFeatures().cxx11Enabled && settings->useAuto) - return "auto " + oo.prettyName(simpleNameAST->name); - return declFromExpr(binaryAST->right_expression, nullptr, simpleNameAST, snapshot(), - context(), currentFile, false); - } - - const BinaryExpressionAST *binaryAST; - const SimpleNameAST *simpleNameAST; -}; - -} // anonymous namespace - -namespace { - -class ConvertToCamelCaseOp: public CppQuickFixOperation -{ -public: - ConvertToCamelCaseOp(const CppQuickFixInterface &interface, const QString &name, - const AST *nameAst, bool test) - : CppQuickFixOperation(interface, -1) - , m_name(name) - , m_nameAst(nameAst) - , m_isAllUpper(name.isUpper()) - , m_test(test) - { - setDescription(Tr::tr("Convert to Camel Case")); - } - - void perform() override - { - CppRefactoringChanges refactoring(snapshot()); - CppRefactoringFilePtr currentFile = refactoring.cppFile(filePath()); - - QString newName = m_isAllUpper ? m_name.toLower() : m_name; - for (int i = 1; i < newName.length(); ++i) { - const QChar c = newName.at(i); - if (c.isUpper() && m_isAllUpper) { - newName[i] = c.toLower(); - } else if (i < newName.length() - 1 && isConvertibleUnderscore(newName, i)) { - newName.remove(i, 1); - newName[i] = newName.at(i).toUpper(); - } - } - if (m_test) { - ChangeSet changeSet; - changeSet.replace(currentFile->range(m_nameAst), newName); - currentFile->setChangeSet(changeSet); - currentFile->apply(); - } else { - editor()->renameUsages(newName); - } - } - - static bool isConvertibleUnderscore(const QString &name, int pos) - { - return name.at(pos) == QLatin1Char('_') && name.at(pos+1).isLetter() - && !(pos == 1 && name.at(0) == QLatin1Char('m')); - } - -private: - const QString m_name; - const AST * const m_nameAst; - const bool m_isAllUpper; - const bool m_test; -}; - -} // anonymous namespace - -void ConvertToCamelCase::doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) -{ - const QList<AST *> &path = interface.path(); - - if (path.isEmpty()) - return; - - AST * const ast = path.last(); - const Name *name = nullptr; - const AST *astForName = nullptr; - if (const NameAST * const nameAst = ast->asName()) { - if (nameAst->name && nameAst->name->asNameId()) { - astForName = nameAst; - name = nameAst->name; - } - } else if (const NamespaceAST * const namespaceAst = ast->asNamespace()) { - astForName = namespaceAst; - name = namespaceAst->symbol->name(); - } - - if (!name) - return; - - QString nameString = QString::fromUtf8(name->identifier()->chars()); - if (nameString.length() < 3) - return; - for (int i = 1; i < nameString.length() - 1; ++i) { - if (ConvertToCamelCaseOp::isConvertibleUnderscore(nameString, i)) { - result << new ConvertToCamelCaseOp(interface, nameString, astForName, m_test); - return; - } - } -} - -AddIncludeForUndefinedIdentifierOp::AddIncludeForUndefinedIdentifierOp( - const CppQuickFixInterface &interface, int priority, const QString &include) - : CppQuickFixOperation(interface, priority) - , m_include(include) -{ - setDescription(Tr::tr("Add #include %1").arg(m_include)); -} - -void AddIncludeForUndefinedIdentifierOp::perform() -{ - CppRefactoringChanges refactoring(snapshot()); - CppRefactoringFilePtr file = refactoring.cppFile(filePath()); - - ChangeSet changes; - insertNewIncludeDirective(m_include, file, semanticInfo().doc, changes); - file->setChangeSet(changes); - file->apply(); -} - -AddForwardDeclForUndefinedIdentifierOp::AddForwardDeclForUndefinedIdentifierOp( - const CppQuickFixInterface &interface, - int priority, - const QString &fqClassName, - int symbolPos) - : CppQuickFixOperation(interface, priority), m_className(fqClassName), m_symbolPos(symbolPos) -{ - setDescription(Tr::tr("Add forward declaration for %1").arg(m_className)); -} - -void AddForwardDeclForUndefinedIdentifierOp::perform() -{ - const QStringList parts = m_className.split("::"); - QTC_ASSERT(!parts.isEmpty(), return); - const QStringList namespaces = parts.mid(0, parts.length() - 1); - - CppRefactoringChanges refactoring(snapshot()); - CppRefactoringFilePtr file = refactoring.cppFile(filePath()); - - NSVisitor visitor(file.data(), namespaces, m_symbolPos); - visitor.accept(file->cppDocument()->translationUnit()->ast()); - const auto stringToInsert = [&visitor, symbol = parts.last()] { - QString s = "\n"; - for (const QString &ns : visitor.remainingNamespaces()) - s += "namespace " + ns + " { "; - s += "class " + symbol + ';'; - for (int i = 0; i < visitor.remainingNamespaces().size(); ++i) - s += " }"; - return s; - }; - - int insertPos = 0; - - // Find the position to insert: - // If we have a matching namespace, we do the insertion there. - // If we don't have a matching namespace, but there is another namespace in the file, - // we assume that to be a good position for our insertion. - // Otherwise, do the insertion after the last include that comes before the use of the symbol. - // If there is no such include, do the insertion before the first token. - if (visitor.enclosingNamespace()) { - insertPos = file->startOf(visitor.enclosingNamespace()->linkage_body) + 1; - } else if (visitor.firstNamespace()) { - insertPos = file->startOf(visitor.firstNamespace()); - } else { - const QTextCursor tc = file->document()->find( - QRegularExpression("^\\s*#include .*$"), - m_symbolPos, - QTextDocument::FindBackward | QTextDocument::FindCaseSensitively); - if (!tc.isNull()) - insertPos = tc.position() + 1; - else if (visitor.firstToken()) - insertPos = file->startOf(visitor.firstToken()); - } - - QString insertion = stringToInsert(); - if (file->charAt(insertPos - 1) != QChar::ParagraphSeparator) - insertion.prepend('\n'); - if (file->charAt(insertPos) != QChar::ParagraphSeparator) - insertion.append('\n'); - ChangeSet s; - s.insert(insertPos, insertion); - file->setChangeSet(s); - file->apply(); -} - -namespace { - -QString findShortestInclude(const QString currentDocumentFilePath, - const QString candidateFilePath, - const ProjectExplorer::HeaderPaths &headerPaths) -{ - QString result; - - const QFileInfo fileInfo(candidateFilePath); - - if (fileInfo.path() == QFileInfo(currentDocumentFilePath).path()) { - result = QLatin1Char('"') + fileInfo.fileName() + QLatin1Char('"'); - } else { - for (const ProjectExplorer::HeaderPath &headerPath : headerPaths) { - if (!candidateFilePath.startsWith(headerPath.path)) - continue; - QString relativePath = candidateFilePath.mid(headerPath.path.size()); - if (!relativePath.isEmpty() && relativePath.at(0) == QLatin1Char('/')) - relativePath = relativePath.mid(1); - if (result.isEmpty() || relativePath.size() + 2 < result.size()) - result = QLatin1Char('<') + relativePath + QLatin1Char('>'); - } - } - - return result; -} - -QString findMatchingInclude(const QString &className, - const ProjectExplorer::HeaderPaths &headerPaths) -{ - const QStringList candidateFileNames{className, className + ".h", className + ".hpp", - className.toLower(), className.toLower() + ".h", className.toLower() + ".hpp"}; - for (const QString &fileName : candidateFileNames) { - for (const ProjectExplorer::HeaderPath &headerPath : headerPaths) { - const QString headerPathCandidate = headerPath.path + QLatin1Char('/') + fileName; - const QFileInfo fileInfo(headerPathCandidate); - if (fileInfo.exists() && fileInfo.isFile()) - return '<' + fileName + '>'; - } - } - return {}; -} - -ProjectExplorer::HeaderPaths relevantHeaderPaths(const QString &filePath) -{ - ProjectExplorer::HeaderPaths headerPaths; - - const QList<ProjectPart::ConstPtr> projectParts = CppModelManager::projectPart(filePath); - if (projectParts.isEmpty()) { // Not part of any project, better use all include paths than none - headerPaths += CppModelManager::headerPaths(); - } else { - for (const ProjectPart::ConstPtr &part : projectParts) - headerPaths += part->headerPaths; - } - - return headerPaths; -} - -NameAST *nameUnderCursor(const QList<AST *> &path) -{ - if (path.isEmpty()) - return nullptr; - - NameAST *nameAst = nullptr; - for (int i = path.size() - 1; i >= 0; --i) { - AST * const ast = path.at(i); - if (SimpleNameAST *simpleName = ast->asSimpleName()) { - nameAst = simpleName; - } else if (TemplateIdAST *templateId = ast->asTemplateId()) { - nameAst = templateId; - } else if (nameAst && ast->asNamedTypeSpecifier()) { - break; // Stop at "Foo" for "N::Bar<@Foo>" - } else if (QualifiedNameAST *qualifiedName = ast->asQualifiedName()) { - nameAst = qualifiedName; - break; - } - } - - return nameAst; -} - -enum class LookupResult { Declared, ForwardDeclared, NotDeclared }; -LookupResult lookUpDefinition(const CppQuickFixInterface &interface, const NameAST *nameAst) -{ - QTC_ASSERT(nameAst && nameAst->name, return LookupResult::NotDeclared); - - // Find the enclosing scope - int line, column; - const Document::Ptr doc = interface.semanticInfo().doc; - doc->translationUnit()->getTokenPosition(nameAst->firstToken(), &line, &column); - Scope *scope = doc->scopeAt(line, column); - if (!scope) - return LookupResult::NotDeclared; - - // Try to find the class/template definition - const Name *name = nameAst->name; - const QList<LookupItem> results = interface.context().lookup(name, scope); - LookupResult best = LookupResult::NotDeclared; - for (const LookupItem &item : results) { - if (Symbol *declaration = item.declaration()) { - if (declaration->asClass()) - return LookupResult::Declared; - if (declaration->asForwardClassDeclaration()) { - best = LookupResult::ForwardDeclared; - continue; - } - if (Template *templ = declaration->asTemplate()) { - if (Symbol *declaration = templ->declaration()) { - if (declaration->asClass()) - return LookupResult::Declared; - if (declaration->asForwardClassDeclaration()) { - best = LookupResult::ForwardDeclared; - continue; - } - } - } - return LookupResult::Declared; - } - } - - return best; -} - -QString templateNameAsString(const TemplateNameId *templateName) -{ - const Identifier *id = templateName->identifier(); - return QString::fromUtf8(id->chars(), id->size()); -} - -Snapshot forwardingHeaders(const CppQuickFixInterface &interface) -{ - Snapshot result; - - const Snapshot docs = interface.snapshot(); - for (Document::Ptr doc : docs) { - if (doc->globalSymbolCount() == 0 && doc->resolvedIncludes().size() == 1) - result.insert(doc); - } - - return result; -} - -QList<IndexItem::Ptr> matchName(const Name *name, QString *className) -{ - if (!name) - return {}; - - QString simpleName; - QList<IndexItem::Ptr> matches; - CppLocatorData *locatorData = CppModelManager::locatorData(); - const Overview oo; - if (const QualifiedNameId *qualifiedName = name->asQualifiedNameId()) { - const Name *name = qualifiedName->name(); - if (const TemplateNameId *templateName = name->asTemplateNameId()) { - *className = templateNameAsString(templateName); - } else { - simpleName = oo.prettyName(name); - *className = simpleName; - matches = locatorData->findSymbols(IndexItem::Class, *className); - if (matches.isEmpty()) { - if (const Name *name = qualifiedName->base()) { - if (const TemplateNameId *templateName = name->asTemplateNameId()) - *className = templateNameAsString(templateName); - else - *className = oo.prettyName(name); - } - } - } - } else if (const TemplateNameId *templateName = name->asTemplateNameId()) { - *className = templateNameAsString(templateName); - } else { - *className = oo.prettyName(name); - } - - if (matches.isEmpty()) - matches = locatorData->findSymbols(IndexItem::Class, *className); - - if (matches.isEmpty() && !simpleName.isEmpty()) - *className = simpleName; - - return matches; -} - -} // anonymous namespace - -void AddIncludeForUndefinedIdentifier::doMatch(const CppQuickFixInterface &interface, - QuickFixOperations &result) -{ - const NameAST *nameAst = nameUnderCursor(interface.path()); - if (!nameAst || !nameAst->name) - return; - - const LookupResult lookupResult = lookUpDefinition(interface, nameAst); - if (lookupResult == LookupResult::Declared) - return; - - QString className; - const QString currentDocumentFilePath = interface.semanticInfo().doc->filePath().toString(); - const ProjectExplorer::HeaderPaths headerPaths = relevantHeaderPaths(currentDocumentFilePath); - FilePaths headers; - - const QList<IndexItem::Ptr> matches = matchName(nameAst->name, &className); - // Find an include file through the locator - if (!matches.isEmpty()) { - QList<IndexItem::Ptr> indexItems; - const Snapshot forwardHeaders = forwardingHeaders(interface); - for (const IndexItem::Ptr &info : matches) { - if (!info || info->symbolName() != className) - continue; - indexItems << info; - - Snapshot localForwardHeaders = forwardHeaders; - localForwardHeaders.insert(interface.snapshot().document(info->filePath())); - FilePaths headerAndItsForwardingHeaders; - headerAndItsForwardingHeaders << info->filePath(); - headerAndItsForwardingHeaders += localForwardHeaders.filesDependingOn(info->filePath()); - - for (const FilePath &header : std::as_const(headerAndItsForwardingHeaders)) { - const QString include = findShortestInclude(currentDocumentFilePath, - header.toString(), - headerPaths); - if (include.size() > 2) { - const QString headerFileName = info->filePath().fileName(); - QTC_ASSERT(!headerFileName.isEmpty(), break); - - int priority = 0; - if (headerFileName == className) - priority = 2; - else if (headerFileName.at(1).isUpper()) - priority = 1; - - result << new AddIncludeForUndefinedIdentifierOp(interface, priority, - include); - headers << header; - } - } - } - - if (lookupResult == LookupResult::NotDeclared && indexItems.size() == 1) { - QString qualifiedName = Overview().prettyName(nameAst->name); - if (qualifiedName.startsWith("::")) - qualifiedName.remove(0, 2); - if (indexItems.first()->scopedSymbolName().endsWith(qualifiedName)) { - const ProjectExplorer::Node * const node = ProjectExplorer::ProjectTree - ::nodeForFile(interface.filePath()); - ProjectExplorer::FileType fileType = node && node->asFileNode() - ? node->asFileNode()->fileType() : ProjectExplorer::FileType::Unknown; - if (fileType == ProjectExplorer::FileType::Unknown - && ProjectFile::isHeader(ProjectFile::classify(interface.filePath().toString()))) { - fileType = ProjectExplorer::FileType::Header; - } - if (fileType == ProjectExplorer::FileType::Header) { - result << new AddForwardDeclForUndefinedIdentifierOp( - interface, 0, indexItems.first()->scopedSymbolName(), - interface.currentFile()->startOf(nameAst)); - } - } - } - } - - if (className.isEmpty()) - return; - - // Fallback: Check the include paths for files that look like candidates - // for the given name. - if (!Utils::contains(headers, - [&className](const Utils::FilePath &fp) { return fp.fileName() == className; })) { - const QString include = findMatchingInclude(className, headerPaths); - const auto matcher = [&include](const QuickFixOperation::Ptr &o) { - const auto includeOp = o.dynamicCast<AddIncludeForUndefinedIdentifierOp>(); - return includeOp && includeOp->include() == include; - }; - if (!include.isEmpty() && !Utils::contains(result, matcher)) - result << new AddIncludeForUndefinedIdentifierOp(interface, 1, include); - } -} - -namespace { - -class RearrangeParamDeclarationListOp: public CppQuickFixOperation -{ -public: - enum Target { TargetPrevious, TargetNext }; - - RearrangeParamDeclarationListOp(const CppQuickFixInterface &interface, AST *currentParam, - AST *targetParam, Target target) - : CppQuickFixOperation(interface) - , m_currentParam(currentParam) - , m_targetParam(targetParam) - { - QString targetString; - if (target == TargetPrevious) - targetString = Tr::tr("Switch with Previous Parameter"); - else - targetString = Tr::tr("Switch with Next Parameter"); - setDescription(targetString); - } - - void perform() override - { - CppRefactoringChanges refactoring(snapshot()); - CppRefactoringFilePtr currentFile = refactoring.cppFile(filePath()); - - int targetEndPos = currentFile->endOf(m_targetParam); - ChangeSet changes; - changes.flip(currentFile->startOf(m_currentParam), currentFile->endOf(m_currentParam), - currentFile->startOf(m_targetParam), targetEndPos); - currentFile->setChangeSet(changes); - currentFile->setOpenEditor(false, targetEndPos); - currentFile->apply(); - } - -private: - AST *m_currentParam; - AST *m_targetParam; -}; - -} // anonymous namespace - -void RearrangeParamDeclarationList::doMatch(const CppQuickFixInterface &interface, - QuickFixOperations &result) -{ - const QList<AST *> path = interface.path(); - - ParameterDeclarationAST *paramDecl = nullptr; - int index = path.size() - 1; - for (; index != -1; --index) { - paramDecl = path.at(index)->asParameterDeclaration(); - if (paramDecl) - break; - } - - if (index < 1) - return; - - ParameterDeclarationClauseAST *paramDeclClause = path.at(index-1)->asParameterDeclarationClause(); - QTC_ASSERT(paramDeclClause && paramDeclClause->parameter_declaration_list, return); - - ParameterDeclarationListAST *paramListNode = paramDeclClause->parameter_declaration_list; - ParameterDeclarationListAST *prevParamListNode = nullptr; - while (paramListNode) { - if (paramDecl == paramListNode->value) - break; - prevParamListNode = paramListNode; - paramListNode = paramListNode->next; - } - - if (!paramListNode) - return; - - if (prevParamListNode) - result << new RearrangeParamDeclarationListOp(interface, paramListNode->value, - prevParamListNode->value, RearrangeParamDeclarationListOp::TargetPrevious); - if (paramListNode->next) - result << new RearrangeParamDeclarationListOp(interface, paramListNode->value, - paramListNode->next->value, RearrangeParamDeclarationListOp::TargetNext); -} - -namespace { - -class ReformatPointerDeclarationOp: public CppQuickFixOperation -{ -public: - ReformatPointerDeclarationOp(const CppQuickFixInterface &interface, const ChangeSet change) - : CppQuickFixOperation(interface) - , m_change(change) - { - QString description; - if (m_change.operationList().size() == 1) { - description = Tr::tr( - "Reformat to \"%1\"").arg(m_change.operationList().constFirst().text()); - } else { // > 1 - description = Tr::tr("Reformat Pointers or References"); - } - setDescription(description); - } - - void perform() override - { - CppRefactoringChanges refactoring(snapshot()); - CppRefactoringFilePtr currentFile = refactoring.cppFile(filePath()); - currentFile->setChangeSet(m_change); - currentFile->apply(); - } - -private: - ChangeSet m_change; -}; - -/// Filter the results of ASTPath. -/// The resulting list contains the supported AST types only once. -/// For this, the results of ASTPath are iterated in reverse order. -class ReformatPointerDeclarationASTPathResultsFilter -{ -public: - QList<AST*> filter(const QList<AST*> &astPathList) - { - QList<AST*> filtered; - - for (int i = astPathList.size() - 1; i >= 0; --i) { - AST *ast = astPathList.at(i); - - if (!m_hasSimpleDeclaration && ast->asSimpleDeclaration()) { - m_hasSimpleDeclaration = true; - filtered.append(ast); - } else if (!m_hasFunctionDefinition && ast->asFunctionDefinition()) { - m_hasFunctionDefinition = true; - filtered.append(ast); - } else if (!m_hasParameterDeclaration && ast->asParameterDeclaration()) { - m_hasParameterDeclaration = true; - filtered.append(ast); - } else if (!m_hasIfStatement && ast->asIfStatement()) { - m_hasIfStatement = true; - filtered.append(ast); - } else if (!m_hasWhileStatement && ast->asWhileStatement()) { - m_hasWhileStatement = true; - filtered.append(ast); - } else if (!m_hasForStatement && ast->asForStatement()) { - m_hasForStatement = true; - filtered.append(ast); - } else if (!m_hasForeachStatement && ast->asForeachStatement()) { - m_hasForeachStatement = true; - filtered.append(ast); - } - } - - return filtered; - } - -private: - bool m_hasSimpleDeclaration = false; - bool m_hasFunctionDefinition = false; - bool m_hasParameterDeclaration = false; - bool m_hasIfStatement = false; - bool m_hasWhileStatement = false; - bool m_hasForStatement = false; - bool m_hasForeachStatement = false; -}; - -} // anonymous namespace - -void ReformatPointerDeclaration::doMatch(const CppQuickFixInterface &interface, - QuickFixOperations &result) -{ - const QList<AST *> &path = interface.path(); - CppRefactoringFilePtr file = interface.currentFile(); - - Overview overview = CppCodeStyleSettings::currentProjectCodeStyleOverview(); - overview.showArgumentNames = true; - overview.showReturnTypes = true; - - const QTextCursor cursor = file->cursor(); - ChangeSet change; - PointerDeclarationFormatter formatter(file, overview, - PointerDeclarationFormatter::RespectCursor); - - if (cursor.hasSelection()) { - // This will no work always as expected since this function is only called if - // interface-path() is not empty. If the user selects the whole document via - // ctrl-a and there is an empty line in the end, then the cursor is not on - // any AST and therefore no quick fix will be triggered. - change = formatter.format(file->cppDocument()->translationUnit()->ast()); - if (!change.isEmpty()) - result << new ReformatPointerDeclarationOp(interface, change); - } else { - const QList<AST *> suitableASTs - = ReformatPointerDeclarationASTPathResultsFilter().filter(path); - for (AST *ast : suitableASTs) { - change = formatter.format(ast); - if (!change.isEmpty()) { - result << new ReformatPointerDeclarationOp(interface, change); - return; - } - } - } -} - -namespace { - -class CaseStatementCollector : public ASTVisitor -{ -public: - CaseStatementCollector(Document::Ptr document, const Snapshot &snapshot, - Scope *scope) - : ASTVisitor(document->translationUnit()), - document(document), - scope(scope) - { - typeOfExpression.init(document, snapshot); - } - - QStringList operator ()(AST *ast) - { - values.clear(); - foundCaseStatementLevel = false; - accept(ast); - return values; - } - - bool preVisit(AST *ast) override { - if (CaseStatementAST *cs = ast->asCaseStatement()) { - foundCaseStatementLevel = true; - if (ExpressionAST *csExpression = cs->expression) { - if (ExpressionAST *expression = csExpression->asIdExpression()) { - QList<LookupItem> candidates = typeOfExpression(expression, document, scope); - if (!candidates.isEmpty() && candidates.first().declaration()) { - Symbol *decl = candidates.first().declaration(); - values << prettyPrint.prettyName(LookupContext::fullyQualifiedName(decl)); - } - } - } - return true; - } else if (foundCaseStatementLevel) { - return false; - } - return true; - } - - Overview prettyPrint; - bool foundCaseStatementLevel = false; - QStringList values; - TypeOfExpression typeOfExpression; - Document::Ptr document; - Scope *scope; -}; - -class CompleteSwitchCaseStatementOp: public CppQuickFixOperation -{ -public: - CompleteSwitchCaseStatementOp(const CppQuickFixInterface &interface, - int priority, CompoundStatementAST *compoundStatement, const QStringList &values) - : CppQuickFixOperation(interface, priority) - , compoundStatement(compoundStatement) - , values(values) - { - setDescription(Tr::tr("Complete Switch Statement")); - } - - void perform() override - { - CppRefactoringChanges refactoring(snapshot()); - CppRefactoringFilePtr currentFile = refactoring.cppFile(filePath()); - - ChangeSet changes; - int start = currentFile->endOf(compoundStatement->lbrace_token); - changes.insert(start, QLatin1String("\ncase ") - + values.join(QLatin1String(":\nbreak;\ncase ")) - + QLatin1String(":\nbreak;")); - currentFile->setChangeSet(changes); - currentFile->apply(); - } - - CompoundStatementAST *compoundStatement; - QStringList values; -}; - -static Enum *findEnum(const QList<LookupItem> &results, const LookupContext &ctxt) -{ - for (const LookupItem &result : results) { - const FullySpecifiedType fst = result.type(); - - Type *type = result.declaration() ? result.declaration()->type().type() - : fst.type(); - - if (!type) - continue; - if (Enum *e = type->asEnumType()) - return e; - if (const NamedType *namedType = type->asNamedType()) { - if (ClassOrNamespace *con = ctxt.lookupType(namedType->name(), result.scope())) { - QList<Enum *> enums = con->unscopedEnums(); - const QList<Symbol *> symbols = con->symbols(); - for (Symbol * const s : symbols) { - if (const auto e = s->asEnum()) - enums << e; - } - const Name *referenceName = namedType->name(); - if (const QualifiedNameId *qualifiedName = referenceName->asQualifiedNameId()) - referenceName = qualifiedName->name(); - for (Enum *e : std::as_const(enums)) { - if (const Name *candidateName = e->name()) { - if (candidateName->match(referenceName)) - return e; - } - } - } - } - } - - return nullptr; -} - -Enum *conditionEnum(const CppQuickFixInterface &interface, SwitchStatementAST *statement) -{ - Block *block = statement->symbol; - Scope *scope = interface.semanticInfo().doc->scopeAt(block->line(), block->column()); - TypeOfExpression typeOfExpression; - typeOfExpression.setExpandTemplates(true); - typeOfExpression.init(interface.semanticInfo().doc, interface.snapshot()); - const QList<LookupItem> results = typeOfExpression(statement->condition, - interface.semanticInfo().doc, - scope); - - return findEnum(results, typeOfExpression.context()); -} - -} // anonymous namespace - -void CompleteSwitchCaseStatement::doMatch(const CppQuickFixInterface &interface, - QuickFixOperations &result) -{ - const QList<AST *> &path = interface.path(); - - if (path.isEmpty()) - return; - - // look for switch statement - for (int depth = path.size() - 1; depth >= 0; --depth) { - AST *ast = path.at(depth); - SwitchStatementAST *switchStatement = ast->asSwitchStatement(); - if (switchStatement) { - if (!switchStatement->statement || !switchStatement->symbol) - return; - CompoundStatementAST *compoundStatement = switchStatement->statement->asCompoundStatement(); - if (!compoundStatement) // we ignore pathologic case "switch (t) case A: ;" - return; - // look if the condition's type is an enum - if (Enum *e = conditionEnum(interface, switchStatement)) { - // check the possible enum values - QStringList values; - Overview prettyPrint; - for (int i = 0; i < e->memberCount(); ++i) { - if (Declaration *decl = e->memberAt(i)->asDeclaration()) - values << prettyPrint.prettyName(LookupContext::fullyQualifiedName(decl)); - } - // Get the used values - Block *block = switchStatement->symbol; - CaseStatementCollector caseValues(interface.semanticInfo().doc, interface.snapshot(), - interface.semanticInfo().doc->scopeAt(block->line(), block->column())); - const QStringList usedValues = caseValues(switchStatement); - // save the values that would be added - for (const QString &usedValue : usedValues) - values.removeAll(usedValue); - if (!values.isEmpty()) - result << new CompleteSwitchCaseStatementOp(interface, depth, - compoundStatement, values); - return; - } - - return; - } - } -} - -namespace { - -class InsertDeclOperation: public CppQuickFixOperation -{ -public: - InsertDeclOperation(const CppQuickFixInterface &interface, - const FilePath &targetFilePath, const Class *targetSymbol, - InsertionPointLocator::AccessSpec xsSpec, const QString &decl, int priority) - : CppQuickFixOperation(interface, priority) - , m_targetFilePath(targetFilePath) - , m_targetSymbol(targetSymbol) - , m_xsSpec(xsSpec) - , m_decl(decl) - { - setDescription(Tr::tr("Add %1 Declaration") - .arg(InsertionPointLocator::accessSpecToString(xsSpec))); - } - - void perform() override - { - CppRefactoringChanges refactoring(snapshot()); - - InsertionPointLocator locator(refactoring); - const InsertionLocation loc = locator.methodDeclarationInClass( - m_targetFilePath, m_targetSymbol, m_xsSpec); - QTC_ASSERT(loc.isValid(), return); - - CppRefactoringFilePtr targetFile = refactoring.cppFile(m_targetFilePath); - int targetPosition = targetFile->position(loc.line(), loc.column()); - - ChangeSet target; - target.insert(targetPosition, loc.prefix() + m_decl); - targetFile->setChangeSet(target); - targetFile->setOpenEditor(true, targetPosition); - targetFile->apply(); - } - - static QString generateDeclaration(const Function *function); - -private: - FilePath m_targetFilePath; - const Class *m_targetSymbol; - InsertionPointLocator::AccessSpec m_xsSpec; - QString m_decl; -}; - -class DeclOperationFactory -{ -public: - DeclOperationFactory(const CppQuickFixInterface &interface, const FilePath &filePath, - const Class *matchingClass, const QString &decl) - : m_interface(interface) - , m_filePath(filePath) - , m_matchingClass(matchingClass) - , m_decl(decl) - {} - - QuickFixOperation *operator()(InsertionPointLocator::AccessSpec xsSpec, int priority) - { - return new InsertDeclOperation(m_interface, m_filePath, m_matchingClass, xsSpec, m_decl, priority); - } - -private: - const CppQuickFixInterface &m_interface; - const FilePath &m_filePath; - const Class *m_matchingClass; - const QString &m_decl; -}; - -} // anonymous namespace - -void InsertDeclFromDef::doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) -{ - const QList<AST *> &path = interface.path(); - CppRefactoringFilePtr file = interface.currentFile(); - - FunctionDefinitionAST *funDef = nullptr; - int idx = 0; - for (; idx < path.size(); ++idx) { - AST *node = path.at(idx); - if (idx > 1) { - if (DeclaratorIdAST *declId = node->asDeclaratorId()) { - if (file->isCursorOn(declId)) { - if (FunctionDefinitionAST *candidate = path.at(idx - 2)->asFunctionDefinition()) { - funDef = candidate; - break; - } - } - } - } - - if (node->asClassSpecifier()) - return; - } - - if (!funDef || !funDef->symbol) - return; - - Function *fun = funDef->symbol; - if (Class *matchingClass = isMemberFunction(interface.context(), fun)) { - const QualifiedNameId *qName = fun->name()->asQualifiedNameId(); - for (Symbol *symbol = matchingClass->find(qName->identifier()); - symbol; symbol = symbol->next()) { - Symbol *s = symbol; - if (fun->enclosingScope()->asTemplate()) { - if (const Template *templ = s->type()->asTemplateType()) { - if (Symbol *decl = templ->declaration()) { - if (decl->type()->asFunctionType()) - s = decl; - } - } - } - if (!s->name() - || !qName->identifier()->match(s->identifier()) - || !s->type()->asFunctionType()) - continue; - - if (s->type().match(fun->type())) { - // Declaration exists. - return; - } - } - const FilePath fileName = matchingClass->filePath(); - const QString decl = InsertDeclOperation::generateDeclaration(fun); - - // Add several possible insertion locations for declaration - DeclOperationFactory operation(interface, fileName, matchingClass, decl); - - result << operation(InsertionPointLocator::Public, 5) - << operation(InsertionPointLocator::PublicSlot, 4) - << operation(InsertionPointLocator::Protected, 3) - << operation(InsertionPointLocator::ProtectedSlot, 2) - << operation(InsertionPointLocator::Private, 1) - << operation(InsertionPointLocator::PrivateSlot, 0); - } -} - -QString InsertDeclOperation::generateDeclaration(const Function *function) -{ - Overview oo = CppCodeStyleSettings::currentProjectCodeStyleOverview(); - oo.showFunctionSignatures = true; - oo.showReturnTypes = true; - oo.showArgumentNames = true; - oo.showEnclosingTemplate = true; - - QString decl; - decl += oo.prettyType(function->type(), function->unqualifiedName()); - decl += QLatin1String(";\n"); - - return decl; -} - -namespace { - -class InsertDefOperation: public CppQuickFixOperation -{ -public: - // Make sure that either loc is valid or targetFileName is not empty. - InsertDefOperation(const CppQuickFixInterface &interface, - Declaration *decl, DeclaratorAST *declAST, const InsertionLocation &loc, - const DefPos defpos, const FilePath &targetFileName = {}, - bool freeFunction = false) - : CppQuickFixOperation(interface, 0) - , m_decl(decl) - , m_declAST(declAST) - , m_loc(loc) - , m_defpos(defpos) - , m_targetFilePath(targetFileName) - { - if (m_defpos == DefPosImplementationFile) { - const FilePath declFile = decl->filePath(); - const FilePath targetFile = m_loc.isValid() ? m_loc.filePath() : m_targetFilePath; - const FilePath resolved = targetFile.relativePathFrom(declFile.parentDir()); - setPriority(2); - setDescription(Tr::tr("Add Definition in %1").arg(resolved.displayName())); - } else if (freeFunction) { - setDescription(Tr::tr("Add Definition Here")); - } else if (m_defpos == DefPosInsideClass) { - setDescription(Tr::tr("Add Definition Inside Class")); - } else if (m_defpos == DefPosOutsideClass) { - setPriority(1); - setDescription(Tr::tr("Add Definition Outside Class")); - } - } - - static void insertDefinition( - const CppQuickFixOperation *op, - InsertionLocation loc, - DefPos defPos, - DeclaratorAST *declAST, - Declaration *decl, - const FilePath &targetFilePath, - ChangeSet *changeSet = nullptr) - { - CppRefactoringChanges refactoring(op->snapshot()); - if (!loc.isValid()) - loc = insertLocationForMethodDefinition(decl, true, NamespaceHandling::Ignore, - refactoring, targetFilePath); - QTC_ASSERT(loc.isValid(), return); - - CppRefactoringFilePtr targetFile = refactoring.cppFile(loc.filePath()); - Overview oo = CppCodeStyleSettings::currentProjectCodeStyleOverview(); - oo.showFunctionSignatures = true; - oo.showReturnTypes = true; - oo.showArgumentNames = true; - oo.showEnclosingTemplate = true; - - // What we really want is to show template parameters for the class, but not for the - // function, but we cannot express that. This is an approximation that will work - // as long as either the surrounding class or the function is not a template. - oo.showTemplateParameters = decl->enclosingClass() - && decl->enclosingClass()->enclosingTemplate(); - - if (defPos == DefPosInsideClass) { - const int targetPos = targetFile->position(loc.line(), loc.column()); - ChangeSet localChangeSet; - ChangeSet * const target = changeSet ? changeSet : &localChangeSet; - target->replace(targetPos - 1, targetPos, QLatin1String("\n {\n\n}")); // replace ';' - - if (!changeSet) { - targetFile->setChangeSet(*target); - targetFile->setOpenEditor(true, targetPos); - targetFile->apply(); - - // Move cursor inside definition - QTextCursor c = targetFile->cursor(); - c.setPosition(targetPos); - c.movePosition(QTextCursor::Down); - c.movePosition(QTextCursor::EndOfLine); - op->editor()->setTextCursor(c); - } - } else { - // make target lookup context - Document::Ptr targetDoc = targetFile->cppDocument(); - Scope *targetScope = targetDoc->scopeAt(loc.line(), loc.column()); - - // Correct scope in case of a function try-block. See QTCREATORBUG-14661. - if (targetScope && targetScope->asBlock()) { - if (Class * const enclosingClass = targetScope->enclosingClass()) - targetScope = enclosingClass; - else - targetScope = targetScope->enclosingNamespace(); - } - - LookupContext targetContext(targetDoc, op->snapshot()); - ClassOrNamespace *targetCoN = targetContext.lookupType(targetScope); - if (!targetCoN) - targetCoN = targetContext.globalNamespace(); - - // setup rewriting to get minimally qualified names - SubstitutionEnvironment env; - env.setContext(op->context()); - env.switchScope(decl->enclosingScope()); - UseMinimalNames q(targetCoN); - env.enter(&q); - Control *control = op->context().bindings()->control().get(); - - // rewrite the function type - const FullySpecifiedType tn = rewriteType(decl->type(), &env, control); - - // rewrite the function name - if (nameIncludesOperatorName(decl->name())) { - CppRefactoringFilePtr file = refactoring.cppFile(op->filePath()); - const QString operatorNameText = file->textOf(declAST->core_declarator); - oo.includeWhiteSpaceInOperatorName = operatorNameText.contains(QLatin1Char(' ')); - } - const QString name = oo.prettyName(LookupContext::minimalName(decl, targetCoN, - control)); - - const QString inlinePref = inlinePrefix(targetFilePath, [defPos] { - return defPos == DefPosOutsideClass; - }); - - const QString prettyType = oo.prettyType(tn, name); - - QString input = prettyType; - int index = 0; - while (input.startsWith("template")) { - QRegularExpression templateRegex("template\\s*<[^>]*>"); - QRegularExpressionMatch match = templateRegex.match(input); - if (match.hasMatch()) { - index += match.captured().size() + 1; - input = input.mid(match.captured().size() + 1); - } - } - - QString defText = prettyType; - defText.insert(index, inlinePref); - defText += QLatin1String("\n{\n\n}"); - - ChangeSet localChangeSet; - ChangeSet * const target = changeSet ? changeSet : &localChangeSet; - const int targetPos = targetFile->position(loc.line(), loc.column()); - target->insert(targetPos, loc.prefix() + defText + loc.suffix()); - - if (!changeSet) { - targetFile->setChangeSet(*target); - targetFile->setOpenEditor(true, targetPos); - targetFile->apply(); - - // Move cursor inside definition - QTextCursor c = targetFile->cursor(); - c.setPosition(targetPos); - c.movePosition(QTextCursor::Down, QTextCursor::MoveAnchor, - loc.prefix().count(QLatin1String("\n")) + 2); - c.movePosition(QTextCursor::EndOfLine); - if (defPos == DefPosImplementationFile) { - if (targetFile->editor()) - targetFile->editor()->setTextCursor(c); - } else { - op->editor()->setTextCursor(c); - } - } - } - } - -private: - void perform() override - { - insertDefinition(this, m_loc, m_defpos, m_declAST, m_decl, m_targetFilePath); - } - - Declaration *m_decl; - DeclaratorAST *m_declAST; - InsertionLocation m_loc; - const DefPos m_defpos; - const FilePath m_targetFilePath; -}; - -} // anonymous namespace - -void InsertDefFromDecl::doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) -{ - const QList<AST *> &path = interface.path(); - - int idx = path.size() - 1; - for (; idx >= 0; --idx) { - AST *node = path.at(idx); - if (SimpleDeclarationAST *simpleDecl = node->asSimpleDeclaration()) { - if (idx > 0 && path.at(idx - 1)->asStatement()) - return; - if (simpleDecl->symbols && !simpleDecl->symbols->next) { - if (Symbol *symbol = simpleDecl->symbols->value) { - if (Declaration *decl = symbol->asDeclaration()) { - if (Function *func = decl->type()->asFunctionType()) { - if (func->isSignal() || func->isPureVirtual() || func->isFriend()) - return; - - // Check if there is already a definition - SymbolFinder symbolFinder; - if (symbolFinder.findMatchingDefinition(decl, interface.snapshot(), - true)) { - return; - } - - // Insert Position: Implementation File - DeclaratorAST *declAST = simpleDecl->declarator_list->value; - InsertDefOperation *op = nullptr; - ProjectFile::Kind kind = ProjectFile::classify(interface.filePath().toString()); - const bool isHeaderFile = ProjectFile::isHeader(kind); - if (isHeaderFile) { - CppRefactoringChanges refactoring(interface.snapshot()); - InsertionPointLocator locator(refactoring); - // find appropriate implementation file, but do not use this - // location, because insertLocationForMethodDefinition() should - // be used in perform() to get consistent insert positions. - for (const InsertionLocation &location : - locator.methodDefinition(decl, false, {})) { - if (!location.isValid()) - continue; - - const FilePath filePath = location.filePath(); - if (ProjectFile::isHeader(ProjectFile::classify(filePath.path()))) { - const FilePath source = correspondingHeaderOrSource(filePath); - if (!source.isEmpty()) { - op = new InsertDefOperation(interface, decl, declAST, - InsertionLocation(), - DefPosImplementationFile, - source); - } - } else { - op = new InsertDefOperation(interface, decl, declAST, - InsertionLocation(), - DefPosImplementationFile, - filePath); - } - - if (op) - result << op; - break; - } - } - - // Determine if we are dealing with a free function - const bool isFreeFunction = func->enclosingClass() == nullptr; - - // Insert Position: Outside Class - if (!isFreeFunction || m_defPosOutsideClass) { - result << new InsertDefOperation(interface, decl, declAST, - InsertionLocation(), - DefPosOutsideClass, - interface.filePath()); - } - - // Insert Position: Inside Class - // Determine insert location direct after the declaration. - int line, column; - const CppRefactoringFilePtr file = interface.currentFile(); - file->lineAndColumn(file->endOf(simpleDecl), &line, &column); - const InsertionLocation loc - = InsertionLocation(interface.filePath(), QString(), - QString(), line, column); - result << new InsertDefOperation(interface, decl, declAST, loc, - DefPosInsideClass, FilePath(), - isFreeFunction); - - return; - } - } - } - } - break; - } - } -} - -class InsertMemberFromInitializationOp : public CppQuickFixOperation -{ -public: - InsertMemberFromInitializationOp( - const CppQuickFixInterface &interface, - const Class *theClass, - const NameAST *memberName, - const TypeOrExpr &typeOrExpr, - const CallAST *call, - InsertionPointLocator::AccessSpec accessSpec, - bool makeStatic, - bool makeConst) - : CppQuickFixOperation(interface), - m_class(theClass), m_memberName(memberName), m_typeOrExpr(typeOrExpr), m_call(call), - m_accessSpec(accessSpec), m_makeStatic(makeStatic), m_makeConst(makeConst) - { - if (call) - setDescription(Tr::tr("Add Member Function \"%1\"").arg(nameString(memberName))); - else - setDescription(Tr::tr("Add Class Member \"%1\"").arg(nameString(memberName))); - } - -private: - void perform() override - { - QString decl = declFromExpr(m_typeOrExpr, m_call, m_memberName, snapshot(), context(), - currentFile(), m_makeConst); - if (decl.isEmpty()) - return; - if (m_makeStatic) - decl.prepend("static "); - - const CppRefactoringChanges refactoring(snapshot()); - const InsertionPointLocator locator(refactoring); - const FilePath filePath = FilePath::fromUtf8(m_class->fileName()); - const InsertionLocation loc = locator.methodDeclarationInClass( - filePath, m_class, m_accessSpec); - QTC_ASSERT(loc.isValid(), return); - - CppRefactoringFilePtr targetFile = refactoring.cppFile(filePath); - const int targetPosition = targetFile->position(loc.line(), loc.column()); - ChangeSet target; - target.insert(targetPosition, loc.prefix() + decl + ";\n"); - targetFile->setChangeSet(target); - targetFile->apply(); - } - - const Class * const m_class; - const NameAST * const m_memberName; - const TypeOrExpr m_typeOrExpr; - const CallAST * m_call; - const InsertionPointLocator::AccessSpec m_accessSpec; - const bool m_makeStatic; - const bool m_makeConst; -}; - -void AddDeclarationForUndeclaredIdentifier::doMatch(const CppQuickFixInterface &interface, - QuickFixOperations &result) -{ - // Are we on a name? - const QList<AST *> &path = interface.path(); - if (path.isEmpty()) - return; - if (!path.last()->asSimpleName()) - return; - - // Special case: Member initializer. - if (!checkForMemberInitializer(interface, result)) - return; - - // Are we inside a function? - const FunctionDefinitionAST *func = nullptr; - for (auto it = path.rbegin(); !func && it != path.rend(); ++it) - func = (*it)->asFunctionDefinition(); - if (!func) - return; - - // Is this name declared somewhere already? - const CursorInEditor cursorInEditor(interface.cursor(), interface.filePath(), - interface.editor(), interface.editor()->textDocument()); - const auto followSymbolFallback = [&](const Link &link) { - if (!link.hasValidTarget()) - collectOperations(interface, result); - }; - CppModelManager::followSymbol(cursorInEditor, followSymbolFallback, false, false, - FollowSymbolMode::Exact, - CppModelManager::Backend::Builtin); -} - -void AddDeclarationForUndeclaredIdentifier::collectOperations( - const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result) -{ - const QList<AST *> &path = interface.path(); - const CppRefactoringFilePtr &file = interface.currentFile(); - for (int index = path.size() - 1; index != -1; --index) { - if (const auto call = path.at(index)->asCall()) - return handleCall(call, interface, result); - - // We only trigger if the identifier appears on the left-hand side of an - // assignment expression. - const auto binExpr = path.at(index)->asBinaryExpression(); - if (!binExpr) - continue; - if (!binExpr->left_expression || !binExpr->right_expression - || file->tokenAt(binExpr->binary_op_token).kind() != T_EQUAL - || !interface.isCursorOn(binExpr->left_expression)) { - return; - } - - // In the case of "a.|b = c", find out the type of a, locate the class declaration - // and add a member b there. - if (const auto memberAccess = binExpr->left_expression->asMemberAccess()) { - if (interface.isCursorOn(memberAccess->member_name) - && memberAccess->member_name == path.last()) { - maybeAddMember(interface, file->scopeAt(memberAccess->firstToken()), - file->textOf(memberAccess->base_expression).toUtf8(), - binExpr->right_expression, nullptr, result); - } - return; - } - - const auto idExpr = binExpr->left_expression->asIdExpression(); - if (!idExpr || !idExpr->name) - return; - - // In the case of "A::|b = c", add a static member b to A. - if (const auto qualName = idExpr->name->asQualifiedName()) { - return maybeAddStaticMember(interface, qualName, binExpr->right_expression, nullptr, - result); - } - - // For an unqualified access, offer a local declaration and, if we are - // in a member function, a member declaration. - if (const auto simpleName = idExpr->name->asSimpleName()) { - if (!m_membersOnly) - result << new AddLocalDeclarationOp(interface, index, binExpr, simpleName); - maybeAddMember(interface, file->scopeAt(idExpr->firstToken()), "this", - binExpr->right_expression, nullptr, result); - return; - } - } -} - -void AddDeclarationForUndeclaredIdentifier::handleCall( - const CallAST *call, const CppQuickFixInterface &interface, - TextEditor::QuickFixOperations &result) -{ - if (!call->base_expression) - return; - - // In order to find out the return type, we need to check the context of the call. - // If it is a statement expression, the type is void, if it's a binary expression, - // we assume the type of the other side of the expression, if it's a return statement, - // we use the return type of the surrounding function, and if it's a declaration, - // we use the type of the variable. Other cases are not supported. - const QList<AST *> &path = interface.path(); - const CppRefactoringFilePtr &file = interface.currentFile(); - TypeOrExpr returnTypeOrExpr; - for (auto it = path.rbegin(); it != path.rend(); ++it) { - if ((*it)->asCompoundStatement()) - return; - if ((*it)->asExpressionStatement()) { - returnTypeOrExpr = FullySpecifiedType(new VoidType); - break; - } - if (const auto binExpr = (*it)->asBinaryExpression()) { - returnTypeOrExpr = interface.isCursorOn(binExpr->left_expression) - ? binExpr->right_expression : binExpr->left_expression; - break; - } - if (const auto returnExpr = (*it)->asReturnStatement()) { - for (auto it2 = std::next(it); it2 != path.rend(); ++it2) { - if (const auto func = (*it2)->asFunctionDefinition()) { - if (!func->symbol) - return; - returnTypeOrExpr = func->symbol->returnType(); - break; - } - } - break; - } - if (const auto declarator = (*it)->asDeclarator()) { - if (!interface.isCursorOn(declarator->initializer)) - return; - const auto decl = (*std::next(it))->asSimpleDeclaration(); - if (!decl || !decl->symbols) - return; - if (!decl->symbols->value->type().isValid()) - return; - returnTypeOrExpr = decl->symbols->value->type(); - break; - } - } - - if (std::holds_alternative<const ExpressionAST *>(returnTypeOrExpr) - && !std::get<const ExpressionAST *>(returnTypeOrExpr)) { - return; - } - - // a.f() - if (const auto memberAccess = call->base_expression->asMemberAccess()) { - if (!interface.isCursorOn(memberAccess->member_name)) - return; - maybeAddMember( - interface, file->scopeAt(call->firstToken()), - file->textOf(memberAccess->base_expression).toUtf8(), returnTypeOrExpr, call, result); - } - - const auto idExpr = call->base_expression->asIdExpression(); - if (!idExpr || !idExpr->name) - return; - - // A::f() - if (const auto qualName = idExpr->name->asQualifiedName()) - return maybeAddStaticMember(interface, qualName, returnTypeOrExpr, call, result); - - // f() - if (const auto simpleName = idExpr->name->asSimpleName()) { - maybeAddMember(interface, file->scopeAt(idExpr->firstToken()), "this", - returnTypeOrExpr, call, result); - } -} - -bool AddDeclarationForUndeclaredIdentifier::checkForMemberInitializer( - const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result) -{ - const QList<AST *> &path = interface.path(); - const int size = path.size(); - if (size < 4) - return true; - const MemInitializerAST * const memInitializer = path.at(size - 2)->asMemInitializer(); - if (!memInitializer) - return true; - if (!path.at(size - 3)->asCtorInitializer()) - return true; - const FunctionDefinitionAST * ctor = path.at(size - 4)->asFunctionDefinition(); - if (!ctor) - return false; - - // Now find the class. - const Class *theClass = nullptr; - if (size > 4) { - const ClassSpecifierAST * const classSpec = path.at(size - 5)->asClassSpecifier(); - if (classSpec) // Inline constructor. We get the class directly. - theClass = classSpec->symbol; - } - if (!theClass) { - // Out-of-line constructor. We need to find the class. - SymbolFinder finder; - const QList<Declaration *> matches = finder.findMatchingDeclaration( - LookupContext(interface.currentFile()->cppDocument(), interface.snapshot()), - ctor->symbol); - if (!matches.isEmpty()) - theClass = matches.first()->enclosingClass(); - } - - if (!theClass) - return false; - - const SimpleNameAST * const name = path.at(size - 1)->asSimpleName(); - QTC_ASSERT(name, return false); - - // Check whether the member exists already. - if (theClass->find(interface.currentFile()->cppDocument()->translationUnit()->identifier( - name->identifier_token))) { - return false; - } - - result << new InsertMemberFromInitializationOp( - interface, theClass, memInitializer->name->asSimpleName(), memInitializer->expression, - nullptr, InsertionPointLocator::Private, false, false); - return false; -} - -void AddDeclarationForUndeclaredIdentifier::maybeAddMember( - const CppQuickFixInterface &interface, Scope *scope, const QByteArray &classTypeExpr, - const TypeOrExpr &typeOrExpr, const CallAST *call, TextEditor::QuickFixOperations &result) -{ - const QList<AST *> &path = interface.path(); - - TypeOfExpression typeOfExpression; - typeOfExpression.init(interface.semanticInfo().doc, interface.snapshot(), - interface.context().bindings()); - const QList<LookupItem> lhsTypes = typeOfExpression( - classTypeExpr, scope, - TypeOfExpression::Preprocess); - if (lhsTypes.isEmpty()) - return; - - const Type *type = lhsTypes.first().type().type(); - if (!type) - return; - if (type->asPointerType()) { - type = type->asPointerType()->elementType().type(); - if (!type) - return; - } - const auto namedType = type->asNamedType(); - if (!namedType) - return; - const ClassOrNamespace * const classOrNamespace - = interface.context().lookupType(namedType->name(), scope); - if (!classOrNamespace || !classOrNamespace->rootClass()) - return; - - const Class * const theClass = classOrNamespace->rootClass(); - bool needsStatic = lhsTypes.first().type().isStatic(); - - // If the base expression refers to the same class that the member function is in, - // then we want to insert a private member, otherwise a public one. - const FunctionDefinitionAST *func = nullptr; - for (auto it = path.rbegin(); !func && it != path.rend(); ++it) - func = (*it)->asFunctionDefinition(); - QTC_ASSERT(func, return); - InsertionPointLocator::AccessSpec accessSpec = InsertionPointLocator::Public; - for (int i = 0; i < theClass->memberCount(); ++i) { - if (theClass->memberAt(i) == func->symbol) { - accessSpec = InsertionPointLocator::Private; - needsStatic = func->symbol->isStatic(); - break; - } - } - if (accessSpec == InsertionPointLocator::Public) { - QList<Declaration *> decls; - QList<Declaration *> dummy; - SymbolFinder().findMatchingDeclaration(interface.context(), func->symbol, &decls, - &dummy, &dummy); - for (const Declaration * const decl : std::as_const(decls)) { - for (int i = 0; i < theClass->memberCount(); ++i) { - if (theClass->memberAt(i) == decl) { - accessSpec = InsertionPointLocator::Private; - needsStatic = decl->isStatic(); - break; - } - } - if (accessSpec == InsertionPointLocator::Private) - break; - } - } - result << new InsertMemberFromInitializationOp(interface, theClass, path.last()->asName(), - typeOrExpr, call, accessSpec, needsStatic, - func->symbol->isConst()); -} - -void AddDeclarationForUndeclaredIdentifier::maybeAddStaticMember( - const CppQuickFixInterface &interface, const QualifiedNameAST *qualName, - const TypeOrExpr &typeOrExpr, const CallAST *call, TextEditor::QuickFixOperations &result) -{ - const QList<AST *> &path = interface.path(); - - if (!interface.isCursorOn(qualName->unqualified_name)) - return; - if (qualName->unqualified_name != path.last()) - return; - if (!qualName->nested_name_specifier_list) - return; - - const NameAST * const topLevelName - = qualName->nested_name_specifier_list->value->class_or_namespace_name; - if (!topLevelName) - return; - ClassOrNamespace * const classOrNamespace = interface.context().lookupType( - topLevelName->name, interface.currentFile()->scopeAt(qualName->firstToken())); - if (!classOrNamespace) - return; - QList<const Name *> otherNames; - for (auto it = qualName->nested_name_specifier_list->next; it; it = it->next) { - if (!it->value || !it->value->class_or_namespace_name) - return; - otherNames << it->value->class_or_namespace_name->name; - } - - const Class *theClass = nullptr; - if (!otherNames.isEmpty()) { - const Symbol * const symbol = classOrNamespace->lookupInScope(otherNames); - if (!symbol) - return; - theClass = symbol->asClass(); - } else { - theClass = classOrNamespace->rootClass(); - } - if (theClass) { - result << new InsertMemberFromInitializationOp( - interface, theClass, path.last()->asName(), typeOrExpr, call, - InsertionPointLocator::Public, true, false); - } -} - -class MemberFunctionImplSetting -{ -public: - Symbol *func = nullptr; - DefPos defPos = DefPosImplementationFile; -}; -using MemberFunctionImplSettings = QList<MemberFunctionImplSetting>; - -class AddImplementationsDialog : public QDialog -{ -public: - AddImplementationsDialog(const QList<Symbol *> &candidates, const FilePath &implFile) - : QDialog(Core::ICore::dialogParent()), m_candidates(candidates) - { - setWindowTitle(Tr::tr("Member Function Implementations")); - - const auto defaultImplTargetComboBox = new QComboBox; - QStringList implTargetStrings{Tr::tr("None"), Tr::tr("Inline"), Tr::tr("Outside Class")}; - if (!implFile.isEmpty()) - implTargetStrings.append(implFile.fileName()); - defaultImplTargetComboBox->insertItems(0, implTargetStrings); - connect(defaultImplTargetComboBox, &QComboBox::currentIndexChanged, this, - [this](int index) { - for (int i = 0; i < m_implTargetBoxes.size(); ++i) { - if (!m_candidates.at(i)->type()->asFunctionType()->isPureVirtual()) - static_cast<QComboBox *>(m_implTargetBoxes.at(i))->setCurrentIndex(index); - } - }); - const auto defaultImplTargetLayout = new QHBoxLayout; - defaultImplTargetLayout->addWidget(new QLabel(Tr::tr("Default implementation location:"))); - defaultImplTargetLayout->addWidget(defaultImplTargetComboBox); - - const auto candidatesLayout = new QGridLayout; - Overview oo = CppCodeStyleSettings::currentProjectCodeStyleOverview(); - oo.showFunctionSignatures = true; - oo.showReturnTypes = true; - for (int i = 0; i < m_candidates.size(); ++i) { - const Function * const func = m_candidates.at(i)->type()->asFunctionType(); - QTC_ASSERT(func, continue); - const auto implTargetComboBox = new QComboBox; - m_implTargetBoxes.append(implTargetComboBox); - implTargetComboBox->insertItems(0, implTargetStrings); - if (func->isPureVirtual()) - implTargetComboBox->setCurrentIndex(0); - candidatesLayout->addWidget(new QLabel(oo.prettyType(func->type(), func->name())), - i, 0); - candidatesLayout->addWidget(implTargetComboBox, i, 1); - } - - const auto buttonBox - = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); - connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); - - defaultImplTargetComboBox->setCurrentIndex(implTargetStrings.size() - 1); - const auto mainLayout = new QVBoxLayout(this); - mainLayout->addLayout(defaultImplTargetLayout); - mainLayout->addWidget(Layouting::createHr(this)); - mainLayout->addLayout(candidatesLayout); - mainLayout->addWidget(buttonBox); - } - - MemberFunctionImplSettings settings() const - { - QTC_ASSERT(m_candidates.size() == m_implTargetBoxes.size(), return {}); - MemberFunctionImplSettings settings; - for (int i = 0; i < m_candidates.size(); ++i) { - MemberFunctionImplSetting setting; - const int index = m_implTargetBoxes.at(i)->currentIndex(); - const bool addImplementation = index != 0; - if (!addImplementation) - continue; - setting.func = m_candidates.at(i); - setting.defPos = static_cast<DefPos>(index - 1); - settings << setting; - } - return settings; - } - -private: - const QList<Symbol *> m_candidates; - QList<QComboBox *> m_implTargetBoxes; -}; - -class InsertDefsOperation: public CppQuickFixOperation -{ -public: - InsertDefsOperation(const CppQuickFixInterface &interface) - : CppQuickFixOperation(interface) - { - setDescription(Tr::tr("Create Implementations for Member Functions")); - - m_classAST = astForClassOperations(interface); - if (!m_classAST) - return; - const Class * const theClass = m_classAST->symbol; - if (!theClass) - return; - - // Collect all member functions. - for (auto it = theClass->memberBegin(); it != theClass->memberEnd(); ++it) { - Symbol * const s = *it; - if (!s->identifier() || !s->type() || !s->asDeclaration() || s->asFunction()) - continue; - Function * const func = s->type()->asFunctionType(); - if (!func || func->isSignal() || func->isFriend()) - continue; - Overview oo = CppCodeStyleSettings::currentProjectCodeStyleOverview(); - oo.showFunctionSignatures = true; - if (magicQObjectFunctions().contains(oo.prettyName(func->name()))) - continue; - m_declarations << s; - } - } - - bool isApplicable() const { return !m_declarations.isEmpty(); } - void setMode(InsertDefsFromDecls::Mode mode) { m_mode = mode; } - -private: - void perform() override - { - QList<Symbol *> unimplemented; - SymbolFinder symbolFinder; - for (Symbol * const s : std::as_const(m_declarations)) { - if (!symbolFinder.findMatchingDefinition(s, snapshot())) - unimplemented << s; - } - if (unimplemented.isEmpty()) - return; - - CppRefactoringChanges refactoring(snapshot()); - const bool isHeaderFile = ProjectFile::isHeader(ProjectFile::classify(filePath().toString())); - FilePath cppFile; // Only set if the class is defined in a header file. - if (isHeaderFile) { - InsertionPointLocator locator(refactoring); - for (const InsertionLocation &location - : locator.methodDefinition(unimplemented.first(), false, {})) { - if (!location.isValid()) - continue; - const FilePath filePath = location.filePath(); - if (ProjectFile::isHeader(ProjectFile::classify(filePath.path()))) { - const FilePath source = correspondingHeaderOrSource(filePath); - if (!source.isEmpty()) - cppFile = source; - } else { - cppFile = filePath; - } - break; - } - } - - MemberFunctionImplSettings settings; - switch (m_mode) { - case InsertDefsFromDecls::Mode::User: { - AddImplementationsDialog dlg(unimplemented, cppFile); - if (dlg.exec() == QDialog::Accepted) - settings = dlg.settings(); - break; - } - case InsertDefsFromDecls::Mode::Impl: { - for (Symbol * const func : std::as_const(unimplemented)) { - MemberFunctionImplSetting setting; - setting.func = func; - setting.defPos = DefPosImplementationFile; - settings << setting; - } - break; - } - case InsertDefsFromDecls::Mode::Alternating: { - int defPos = DefPosImplementationFile; - const auto incDefPos = [&defPos] { - defPos = (defPos + 1) % (DefPosImplementationFile + 2); - }; - for (Symbol * const func : std::as_const(unimplemented)) { - incDefPos(); - if (defPos > DefPosImplementationFile) - continue; - MemberFunctionImplSetting setting; - setting.func = func; - setting.defPos = static_cast<DefPos>(defPos); - settings << setting; - } - break; - } - case InsertDefsFromDecls::Mode::Off: - break; - } - - if (settings.isEmpty()) - return; - - class DeclFinder : public ASTVisitor - { - public: - DeclFinder(const CppRefactoringFile *file, const Symbol *func) - : ASTVisitor(file->cppDocument()->translationUnit()), m_func(func) {} - - SimpleDeclarationAST *decl() const { return m_decl; } - - private: - bool visit(SimpleDeclarationAST *decl) override - { - if (m_decl) - return false; - if (decl->symbols && decl->symbols->value == m_func) - m_decl = decl; - return !m_decl; - } - - const Symbol * const m_func; - SimpleDeclarationAST *m_decl = nullptr; - }; - - QHash<FilePath, ChangeSet> changeSets; - for (const MemberFunctionImplSetting &setting : std::as_const(settings)) { - DeclFinder finder(currentFile().data(), setting.func); - finder.accept(m_classAST); - QTC_ASSERT(finder.decl(), continue); - InsertionLocation loc; - const FilePath targetFilePath = setting.defPos == DefPosImplementationFile - ? cppFile : filePath(); - QTC_ASSERT(!targetFilePath.isEmpty(), continue); - if (setting.defPos == DefPosInsideClass) { - int line, column; - currentFile()->lineAndColumn(currentFile()->endOf(finder.decl()), &line, &column); - loc = InsertionLocation(filePath(), QString(), QString(), line, column); - } - ChangeSet &changeSet = changeSets[targetFilePath]; - InsertDefOperation::insertDefinition( - this, loc, setting.defPos, finder.decl()->declarator_list->value, - setting.func->asDeclaration(),targetFilePath, &changeSet); - } - for (auto it = changeSets.cbegin(); it != changeSets.cend(); ++it) { - const CppRefactoringFilePtr file = refactoring.cppFile(it.key()); - file->setChangeSet(it.value()); - file->apply(); - } - } - - ClassSpecifierAST *m_classAST = nullptr; - InsertDefsFromDecls::Mode m_mode; - QList<Symbol *> m_declarations; -}; - - -void InsertDefsFromDecls::doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) -{ - const auto op = QSharedPointer<InsertDefsOperation>::create(interface); - op->setMode(m_mode); - if (op->isApplicable()) - result << op; -} - -namespace { - -std::optional<FullySpecifiedType> 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<FullySpecifiedType> getFirstTemplateParameter(Type *type) -{ - if (NamedType *namedType = type->asNamedType()) - return getFirstTemplateParameter(namedType->name()); - - return {}; -} - -std::optional<FullySpecifiedType> 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<LookupItem> 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<InsertionPointLocator::AccessSpec, InsertionLocation> m_headerInsertionPoints; - InsertionLocation m_sourceFileInsertionPoint; - QString m_sourceFileCode; - QMap<InsertionPointLocator::AccessSpec, QString> 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<int>' - // 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<FullySpecifiedType> 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<LookupItem> 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 <parameterName> 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<Symbol *> 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<Symbol *> getMemberFunctions(const Class *clazz) -{ - QList<Symbol *> 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<AST *> &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<Symbol *> *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<MemberInfo>; - -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<int>(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<int>(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<int>(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<TreeItem, CandidateTreeItem>(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<QCheckBox *, 4> 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<Symbol *> dataMembers; - QList<Symbol *> 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<GenerateGettersSettersOperation>::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 -{ -public: - static bool isValidFunctionName(const QString &name) - { - return !name.isEmpty() && isValidIdentifier(name); - } - - bool hasValidFunctionName() const - { - return isValidFunctionName(funcName); - } - - QString funcName; - InsertionPointLocator::AccessSpec access = InsertionPointLocator::Public; -}; - -class ExtractFunctionOperation : public CppQuickFixOperation -{ -public: - ExtractFunctionOperation(const CppQuickFixInterface &interface, - int extractionStart, - int extractionEnd, - FunctionDefinitionAST *refFuncDef, - Symbol *funcReturn, - QList<QPair<QString, QString> > relevantDecls, - ExtractFunction::FunctionNameGetter functionNameGetter - = ExtractFunction::FunctionNameGetter()) - : CppQuickFixOperation(interface) - , m_extractionStart(extractionStart) - , m_extractionEnd(extractionEnd) - , m_refFuncDef(refFuncDef) - , m_funcReturn(funcReturn) - , m_relevantDecls(relevantDecls) - , m_functionNameGetter(functionNameGetter) - { - setDescription(Tr::tr("Extract Function")); - } - - void perform() override - { - QTC_ASSERT(!m_funcReturn || !m_relevantDecls.isEmpty(), return); - CppRefactoringChanges refactoring(snapshot()); - CppRefactoringFilePtr currentFile = refactoring.cppFile(filePath()); - - ExtractFunctionOptions options; - if (m_functionNameGetter) - options.funcName = m_functionNameGetter(); - else - options = getOptions(); - - if (!options.hasValidFunctionName()) - return; - const QString &funcName = options.funcName; - - Function *refFunc = m_refFuncDef->symbol; - - // We don't need to rewrite the type for declarations made inside the reference function, - // since their scope will remain the same. Then we preserve the original spelling style. - // However, we must do so for the return type in the definition. - SubstitutionEnvironment env; - env.setContext(context()); - env.switchScope(refFunc); - ClassOrNamespace *targetCoN = context().lookupType(refFunc->enclosingScope()); - if (!targetCoN) - targetCoN = context().globalNamespace(); - UseMinimalNames subs(targetCoN); - env.enter(&subs); - - Overview printer = CppCodeStyleSettings::currentProjectCodeStyleOverview(); - Control *control = context().bindings()->control().get(); - QString funcDef; - QString funcDecl; // We generate a declaration only in the case of a member function. - QString funcCall; - - Class *matchingClass = isMemberFunction(context(), refFunc); - - // Write return type. - if (!m_funcReturn) { - funcDef.append(QLatin1String("void ")); - if (matchingClass) - funcDecl.append(QLatin1String("void ")); - } else { - const FullySpecifiedType &fullType = rewriteType(m_funcReturn->type(), &env, control); - funcDef.append(printer.prettyType(fullType) + QLatin1Char(' ')); - funcDecl.append(printer.prettyType(m_funcReturn->type()) + QLatin1Char(' ')); - } - - // Write class qualification, if any. - if (matchingClass) { - const Scope *current = matchingClass; - QVector<const Name *> classes{matchingClass->name()}; - while (current->enclosingScope()->asClass()) { - current = current->enclosingScope()->asClass(); - classes.prepend(current->name()); - } - while (current->enclosingScope() && current->enclosingScope()->asNamespace()) { - current = current->enclosingScope()->asNamespace(); - if (current->name()) - classes.prepend(current->name()); - } - for (const Name *n : classes) { - const Name *name = rewriteName(n, &env, control); - funcDef.append(printer.prettyName(name)); - funcDef.append(QLatin1String("::")); - } - } - - // Write the extracted function itself and its call. - funcDef.append(funcName); - if (matchingClass) - funcDecl.append(funcName); - funcCall.append(funcName); - funcDef.append(QLatin1Char('(')); - if (matchingClass) - funcDecl.append(QLatin1Char('(')); - funcCall.append(QLatin1Char('(')); - for (int i = m_funcReturn ? 1 : 0; i < m_relevantDecls.length(); ++i) { - QPair<QString, QString> p = m_relevantDecls.at(i); - funcCall.append(p.first); - funcDef.append(p.second); - if (matchingClass) - funcDecl.append(p.second); - if (i < m_relevantDecls.length() - 1) { - funcCall.append(QLatin1String(", ")); - funcDef.append(QLatin1String(", ")); - if (matchingClass) - funcDecl.append(QLatin1String(", ")); - } - } - funcDef.append(QLatin1Char(')')); - if (matchingClass) - funcDecl.append(QLatin1Char(')')); - funcCall.append(QLatin1Char(')')); - if (refFunc->isConst()) { - funcDef.append(QLatin1String(" const")); - funcDecl.append(QLatin1String(" const")); - } - funcDef.append(QLatin1String("\n{\n")); - QString extract = currentFile->textOf(m_extractionStart, m_extractionEnd); - extract.replace(QChar::ParagraphSeparator, QLatin1String("\n")); - if (!extract.endsWith(QLatin1Char('\n')) && m_funcReturn) - extract.append(QLatin1Char('\n')); - funcDef.append(extract); - if (matchingClass) - funcDecl.append(QLatin1String(";\n")); - if (m_funcReturn) { - funcDef.append(QLatin1String("\nreturn ") - + m_relevantDecls.at(0).first - + QLatin1Char(';')); - funcCall.prepend(m_relevantDecls.at(0).second + QLatin1String(" = ")); - } - funcDef.append(QLatin1String("\n}\n\n")); - funcDef.replace(QChar::ParagraphSeparator, QLatin1String("\n")); - funcDef.prepend(inlinePrefix(currentFile->filePath())); - funcCall.append(QLatin1Char(';')); - - // Do not insert right between the function and an associated comment. - int position = currentFile->startOf(m_refFuncDef); - const QList<Token> functionDoc = commentsForDeclaration( - m_refFuncDef->symbol, m_refFuncDef, *currentFile->document(), - currentFile->cppDocument()); - if (!functionDoc.isEmpty()) { - position = currentFile->cppDocument()->translationUnit()->getTokenPositionInDocument( - functionDoc.first(), currentFile->document()); - } - - ChangeSet change; - change.insert(position, funcDef); - change.replace(m_extractionStart, m_extractionEnd, funcCall); - currentFile->setChangeSet(change); - currentFile->apply(); - - // Write declaration, if necessary. - if (matchingClass) { - InsertionPointLocator locator(refactoring); - const FilePath filePath = FilePath::fromUtf8(matchingClass->fileName()); - const InsertionLocation &location = - locator.methodDeclarationInClass(filePath, matchingClass, options.access); - CppRefactoringFilePtr declFile = refactoring.cppFile(filePath); - change.clear(); - position = declFile->position(location.line(), location.column()); - change.insert(position, location.prefix() + funcDecl + location.suffix()); - declFile->setChangeSet(change); - declFile->apply(); - } - } - - ExtractFunctionOptions getOptions() const - { - QDialog dlg(Core::ICore::dialogParent()); - dlg.setWindowTitle(Tr::tr("Extract Function Refactoring")); - auto layout = new QFormLayout(&dlg); - - auto funcNameEdit = new FancyLineEdit; - funcNameEdit->setValidationFunction([](FancyLineEdit *edit, QString *) { - return ExtractFunctionOptions::isValidFunctionName(edit->text()); - }); - layout->addRow(Tr::tr("Function name"), funcNameEdit); - - auto accessCombo = new QComboBox; - accessCombo->addItem( - InsertionPointLocator::accessSpecToString(InsertionPointLocator::Public), - InsertionPointLocator::Public); - accessCombo->addItem( - InsertionPointLocator::accessSpecToString(InsertionPointLocator::PublicSlot), - InsertionPointLocator::PublicSlot); - accessCombo->addItem( - InsertionPointLocator::accessSpecToString(InsertionPointLocator::Protected), - InsertionPointLocator::Protected); - accessCombo->addItem( - InsertionPointLocator::accessSpecToString(InsertionPointLocator::ProtectedSlot), - InsertionPointLocator::ProtectedSlot); - accessCombo->addItem( - InsertionPointLocator::accessSpecToString(InsertionPointLocator::Private), - InsertionPointLocator::Private); - accessCombo->addItem( - InsertionPointLocator::accessSpecToString(InsertionPointLocator::PrivateSlot), - InsertionPointLocator::PrivateSlot); - layout->addRow(Tr::tr("Access"), accessCombo); - - auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - QObject::connect(buttonBox, &QDialogButtonBox::accepted, &dlg, &QDialog::accept); - QObject::connect(buttonBox, &QDialogButtonBox::rejected, &dlg, &QDialog::reject); - QPushButton *ok = buttonBox->button(QDialogButtonBox::Ok); - ok->setEnabled(false); - QObject::connect(funcNameEdit, &Utils::FancyLineEdit::validChanged, - ok, &QPushButton::setEnabled); - layout->addWidget(buttonBox); - - if (dlg.exec() == QDialog::Accepted) { - ExtractFunctionOptions options; - options.funcName = funcNameEdit->text(); - options.access = static_cast<InsertionPointLocator::AccessSpec>(accessCombo-> - currentData().toInt()); - return options; - } - return ExtractFunctionOptions(); - } - - int m_extractionStart; - int m_extractionEnd; - FunctionDefinitionAST *m_refFuncDef; - Symbol *m_funcReturn; - QList<QPair<QString, QString> > m_relevantDecls; - ExtractFunction::FunctionNameGetter m_functionNameGetter; -}; - -QPair<QString, QString> assembleDeclarationData(const QString &specifiers, DeclaratorAST *decltr, - const CppRefactoringFilePtr &file, - const Overview &printer) -{ - QTC_ASSERT(decltr, return (QPair<QString, QString>())); - if (decltr->core_declarator - && decltr->core_declarator->asDeclaratorId() - && decltr->core_declarator->asDeclaratorId()->name) { - QString decltrText = file->textOf(file->startOf(decltr), - file->endOf(decltr->core_declarator)); - if (!decltrText.isEmpty()) { - const QString &name = printer.prettyName( - decltr->core_declarator->asDeclaratorId()->name->name); - QString completeDecl = specifiers; - if (!decltrText.contains(QLatin1Char(' '))) - completeDecl.append(QLatin1Char(' ') + decltrText); - else - completeDecl.append(decltrText); - return {name, completeDecl}; - } - } - return QPair<QString, QString>(); -} - -class FunctionExtractionAnalyser : public ASTVisitor -{ -public: - FunctionExtractionAnalyser(TranslationUnit *unit, - const int selStart, - const int selEnd, - const CppRefactoringFilePtr &file, - const Overview &printer) - : ASTVisitor(unit) - , m_done(false) - , m_failed(false) - , m_selStart(selStart) - , m_selEnd(selEnd) - , m_extractionStart(0) - , m_extractionEnd(0) - , m_file(file) - , m_printer(printer) - {} - - bool operator()(FunctionDefinitionAST *refFunDef) - { - accept(refFunDef); - - if (!m_failed && m_extractionStart == m_extractionEnd) - m_failed = true; - - return !m_failed; - } - - bool preVisit(AST *) override - { - return !m_done; - } - - void statement(StatementAST *stmt) - { - if (!stmt) - return; - - const int stmtStart = m_file->startOf(stmt); - const int stmtEnd = m_file->endOf(stmt); - - if (stmtStart >= m_selEnd - || (m_extractionStart && stmtEnd > m_selEnd)) { - m_done = true; - return; - } - - if (stmtStart >= m_selStart && !m_extractionStart) - m_extractionStart = stmtStart; - if (stmtEnd > m_extractionEnd && m_extractionStart) - m_extractionEnd = stmtEnd; - - accept(stmt); - } - - bool visit(CaseStatementAST *stmt) override - { - statement(stmt->statement); - return false; - } - - bool visit(CompoundStatementAST *stmt) override - { - for (StatementListAST *it = stmt->statement_list; it; it = it->next) { - statement(it->value); - if (m_done) - break; - } - return false; - } - - bool visit(DoStatementAST *stmt) override - { - statement(stmt->statement); - return false; - } - - bool visit(ForeachStatementAST *stmt) override - { - statement(stmt->statement); - return false; - } - - bool visit(RangeBasedForStatementAST *stmt) override - { - statement(stmt->statement); - return false; - } - - bool visit(ForStatementAST *stmt) override - { - statement(stmt->initializer); - if (!m_done) - statement(stmt->statement); - return false; - } - - bool visit(IfStatementAST *stmt) override - { - statement(stmt->statement); - if (!m_done) - statement(stmt->else_statement); - return false; - } - - bool visit(TryBlockStatementAST *stmt) override - { - statement(stmt->statement); - for (CatchClauseListAST *it = stmt->catch_clause_list; it; it = it->next) { - statement(it->value); - if (m_done) - break; - } - return false; - } - - bool visit(WhileStatementAST *stmt) override - { - statement(stmt->statement); - return false; - } - - bool visit(DeclarationStatementAST *declStmt) override - { - // We need to collect the declarations we see before the extraction or even inside it. - // They might need to be used as either a parameter or return value. Actually, we could - // still obtain their types from the local uses, but it's good to preserve the original - // typing style. - if (declStmt - && declStmt->declaration - && declStmt->declaration->asSimpleDeclaration()) { - SimpleDeclarationAST *simpleDecl = declStmt->declaration->asSimpleDeclaration(); - if (simpleDecl->decl_specifier_list - && simpleDecl->declarator_list) { - const QString &specifiers = - m_file->textOf(m_file->startOf(simpleDecl), - m_file->endOf(simpleDecl->decl_specifier_list->lastValue())); - for (DeclaratorListAST *decltrList = simpleDecl->declarator_list; - decltrList; - decltrList = decltrList->next) { - const QPair<QString, QString> p = - assembleDeclarationData(specifiers, decltrList->value, m_file, m_printer); - if (!p.first.isEmpty()) - m_knownDecls.insert(p.first, p.second); - } - } - } - - return false; - } - - bool visit(ReturnStatementAST *) override - { - if (m_extractionStart) { - m_done = true; - m_failed = true; - } - - return false; - } - - bool m_done; - bool m_failed; - const int m_selStart; - const int m_selEnd; - int m_extractionStart; - int m_extractionEnd; - QHash<QString, QString> m_knownDecls; - CppRefactoringFilePtr m_file; - const Overview &m_printer; -}; - -} // anonymous namespace - -ExtractFunction::ExtractFunction(FunctionNameGetter functionNameGetter) - : m_functionNameGetter(functionNameGetter) -{ -} - -void ExtractFunction::doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) -{ - const CppRefactoringFilePtr file = interface.currentFile(); - - // TODO: Fix upstream and uncomment; see QTCREATORBUG-28030. -// if (CppModelManager::usesClangd(file->editor()->textDocument()) -// && file->cppDocument()->languageFeatures().cxxEnabled) { -// return; -// } - - QTextCursor cursor = file->cursor(); - if (!cursor.hasSelection()) - return; - - const QList<AST *> &path = interface.path(); - FunctionDefinitionAST *refFuncDef = nullptr; // The "reference" function, which we will extract from. - for (int i = path.size() - 1; i >= 0; --i) { - refFuncDef = path.at(i)->asFunctionDefinition(); - if (refFuncDef) - break; - } - - if (!refFuncDef - || !refFuncDef->function_body - || !refFuncDef->function_body->asCompoundStatement() - || !refFuncDef->function_body->asCompoundStatement()->statement_list - || !refFuncDef->symbol - || !refFuncDef->symbol->name() - || refFuncDef->symbol->enclosingScope()->asTemplate() /* TODO: Templates... */) { - return; - } - - // Adjust selection ends. - int selStart = cursor.selectionStart(); - int selEnd = cursor.selectionEnd(); - if (selStart > selEnd) - std::swap(selStart, selEnd); - - Overview printer; - - // Analyze the content to be extracted, which consists of determining the statements - // which are complete and collecting the declarations seen. - FunctionExtractionAnalyser analyser(interface.semanticInfo().doc->translationUnit(), - selStart, selEnd, - file, - printer); - if (!analyser(refFuncDef)) - return; - - // We also need to collect the declarations of the parameters from the reference function. - QSet<QString> refFuncParams; - if (refFuncDef->declarator->postfix_declarator_list - && refFuncDef->declarator->postfix_declarator_list->value - && refFuncDef->declarator->postfix_declarator_list->value->asFunctionDeclarator()) { - FunctionDeclaratorAST *funcDecltr = - refFuncDef->declarator->postfix_declarator_list->value->asFunctionDeclarator(); - if (funcDecltr->parameter_declaration_clause - && funcDecltr->parameter_declaration_clause->parameter_declaration_list) { - for (ParameterDeclarationListAST *it = - funcDecltr->parameter_declaration_clause->parameter_declaration_list; - it; - it = it->next) { - ParameterDeclarationAST *paramDecl = it->value->asParameterDeclaration(); - if (paramDecl->declarator) { - const QString &specifiers = - file->textOf(file->startOf(paramDecl), - file->endOf(paramDecl->type_specifier_list->lastValue())); - const QPair<QString, QString> &p = - assembleDeclarationData(specifiers, paramDecl->declarator, - file, printer); - if (!p.first.isEmpty()) { - analyser.m_knownDecls.insert(p.first, p.second); - refFuncParams.insert(p.first); - } - } - } - } - } - - // Identify what would be parameters for the new function and its return value, if any. - Symbol *funcReturn = nullptr; - QList<QPair<QString, QString> > relevantDecls; - const SemanticInfo::LocalUseMap localUses = interface.semanticInfo().localUses; - for (auto it = localUses.cbegin(), end = localUses.cend(); it != end; ++it) { - bool usedBeforeExtraction = false; - bool usedAfterExtraction = false; - bool usedInsideExtraction = false; - const QList<SemanticInfo::Use> &uses = it.value(); - for (const SemanticInfo::Use &use : uses) { - if (use.isInvalid()) - continue; - - const int position = file->position(use.line, use.column); - if (position < analyser.m_extractionStart) - usedBeforeExtraction = true; - else if (position >= analyser.m_extractionEnd) - usedAfterExtraction = true; - else - usedInsideExtraction = true; - } - - const QString &name = printer.prettyName(it.key()->name()); - - if ((usedBeforeExtraction && usedInsideExtraction) - || (usedInsideExtraction && refFuncParams.contains(name))) { - QTC_ASSERT(analyser.m_knownDecls.contains(name), return); - relevantDecls.push_back({name, analyser.m_knownDecls.value(name)}); - } - - // We assume that the first use of a local corresponds to its declaration. - if (usedInsideExtraction && usedAfterExtraction && !usedBeforeExtraction) { - if (!funcReturn) { - QTC_ASSERT(analyser.m_knownDecls.contains(name), return); - // The return, if any, is stored as the first item in the list. - relevantDecls.push_front({name, analyser.m_knownDecls.value(name)}); - funcReturn = it.key(); - } else { - // Would require multiple returns. (Unless we do fancy things, as pointed below.) - return; - } - } - } - - // The current implementation doesn't try to be too smart since it preserves the original form - // of the declarations. This might be or not the desired effect. An improvement would be to - // let the user somehow customize the function interface. - result << new ExtractFunctionOperation(interface, - analyser.m_extractionStart, - analyser.m_extractionEnd, - refFuncDef, funcReturn, relevantDecls, - m_functionNameGetter); -} - -namespace { - -struct ReplaceLiteralsResult -{ - Token token; - QString literalText; -}; - -template <class T> -class ReplaceLiterals : private ASTVisitor -{ -public: - ReplaceLiterals(const CppRefactoringFilePtr &file, ChangeSet *changes, T *literal) - : ASTVisitor(file->cppDocument()->translationUnit()), m_file(file), m_changes(changes), - m_literal(literal) - { - m_result.token = m_file->tokenAt(literal->firstToken()); - m_literalTokenText = m_result.token.spell(); - m_result.literalText = QLatin1String(m_literalTokenText); - if (m_result.token.isCharLiteral()) { - m_result.literalText.prepend(QLatin1Char('\'')); - m_result.literalText.append(QLatin1Char('\'')); - if (m_result.token.kind() == T_WIDE_CHAR_LITERAL) - m_result.literalText.prepend(QLatin1Char('L')); - else if (m_result.token.kind() == T_UTF16_CHAR_LITERAL) - m_result.literalText.prepend(QLatin1Char('u')); - else if (m_result.token.kind() == T_UTF32_CHAR_LITERAL) - m_result.literalText.prepend(QLatin1Char('U')); - } else if (m_result.token.isStringLiteral()) { - m_result.literalText.prepend(QLatin1Char('"')); - m_result.literalText.append(QLatin1Char('"')); - if (m_result.token.kind() == T_WIDE_STRING_LITERAL) - m_result.literalText.prepend(QLatin1Char('L')); - else if (m_result.token.kind() == T_UTF16_STRING_LITERAL) - m_result.literalText.prepend(QLatin1Char('u')); - else if (m_result.token.kind() == T_UTF32_STRING_LITERAL) - m_result.literalText.prepend(QLatin1Char('U')); - } - } - - ReplaceLiteralsResult apply(AST *ast) - { - ast->accept(this); - return m_result; - } - -private: - bool visit(T *ast) override - { - if (ast != m_literal - && strcmp(m_file->tokenAt(ast->firstToken()).spell(), m_literalTokenText) != 0) { - return true; - } - int start, end; - m_file->startAndEndOf(ast->firstToken(), &start, &end); - m_changes->replace(start, end, QLatin1String("newParameter")); - return true; - } - - const CppRefactoringFilePtr &m_file; - ChangeSet *m_changes; - T *m_literal; - const char *m_literalTokenText; - ReplaceLiteralsResult m_result; -}; - -class ExtractLiteralAsParameterOp : public CppQuickFixOperation -{ -public: - ExtractLiteralAsParameterOp(const CppQuickFixInterface &interface, int priority, - ExpressionAST *literal, FunctionDefinitionAST *function) - : CppQuickFixOperation(interface, priority), - m_literal(literal), - m_functionDefinition(function) - { - setDescription(Tr::tr("Extract Constant as Function Parameter")); - } - - struct FoundDeclaration - { - FunctionDeclaratorAST *ast = nullptr; - CppRefactoringFilePtr file; - }; - - FoundDeclaration findDeclaration(const CppRefactoringChanges &refactoring, - FunctionDefinitionAST *ast) - { - FoundDeclaration result; - Function *func = ast->symbol; - if (Class *matchingClass = isMemberFunction(context(), func)) { - // Dealing with member functions - const QualifiedNameId *qName = func->name()->asQualifiedNameId(); - for (Symbol *s = matchingClass->find(qName->identifier()); s; s = s->next()) { - if (!s->name() - || !qName->identifier()->match(s->identifier()) - || !s->type()->asFunctionType() - || !s->type().match(func->type()) - || s->asFunction()) { - continue; - } - - const FilePath declFilePath = matchingClass->filePath(); - result.file = refactoring.cppFile(declFilePath); - ASTPath astPath(result.file->cppDocument()); - const QList<AST *> path = astPath(s->line(), s->column()); - SimpleDeclarationAST *simpleDecl = nullptr; - for (AST *node : path) { - simpleDecl = node->asSimpleDeclaration(); - if (simpleDecl) { - if (simpleDecl->symbols && !simpleDecl->symbols->next) { - result.ast = functionDeclarator(simpleDecl); - return result; - } - } - } - - if (simpleDecl) - break; - } - } else if (Namespace *matchingNamespace = isNamespaceFunction(context(), func)) { - // Dealing with free functions and inline member functions. - bool isHeaderFile; - FilePath declFilePath = correspondingHeaderOrSource(filePath(), &isHeaderFile); - if (!declFilePath.exists()) - return FoundDeclaration(); - result.file = refactoring.cppFile(declFilePath); - if (!result.file) - return FoundDeclaration(); - const LookupContext lc(result.file->cppDocument(), snapshot()); - const QList<LookupItem> candidates = lc.lookup(func->name(), matchingNamespace); - for (const LookupItem &candidate : candidates) { - if (Symbol *s = candidate.declaration()) { - if (s->asDeclaration()) { - ASTPath astPath(result.file->cppDocument()); - const QList<AST *> path = astPath(s->line(), s->column()); - for (AST *node : path) { - SimpleDeclarationAST *simpleDecl = node->asSimpleDeclaration(); - if (simpleDecl) { - result.ast = functionDeclarator(simpleDecl); - return result; - } - } - } - } - } - } - return result; - } - - void perform() override - { - FunctionDeclaratorAST *functionDeclaratorOfDefinition - = functionDeclarator(m_functionDefinition); - const CppRefactoringChanges refactoring(snapshot()); - const CppRefactoringFilePtr currentFile = refactoring.cppFile(filePath()); - deduceTypeNameOfLiteral(currentFile->cppDocument()); - - ChangeSet changes; - if (NumericLiteralAST *concreteLiteral = m_literal->asNumericLiteral()) { - m_literalInfo = ReplaceLiterals<NumericLiteralAST>(currentFile, &changes, - concreteLiteral) - .apply(m_functionDefinition->function_body); - } else if (StringLiteralAST *concreteLiteral = m_literal->asStringLiteral()) { - m_literalInfo = ReplaceLiterals<StringLiteralAST>(currentFile, &changes, - concreteLiteral) - .apply(m_functionDefinition->function_body); - } else if (BoolLiteralAST *concreteLiteral = m_literal->asBoolLiteral()) { - m_literalInfo = ReplaceLiterals<BoolLiteralAST>(currentFile, &changes, - concreteLiteral) - .apply(m_functionDefinition->function_body); - } - const FoundDeclaration functionDeclaration - = findDeclaration(refactoring, m_functionDefinition); - appendFunctionParameter(functionDeclaratorOfDefinition, currentFile, &changes, - !functionDeclaration.ast); - if (functionDeclaration.ast) { - if (currentFile->filePath() != functionDeclaration.file->filePath()) { - ChangeSet declChanges; - appendFunctionParameter(functionDeclaration.ast, functionDeclaration.file, &declChanges, - true); - functionDeclaration.file->setChangeSet(declChanges); - functionDeclaration.file->apply(); - } else { - appendFunctionParameter(functionDeclaration.ast, currentFile, &changes, - true); - } - } - currentFile->setChangeSet(changes); - currentFile->apply(); - QTextCursor c = currentFile->cursor(); - c.setPosition(c.position() - parameterName().length()); - editor()->setTextCursor(c); - editor()->renameSymbolUnderCursor(); - } - -private: - bool hasParameters(FunctionDeclaratorAST *ast) const - { - return ast->parameter_declaration_clause - && ast->parameter_declaration_clause->parameter_declaration_list - && ast->parameter_declaration_clause->parameter_declaration_list->value; - } - - void deduceTypeNameOfLiteral(const Document::Ptr &document) - { - TypeOfExpression typeOfExpression; - typeOfExpression.init(document, snapshot()); - Overview overview; - Scope *scope = m_functionDefinition->symbol->enclosingScope(); - const QList<LookupItem> items = typeOfExpression(m_literal, document, scope); - if (!items.isEmpty()) - m_typeName = overview.prettyType(items.first().type()); - } - - static QString parameterName() { return QLatin1String("newParameter"); } - - QString parameterDeclarationTextToInsert(FunctionDeclaratorAST *ast) const - { - QString str; - if (hasParameters(ast)) - str = QLatin1String(", "); - str += m_typeName; - if (!m_typeName.endsWith(QLatin1Char('*'))) - str += QLatin1Char(' '); - str += parameterName(); - return str; - } - - FunctionDeclaratorAST *functionDeclarator(SimpleDeclarationAST *ast) const - { - for (DeclaratorListAST *decls = ast->declarator_list; decls; decls = decls->next) { - FunctionDeclaratorAST * const functionDeclaratorAST = functionDeclarator(decls->value); - if (functionDeclaratorAST) - return functionDeclaratorAST; - } - return nullptr; - } - - FunctionDeclaratorAST *functionDeclarator(DeclaratorAST *ast) const - { - for (PostfixDeclaratorListAST *pds = ast->postfix_declarator_list; pds; pds = pds->next) { - FunctionDeclaratorAST *funcdecl = pds->value->asFunctionDeclarator(); - if (funcdecl) - return funcdecl; - } - return nullptr; - } - - FunctionDeclaratorAST *functionDeclarator(FunctionDefinitionAST *ast) const - { - return functionDeclarator(ast->declarator); - } - - void appendFunctionParameter(FunctionDeclaratorAST *ast, const CppRefactoringFileConstPtr &file, - ChangeSet *changes, bool addDefaultValue) - { - if (!ast) - return; - if (m_declarationInsertionString.isEmpty()) - m_declarationInsertionString = parameterDeclarationTextToInsert(ast); - QString insertion = m_declarationInsertionString; - if (addDefaultValue) - insertion += QLatin1String(" = ") + m_literalInfo.literalText; - changes->insert(file->startOf(ast->rparen_token), insertion); - } - - ExpressionAST *m_literal; - FunctionDefinitionAST *m_functionDefinition; - QString m_typeName; - QString m_declarationInsertionString; - ReplaceLiteralsResult m_literalInfo; -}; - -} // anonymous namespace - -void ExtractLiteralAsParameter::doMatch(const CppQuickFixInterface &interface, - QuickFixOperations &result) -{ - const QList<AST *> &path = interface.path(); - if (path.count() < 2) - return; - - AST * const lastAst = path.last(); - ExpressionAST *literal; - if (!((literal = lastAst->asNumericLiteral()) - || (literal = lastAst->asStringLiteral()) - || (literal = lastAst->asBoolLiteral()))) { - return; - } - - FunctionDefinitionAST *function; - int i = path.count() - 2; - while (!(function = path.at(i)->asFunctionDefinition())) { - // Ignore literals in lambda expressions for now. - if (path.at(i)->asLambdaExpression()) - return; - if (--i < 0) - return; - } - - PostfixDeclaratorListAST * const declaratorList = function->declarator->postfix_declarator_list; - if (!declaratorList) - return; - if (FunctionDeclaratorAST *declarator = declaratorList->value->asFunctionDeclarator()) { - if (declarator->parameter_declaration_clause - && declarator->parameter_declaration_clause->dot_dot_dot_token) { - // Do not handle functions with ellipsis parameter. - return; - } - } - - const int priority = path.size() - 1; - result << new ExtractLiteralAsParameterOp(interface, priority, literal, function); -} - -namespace { - -class ConvertFromAndToPointerOp : public CppQuickFixOperation -{ -public: - enum Mode { FromPointer, FromVariable, FromReference }; - - ConvertFromAndToPointerOp(const CppQuickFixInterface &interface, int priority, Mode mode, - bool isAutoDeclaration, - const SimpleDeclarationAST *simpleDeclaration, - const DeclaratorAST *declaratorAST, - const SimpleNameAST *identifierAST, - Symbol *symbol) - : CppQuickFixOperation(interface, priority) - , m_mode(mode) - , m_isAutoDeclaration(isAutoDeclaration) - , m_simpleDeclaration(simpleDeclaration) - , m_declaratorAST(declaratorAST) - , m_identifierAST(identifierAST) - , m_symbol(symbol) - , m_refactoring(snapshot()) - , m_file(m_refactoring.cppFile(filePath())) - , m_document(interface.semanticInfo().doc) - { - setDescription( - mode == FromPointer - ? Tr::tr("Convert to Stack Variable") - : Tr::tr("Convert to Pointer")); - } - - void perform() override - { - ChangeSet changes; - - switch (m_mode) { - case FromPointer: - removePointerOperator(changes); - convertToStackVariable(changes); - break; - case FromReference: - removeReferenceOperator(changes); - Q_FALLTHROUGH(); - case FromVariable: - convertToPointer(changes); - break; - } - - m_file->setChangeSet(changes); - m_file->apply(); - } - -private: - void removePointerOperator(ChangeSet &changes) const - { - if (!m_declaratorAST->ptr_operator_list) - return; - PointerAST *ptrAST = m_declaratorAST->ptr_operator_list->value->asPointer(); - QTC_ASSERT(ptrAST, return); - const int pos = m_file->startOf(ptrAST->star_token); - changes.remove(pos, pos + 1); - } - - void removeReferenceOperator(ChangeSet &changes) const - { - ReferenceAST *refAST = m_declaratorAST->ptr_operator_list->value->asReference(); - QTC_ASSERT(refAST, return); - const int pos = m_file->startOf(refAST->reference_token); - changes.remove(pos, pos + 1); - } - - void removeNewExpression(ChangeSet &changes, NewExpressionAST *newExprAST) const - { - ExpressionListAST *exprlist = nullptr; - if (newExprAST->new_initializer) { - if (ExpressionListParenAST *ast = newExprAST->new_initializer->asExpressionListParen()) - exprlist = ast->expression_list; - else if (BracedInitializerAST *ast = newExprAST->new_initializer->asBracedInitializer()) - exprlist = ast->expression_list; - } - - if (exprlist) { - // remove 'new' keyword and type before initializer - changes.remove(m_file->startOf(newExprAST->new_token), - m_file->startOf(newExprAST->new_initializer)); - - changes.remove(m_file->endOf(m_declaratorAST->equal_token - 1), - m_file->startOf(m_declaratorAST->equal_token + 1)); - } else { - // remove the whole new expression - changes.remove(m_file->endOf(m_identifierAST->firstToken()), - m_file->startOf(newExprAST->lastToken())); - } - } - - void removeNewKeyword(ChangeSet &changes, NewExpressionAST *newExprAST) const - { - // remove 'new' keyword before initializer - changes.remove(m_file->startOf(newExprAST->new_token), - m_file->startOf(newExprAST->new_type_id)); - } - - void convertToStackVariable(ChangeSet &changes) const - { - // Handle the initializer. - if (m_declaratorAST->initializer) { - if (NewExpressionAST *newExpression = m_declaratorAST->initializer->asNewExpression()) { - if (m_isAutoDeclaration) { - if (!newExpression->new_initializer) - changes.insert(m_file->endOf(newExpression), QStringLiteral("()")); - removeNewKeyword(changes, newExpression); - } else { - removeNewExpression(changes, newExpression); - } - } - } - - // Fix all occurrences of the identifier in this function. - ASTPath astPath(m_document); - const QList<SemanticInfo::Use> uses = semanticInfo().localUses.value(m_symbol); - for (const SemanticInfo::Use &use : uses) { - const QList<AST *> path = astPath(use.line, use.column); - AST *idAST = path.last(); - bool declarationFound = false; - bool starFound = false; - int ampersandPos = 0; - bool memberAccess = false; - bool deleteCall = false; - - for (int i = path.count() - 2; i >= 0; --i) { - if (path.at(i) == m_declaratorAST) { - declarationFound = true; - break; - } - if (MemberAccessAST *memberAccessAST = path.at(i)->asMemberAccess()) { - if (m_file->tokenAt(memberAccessAST->access_token).kind() != T_ARROW) - continue; - int pos = m_file->startOf(memberAccessAST->access_token); - changes.replace(pos, pos + 2, QLatin1String(".")); - memberAccess = true; - break; - } else if (DeleteExpressionAST *deleteAST = path.at(i)->asDeleteExpression()) { - const int pos = m_file->startOf(deleteAST->delete_token); - changes.insert(pos, QLatin1String("// ")); - deleteCall = true; - break; - } else if (UnaryExpressionAST *unaryExprAST = path.at(i)->asUnaryExpression()) { - const Token tk = m_file->tokenAt(unaryExprAST->unary_op_token); - if (tk.kind() == T_STAR) { - if (!starFound) { - int pos = m_file->startOf(unaryExprAST->unary_op_token); - changes.remove(pos, pos + 1); - } - starFound = true; - } else if (tk.kind() == T_AMPER) { - ampersandPos = m_file->startOf(unaryExprAST->unary_op_token); - } - } else if (PointerAST *ptrAST = path.at(i)->asPointer()) { - if (!starFound) { - const int pos = m_file->startOf(ptrAST->star_token); - changes.remove(pos, pos); - } - starFound = true; - } else if (path.at(i)->asFunctionDefinition()) { - break; - } - } - if (!declarationFound && !starFound && !memberAccess && !deleteCall) { - if (ampersandPos) { - changes.insert(ampersandPos, QLatin1String("&(")); - changes.insert(m_file->endOf(idAST->firstToken()), QLatin1String(")")); - } else { - changes.insert(m_file->startOf(idAST), QLatin1String("&")); - } - } - } - } - - QString typeNameOfDeclaration() const - { - if (!m_simpleDeclaration - || !m_simpleDeclaration->decl_specifier_list - || !m_simpleDeclaration->decl_specifier_list->value) { - return QString(); - } - NamedTypeSpecifierAST *namedType - = m_simpleDeclaration->decl_specifier_list->value->asNamedTypeSpecifier(); - if (!namedType) - return QString(); - - Overview overview; - return overview.prettyName(namedType->name->name); - } - - void insertNewExpression(ChangeSet &changes, ExpressionAST *ast) const - { - const QString typeName = typeNameOfDeclaration(); - if (CallAST *callAST = ast->asCall()) { - if (typeName.isEmpty()) { - changes.insert(m_file->startOf(callAST), QLatin1String("new ")); - } else { - changes.insert(m_file->startOf(callAST), - QLatin1String("new ") + typeName + QLatin1Char('(')); - changes.insert(m_file->startOf(callAST->lastToken()), QLatin1String(")")); - } - } else { - if (typeName.isEmpty()) - return; - changes.insert(m_file->startOf(ast), QLatin1String(" = new ") + typeName); - } - } - - void insertNewExpression(ChangeSet &changes) const - { - const QString typeName = typeNameOfDeclaration(); - if (typeName.isEmpty()) - return; - changes.insert(m_file->endOf(m_identifierAST->firstToken()), - QLatin1String(" = new ") + typeName); - } - - void convertToPointer(ChangeSet &changes) const - { - // Handle initializer. - if (m_declaratorAST->initializer) { - if (IdExpressionAST *idExprAST = m_declaratorAST->initializer->asIdExpression()) { - changes.insert(m_file->startOf(idExprAST), QLatin1String("&")); - } else if (CallAST *callAST = m_declaratorAST->initializer->asCall()) { - insertNewExpression(changes, callAST); - } else if (ExpressionListParenAST *exprListAST = m_declaratorAST->initializer - ->asExpressionListParen()) { - insertNewExpression(changes, exprListAST); - } else if (BracedInitializerAST *bracedInitializerAST = m_declaratorAST->initializer - ->asBracedInitializer()) { - insertNewExpression(changes, bracedInitializerAST); - } - } else { - insertNewExpression(changes); - } - - // Fix all occurrences of the identifier in this function. - ASTPath astPath(m_document); - const QList<SemanticInfo::Use> uses = semanticInfo().localUses.value(m_symbol); - for (const SemanticInfo::Use &use : uses) { - const QList<AST *> path = astPath(use.line, use.column); - AST *idAST = path.last(); - bool insertStar = true; - for (int i = path.count() - 2; i >= 0; --i) { - if (m_isAutoDeclaration && path.at(i) == m_declaratorAST) { - insertStar = false; - break; - } - if (MemberAccessAST *memberAccessAST = path.at(i)->asMemberAccess()) { - const int pos = m_file->startOf(memberAccessAST->access_token); - changes.replace(pos, pos + 1, QLatin1String("->")); - insertStar = false; - break; - } else if (UnaryExpressionAST *unaryExprAST = path.at(i)->asUnaryExpression()) { - if (m_file->tokenAt(unaryExprAST->unary_op_token).kind() == T_AMPER) { - const int pos = m_file->startOf(unaryExprAST->unary_op_token); - changes.remove(pos, pos + 1); - insertStar = false; - break; - } - } else if (path.at(i)->asFunctionDefinition()) { - break; - } - } - if (insertStar) - changes.insert(m_file->startOf(idAST), QLatin1String("*")); - } - } - - const Mode m_mode; - const bool m_isAutoDeclaration; - const SimpleDeclarationAST * const m_simpleDeclaration; - const DeclaratorAST * const m_declaratorAST; - const SimpleNameAST * const m_identifierAST; - Symbol * const m_symbol; - const CppRefactoringChanges m_refactoring; - const CppRefactoringFilePtr m_file; - const Document::Ptr m_document; -}; - -} // anonymous namespace - -void ConvertFromAndToPointer::doMatch(const CppQuickFixInterface &interface, - QuickFixOperations &result) -{ - const QList<AST *> &path = interface.path(); - if (path.count() < 2) - return; - SimpleNameAST *identifier = path.last()->asSimpleName(); - if (!identifier) - return; - SimpleDeclarationAST *simpleDeclaration = nullptr; - DeclaratorAST *declarator = nullptr; - bool isFunctionLocal = false; - bool isClassLocal = false; - ConvertFromAndToPointerOp::Mode mode = ConvertFromAndToPointerOp::FromVariable; - for (int i = path.count() - 2; i >= 0; --i) { - AST *ast = path.at(i); - if (!declarator && (declarator = ast->asDeclarator())) - continue; - if (!simpleDeclaration && (simpleDeclaration = ast->asSimpleDeclaration())) - continue; - if (declarator && simpleDeclaration) { - if (ast->asClassSpecifier()) { - isClassLocal = true; - } else if (ast->asFunctionDefinition() && !isClassLocal) { - isFunctionLocal = true; - break; - } - } - } - if (!isFunctionLocal || !simpleDeclaration || !declarator) - return; - - Symbol *symbol = nullptr; - for (List<Symbol *> *lst = simpleDeclaration->symbols; lst; lst = lst->next) { - if (lst->value->name() == identifier->name) { - symbol = lst->value; - break; - } - } - if (!symbol) - return; - - bool isAutoDeclaration = false; - if (symbol->storage() == Symbol::Auto) { - // For auto variables we must deduce the type from the initializer. - if (!declarator->initializer) - return; - - isAutoDeclaration = true; - TypeOfExpression typeOfExpression; - typeOfExpression.init(interface.semanticInfo().doc, interface.snapshot()); - typeOfExpression.setExpandTemplates(true); - CppRefactoringFilePtr file = interface.currentFile(); - Scope *scope = file->scopeAt(declarator->firstToken()); - QList<LookupItem> result = typeOfExpression(file->textOf(declarator->initializer).toUtf8(), - scope, TypeOfExpression::Preprocess); - if (!result.isEmpty() && result.first().type()->asPointerType()) - mode = ConvertFromAndToPointerOp::FromPointer; - } else if (declarator->ptr_operator_list) { - for (PtrOperatorListAST *ops = declarator->ptr_operator_list; ops; ops = ops->next) { - if (ops != declarator->ptr_operator_list) { - // Bail out on more complex pointer types (e.g. pointer of pointer, - // or reference of pointer). - return; - } - if (ops->value->asPointer()) - mode = ConvertFromAndToPointerOp::FromPointer; - else if (ops->value->asReference()) - mode = ConvertFromAndToPointerOp::FromReference; - } - } - - const int priority = path.size() - 1; - result << new ConvertFromAndToPointerOp(interface, priority, mode, isAutoDeclaration, - simpleDeclaration, declarator, identifier, symbol); -} - -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<AST *> &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: - explicit ApplyDeclDefLinkOperation(const CppQuickFixInterface &interface, - const std::shared_ptr<FunctionDeclDefLink> &link) - : CppQuickFixOperation(interface, 100) - , m_link(link) - {} - - void perform() override - { - if (editor()->declDefLink() == m_link) - editor()->applyDeclDefLinkChanges(/*don't jump*/false); - } - -protected: - virtual void performChanges(const CppRefactoringFilePtr &, const CppRefactoringChanges &) - { /* never called since perform is overridden */ } - -private: - std::shared_ptr<FunctionDeclDefLink> m_link; -}; - -} // anonymous namespace - -void ApplyDeclDefLinkChanges::doMatch(const CppQuickFixInterface &interface, - QuickFixOperations &result) -{ - std::shared_ptr<FunctionDeclDefLink> link = interface.editor()->declDefLink(); - if (!link || !link->isMarkerVisible()) - return; - - auto op = new ApplyDeclDefLinkOperation(interface, link); - op->setDescription(Tr::tr("Apply Function Signature Changes")); - result << op; -} - -namespace { - -QString definitionSignature(const CppQuickFixInterface *assist, - FunctionDefinitionAST *functionDefinitionAST, - CppRefactoringFilePtr &baseFile, - CppRefactoringFilePtr &targetFile, - Scope *scope) -{ - QTC_ASSERT(assist, return QString()); - QTC_ASSERT(functionDefinitionAST, return QString()); - QTC_ASSERT(scope, return QString()); - Function *func = functionDefinitionAST->symbol; - QTC_ASSERT(func, return QString()); - - LookupContext cppContext(targetFile->cppDocument(), assist->snapshot()); - ClassOrNamespace *cppCoN = cppContext.lookupType(scope); - if (!cppCoN) - cppCoN = cppContext.globalNamespace(); - SubstitutionEnvironment env; - env.setContext(assist->context()); - env.switchScope(func->enclosingScope()); - UseMinimalNames q(cppCoN); - env.enter(&q); - Control *control = assist->context().bindings()->control().get(); - Overview oo = CppCodeStyleSettings::currentProjectCodeStyleOverview(); - oo.showFunctionSignatures = true; - oo.showReturnTypes = true; - oo.showArgumentNames = true; - oo.showEnclosingTemplate = true; - oo.showTemplateParameters = true; - oo.trailingReturnType = functionDefinitionAST->declarator - && functionDefinitionAST->declarator->postfix_declarator_list - && functionDefinitionAST->declarator->postfix_declarator_list->value - && functionDefinitionAST->declarator->postfix_declarator_list - ->value->asFunctionDeclarator() - && functionDefinitionAST->declarator->postfix_declarator_list - ->value->asFunctionDeclarator()->trailing_return_type; - const Name *name = func->name(); - if (name && nameIncludesOperatorName(name)) { - CoreDeclaratorAST *coreDeclarator = functionDefinitionAST->declarator->core_declarator; - const QString operatorNameText = baseFile->textOf(coreDeclarator); - oo.includeWhiteSpaceInOperatorName = operatorNameText.contains(QLatin1Char(' ')); - } - const QString nameText = oo.prettyName(LookupContext::minimalName(func, cppCoN, control)); - oo.showTemplateParameters = false; - const FullySpecifiedType tn = rewriteType(func->type(), &env, control); - - return oo.prettyType(tn, nameText); -} - -class MoveFuncDefRefactoringHelper -{ -public: - enum MoveType { - MoveOutside, - MoveToCppFile, - MoveOutsideMemberToCppFile - }; - - MoveFuncDefRefactoringHelper(CppQuickFixOperation *operation, MoveType type, - const FilePath &fromFile, const FilePath &toFile) - : m_operation(operation), m_type(type), m_changes(m_operation->snapshot()) - { - m_fromFile = m_changes.cppFile(fromFile); - m_toFile = (m_type == MoveOutside) ? m_fromFile : m_changes.cppFile(toFile); - } - - void performMove(FunctionDefinitionAST *funcAST) - { - // Determine file, insert position and scope - InsertionLocation l = insertLocationForMethodDefinition( - funcAST->symbol, false, NamespaceHandling::Ignore, - m_changes, m_toFile->filePath()); - const QString prefix = l.prefix(); - const QString suffix = l.suffix(); - const int insertPos = m_toFile->position(l.line(), l.column()); - Scope *scopeAtInsertPos = m_toFile->cppDocument()->scopeAt(l.line(), l.column()); - - // construct definition - const QString funcDec = inlinePrefix(m_toFile->filePath(), [this] { return m_type == MoveOutside; }) - + definitionSignature(m_operation, funcAST, m_fromFile, m_toFile, - scopeAtInsertPos); - QString funcDef = prefix + funcDec; - const int startPosition = m_fromFile->endOf(funcAST->declarator); - const int endPosition = m_fromFile->endOf(funcAST); - funcDef += m_fromFile->textOf(startPosition, endPosition); - funcDef += suffix; - - // insert definition at new position - m_toFileChangeSet.insert(insertPos, funcDef); - m_toFile->setOpenEditor(true, insertPos); - - // remove definition from fromFile - if (m_type == MoveOutsideMemberToCppFile) { - m_fromFileChangeSet.remove(m_fromFile->range(funcAST)); - } else { - QString textFuncDecl = m_fromFile->textOf(funcAST); - textFuncDecl.truncate(startPosition - m_fromFile->startOf(funcAST)); - if (textFuncDecl.left(7) == QLatin1String("inline ")) - textFuncDecl = textFuncDecl.mid(7); - else - textFuncDecl.replace(" inline ", QLatin1String(" ")); - textFuncDecl = textFuncDecl.trimmed() + QLatin1Char(';'); - m_fromFileChangeSet.replace(m_fromFile->range(funcAST), textFuncDecl); - } - } - - void applyChanges() - { - if (!m_toFileChangeSet.isEmpty()) { - m_toFile->setChangeSet(m_toFileChangeSet); - m_toFile->apply(); - } - if (!m_fromFileChangeSet.isEmpty()) { - m_fromFile->setChangeSet(m_fromFileChangeSet); - m_fromFile->apply(); - } - } - -private: - CppQuickFixOperation *m_operation; - MoveType m_type; - CppRefactoringChanges m_changes; - CppRefactoringFilePtr m_fromFile; - CppRefactoringFilePtr m_toFile; - ChangeSet m_fromFileChangeSet; - ChangeSet m_toFileChangeSet; -}; - -class MoveFuncDefOutsideOp : public CppQuickFixOperation -{ -public: - MoveFuncDefOutsideOp(const CppQuickFixInterface &interface, - MoveFuncDefRefactoringHelper::MoveType type, - FunctionDefinitionAST *funcDef, const FilePath &cppFilePath) - : CppQuickFixOperation(interface, 0) - , m_funcDef(funcDef) - , m_type(type) - , m_cppFilePath(cppFilePath) - , m_headerFilePath(funcDef->symbol->filePath()) - { - if (m_type == MoveFuncDefRefactoringHelper::MoveOutside) { - setDescription(Tr::tr("Move Definition Outside Class")); - } else { - const FilePath resolved = m_cppFilePath.relativePathFrom(m_headerFilePath.parentDir()); - setDescription(Tr::tr("Move Definition to %1").arg(resolved.displayName())); - } - } - - void perform() override - { - MoveFuncDefRefactoringHelper helper(this, m_type, m_headerFilePath, m_cppFilePath); - helper.performMove(m_funcDef); - helper.applyChanges(); - } - -private: - FunctionDefinitionAST *m_funcDef; - MoveFuncDefRefactoringHelper::MoveType m_type; - const FilePath m_cppFilePath; - const FilePath m_headerFilePath; -}; - -} // anonymous namespace - -void MoveFuncDefOutside::doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) -{ - const QList<AST *> &path = interface.path(); - SimpleDeclarationAST *classAST = nullptr; - FunctionDefinitionAST *funcAST = nullptr; - bool moveOutsideMemberDefinition = false; - - const int pathSize = path.size(); - for (int idx = 1; idx < pathSize; ++idx) { - if ((funcAST = path.at(idx)->asFunctionDefinition())) { - // check cursor position - if (idx != pathSize - 1 // Do not allow "void a() @ {..." - && funcAST->function_body - && !interface.isCursorOn(funcAST->function_body)) { - if (path.at(idx - 1)->asTranslationUnit()) { // normal function - if (idx + 3 < pathSize && path.at(idx + 3)->asQualifiedName()) // Outside member - moveOutsideMemberDefinition = true; // definition - break; - } - - if (idx > 1) { - if ((classAST = path.at(idx - 2)->asSimpleDeclaration())) // member function - break; - if (path.at(idx - 2)->asNamespace()) // normal function in namespace - break; - } - if (idx > 2 && path.at(idx - 1)->asTemplateDeclaration()) { - if ((classAST = path.at(idx - 3)->asSimpleDeclaration())) // member template - break; - } - } - funcAST = nullptr; - } - } - - if (!funcAST || !funcAST->symbol) - return; - - bool isHeaderFile = false; - const FilePath cppFileName = correspondingHeaderOrSource(interface.filePath(), &isHeaderFile); - - if (isHeaderFile && !cppFileName.isEmpty()) { - const MoveFuncDefRefactoringHelper::MoveType type = moveOutsideMemberDefinition - ? MoveFuncDefRefactoringHelper::MoveOutsideMemberToCppFile - : MoveFuncDefRefactoringHelper::MoveToCppFile; - result << new MoveFuncDefOutsideOp(interface, type, funcAST, cppFileName); - } - - if (classAST) - result << new MoveFuncDefOutsideOp(interface, MoveFuncDefRefactoringHelper::MoveOutside, - funcAST, FilePath()); - - return; -} - -namespace { - -class MoveAllFuncDefOutsideOp : public CppQuickFixOperation -{ -public: - MoveAllFuncDefOutsideOp(const CppQuickFixInterface &interface, - MoveFuncDefRefactoringHelper::MoveType type, - ClassSpecifierAST *classDef, const FilePath &cppFileName) - : CppQuickFixOperation(interface, 0) - , m_type(type) - , m_classDef(classDef) - , m_cppFilePath(cppFileName) - , m_headerFilePath(classDef->symbol->filePath()) - { - if (m_type == MoveFuncDefRefactoringHelper::MoveOutside) { - setDescription(Tr::tr("Definitions Outside Class")); - } else { - const FilePath resolved = m_cppFilePath.relativePathFrom(m_headerFilePath.parentDir()); - setDescription(Tr::tr("Move All Function Definitions to %1") - .arg(resolved.displayName())); - } - } - - void perform() override - { - MoveFuncDefRefactoringHelper helper(this, m_type, m_headerFilePath, m_cppFilePath); - for (DeclarationListAST *it = m_classDef->member_specifier_list; it; it = it->next) { - if (FunctionDefinitionAST *funcAST = it->value->asFunctionDefinition()) { - if (funcAST->symbol && !funcAST->symbol->isGenerated()) - helper.performMove(funcAST); - } - } - helper.applyChanges(); - } - -private: - MoveFuncDefRefactoringHelper::MoveType m_type; - ClassSpecifierAST *m_classDef; - const FilePath m_cppFilePath; - const FilePath m_headerFilePath; -}; - -} // anonymous namespace - -void MoveAllFuncDefOutside::doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) -{ - ClassSpecifierAST * const classAST = astForClassOperations(interface); - if (!classAST) - return; - - // Determine if the class has at least one function definition - bool classContainsFunctions = false; - for (DeclarationListAST *it = classAST->member_specifier_list; it; it = it->next) { - if (FunctionDefinitionAST *funcAST = it->value->asFunctionDefinition()) { - if (funcAST->symbol && !funcAST->symbol->isGenerated()) { - classContainsFunctions = true; - break; - } - } - } - if (!classContainsFunctions) - return; - - bool isHeaderFile = false; - const FilePath cppFileName = correspondingHeaderOrSource(interface.filePath(), &isHeaderFile); - if (isHeaderFile && !cppFileName.isEmpty()) { - result << new MoveAllFuncDefOutsideOp(interface, - MoveFuncDefRefactoringHelper::MoveToCppFile, - classAST, cppFileName); - } - result << new MoveAllFuncDefOutsideOp(interface, MoveFuncDefRefactoringHelper::MoveOutside, - classAST, FilePath()); -} - -namespace { - -class MoveFuncDefToDeclOp : public CppQuickFixOperation -{ -public: - enum Type { Push, Pull }; - MoveFuncDefToDeclOp(const CppQuickFixInterface &interface, - const FilePath &fromFilePath, const FilePath &toFilePath, - FunctionDefinitionAST *funcAst, Function *func, const QString &declText, - const ChangeSet::Range &fromRange, - const ChangeSet::Range &toRange, - Type type) - : CppQuickFixOperation(interface, 0) - , m_fromFilePath(fromFilePath) - , m_toFilePath(toFilePath) - , m_funcAST(funcAst) - , m_func(func) - , m_declarationText(declText) - , m_fromRange(fromRange) - , m_toRange(toRange) - { - if (type == Type::Pull) { - setDescription(Tr::tr("Move Definition Here")); - } else if (m_toFilePath == m_fromFilePath) { - setDescription(Tr::tr("Move Definition to Class")); - } else { - const FilePath resolved = m_toFilePath.relativePathFrom(m_fromFilePath.parentDir()); - setDescription(Tr::tr("Move Definition to %1").arg(resolved.displayName())); - } - } - -private: - void perform() override - { - CppRefactoringChanges refactoring(snapshot()); - CppRefactoringFilePtr fromFile = refactoring.cppFile(m_fromFilePath); - CppRefactoringFilePtr toFile = refactoring.cppFile(m_toFilePath); - - ensureFuncDefAstAndRange(*fromFile); - if (!m_funcAST) - return; - - const QString wholeFunctionText = m_declarationText - + fromFile->textOf(fromFile->endOf(m_funcAST->declarator), - fromFile->endOf(m_funcAST->function_body)); - - // Replace declaration with function and delete old definition - ChangeSet toTarget; - toTarget.replace(m_toRange, wholeFunctionText); - if (m_toFilePath == m_fromFilePath) - toTarget.remove(m_fromRange); - toFile->setChangeSet(toTarget); - toFile->setOpenEditor(true, m_toRange.start); - toFile->apply(); - if (m_toFilePath != m_fromFilePath) { - ChangeSet fromTarget; - fromTarget.remove(m_fromRange); - fromFile->setChangeSet(fromTarget); - fromFile->apply(); - } - } - - void ensureFuncDefAstAndRange(CppRefactoringFile &defFile) - { - if (m_funcAST) { - QTC_CHECK(m_fromRange.end > m_fromRange.start); - return; - } - QTC_ASSERT(m_func, return); - const QList<AST *> astPath = ASTPath(defFile.cppDocument())(m_func->line(), - m_func->column()); - if (astPath.isEmpty()) - return; - for (auto it = std::rbegin(astPath); it != std::rend(astPath); ++it) { - m_funcAST = (*it)->asFunctionDefinition(); - if (!m_funcAST) - continue; - AST *astForRange = m_funcAST; - const auto prev = std::next(it); - if (prev != std::rend(astPath)) { - if (const auto templAst = (*prev)->asTemplateDeclaration()) - astForRange = templAst; - } - m_fromRange = defFile.range(astForRange); - return; - } - } - - const FilePath m_fromFilePath; - const FilePath m_toFilePath; - FunctionDefinitionAST *m_funcAST; - Function *m_func; - const QString m_declarationText; - ChangeSet::Range m_fromRange; - const ChangeSet::Range m_toRange; -}; - -} // anonymous namespace - -void MoveFuncDefToDeclPush::doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) -{ - const QList<AST *> &path = interface.path(); - AST *completeDefAST = nullptr; - FunctionDefinitionAST *funcAST = nullptr; - - const int pathSize = path.size(); - for (int idx = 1; idx < pathSize; ++idx) { - if ((funcAST = path.at(idx)->asFunctionDefinition())) { - AST *enclosingAST = path.at(idx - 1); - if (enclosingAST->asClassSpecifier()) - return; - - // check cursor position - if (idx != pathSize - 1 // Do not allow "void a() @ {..." - && funcAST->function_body - && !interface.isCursorOn(funcAST->function_body)) { - completeDefAST = enclosingAST->asTemplateDeclaration() ? enclosingAST : funcAST; - break; - } - funcAST = nullptr; - } - } - - if (!funcAST || !funcAST->symbol) - return; - - const CppRefactoringChanges refactoring(interface.snapshot()); - const CppRefactoringFilePtr defFile = refactoring.cppFile(interface.filePath()); - const ChangeSet::Range defRange = defFile->range(completeDefAST); - - // Determine declaration (file, range, text); - ChangeSet::Range declRange; - QString declText; - FilePath declFilePath; - - Function *func = funcAST->symbol; - if (Class *matchingClass = isMemberFunction(interface.context(), func)) { - // Dealing with member functions - const QualifiedNameId *qName = func->name()->asQualifiedNameId(); - for (Symbol *symbol = matchingClass->find(qName->identifier()); - symbol; symbol = symbol->next()) { - Symbol *s = symbol; - if (func->enclosingScope()->asTemplate()) { - if (const Template *templ = s->type()->asTemplateType()) { - if (Symbol *decl = templ->declaration()) { - if (decl->type()->asFunctionType()) - s = decl; - } - } - } - if (!s->name() - || !qName->identifier()->match(s->identifier()) - || !s->type()->asFunctionType() - || !s->type().match(func->type()) - || s->asFunction()) { - continue; - } - - declFilePath = matchingClass->filePath(); - const CppRefactoringFilePtr declFile = refactoring.cppFile(declFilePath); - ASTPath astPath(declFile->cppDocument()); - const QList<AST *> path = astPath(s->line(), s->column()); - for (int idx = path.size() - 1; idx > 0; --idx) { - AST *node = path.at(idx); - if (SimpleDeclarationAST *simpleDecl = node->asSimpleDeclaration()) { - if (simpleDecl->symbols && !simpleDecl->symbols->next) { - declRange = declFile->range(simpleDecl); - declText = declFile->textOf(simpleDecl); - declText.remove(-1, 1); // remove ';' from declaration text - break; - } - } - } - - if (!declText.isEmpty()) - break; - } - } else if (Namespace *matchingNamespace = isNamespaceFunction(interface.context(), func)) { - // Dealing with free functions - bool isHeaderFile = false; - declFilePath = correspondingHeaderOrSource(interface.filePath(), &isHeaderFile); - if (isHeaderFile) - return; - - const CppRefactoringFilePtr declFile = refactoring.cppFile(declFilePath); - const LookupContext lc(declFile->cppDocument(), interface.snapshot()); - const QList<LookupItem> candidates = lc.lookup(func->name(), matchingNamespace); - for (const LookupItem &candidate : candidates) { - if (Symbol *s = candidate.declaration()) { - if (s->asDeclaration()) { - ASTPath astPath(declFile->cppDocument()); - const QList<AST *> path = astPath(s->line(), s->column()); - for (AST *node : path) { - if (SimpleDeclarationAST *simpleDecl = node->asSimpleDeclaration()) { - declRange = declFile->range(simpleDecl); - declText = declFile->textOf(simpleDecl); - declText.remove(-1, 1); // remove ';' from declaration text - break; - } - } - } - } - - if (!declText.isEmpty()) { - declText.prepend(inlinePrefix(declFilePath)); - break; - } - } - } - - if (!declFilePath.isEmpty() && !declText.isEmpty()) - result << new MoveFuncDefToDeclOp(interface, - interface.filePath(), - declFilePath, - funcAST, func, declText, - defRange, declRange, MoveFuncDefToDeclOp::Push); -} - -void MoveFuncDefToDeclPull::doMatch(const CppQuickFixInterface &interface, - QuickFixOperations &result) -{ - const QList<AST *> &path = interface.path(); - for (auto it = std::rbegin(path); it != std::rend(path); ++it) { - SimpleDeclarationAST * const simpleDecl = (*it)->asSimpleDeclaration(); - if (!simpleDecl) - continue; - const auto prev = std::next(it); - if (prev != std::rend(path) && (*prev)->asStatement()) - return; - if (!simpleDecl->symbols || !simpleDecl->symbols->value || simpleDecl->symbols->next) - return; - Declaration * const decl = simpleDecl->symbols->value->asDeclaration(); - if (!decl) - return; - Function * const funcDecl = decl->type()->asFunctionType(); - if (!funcDecl) - return; - if (funcDecl->isSignal() || funcDecl->isPureVirtual() || funcDecl->isFriend()) - return; - - // Is there a definition? - SymbolFinder symbolFinder; - Function * const funcDef = symbolFinder.findMatchingDefinition(decl, interface.snapshot(), - true); - if (!funcDef) - return; - - QString declText = interface.currentFile()->textOf(simpleDecl); - declText.chop(1); // semicolon - declText.prepend(inlinePrefix(interface.filePath(), [funcDecl] { - return !funcDecl->enclosingScope()->asClass(); - })); - result << new MoveFuncDefToDeclOp(interface, funcDef->filePath(), decl->filePath(), nullptr, - funcDef, declText, {}, - interface.currentFile()->range(simpleDecl), - MoveFuncDefToDeclOp::Pull); - return; - } -} - - -namespace { - -class AssignToLocalVariableOperation : public CppQuickFixOperation -{ -public: - explicit AssignToLocalVariableOperation(const CppQuickFixInterface &interface, - const int insertPos, const AST *ast, const Name *name) - : CppQuickFixOperation(interface) - , m_insertPos(insertPos) - , m_ast(ast) - , m_name(name) - , m_oo(CppCodeStyleSettings::currentProjectCodeStyleOverview()) - , m_originalName(m_oo.prettyName(m_name)) - , m_file(CppRefactoringChanges(snapshot()).cppFile(filePath())) - { - setDescription(Tr::tr("Assign to Local Variable")); - } - -private: - void perform() override - { - QString type = deduceType(); - if (type.isEmpty()) - return; - const int origNameLength = m_originalName.length(); - const QString varName = constructVarName(); - const QString insertString = type.replace(type.length() - origNameLength, origNameLength, - varName + QLatin1String(" = ")); - ChangeSet changes; - changes.insert(m_insertPos, insertString); - m_file->setChangeSet(changes); - m_file->apply(); - - // move cursor to new variable name - QTextCursor c = m_file->cursor(); - c.setPosition(m_insertPos + insertString.length() - varName.length() - 3); - c.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor); - editor()->setTextCursor(c); - } - - QString deduceType() const - { - const auto settings = CppQuickFixProjectsSettings::getQuickFixSettings( - ProjectExplorer::ProjectTree::currentProject()); - if (m_file->cppDocument()->languageFeatures().cxx11Enabled && settings->useAuto) - return "auto " + m_originalName; - - TypeOfExpression typeOfExpression; - typeOfExpression.init(semanticInfo().doc, snapshot(), context().bindings()); - typeOfExpression.setExpandTemplates(true); - Scope * const scope = m_file->scopeAt(m_ast->firstToken()); - const QList<LookupItem> result = typeOfExpression(m_file->textOf(m_ast).toUtf8(), - scope, TypeOfExpression::Preprocess); - if (result.isEmpty()) - return {}; - - SubstitutionEnvironment env; - env.setContext(context()); - env.switchScope(result.first().scope()); - ClassOrNamespace *con = typeOfExpression.context().lookupType(scope); - if (!con) - con = typeOfExpression.context().globalNamespace(); - UseMinimalNames q(con); - env.enter(&q); - - Control *control = context().bindings()->control().get(); - FullySpecifiedType type = rewriteType(result.first().type(), &env, control); - - return m_oo.prettyType(type, m_name); - } - - QString constructVarName() const - { - QString newName = m_originalName; - if (newName.startsWith(QLatin1String("get"), Qt::CaseInsensitive) - && newName.length() > 3 - && newName.at(3).isUpper()) { - newName.remove(0, 3); - newName.replace(0, 1, newName.at(0).toLower()); - } else if (newName.startsWith(QLatin1String("to"), Qt::CaseInsensitive) - && newName.length() > 2 - && newName.at(2).isUpper()) { - newName.remove(0, 2); - newName.replace(0, 1, newName.at(0).toLower()); - } else { - newName.replace(0, 1, newName.at(0).toUpper()); - newName.prepend(QLatin1String("local")); - } - return newName; - } - - const int m_insertPos; - const AST * const m_ast; - const Name * const m_name; - const Overview m_oo; - const QString m_originalName; - const CppRefactoringFilePtr m_file; -}; - -} // anonymous namespace - -void AssignToLocalVariable::doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) -{ - const QList<AST *> &path = interface.path(); - AST *outerAST = nullptr; - SimpleNameAST *nameAST = nullptr; - - for (int i = path.size() - 3; i >= 0; --i) { - if (CallAST *callAST = path.at(i)->asCall()) { - if (!interface.isCursorOn(callAST)) - return; - if (i - 2 >= 0) { - const int idx = i - 2; - if (path.at(idx)->asSimpleDeclaration()) - return; - if (path.at(idx)->asExpressionStatement()) - return; - if (path.at(idx)->asMemInitializer()) - return; - if (path.at(idx)->asCall()) { // Fallback if we have a->b()->c()... - --i; - continue; - } - } - for (int a = i - 1; a > 0; --a) { - if (path.at(a)->asBinaryExpression()) - return; - if (path.at(a)->asReturnStatement()) - return; - if (path.at(a)->asCall()) - return; - } - - if (MemberAccessAST *member = path.at(i + 1)->asMemberAccess()) { // member - if (NameAST *name = member->member_name) - nameAST = name->asSimpleName(); - } else if (QualifiedNameAST *qname = path.at(i + 2)->asQualifiedName()) { // static or - nameAST = qname->unqualified_name->asSimpleName(); // func in ns - } else { // normal - nameAST = path.at(i + 2)->asSimpleName(); - } - - if (nameAST) { - outerAST = callAST; - break; - } - } else if (NewExpressionAST *newexp = path.at(i)->asNewExpression()) { - if (!interface.isCursorOn(newexp)) - return; - if (i - 2 >= 0) { - const int idx = i - 2; - if (path.at(idx)->asSimpleDeclaration()) - return; - if (path.at(idx)->asExpressionStatement()) - return; - if (path.at(idx)->asMemInitializer()) - return; - } - for (int a = i - 1; a > 0; --a) { - if (path.at(a)->asReturnStatement()) - return; - if (path.at(a)->asCall()) - return; - } - - if (NamedTypeSpecifierAST *ts = path.at(i + 2)->asNamedTypeSpecifier()) { - nameAST = ts->name->asSimpleName(); - outerAST = newexp; - break; - } - } - } - - if (outerAST && nameAST) { - const CppRefactoringFilePtr file = interface.currentFile(); - QList<LookupItem> items; - TypeOfExpression typeOfExpression; - typeOfExpression.init(interface.semanticInfo().doc, interface.snapshot(), - interface.context().bindings()); - typeOfExpression.setExpandTemplates(true); - - // If items are empty, AssignToLocalVariableOperation will fail. - items = typeOfExpression(file->textOf(outerAST).toUtf8(), - file->scopeAt(outerAST->firstToken()), - TypeOfExpression::Preprocess); - if (items.isEmpty()) - return; - - if (CallAST *callAST = outerAST->asCall()) { - items = typeOfExpression(file->textOf(callAST->base_expression).toUtf8(), - file->scopeAt(callAST->base_expression->firstToken()), - TypeOfExpression::Preprocess); - } else { - items = typeOfExpression(file->textOf(nameAST).toUtf8(), - file->scopeAt(nameAST->firstToken()), - TypeOfExpression::Preprocess); - } - - for (const LookupItem &item : std::as_const(items)) { - if (!item.declaration()) - continue; - - if (Function *func = item.declaration()->asFunction()) { - if (func->isSignal() || func->returnType()->asVoidType()) - return; - } else if (Declaration *dec = item.declaration()->asDeclaration()) { - if (Function *func = dec->type()->asFunctionType()) { - if (func->isSignal() || func->returnType()->asVoidType()) - return; - } - } - - const Name *name = nameAST->name; - const int insertPos = interface.currentFile()->startOf(outerAST); - result << new AssignToLocalVariableOperation(interface, insertPos, outerAST, name); - return; - } - } -} - -namespace { - -class OptimizeForLoopOperation: public CppQuickFixOperation -{ -public: - OptimizeForLoopOperation(const CppQuickFixInterface &interface, const ForStatementAST *forAst, - const bool optimizePostcrement, const ExpressionAST *expression, - const FullySpecifiedType &type) - : CppQuickFixOperation(interface) - , m_forAst(forAst) - , m_optimizePostcrement(optimizePostcrement) - , m_expression(expression) - , m_type(type) - { - setDescription(Tr::tr("Optimize for-Loop")); - } - - void perform() override - { - QTC_ASSERT(m_forAst, return); - - const Utils::FilePath filePath = currentFile()->filePath(); - const CppRefactoringChanges refactoring(snapshot()); - const CppRefactoringFilePtr file = refactoring.cppFile(filePath); - ChangeSet change; - - // Optimize post (in|de)crement operator to pre (in|de)crement operator - if (m_optimizePostcrement && m_forAst->expression) { - PostIncrDecrAST *incrdecr = m_forAst->expression->asPostIncrDecr(); - if (incrdecr && incrdecr->base_expression && incrdecr->incr_decr_token) { - change.flip(file->range(incrdecr->base_expression), - file->range(incrdecr->incr_decr_token)); - } - } - - // Optimize Condition - int renamePos = -1; - if (m_expression) { - QString varName = QLatin1String("total"); - - if (file->textOf(m_forAst->initializer).length() == 1) { - Overview oo = CppCodeStyleSettings::currentProjectCodeStyleOverview(); - const QString typeAndName = oo.prettyType(m_type, varName); - renamePos = file->endOf(m_forAst->initializer) - 1 + typeAndName.length(); - change.insert(file->endOf(m_forAst->initializer) - 1, // "-1" because of ";" - typeAndName + QLatin1String(" = ") + file->textOf(m_expression)); - } else { - // Check if varName is already used - if (DeclarationStatementAST *ds = m_forAst->initializer->asDeclarationStatement()) { - if (DeclarationAST *decl = ds->declaration) { - if (SimpleDeclarationAST *sdecl = decl->asSimpleDeclaration()) { - for (;;) { - bool match = false; - for (DeclaratorListAST *it = sdecl->declarator_list; it; - it = it->next) { - if (file->textOf(it->value->core_declarator) == varName) { - varName += QLatin1Char('X'); - match = true; - break; - } - } - if (!match) - break; - } - } - } - } - - renamePos = file->endOf(m_forAst->initializer) + 1; - change.insert(file->endOf(m_forAst->initializer) - 1, // "-1" because of ";" - QLatin1String(", ") + varName + QLatin1String(" = ") - + file->textOf(m_expression)); - } - - ChangeSet::Range exprRange(file->startOf(m_expression), file->endOf(m_expression)); - change.replace(exprRange, varName); - } - - file->setChangeSet(change); - file->apply(); - - // Select variable name and trigger symbol rename - if (renamePos != -1) { - QTextCursor c = file->cursor(); - c.setPosition(renamePos); - editor()->setTextCursor(c); - editor()->renameSymbolUnderCursor(); - c.select(QTextCursor::WordUnderCursor); - editor()->setTextCursor(c); - } - } - -private: - const ForStatementAST *m_forAst; - const bool m_optimizePostcrement; - const ExpressionAST *m_expression; - const FullySpecifiedType m_type; -}; - -} // anonymous namespace - -void OptimizeForLoop::doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) -{ - const QList<AST *> path = interface.path(); - ForStatementAST *forAst = nullptr; - if (!path.isEmpty()) - forAst = path.last()->asForStatement(); - if (!forAst || !interface.isCursorOn(forAst)) - return; - - // Check for optimizing a postcrement - const CppRefactoringFilePtr file = interface.currentFile(); - bool optimizePostcrement = false; - if (forAst->expression) { - if (PostIncrDecrAST *incrdecr = forAst->expression->asPostIncrDecr()) { - const Token t = file->tokenAt(incrdecr->incr_decr_token); - if (t.is(T_PLUS_PLUS) || t.is(T_MINUS_MINUS)) - optimizePostcrement = true; - } - } - - // Check for optimizing condition - bool optimizeCondition = false; - FullySpecifiedType conditionType; - ExpressionAST *conditionExpression = nullptr; - if (forAst->initializer && forAst->condition) { - if (BinaryExpressionAST *binary = forAst->condition->asBinaryExpression()) { - // Get the expression against which we should evaluate - IdExpressionAST *conditionId = binary->left_expression->asIdExpression(); - if (conditionId) { - conditionExpression = binary->right_expression; - } else { - conditionId = binary->right_expression->asIdExpression(); - conditionExpression = binary->left_expression; - } - - if (conditionId && conditionExpression - && !(conditionExpression->asNumericLiteral() - || conditionExpression->asStringLiteral() - || conditionExpression->asIdExpression() - || conditionExpression->asUnaryExpression())) { - // Determine type of for initializer - FullySpecifiedType initializerType; - if (DeclarationStatementAST *stmt = forAst->initializer->asDeclarationStatement()) { - if (stmt->declaration) { - if (SimpleDeclarationAST *decl = stmt->declaration->asSimpleDeclaration()) { - if (decl->symbols) { - if (Symbol *symbol = decl->symbols->value) - initializerType = symbol->type(); - } - } - } - } - - // Determine type of for condition - TypeOfExpression typeOfExpression; - typeOfExpression.init(interface.semanticInfo().doc, interface.snapshot(), - interface.context().bindings()); - typeOfExpression.setExpandTemplates(true); - Scope *scope = file->scopeAt(conditionId->firstToken()); - const QList<LookupItem> conditionItems = typeOfExpression( - conditionId, interface.semanticInfo().doc, scope); - if (!conditionItems.isEmpty()) - conditionType = conditionItems.first().type(); - - if (conditionType.isValid() - && (file->textOf(forAst->initializer) == QLatin1String(";") - || initializerType == conditionType)) { - optimizeCondition = true; - } - } - } - } - - if (optimizePostcrement || optimizeCondition) { - result << new OptimizeForLoopOperation(interface, forAst, optimizePostcrement, - optimizeCondition ? conditionExpression : nullptr, - conditionType); - } -} - -namespace { - -class EscapeStringLiteralOperation: public CppQuickFixOperation -{ -public: - EscapeStringLiteralOperation(const CppQuickFixInterface &interface, - ExpressionAST *literal, bool escape) - : CppQuickFixOperation(interface) - , m_literal(literal) - , m_escape(escape) - { - if (m_escape) { - setDescription(Tr::tr("Escape String Literal as UTF-8")); - } else { - setDescription(Tr::tr("Unescape String Literal as UTF-8")); - } - } - -private: - static inline bool isDigit(quint8 ch, int base) - { - if (base == 8) - return ch >= '0' && ch < '8'; - if (base == 16) - return isxdigit(ch); - return false; - } - - static QByteArrayList escapeString(const QByteArray &contents) - { - QByteArrayList newContents; - QByteArray chunk; - bool wasEscaped = false; - for (const quint8 c : contents) { - const bool needsEscape = !isascii(c) || !isprint(c); - if (!needsEscape && wasEscaped && std::isxdigit(c) && !chunk.isEmpty()) { - newContents << chunk; - chunk.clear(); - } - if (needsEscape) - chunk += QByteArray("\\x") + QByteArray::number(c, 16).rightJustified(2, '0'); - else - chunk += c; - wasEscaped = needsEscape; - } - if (!chunk.isEmpty()) - newContents << chunk; - return newContents; - } - - static QByteArray unescapeString(const QByteArray &contents) - { - QByteArray newContents; - const int len = contents.length(); - for (int i = 0; i < len; ++i) { - quint8 c = contents.at(i); - if (c == '\\' && i < len - 1) { - int idx = i + 1; - quint8 ch = contents.at(idx); - int base = 0; - int maxlen = 0; - if (isDigit(ch, 8)) { - base = 8; - maxlen = 3; - } else if ((ch == 'x' || ch == 'X') && idx < len - 1) { - base = 16; - maxlen = 2; - ch = contents.at(++idx); - } - if (base > 0) { - QByteArray buf; - while (isDigit(ch, base) && idx < len && buf.length() < maxlen) { - buf += ch; - ++idx; - if (idx == len) - break; - ch = contents.at(idx); - } - if (!buf.isEmpty()) { - bool ok; - uint value = buf.toUInt(&ok, base); - // Don't unescape isascii() && !isprint() - if (ok && (!isascii(value) || isprint(value))) { - newContents += value; - i = idx - 1; - continue; - } - } - } - newContents += c; - c = contents.at(++i); - } - newContents += c; - } - return newContents; - } - - // QuickFixOperation interface -public: - void perform() override - { - CppRefactoringChanges refactoring(snapshot()); - CppRefactoringFilePtr currentFile = refactoring.cppFile(filePath()); - - const int startPos = currentFile->startOf(m_literal); - const int endPos = currentFile->endOf(m_literal); - - StringLiteralAST *stringLiteral = m_literal->asStringLiteral(); - QTC_ASSERT(stringLiteral, return); - const QByteArray oldContents(currentFile->tokenAt(stringLiteral->literal_token). - identifier->chars()); - QByteArrayList newContents; - if (m_escape) - newContents = escapeString(oldContents); - else - newContents = {unescapeString(oldContents)}; - - if (newContents.isEmpty() - || (newContents.size() == 1 && newContents.first() == oldContents)) { - return; - } - - QTextCodec *utf8codec = QTextCodec::codecForName("UTF-8"); - QScopedPointer<QTextDecoder> decoder(utf8codec->makeDecoder()); - ChangeSet changes; - - bool replace = true; - for (const QByteArray &chunk : std::as_const(newContents)) { - const QString str = decoder->toUnicode(chunk); - const QByteArray utf8buf = str.toUtf8(); - if (!utf8codec->canEncode(str) || chunk != utf8buf) - return; - if (replace) - changes.replace(startPos + 1, endPos - 1, str); - else - changes.insert(endPos, "\"" + str + "\""); - replace = false; - } - currentFile->setChangeSet(changes); - currentFile->apply(); - } - -private: - ExpressionAST *m_literal; - bool m_escape; -}; - -} // anonymous namespace - -void EscapeStringLiteral::doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) -{ - const QList<AST *> &path = interface.path(); - if (path.isEmpty()) - return; - - AST * const lastAst = path.last(); - ExpressionAST *literal = lastAst->asStringLiteral(); - if (!literal) - return; - - StringLiteralAST *stringLiteral = literal->asStringLiteral(); - CppRefactoringFilePtr file = interface.currentFile(); - const QByteArray contents(file->tokenAt(stringLiteral->literal_token).identifier->chars()); - - bool canEscape = false; - bool canUnescape = false; - for (int i = 0; i < contents.length(); ++i) { - quint8 c = contents.at(i); - if (!isascii(c) || !isprint(c)) { - canEscape = true; - } else if (c == '\\' && i < contents.length() - 1) { - c = contents.at(++i); - if ((c >= '0' && c < '8') || c == 'x' || c == 'X') - canUnescape = true; - } - } - - if (canEscape) - result << new EscapeStringLiteralOperation(interface, literal, true); - - if (canUnescape) - result << new EscapeStringLiteralOperation(interface, literal, false); -} - - -namespace { - -class ConvertQt4ConnectOperation: public CppQuickFixOperation -{ -public: - ConvertQt4ConnectOperation(const CppQuickFixInterface &interface, const ChangeSet &changes) - : CppQuickFixOperation(interface, 1), m_changes(changes) - { - setDescription(Tr::tr("Convert connect() to Qt 5 Style")); - } - -private: - void perform() override - { - CppRefactoringChanges refactoring(snapshot()); - CppRefactoringFilePtr currentFile = refactoring.cppFile(filePath()); - currentFile->setChangeSet(m_changes); - currentFile->apply(); - } - - const ChangeSet m_changes; -}; - -Symbol *skipForwardDeclarations(const QList<Symbol *> &symbols) -{ - for (Symbol *symbol : symbols) { - if (!symbol->type()->asForwardClassDeclarationType()) - return symbol; - } - - return nullptr; -} - -bool findRawAccessFunction(Class *klass, PointerType *pointerType, QString *objAccessFunction) -{ - QList<Function *> candidates; - for (auto it = klass->memberBegin(), end = klass->memberEnd(); it != end; ++it) { - if (Function *func = (*it)->asFunction()) { - const Name *funcName = func->name(); - if (!funcName->asOperatorNameId() - && !funcName->asConversionNameId() - && func->returnType().type() == pointerType - && func->isConst() - && func->argumentCount() == 0) { - candidates << func; - } - } - } - const Name *funcName = nullptr; - switch (candidates.size()) { - case 0: - return false; - case 1: - funcName = candidates.first()->name(); - break; - default: - // Multiple candidates - prefer a function named data - for (Function *func : std::as_const(candidates)) { - if (!strcmp(func->name()->identifier()->chars(), "data")) { - funcName = func->name(); - break; - } - } - if (!funcName) - funcName = candidates.first()->name(); - } - const Overview oo = CppCodeStyleSettings::currentProjectCodeStyleOverview(); - *objAccessFunction = QLatin1Char('.') + oo.prettyName(funcName) + QLatin1String("()"); - return true; -} - -PointerType *determineConvertedType(NamedType *namedType, const LookupContext &context, - Scope *scope, QString *objAccessFunction) -{ - if (!namedType) - return nullptr; - if (ClassOrNamespace *binding = context.lookupType(namedType->name(), scope)) { - if (Symbol *objectClassSymbol = skipForwardDeclarations(binding->symbols())) { - if (Class *klass = objectClassSymbol->asClass()) { - for (auto it = klass->memberBegin(), end = klass->memberEnd(); it != end; ++it) { - if (Function *func = (*it)->asFunction()) { - if (const ConversionNameId *conversionName = - func->name()->asConversionNameId()) { - if (PointerType *type = conversionName->type()->asPointerType()) { - if (findRawAccessFunction(klass, type, objAccessFunction)) - return type; - } - } - } - } - } - } - } - - return nullptr; -} - -Class *senderOrReceiverClass(const CppQuickFixInterface &interface, - const CppRefactoringFilePtr &file, - const ExpressionAST *objectPointerAST, - Scope *objectPointerScope, - QString *objAccessFunction) -{ - const LookupContext &context = interface.context(); - - QByteArray objectPointerExpression; - if (objectPointerAST) - objectPointerExpression = file->textOf(objectPointerAST).toUtf8(); - else - objectPointerExpression = "this"; - - TypeOfExpression toe; - toe.setExpandTemplates(true); - toe.init(interface.semanticInfo().doc, interface.snapshot(), context.bindings()); - const QList<LookupItem> objectPointerExpressions = toe(objectPointerExpression, - objectPointerScope, TypeOfExpression::Preprocess); - QTC_ASSERT(!objectPointerExpressions.isEmpty(), return nullptr); - - Type *objectPointerTypeBase = objectPointerExpressions.first().type().type(); - QTC_ASSERT(objectPointerTypeBase, return nullptr); - - PointerType *objectPointerType = objectPointerTypeBase->asPointerType(); - if (!objectPointerType) { - objectPointerType = determineConvertedType(objectPointerTypeBase->asNamedType(), context, - objectPointerScope, objAccessFunction); - } - QTC_ASSERT(objectPointerType, return nullptr); - - Type *objectTypeBase = objectPointerType->elementType().type(); // Dereference - QTC_ASSERT(objectTypeBase, return nullptr); - - NamedType *objectType = objectTypeBase->asNamedType(); - QTC_ASSERT(objectType, return nullptr); - - ClassOrNamespace *objectClassCON = context.lookupType(objectType->name(), objectPointerScope); - if (!objectClassCON) { - objectClassCON = objectPointerExpressions.first().binding(); - QTC_ASSERT(objectClassCON, return nullptr); - } - QTC_ASSERT(!objectClassCON->symbols().isEmpty(), return nullptr); - - Symbol *objectClassSymbol = skipForwardDeclarations(objectClassCON->symbols()); - QTC_ASSERT(objectClassSymbol, return nullptr); - - return objectClassSymbol->asClass(); -} - -bool findConnectReplacement(const CppQuickFixInterface &interface, - const ExpressionAST *objectPointerAST, - const QtMethodAST *methodAST, - const CppRefactoringFilePtr &file, - QString *replacement, - QString *objAccessFunction) -{ - // Get name of method - if (!methodAST->declarator || !methodAST->declarator->core_declarator) - return false; - - DeclaratorIdAST *methodDeclIdAST = methodAST->declarator->core_declarator->asDeclaratorId(); - if (!methodDeclIdAST) - return false; - - NameAST *methodNameAST = methodDeclIdAST->name; - if (!methodNameAST) - return false; - - // Lookup object pointer type - Scope *scope = file->scopeAt(methodAST->firstToken()); - Class *objectClass = senderOrReceiverClass(interface, file, objectPointerAST, scope, - objAccessFunction); - QTC_ASSERT(objectClass, return false); - - // Look up member function in call, including base class members. - const LookupContext &context = interface.context(); - const QList<LookupItem> methodResults = context.lookup(methodNameAST->name, objectClass); - if (methodResults.isEmpty()) - return false; // Maybe mis-spelled signal/slot name - - Scope *baseClassScope = methodResults.at(0).scope(); // FIXME: Handle overloads - QTC_ASSERT(baseClassScope, return false); - - Class *classOfMethod = baseClassScope->asClass(); // Declaration point of signal/slot - QTC_ASSERT(classOfMethod, return false); - - Symbol *method = methodResults.at(0).declaration(); - QTC_ASSERT(method, return false); - - // Minimize qualification - Control *control = context.bindings()->control().get(); - ClassOrNamespace *functionCON = context.lookupParent(scope); - const Name *shortName = LookupContext::minimalName(method, functionCON, control); - if (!shortName->asQualifiedNameId()) - shortName = control->qualifiedNameId(classOfMethod->name(), shortName); - - const Overview oo = CppCodeStyleSettings::currentProjectCodeStyleOverview(); - *replacement = QLatin1Char('&') + oo.prettyName(shortName); - return true; -} - -bool onConnectOrDisconnectCall(AST *ast, const ExpressionListAST **arguments) -{ - if (!ast) - return false; - - CallAST *call = ast->asCall(); - if (!call) - return false; - - if (!call->base_expression) - return false; - - const IdExpressionAST *idExpr = call->base_expression->asIdExpression(); - if (!idExpr || !idExpr->name || !idExpr->name->name) - return false; - - const ExpressionListAST *args = call->expression_list; - if (!arguments) - return false; - - const Identifier *id = idExpr->name->name->identifier(); - if (!id) - return false; - - const QByteArray name(id->chars(), id->size()); - if (name != "connect" && name != "disconnect") - return false; - - if (arguments) - *arguments = args; - return true; -} - -// Might modify arg* output arguments even if false is returned. -bool collectConnectArguments(const ExpressionListAST *arguments, - const ExpressionAST **arg1, const QtMethodAST **arg2, - const ExpressionAST **arg3, const QtMethodAST **arg4) -{ - if (!arguments || !arg1 || !arg2 || !arg3 || !arg4) - return false; - - *arg1 = arguments->value; - arguments = arguments->next; - if (!arg1 || !arguments) - return false; - - *arg2 = arguments->value->asQtMethod(); - arguments = arguments->next; - if (!*arg2 || !arguments) - return false; - - *arg3 = arguments->value; - if (!*arg3) - return false; - - // Take care of three-arg version, with 'this' receiver. - if (QtMethodAST *receiverMethod = arguments->value->asQtMethod()) { - *arg3 = nullptr; // Means 'this' - *arg4 = receiverMethod; - return true; - } - - arguments = arguments->next; - if (!arguments) - return false; - - *arg4 = arguments->value->asQtMethod(); - if (!*arg4) - return false; - - return true; -} - -} // anonynomous namespace - -void ConvertQt4Connect::doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) -{ - const QList<AST *> &path = interface.path(); - - for (int i = path.size(); --i >= 0; ) { - const ExpressionListAST *arguments; - if (!onConnectOrDisconnectCall(path.at(i), &arguments)) - continue; - - const ExpressionAST *arg1, *arg3; - const QtMethodAST *arg2, *arg4; - if (!collectConnectArguments(arguments, &arg1, &arg2, &arg3, &arg4)) - continue; - - const CppRefactoringFilePtr file = interface.currentFile(); - - QString newSignal; - QString senderAccessFunc; - if (!findConnectReplacement(interface, arg1, arg2, file, &newSignal, &senderAccessFunc)) - continue; - - QString newMethod; - QString receiverAccessFunc; - if (!findConnectReplacement(interface, arg3, arg4, file, &newMethod, &receiverAccessFunc)) - continue; - - ChangeSet changes; - changes.replace(file->endOf(arg1), file->endOf(arg1), senderAccessFunc); - changes.replace(file->startOf(arg2), file->endOf(arg2), newSignal); - if (!arg3) - newMethod.prepend(QLatin1String("this, ")); - else - changes.replace(file->endOf(arg3), file->endOf(arg3), receiverAccessFunc); - changes.replace(file->startOf(arg4), file->endOf(arg4), newMethod); - - result << new ConvertQt4ConnectOperation(interface, changes); - return; - } -} - -void ExtraRefactoringOperations::doMatch(const CppQuickFixInterface &interface, - QuickFixOperations &result) -{ - const auto processor = CppModelManager::cppEditorDocumentProcessor(interface.filePath()); - if (processor) { - const auto clangFixItOperations = processor->extraRefactoringOperations(interface); - result.append(clangFixItOperations); - } -} - -namespace { - -/** - * @brief The NameCounter class counts the parts of a name. E.g. 2 for std::vector or 1 for variant - */ -class NameCounter : private NameVisitor -{ -public: - int count(const Name *name) - { - counter = 0; - accept(name); - return counter; - } - -private: - void visit(const Identifier *) override { ++counter; } - void visit(const DestructorNameId *) override { ++counter; } - void visit(const TemplateNameId *) override { ++counter; } - void visit(const QualifiedNameId *name) override - { - if (name->base()) - accept(name->base()); - accept(name->name()); - } - int counter; -}; - -/** - * @brief getBaseName returns the base name of a qualified name or nullptr. - * E.g.: foo::bar => foo; bar => bar - * @param name The Name, maybe qualified - * @return The base name of the qualified name or nullptr - */ -const Identifier *getBaseName(const Name *name) -{ - class GetBaseName : public NameVisitor - { - void visit(const Identifier *name) override { baseName = name; } - void visit(const QualifiedNameId *name) override - { - if (name->base()) - accept(name->base()); - else - accept(name->name()); - } - - public: - const Identifier *baseName = nullptr; - }; - GetBaseName getter; - getter.accept(name); - return getter.baseName; -} - -/** - * @brief countNames counts the parts of the Name. - * E.g. if the name is std::vector, the function returns 2, if the name is variant, returns 1 - * @param name The name that should be counted - * @return the number of parts of the name - */ -int countNames(const Name *name) -{ - return NameCounter{}.count(name); -} - -/** - * @brief removeLine removes the whole line in which the ast node is located if there are otherwise only whitespaces - * @param file The file in which the AST node is located - * @param ast The ast node - * @param changeSet The ChangeSet of the file - */ -void removeLine(const CppRefactoringFile *file, AST *ast, ChangeSet &changeSet) -{ - RefactoringFile::Range range = file->range(ast); - --range.start; - while (range.start >= 0) { - QChar current = file->charAt(range.start); - if (!current.isSpace()) { - ++range.start; - break; - } - if (current == QChar::ParagraphSeparator) - break; - --range.start; - } - range.start = std::max(0, range.start); - while (range.end < file->document()->characterCount()) { - QChar current = file->charAt(range.end); - if (!current.isSpace()) - break; - if (current == QChar::ParagraphSeparator) - break; - ++range.end; - } - range.end = std::min(file->document()->characterCount(), range.end); - const bool newLineStart = file->charAt(range.start) == QChar::ParagraphSeparator; - const bool newLineEnd = file->charAt(range.end) == QChar::ParagraphSeparator; - if (!newLineEnd && newLineStart) - ++range.start; - changeSet.remove(range); -} - -/** - * @brief The RemoveNamespaceVisitor class removes a using namespace and rewrites all types that - * are in the namespace if needed - */ -class RemoveNamespaceVisitor : public ASTVisitor -{ -public: - constexpr static int SearchGlobalUsingDirectivePos = std::numeric_limits<int>::max(); - RemoveNamespaceVisitor(const CppRefactoringFile *file, - const Snapshot &snapshot, - const Name *namespace_, - int symbolPos, - bool removeAllAtGlobalScope) - : ASTVisitor(file->cppDocument()->translationUnit()) - , m_file(file) - , m_snapshot(snapshot) - , m_namespace(namespace_) - , m_missingNamespace(toString(namespace_) + "::") - , m_context(m_file->cppDocument(), m_snapshot) - , m_symbolPos(symbolPos) - , m_removeAllAtGlobalScope(removeAllAtGlobalScope) - - {} - - const ChangeSet &getChanges() { return m_changeSet; } - - /** - * @brief isGlobalUsingNamespace return true if the using namespace that should be removed - * is not scoped and other files that include this file will also use the using namespace - * @return true if using namespace statement is global and not scoped, false otherwise - */ - bool isGlobalUsingNamespace() const { return m_parentNode == nullptr; } - - /** - * @brief foundGlobalUsingNamespace return true if removeAllAtGlobalScope is false and - * another using namespace is found at the global scope, so that other files that include this - * file don't have to be processed - * @return true if there was a 'global' second using namespace in this file and - * removeAllAtGlobalScope is false - */ - bool foundGlobalUsingNamespace() const { return m_foundNamespace; } - -private: - bool preVisit(AST *ast) override - { - if (!m_start) { - if (ast->asTranslationUnit()) - return true; - if (UsingDirectiveAST *usingDirective = ast->asUsingDirective()) { - if (nameEqual(usingDirective->name->name, m_namespace)) { - if (m_symbolPos == SearchGlobalUsingDirectivePos) { - // we have found a global using directive, so lets start - m_start = true; - removeLine(m_file, ast, m_changeSet); - return false; - } - // ignore the using namespace that should be removed - if (m_file->endOf(ast) != m_symbolPos) { - if (m_removeAllAtGlobalScope) - removeLine(m_file, ast, m_changeSet); - else - m_done = true; - } - } - } - // if the end of the ast is before we should start, we are not interested in the node - if (m_file->endOf(ast) <= m_symbolPos) - return false; - - if (m_file->startOf(ast) > m_symbolPos) - m_start = true; - } - return !m_foundNamespace && !m_done; - } - - bool visit(NamespaceAST *ast) override - { - if (m_start && nameEqual(m_namespace, ast->symbol->name())) - return false; - - return m_start; - } - - // scopes for using namespace statements: - bool visit(LinkageBodyAST *ast) override { return visitNamespaceScope(ast); } - bool visit(CompoundStatementAST *ast) override { return visitNamespaceScope(ast); } - bool visitNamespaceScope(AST *ast) - { - ++m_namespaceScopeCounter; - if (!m_start) - m_parentNode = ast; - return true; - } - - void endVisit(LinkageBodyAST *ast) override { endVisitNamespaceScope(ast); } - void endVisit(CompoundStatementAST *ast) override { endVisitNamespaceScope(ast); } - void endVisitNamespaceScope(AST *ast) - { - --m_namespaceScopeCounter; - m_foundNamespace = false; - // if we exit the scope of the using namespace we are done - if (ast == m_parentNode) - m_done = true; - } - - bool visit(UsingDirectiveAST *ast) override - { - if (nameEqual(ast->name->name, m_namespace)) { - if (m_removeAllAtGlobalScope && m_namespaceScopeCounter == 0) - removeLine(m_file, ast, m_changeSet); - else - m_foundNamespace = true; - return false; - } - return handleAstWithLongestName(ast); - } - - bool visit(DeclaratorIdAST *ast) override - { - // e.g. we have the following code and get the following Lookup items: - // namespace test { - // struct foo { // 1. item with test::foo - // foo(); // 2. item with test::foo::foo - // }; - // } - // using namespace foo; - // foo::foo() { ... } // 3. item with foo::foo - // Our current name is foo::foo so we have to match with the 2. item / longest name - return handleAstWithLongestName(ast); - } - - template<typename AST> - bool handleAstWithLongestName(AST *ast) - { - if (m_start) { - Scope *scope = m_file->scopeAt(ast->firstToken()); - const QList<LookupItem> localLookup = m_context.lookup(ast->name->name, scope); - QList<const Name *> longestName; - for (const LookupItem &item : localLookup) { - QList<const Name *> names - = m_context.fullyQualifiedName(item.declaration(), - LookupContext::HideInlineNamespaces); - if (names.length() > longestName.length()) - longestName = names; - } - const int currentNameCount = countNames(ast->name->name); - const bool needNew = needMissingNamespaces(std::move(longestName), currentNameCount); - if (needNew) - insertMissingNamespace(ast); - } - return false; - } - - bool visit(NamedTypeSpecifierAST *ast) override { return handleAstWithName(ast); } - - bool visit(IdExpressionAST *ast) override { return handleAstWithName(ast); } - - template<typename AST> - bool handleAstWithName(AST *ast) - { - if (m_start) { - Scope *scope = m_file->scopeAt(ast->firstToken()); - const Name *wantToLookup = ast->name->name; - // first check if the base name is a typedef. Consider the following example: - // using namespace std; - // using vec = std::vector<int>; - // vec::iterator it; // we have to lookup 'vec' and not iterator (would result in - // std::vector<int>::iterator => std::vec::iterator, which is wrong) - const Name *baseName = getBaseName(wantToLookup); - QList<LookupItem> typedefCandidates = m_context.lookup(baseName, scope); - if (!typedefCandidates.isEmpty()) { - if (typedefCandidates.front().declaration()->isTypedef()) - wantToLookup = baseName; - } - - const QList<LookupItem> lookups = m_context.lookup(wantToLookup, scope); - if (!lookups.empty()) { - QList<const Name *> fullName - = m_context.fullyQualifiedName(lookups.first().declaration(), - LookupContext::HideInlineNamespaces); - const int currentNameCount = countNames(wantToLookup); - const bool needNamespace = needMissingNamespaces(std::move(fullName), - currentNameCount); - if (needNamespace) - insertMissingNamespace(ast); - } - } - return true; - } - - template<typename AST> - void insertMissingNamespace(AST *ast) - { - DestructorNameAST *destructorName = ast->name->asDestructorName(); - if (destructorName) - m_changeSet.insert(m_file->startOf(destructorName->unqualified_name), m_missingNamespace); - else - m_changeSet.insert(m_file->startOf(ast->name), m_missingNamespace); - m_changeSet.operationList().last().setFormat1(false); - } - - bool needMissingNamespaces(QList<const Name *> &&fullName, int currentNameCount) - { - if (currentNameCount > fullName.length()) - return false; - - // eg. fullName = std::vector, currentName = vector => result should be std - fullName.erase(fullName.end() - currentNameCount, fullName.end()); - if (fullName.empty()) - return false; - return nameEqual(m_namespace, fullName.last()); - } - - static bool nameEqual(const Name *name1, const Name *name2) - { - return Matcher::match(name1, name2); - } - - QString toString(const Name *id) - { - const Identifier *identifier = id->asNameId(); - QTC_ASSERT(identifier, return {}); - return QString::fromUtf8(identifier->chars(), identifier->size()); - } - - const CppRefactoringFile *const m_file; - const Snapshot &m_snapshot; - - const Name *m_namespace; // the name of the namespace that should be removed - const QString m_missingNamespace; // that should be added if a type was using the namespace - LookupContext m_context; - ChangeSet m_changeSet; - const int m_symbolPos; // the end position of the start symbol - bool m_done = false; - bool m_start = false; - // true if a using namespace was found at a scope and the scope should be left - bool m_foundNamespace = false; - bool m_removeAllAtGlobalScope; - // the scope where the using namespace that should be removed is valid - AST *m_parentNode = nullptr; - int m_namespaceScopeCounter = 0; -}; - -class RemoveUsingNamespaceOperation : public CppQuickFixOperation -{ - struct Node - { - Document::Ptr document; - bool hasGlobalUsingDirective = false; - int unprocessedParents; - std::vector<std::reference_wrapper<Node>> includes; - std::vector<std::reference_wrapper<Node>> includedBy; - Node() = default; - Node(const Node &) = delete; - Node(Node &&) = delete; - }; - -public: - RemoveUsingNamespaceOperation(const CppQuickFixInterface &interface, - UsingDirectiveAST *usingDirective, - bool removeAllAtGlobalScope) - : CppQuickFixOperation(interface, 1) - , m_usingDirective(usingDirective) - , m_removeAllAtGlobalScope(removeAllAtGlobalScope) - { - const QString name = Overview{}.prettyName(usingDirective->name->name); - if (m_removeAllAtGlobalScope) { - setDescription(Tr::tr( - "Remove All Occurrences of \"using namespace %1\" in Global Scope " - "and Adjust Type Names Accordingly") - .arg(name)); - } else { - setDescription(Tr::tr("Remove \"using namespace %1\" and " - "Adjust Type Names Accordingly") - .arg(name)); - } - } - -private: - std::map<Utils::FilePath, Node> buildIncludeGraph(CppRefactoringChanges &refactoring) - { - using namespace ProjectExplorer; - using namespace Utils; - - const Snapshot &s = refactoring.snapshot(); - std::map<Utils::FilePath, Node> includeGraph; - - auto handleFile = [&](const FilePath &filePath, Document::Ptr doc, auto shouldHandle) { - Node &node = includeGraph[filePath]; - node.document = doc; - for (const Document::Include &include : doc->resolvedIncludes()) { - const FilePath filePath = include.resolvedFileName(); - if (shouldHandle(filePath)) { - Node &includedNode = includeGraph[filePath]; - includedNode.includedBy.push_back(node); - node.includes.push_back(includedNode); - } - } - }; - - if (const Project *project = ProjectManager::projectForFile(filePath())) { - const FilePaths files = project->files(ProjectExplorer::Project::SourceFiles); - QSet<FilePath> projectFiles(files.begin(), files.end()); - for (const auto &file : files) { - const Document::Ptr doc = s.document(file); - if (!doc) - continue; - handleFile(file, doc, [&](const FilePath &file) { - return projectFiles.contains(file); - }); - } - } else { - for (auto i = s.begin(); i != s.end(); ++i) { - if (ProjectFile::classify(i.key().toString()) != ProjectFile::Unsupported) { - handleFile(i.key(), i.value(), [](const FilePath &file) { - return ProjectFile::classify(file.toString()) != ProjectFile::Unsupported; - }); - } - } - } - for (auto &[_, node] : includeGraph) { - Q_UNUSED(_) - node.unprocessedParents = static_cast<int>(node.includes.size()); - } - return includeGraph; - } - - void removeAllUsingsAtGlobalScope(CppRefactoringChanges &refactoring) - { - auto includeGraph = buildIncludeGraph(refactoring); - std::vector<std::reference_wrapper<Node>> nodesWithProcessedParents; - for (auto &[_, node] : includeGraph) { - Q_UNUSED(_) - if (!node.unprocessedParents) - nodesWithProcessedParents.push_back(node); - } - while (!nodesWithProcessedParents.empty()) { - Node &node = nodesWithProcessedParents.back(); - nodesWithProcessedParents.pop_back(); - CppRefactoringFilePtr file = refactoring.cppFile(node.document->filePath()); - const bool parentHasUsing = Utils::anyOf(node.includes, &Node::hasGlobalUsingDirective); - const int startPos = parentHasUsing - ? 0 - : RemoveNamespaceVisitor::SearchGlobalUsingDirectivePos; - const bool noGlobalUsing = refactorFile(file, refactoring.snapshot(), startPos); - node.hasGlobalUsingDirective = !noGlobalUsing || parentHasUsing; - - for (Node &subNode : node.includedBy) { - --subNode.unprocessedParents; - if (subNode.unprocessedParents == 0) - nodesWithProcessedParents.push_back(subNode); - } - } - } - - void perform() override - { - CppRefactoringChanges refactoring(snapshot()); - CppRefactoringFilePtr currentFile = refactoring.cppFile(filePath()); - if (m_removeAllAtGlobalScope) { - removeAllUsingsAtGlobalScope(refactoring); - } else if (refactorFile(currentFile, - refactoring.snapshot(), - currentFile->endOf(m_usingDirective), - true)) { - processIncludes(refactoring, filePath()); - } - - for (auto &file : std::as_const(m_changes)) - file->apply(); - } - - /** - * @brief refactorFile remove using namespace xyz in the given file and rewrite types - * @param file The file that should be processed - * @param snapshot The snapshot to work on - * @param startSymbol start processing after this index - * @param removeUsing if the using directive is in this file, remove it - * @return true if the using statement is global and there is no other global using namespace - */ - bool refactorFile(CppRefactoringFilePtr &file, - const Snapshot &snapshot, - int startSymbol, - bool removeUsing = false) - { - RemoveNamespaceVisitor visitor(file.get(), - snapshot, - m_usingDirective->name->name, - startSymbol, - m_removeAllAtGlobalScope); - visitor.accept(file->cppDocument()->translationUnit()->ast()); - Utils::ChangeSet changes = visitor.getChanges(); - if (removeUsing) - removeLine(file.get(), m_usingDirective, changes); - if (!changes.isEmpty()) { - file->setChangeSet(changes); - // apply changes at the end, otherwise the symbol finder will fail to resolve symbols if - // the using namespace is missing - m_changes.insert(file); - } - return visitor.isGlobalUsingNamespace() && !visitor.foundGlobalUsingNamespace(); - } - - void processIncludes(CppRefactoringChanges &refactoring, const FilePath &filePath) - { - QList<Snapshot::IncludeLocation> - includeLocationsOfDocument = refactoring.snapshot().includeLocationsOfDocument(filePath); - for (Snapshot::IncludeLocation &loc : includeLocationsOfDocument) { - if (!Utils::insert(m_processed, loc.first)) - continue; - - CppRefactoringFilePtr file = refactoring.cppFile(loc.first->filePath()); - const bool noGlobalUsing = refactorFile(file, - refactoring.snapshot(), - file->position(loc.second, 1)); - if (noGlobalUsing) - processIncludes(refactoring, loc.first->filePath()); - } - } - - QSet<Document::Ptr> m_processed; - QSet<CppRefactoringFilePtr> m_changes; - - UsingDirectiveAST *m_usingDirective; - bool m_removeAllAtGlobalScope; -}; -} // namespace - -void RemoveUsingNamespace::doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) -{ - const QList<AST *> &path = interface.path(); - // We expect something like - // [0] TranslationUnitAST - // ... - // [] UsingDirectiveAST : if activated at 'using namespace' - // [] NameAST (optional): if activated at the name e.g. 'std' - int n = path.size() - 1; - if (n <= 0) - return; - if (path.last()->asName()) - --n; - UsingDirectiveAST *usingDirective = path.at(n)->asUsingDirective(); - if (usingDirective && usingDirective->name->name->asNameId()) { - result << new RemoveUsingNamespaceOperation(interface, usingDirective, false); - const bool isHeader = ProjectFile::isHeader(ProjectFile::classify(interface.filePath().toString())); - if (isHeader && path.at(n - 1)->asTranslationUnit()) // using namespace at global scope - result << new RemoveUsingNamespaceOperation(interface, usingDirective, true); - } -} - -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<ConstructorMemberInfo> candidates; - std::vector<ConstructorMemberInfo *> 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<typename... _Args> - void emplaceBackParameter(_Args &&...__args) - { - candidates.emplace_back(std::forward<_Args>(__args)...); - infos.push_back(&candidates.back()); - } - const std::vector<ConstructorMemberInfo *> &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<ParentClassConstructorParameter> 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<ParentClassConstructorInfo>; - -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<ParentClassConstructorInfo *>(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<int>(constructors.size()); - auto info = static_cast<ParentClassConstructorInfo *>(parent.internalPointer()); - if (!info) - return static_cast<int>(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<ParentClassConstructorInfo *>(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<ParentClassConstructorInfo *>(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<ParentClassConstructorInfo *>(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<int>(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<A>(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<LookupItem> 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<ConstructorMemberInfo *> 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<int>(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<GenerateConstructorOperation>::create(interface); - if (!op->isApplicable()) - return; - op->setTest(m_test); - result << op; -} - -namespace { -class ConvertCommentStyleOp : public CppQuickFixOperation -{ -public: - ConvertCommentStyleOp(const CppQuickFixInterface &interface, const QList<Token> &tokens, - Kind kind) - : CppQuickFixOperation(interface), - m_tokens(tokens), - m_kind(kind), - m_wasCxxStyle(m_kind == T_CPP_COMMENT || m_kind == T_CPP_DOXY_COMMENT), - m_isDoxygen(m_kind == T_DOXY_COMMENT || m_kind == T_CPP_DOXY_COMMENT) - { - setDescription(m_wasCxxStyle ? Tr::tr("Convert Comment to C-Style") - : Tr::tr("Convert Comment to C++-Style")); - } - -private: - // Turns every line of a C-style comment into a C++-style comment and vice versa. - // For C++ -> C, we use one /* */ comment block per line. However, doxygen - // requires a single comment, so there we just replace the prefix with whitespace and - // add the start and end comment in extra lines. - // For cosmetic reasons, we offer some convenience functionality: - // - Turn /***** ... into ////// ... and vice versa - // - With C -> C++, remove leading asterisks. - // - With C -> C++, remove the first and last line of a block if they have no content - // other than the comment start and end characters. - // - With C++ -> C, try to align the end comment characters. - // These are obviously heuristics; we do not guarantee perfect results for everybody. - // We also don't second-guess the users's selection: E.g. if there is an empty - // line between the tokens, then it's not the same doxygen comment, but we merge - // it anyway in C++ to C mode. - void perform() override - { - TranslationUnit * const tu = currentFile()->cppDocument()->translationUnit(); - const QString newCommentStart = getNewCommentStart(); - ChangeSet changeSet; - int endCommentColumn = -1; - const QChar oldFillChar = m_wasCxxStyle ? '/' : '*'; - const QChar newFillChar = m_wasCxxStyle ? '*' : '/'; - - for (const Token &token : m_tokens) { - const int startPos = tu->getTokenPositionInDocument(token, textDocument()); - const int endPos = tu->getTokenEndPositionInDocument(token, textDocument()); - - if (m_wasCxxStyle && m_isDoxygen) { - // Replace "///" characters with whitespace (to keep alignment). - // The insertion of "/*" and "*/" is done once after the loop. - changeSet.replace(startPos, startPos + 3, " "); - continue; - } - - const QTextBlock firstBlock = textDocument()->findBlock(startPos); - const QTextBlock lastBlock = textDocument()->findBlock(endPos); - for (QTextBlock block = firstBlock; block.isValid() && block.position() <= endPos; - block = block.next()) { - const QString &blockText = block.text(); - const int firstColumn = block == firstBlock ? startPos - block.position() : 0; - const int endColumn = block == lastBlock ? endPos - block.position() - : block.length(); - - // Returns true if the current line looks like "/********/" or "//////////", - // as is often the case at the start and end of comment blocks. - const auto fillChecker = [&] { - if (m_isDoxygen) - return false; - QString textToCheck = blockText; - if (block == firstBlock) - textToCheck.remove(0, 1); - if (block == lastBlock) - textToCheck.chop(block.length() - endColumn); - return Utils::allOf(textToCheck, [oldFillChar](const QChar &c) - { return c == oldFillChar || c == ' '; - }) && textToCheck.count(oldFillChar) > 2; - }; - - // Returns the index of the first character of actual comment content, - // as opposed to visual stuff like slashes, stars or whitespace. - const auto indexOfActualContent = [&] { - const int offset = block == firstBlock ? firstColumn + newCommentStart.length() - : firstColumn; - - for (int i = offset, lastFillChar = -1; i < blockText.length(); ++i) { - if (blockText.at(i) == oldFillChar) { - lastFillChar = i; - continue; - } - if (!blockText.at(i).isSpace()) - return lastFillChar + 1; - } - return -1; - }; - - if (fillChecker()) { - const QString replacement = QString(endColumn - 1 - firstColumn, newFillChar); - changeSet.replace(block.position() + firstColumn, - block.position() + endColumn - 1, - replacement); - if (m_wasCxxStyle) { - changeSet.replace(block.position() + firstColumn, - block.position() + firstColumn + 1, "/"); - changeSet.insert(block.position() + endColumn - 1, "*"); - endCommentColumn = endColumn - 1; - } - continue; - } - - // Remove leading noise or even the entire block, if applicable. - const bool blockIsRemovable = (block == firstBlock || block == lastBlock) - && firstBlock != lastBlock; - const auto removeBlock = [&] { - changeSet.remove(block.position() + firstColumn, block.position() + endColumn); - }; - const int contentIndex = indexOfActualContent(); - int removed = 0; - if (contentIndex == -1) { - if (blockIsRemovable) { - removeBlock(); - continue; - } else if (!m_wasCxxStyle) { - changeSet.replace(block.position() + firstColumn, - block.position() + endColumn - 1, newCommentStart); - continue; - } - } else if (block == lastBlock && contentIndex == endColumn - 1) { - if (blockIsRemovable) { - removeBlock(); - break; - } - } else { - changeSet.remove(block.position() + firstColumn, - block.position() + firstColumn + contentIndex); - removed = contentIndex; - } - - if (block == firstBlock) { - changeSet.replace(startPos, startPos + newCommentStart.length(), - newCommentStart); - } else { - // If the line starts with enough whitespace, replace it with the - // comment start characters, so we don't move the content to the right - // unnecessarily. Otherwise, insert the comment start characters. - if (blockText.startsWith(QString(newCommentStart.size() + removed + 1, ' '))) { - changeSet.replace(block.position(), - block.position() + newCommentStart.length(), - newCommentStart); - } else { - changeSet.insert(block.position(), newCommentStart); - } - } - - if (block == lastBlock) { - if (m_wasCxxStyle) { - // This is for proper alignment of the end comment character. - if (endCommentColumn != -1) { - const int endCommentPos = block.position() + endCommentColumn; - if (endPos < endCommentPos) - changeSet.insert(endPos, QString(endCommentPos - endPos - 1, ' ')); - } - changeSet.insert(endPos, " */"); - } else { - changeSet.remove(endPos - 2, endPos); - } - } - } - } - - if (m_wasCxxStyle && m_isDoxygen) { - const int startPos = tu->getTokenPositionInDocument(m_tokens.first(), textDocument()); - const int endPos = tu->getTokenEndPositionInDocument(m_tokens.last(), textDocument()); - changeSet.insert(startPos, "/*!\n"); - changeSet.insert(endPos, "\n*/"); - } - - changeSet.apply(textDocument()); - } - - QString getNewCommentStart() const - { - if (m_wasCxxStyle) { - if (m_isDoxygen) - return "/*!"; - return "/*"; - } - if (m_isDoxygen) - return "//!"; - return "//"; - } - - const QList<Token> m_tokens; - const Kind m_kind; - const bool m_wasCxxStyle; - const bool m_isDoxygen; -}; -} // namespace - -void ConvertCommentStyle::doMatch(const CppQuickFixInterface &interface, - TextEditor::QuickFixOperations &result) -{ - // If there's a selection, then it must entirely consist of comment tokens. - // If there's no selection, the cursor must be on a comment. - const QList<Token> &cursorTokens = interface.currentFile()->tokensForCursor(); - if (cursorTokens.empty()) - return; - if (!cursorTokens.front().isComment()) - return; - - // All tokens must be the same kind of comment, but we make an exception for doxygen comments - // that start with "///", as these are often not intended to be doxygen. For our purposes, - // we treat them as normal comments. - const auto effectiveKind = [&interface](const Token &token) { - if (token.kind() != T_CPP_DOXY_COMMENT) - return token.kind(); - TranslationUnit * const tu = interface.currentFile()->cppDocument()->translationUnit(); - const int startPos = tu->getTokenPositionInDocument(token, interface.textDocument()); - const QString commentStart = interface.textAt(startPos, 3); - return commentStart == "///" ? T_CPP_COMMENT : T_CPP_DOXY_COMMENT; - }; - const Kind kind = effectiveKind(cursorTokens.first()); - for (int i = 1; i < cursorTokens.count(); ++i) { - if (effectiveKind(cursorTokens.at(i)) != kind) - return; - } - - // Ok, all tokens are of same(ish) comment type, offer quickfix. - result << new ConvertCommentStyleOp(interface, cursorTokens, kind); -} - -namespace { -class MoveFunctionCommentsOp : public CppQuickFixOperation -{ -public: - enum class Direction { ToDecl, ToDef }; - MoveFunctionCommentsOp(const CppQuickFixInterface &interface, const Symbol *symbol, - const QList<Token> &commentTokens, Direction direction) - : CppQuickFixOperation(interface), m_symbol(symbol), m_commentTokens(commentTokens) - { - setDescription(direction == Direction::ToDecl - ? Tr::tr("Move Function Documentation to Declaration") - : Tr::tr("Move Function Documentation to Definition")); - } - -private: - void perform() override - { - const auto textDoc = const_cast<QTextDocument *>(currentFile()->document()); - const int pos = currentFile()->cppDocument()->translationUnit()->getTokenPositionInDocument( - m_symbol->sourceLocation(), textDoc); - QTextCursor cursor(textDoc); - cursor.setPosition(pos); - const CursorInEditor cursorInEditor(cursor, currentFile()->filePath(), editor(), - editor()->textDocument()); - const auto callback = [symbolLoc = m_symbol->toLink(), comments = m_commentTokens] - (const Link &link) { - moveComments(link, symbolLoc, comments); - }; - CppModelManager::followSymbol(cursorInEditor, callback, true, false, - FollowSymbolMode::Exact); - } - - static void moveComments(const Link &targetLoc, const Link &symbolLoc, - const QList<Token> &comments) - { - if (!targetLoc.hasValidTarget() || targetLoc.hasSameLocation(symbolLoc)) - return; - - CppRefactoringChanges changes(CppModelManager::snapshot()); - const CppRefactoringFilePtr sourceFile = changes.cppFile(symbolLoc.targetFilePath); - const CppRefactoringFilePtr targetFile - = targetLoc.targetFilePath == symbolLoc.targetFilePath - ? sourceFile - : changes.cppFile(targetLoc.targetFilePath); - const Document::Ptr &targetCppDoc = targetFile->cppDocument(); - const QList<AST *> targetAstPath = ASTPath(targetCppDoc)( - targetLoc.targetLine, targetLoc.targetColumn + 1); - if (targetAstPath.isEmpty()) - return; - const AST *targetDeclAst = nullptr; - for (auto it = std::next(std::rbegin(targetAstPath)); - it != std::rend(targetAstPath); ++it) { - AST * const node = *it; - if (node->asDeclaration()) { - targetDeclAst = node; - continue; - } - if (targetDeclAst) - break; - } - if (!targetDeclAst) - return; - const int insertionPos = targetCppDoc->translationUnit()->getTokenPositionInDocument( - targetDeclAst->firstToken(), targetFile->document()); - const TranslationUnit * const sourceTu = sourceFile->cppDocument()->translationUnit(); - const int sourceCommentStartPos = sourceTu->getTokenPositionInDocument( - comments.first(), sourceFile->document()); - const int sourceCommentEndPos = sourceTu->getTokenEndPositionInDocument( - comments.last(), sourceFile->document()); - - // Manually adjust indentation, as both our built-in indenter and ClangFormat - // are unreliable with regards to comment continuation lines. - auto tabSettings = [](CppRefactoringFilePtr file) { - if (auto editor = file->editor()) - return editor->textDocument()->tabSettings(); - return ProjectExplorer::actualTabSettings(file->filePath(), nullptr); - }; - const TabSettings &sts = tabSettings(sourceFile); - const TabSettings &tts = tabSettings(targetFile); - const QTextBlock insertionBlock = targetFile->document()->findBlock(insertionPos); - const int insertionColumn = tts.columnAt(insertionBlock.text(), - insertionPos - insertionBlock.position()); - const QTextBlock removalBlock = sourceFile->document()->findBlock(sourceCommentStartPos); - const QTextBlock removalBlockEnd = sourceFile->document()->findBlock(sourceCommentEndPos); - const int removalColumn = sts.columnAt(removalBlock.text(), - sourceCommentStartPos - removalBlock.position()); - const int columnOffset = insertionColumn - removalColumn; - QString functionDoc; - if (columnOffset != 0) { - for (QTextBlock block = removalBlock; - block.isValid() && block != removalBlockEnd.next(); - block = block.next()) { - QString text = block.text() + QChar::ParagraphSeparator; - if (block == removalBlockEnd) - text = text.left(sourceCommentEndPos - block.position()); - if (block == removalBlock) { - text = text.mid(sourceCommentStartPos - block.position()); - } else { - int lineIndentColumn = sts.indentationColumn(text) + columnOffset; - text.replace(0, - TabSettings::firstNonSpace(text), - tts.indentationString(0, lineIndentColumn, 0, insertionBlock)); - } - functionDoc += text; - } - } else { - functionDoc = sourceFile->textOf(sourceCommentStartPos, sourceCommentEndPos); - } - - // Remove comment plus leading and trailing whitespace, including trailing newline. - const auto removeAtSource = [&](ChangeSet &changeSet) { - int removalPos = sourceCommentStartPos; - const QChar newline(QChar::ParagraphSeparator); - while (true) { - const int prev = removalPos - 1; - if (prev < 0) - break; - const QChar prevChar = sourceFile->charAt(prev); - if (!prevChar.isSpace() || prevChar == newline) - break; - removalPos = prev; - } - int removalEndPos = sourceCommentEndPos; - while (true) { - if (removalEndPos == sourceFile->document()->characterCount()) - break; - const QChar nextChar = sourceFile->charAt(removalEndPos); - if (!nextChar.isSpace()) - break; - ++removalEndPos; - if (nextChar == newline) - break; - } - changeSet.remove(removalPos, removalEndPos); - }; - - ChangeSet targetChangeSet; - targetChangeSet.insert(insertionPos, functionDoc); - targetChangeSet.insert(insertionPos, "\n"); - targetChangeSet.insert(insertionPos, QString(insertionColumn, ' ')); - if (targetFile == sourceFile) - removeAtSource(targetChangeSet); - targetFile->setChangeSet(targetChangeSet); - const bool targetFileSuccess = targetFile->apply(); - if (targetFile == sourceFile || !targetFileSuccess) - return; - ChangeSet sourceChangeSet; - removeAtSource(sourceChangeSet); - sourceFile->setChangeSet(sourceChangeSet); - sourceFile->apply(); - } - - const Symbol * const m_symbol; - const QList<Token> m_commentTokens; -}; -} // namespace - -void MoveFunctionComments::doMatch(const CppQuickFixInterface &interface, - TextEditor::QuickFixOperations &result) -{ - const QList<AST *> &astPath = interface.path(); - if (astPath.isEmpty()) - return; - const Symbol *symbol = nullptr; - MoveFunctionCommentsOp::Direction direction = MoveFunctionCommentsOp::Direction::ToDecl; - for (auto it = std::next(std::rbegin(astPath)); it != std::rend(astPath); ++it) { - if (const auto func = (*it)->asFunctionDefinition()) { - symbol = func->symbol; - direction = MoveFunctionCommentsOp::Direction::ToDecl; - break; - } - const auto decl = (*it)->asSimpleDeclaration(); - if (!decl || !decl->declarator_list) - continue; - for (auto it = decl->declarator_list->begin(); - !symbol && it != decl->declarator_list->end(); ++it) { - PostfixDeclaratorListAST * const funcDecls = (*it)->postfix_declarator_list; - if (!funcDecls) - continue; - for (auto it = funcDecls->begin(); it != funcDecls->end(); ++it) { - if (const auto func = (*it)->asFunctionDeclarator()) { - symbol = func->symbol; - direction = MoveFunctionCommentsOp::Direction::ToDef; - break; - } - } - } - - } - if (!symbol) - return; - - if (const QList<Token> commentTokens = commentsForDeclaration( - symbol, *interface.textDocument(), interface.currentFile()->cppDocument()); - !commentTokens.isEmpty()) { - result << new MoveFunctionCommentsOp(interface, symbol, commentTokens, direction); - } -} - -namespace { -class ConvertToMetaMethodCallOp : public CppQuickFixOperation -{ -public: - ConvertToMetaMethodCallOp(const CppQuickFixInterface &interface, CallAST *callAst) - : CppQuickFixOperation(interface), m_callAst(callAst) - { - setDescription(Tr::tr("Convert function call to Qt meta-method invocation")); - } - -private: - void perform() override - { - // Construct the argument list. - Overview ov; - QStringList arguments; - for (ExpressionListAST *it = m_callAst->expression_list; it; it = it->next) { - if (!it->value) - return; - const FullySpecifiedType argType - = typeOfExpr(it->value, currentFile(), snapshot(), context()); - if (!argType.isValid()) - return; - arguments << QString::fromUtf8("Q_ARG(%1, %2)") - .arg(ov.prettyType(argType), currentFile()->textOf(it->value)); - } - QString argsString = arguments.join(", "); - if (!argsString.isEmpty()) - argsString.prepend(", "); - - // Construct the replace string. - const auto memberAccessAst = m_callAst->base_expression->asMemberAccess(); - QTC_ASSERT(memberAccessAst, return); - QString baseExpr = currentFile()->textOf(memberAccessAst->base_expression); - const FullySpecifiedType baseExprType - = typeOfExpr(memberAccessAst->base_expression, currentFile(), snapshot(), context()); - if (!baseExprType.isValid()) - return; - if (!baseExprType->asPointerType()) - baseExpr.prepend('&'); - const QString functionName = currentFile()->textOf(memberAccessAst->member_name); - const QString qMetaObject = "QMetaObject"; - const QString newCall = QString::fromUtf8("%1::invokeMethod(%2, \"%3\"%4)") - .arg(qMetaObject, baseExpr, functionName, argsString); - - // Determine the start and end positions of the replace operation. - // If the call is preceded by an "emit" keyword, that one has to be removed as well. - int firstToken = m_callAst->firstToken(); - if (firstToken > 0) - switch (semanticInfo().doc->translationUnit()->tokenKind(firstToken - 1)) { - case T_EMIT: case T_Q_EMIT: --firstToken; break; - default: break; - } - const TranslationUnit *const tu = semanticInfo().doc->translationUnit(); - const int startPos = tu->getTokenPositionInDocument(firstToken, textDocument()); - const int endPos = tu->getTokenPositionInDocument(m_callAst->lastToken(), textDocument()); - - // Replace the old call with the new one. - ChangeSet changes; - changes.replace(startPos, endPos, newCall); - - // Insert include for QMetaObject, if necessary. - const Identifier qMetaObjectId(qPrintable(qMetaObject), qMetaObject.size()); - Scope * const scope = currentFile()->scopeAt(firstToken); - const QList<LookupItem> results = context().lookup(&qMetaObjectId, scope); - bool isDeclared = false; - for (const LookupItem &item : results) { - if (Symbol *declaration = item.declaration(); declaration && declaration->asClass()) { - isDeclared = true; - break; - } - } - if (!isDeclared) { - insertNewIncludeDirective('<' + qMetaObject + '>', currentFile(), semanticInfo().doc, - changes); - } - - // Apply the changes. - currentFile()->setChangeSet(changes); - currentFile()->apply(); - } - - const CallAST * const m_callAst; -}; -} // namespace - -void ConvertToMetaMethodCall::doMatch(const CppQuickFixInterface &interface, - TextEditor::QuickFixOperations &result) -{ - const Document::Ptr &cppDoc = interface.currentFile()->cppDocument(); - const QList<AST *> path = ASTPath(cppDoc)(interface.cursor()); - if (path.isEmpty()) - return; - - // Are we on a member function call? - CallAST *callAst = nullptr; - for (auto it = path.crbegin(); it != path.crend(); ++it) { - if ((callAst = (*it)->asCall())) - break; - } - if (!callAst || !callAst->base_expression) - return; - ExpressionAST *baseExpr = nullptr; - const NameAST *nameAst = nullptr; - if (const MemberAccessAST * const ast = callAst->base_expression->asMemberAccess()) { - baseExpr = ast->base_expression; - nameAst = ast->member_name; - } - if (!baseExpr || !nameAst || !nameAst->name) - return; - - // Locate called function and check whether it is invokable. - Scope *scope = cppDoc->globalNamespace(); - for (auto it = path.crbegin(); it != path.crend(); ++it) { - if (const CompoundStatementAST * const stmtAst = (*it)->asCompoundStatement()) { - scope = stmtAst->symbol; - break; - } - } - const LookupContext context(cppDoc, interface.snapshot()); - TypeOfExpression exprType; - exprType.setExpandTemplates(true); - exprType.init(cppDoc, interface.snapshot()); - const QList<LookupItem> typeMatches = exprType(callAst->base_expression, cppDoc, scope); - for (const LookupItem &item : typeMatches) { - if (const auto func = item.type()->asFunctionType(); func && func->methodKey()) { - result << new ConvertToMetaMethodCallOp(interface, callAst); - return; - } - } -} - -void createCppQuickFixes() -{ - new AddIncludeForUndefinedIdentifier; - - new FlipLogicalOperands; - new InverseLogicalComparison; - new RewriteLogicalAnd; - - new ConvertToCamelCase; - - new ConvertCStringToNSString; - new ConvertNumericLiteral; - new TranslateStringLiteral; - new WrapStringLiteral; - - new MoveDeclarationOutOfIf; - new MoveDeclarationOutOfWhile; - - new SplitIfStatement; - new SplitSimpleDeclaration; - - new AddBracesToControlStatement; - new RearrangeParamDeclarationList; - 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; - new InsertDefsFromDecls; - - new MoveFuncDefOutside; - new MoveAllFuncDefOutside; - new MoveFuncDefToDeclPush; - new MoveFuncDefToDeclPull; - - new AssignToLocalVariable; - - new InsertVirtualMethods; - - new OptimizeForLoop; - - new EscapeStringLiteral; - - new ExtraRefactoringOperations; - - new RemoveUsingNamespace; - new GenerateConstructor; - new ConvertCommentStyle; - new MoveFunctionComments; - new ConvertToMetaMethodCall; -} - -void destroyCppQuickFixes() -{ - for (int i = g_cppQuickFixFactories.size(); --i >= 0; ) - delete g_cppQuickFixFactories.at(i); -} - -} // namespace Internal -} // namespace CppEditor - -#include "cppquickfixes.moc" diff --git a/src/plugins/cppeditor/cppquickfixes.h b/src/plugins/cppeditor/cppquickfixes.h deleted file mode 100644 index bd47b318c7..0000000000 --- a/src/plugins/cppeditor/cppquickfixes.h +++ /dev/null @@ -1,630 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#pragma once - -#include "cppquickfix.h" - -#include <variant> - -/// -/// Adding New Quick Fixes -/// -/// When adding new Quick Fixes, make sure that the doMatch() function is "cheap". -/// Otherwise, since the match() functions are also called to generate context menu -/// entries, the user might experience a delay opening the context menu. -/// - -namespace CppEditor { -namespace Internal { -using TypeOrExpr = std::variant<const CPlusPlus::ExpressionAST *, CPlusPlus::FullySpecifiedType>; - -void createCppQuickFixes(); -void destroyCppQuickFixes(); - -class ExtraRefactoringOperations : public CppQuickFixFactory -{ -public: - void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override; -}; - -/*! - Adds an include for an undefined identifier or only forward declared identifier. - - Activates on: the undefined identifier -*/ -class AddIncludeForUndefinedIdentifier : public CppQuickFixFactory -{ -public: - void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override; -}; - -// Exposed for tests -class AddIncludeForUndefinedIdentifierOp: public CppQuickFixOperation -{ -public: - AddIncludeForUndefinedIdentifierOp(const CppQuickFixInterface &interface, int priority, - const QString &include); - void perform() override; - - QString include() const { return m_include; } - -private: - QString m_include; -}; - -class AddForwardDeclForUndefinedIdentifierOp: public CppQuickFixOperation -{ -public: - AddForwardDeclForUndefinedIdentifierOp(const CppQuickFixInterface &interface, int priority, - const QString &fqClassName, int symbolPos); -private: - void perform() override; - - const QString m_className; - const int m_symbolPos; -}; - -/*! - Rewrite - a op b - - As - b flipop a - - Activates on: <= < > >= == != && || -*/ -class FlipLogicalOperands: public CppQuickFixFactory -{ -public: - void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override; -}; - -/*! - Rewrite - a op b -> !(a invop b) - (a op b) -> !(a invop b) - !(a op b) -> (a invob b) - - Activates on: <= < > >= == != -*/ -class InverseLogicalComparison: public CppQuickFixFactory -{ -public: - void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override; -}; - -/*! - Rewrite - !a && !b - - As - !(a || b) - - Activates on: && -*/ -class RewriteLogicalAnd: public CppQuickFixFactory -{ -public: - void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override; -}; - -/*! - Replace - "abcd" - QLatin1String("abcd") - QLatin1Literal("abcd") - - With - @"abcd" - - Activates on: the string literal, if the file type is a Objective-C(++) file. -*/ -class ConvertCStringToNSString: public CppQuickFixFactory -{ -public: - void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override; -}; - -/*! - Base class for converting numeric literals between decimal, octal and hex. - Does the base check for the specific ones and parses the number. - - Test cases: - 0xFA0Bu; - 0X856A; - 298.3; - 199; - 074; - 199L; - 074L; - -199; - -017; - 0783; // invalid octal - 0; // border case, allow only hex<->decimal - - Activates on: numeric literals -*/ -class ConvertNumericLiteral: public CppQuickFixFactory -{ -public: - void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override; -}; - -/*! - Replace - "abcd" - - With - tr("abcd") or - QCoreApplication::translate("CONTEXT", "abcd") or - QT_TRANSLATE_NOOP("GLOBAL", "abcd") - - depending on what is available. - - Activates on: the string literal -*/ -class TranslateStringLiteral: public CppQuickFixFactory -{ -public: - void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override; -}; - -/*! - Replace - "abcd" -> QLatin1String("abcd") - @"abcd" -> QLatin1String("abcd") (Objective C) - 'a' -> QLatin1Char('a') - 'a' -> "a" - "a" -> 'a' or QLatin1Char('a') (Single character string constants) - "\n" -> '\n', QLatin1Char('\n') - - Except if they are already enclosed in - QLatin1Char, QT_TRANSLATE_NOOP, tr, - trUtf8, QLatin1Literal, QLatin1String - - Activates on: the string or character literal -*/ - -class WrapStringLiteral: public CppQuickFixFactory -{ -public: - void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override; -}; - -/*! - Turns "an_example_symbol" into "anExampleSymbol" and - "AN_EXAMPLE_SYMBOL" into "AnExampleSymbol". - - Activates on: identifiers -*/ -class ConvertToCamelCase : public CppQuickFixFactory -{ -public: - ConvertToCamelCase(bool test = false) : CppQuickFixFactory(), m_test(test) {} - - void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override; - -private: - const bool m_test; -}; - -/*! - Replace - if (Type name = foo()) {...} - - With - Type name = foo(); - if (name) {...} - - Activates on: the name of the introduced variable -*/ -class MoveDeclarationOutOfIf: public CppQuickFixFactory -{ -public: - void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override; -}; - -/*! - Replace - while (Type name = foo()) {...} - - With - Type name; - while ((name = foo()) != 0) {...} - - Activates on: the name of the introduced variable -*/ -class MoveDeclarationOutOfWhile: public CppQuickFixFactory -{ -public: - void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override; -}; - -/*! - Replace - if (something && something_else) { - } - - with - if (something) - if (something_else) { - } - } - - and - if (something || something_else) - x; - - with - if (something) - x; - else if (something_else) - x; - - Activates on: && or || -*/ -class SplitIfStatement: public CppQuickFixFactory -{ -public: - void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override; -}; - -/*! - Rewrite - int *a, b; - - As - int *a; - int b; - - Activates on: the type or the variable names. -*/ -class SplitSimpleDeclaration: public CppQuickFixFactory -{ -public: - void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override; -}; - -/*! - Add curly braces to a control statement that doesn't already contain a - compound statement. I.e. - - if (a) - b; - becomes - if (a) { - b; - } - - Activates on: the keyword -*/ -class AddBracesToControlStatement : public CppQuickFixFactory -{ -public: - void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override; -}; - -/*! - Switches places of the parameter declaration under cursor - with the next or the previous one in the parameter declaration list - - Activates on: parameter declarations -*/ -class RearrangeParamDeclarationList : public CppQuickFixFactory -{ -public: - void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override; -}; - -/*! - Reformats a pointer, reference or rvalue reference type/declaration. - - Works also with selections (except when the cursor is not on any AST). - - Activates on: simple declarations, parameters and return types of function - declarations and definitions, control flow statements (if, - while, for, foreach) with declarations. -*/ -class ReformatPointerDeclaration : public CppQuickFixFactory -{ -public: - void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override; -}; - -/*! - Adds missing case statements for "switch (enumVariable)" - */ -class CompleteSwitchCaseStatement: public CppQuickFixFactory -{ -public: - CompleteSwitchCaseStatement() { setClangdReplacement({12}); } - -private: - void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override; -}; - -/*! - Adds a declarations to a definition - */ -class InsertDeclFromDef: public CppQuickFixFactory -{ -public: - void doMatch(const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result) override; -}; - -/*! - Adds a definition for a declaration. - */ -class InsertDefFromDecl: public CppQuickFixFactory -{ -public: - void doMatch(const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result) override; - bool m_defPosOutsideClass = false; -}; - -class AddDeclarationForUndeclaredIdentifier : public CppQuickFixFactory -{ -public: - void doMatch(const CppQuickFixInterface &interface, - TextEditor::QuickFixOperations &result) override; - -#ifdef WITH_TESTS - void setMembersOnly() { m_membersOnly = true; } -#endif - -private: - void collectOperations(const CppQuickFixInterface &interface, - TextEditor::QuickFixOperations &result); - void handleCall(const CPlusPlus::CallAST *call, const CppQuickFixInterface &interface, - TextEditor::QuickFixOperations &result); - - // Returns whether to still do other checks. - bool checkForMemberInitializer(const CppQuickFixInterface &interface, - TextEditor::QuickFixOperations &result); - - void maybeAddMember(const CppQuickFixInterface &interface, CPlusPlus::Scope *scope, - const QByteArray &classTypeExpr, const TypeOrExpr &typeOrExpr, - const CPlusPlus::CallAST *call, TextEditor::QuickFixOperations &result); - - void maybeAddStaticMember( - const CppQuickFixInterface &interface, const CPlusPlus::QualifiedNameAST *qualName, - const TypeOrExpr &typeOrExpr, const CPlusPlus::CallAST *call, - TextEditor::QuickFixOperations &result); - - bool m_membersOnly = false; -}; - -/*! - Adds a definition for any number of member function declarations. - */ -class InsertDefsFromDecls : public CppQuickFixFactory -{ -public: - void doMatch(const CppQuickFixInterface &interface, - TextEditor::QuickFixOperations &result) override; - - enum class Mode { - Off, // Testing: simulates user canceling the dialog - Impl, // Testing: simulates user choosing cpp file for every function - Alternating, // Testing: simulates user choosing a different DefPos for every function - User // Normal interactive mode - }; - void setMode(Mode mode) { m_mode = mode; } - -private: - Mode m_mode = Mode::User; -}; - -/*! - Extracts the selected code and puts it to a function - */ -class ExtractFunction : public CppQuickFixFactory -{ -public: - using FunctionNameGetter = std::function<QString()>; - - ExtractFunction(FunctionNameGetter functionNameGetter = FunctionNameGetter()); - void doMatch(const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result) override; - -private: - FunctionNameGetter m_functionNameGetter; // For tests to avoid GUI pop-up. -}; - -/*! - Extracts the selected constant and converts it to a parameter of the current function. - - Activates on numeric, bool, character, or string literal in the function body. - */ -class ExtractLiteralAsParameter : public CppQuickFixFactory -{ -public: - void doMatch(const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result) override; -}; - -/*! - Converts the selected variable to a pointer if it is a stack variable or reference, or vice versa. - - Activates on variable declarations. - */ -class ConvertFromAndToPointer : public CppQuickFixFactory -{ -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. - */ -class ConvertQt4Connect : public CppQuickFixFactory -{ -public: - void doMatch(const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result) override; -}; - -/*! - Applies function signature changes - */ -class ApplyDeclDefLinkChanges: public CppQuickFixFactory -{ -public: - void doMatch(const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result) override; -}; - -/*! - Moves the definition of a member function outside the class or moves the definition of a member - function or a normal function to the implementation file. - */ -class MoveFuncDefOutside: public CppQuickFixFactory -{ -public: - void doMatch(const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result) override; -}; - -/*! - Moves all member function definitions outside the class or to the implementation file. - */ -class MoveAllFuncDefOutside: public CppQuickFixFactory -{ -public: - void doMatch(const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result) override; -}; - -/*! - Moves the definition of a function to its declaration, with the cursor on the definition. - */ -class MoveFuncDefToDeclPush : public CppQuickFixFactory -{ -public: - void doMatch(const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result) override; -}; - -/*! - Moves the definition of a function to its declaration, with the cursor on the declaration. - */ -class MoveFuncDefToDeclPull : public CppQuickFixFactory -{ -public: - void doMatch(const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result) override; -}; - -/*! - Assigns the return value of a function call or a new expression to a local variable - */ -class AssignToLocalVariable : public CppQuickFixFactory -{ -public: - void doMatch(const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result) override; -}; - -/*! - Optimizes a for loop to avoid permanent condition check and forces to use preincrement - or predecrement operators in the expression of the for loop. - */ -class OptimizeForLoop : public CppQuickFixFactory -{ -public: - void doMatch(const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result) override; -}; - -/*! - Escapes or unescapes a string literal as UTF-8. - - Escapes non-ASCII characters in a string literal to hexadecimal escape sequences. - Unescapes octal or hexadecimal escape sequences in a string literal. - String literals are handled as UTF-8 even if file's encoding is not UTF-8. - */ -class EscapeStringLiteral : public CppQuickFixFactory -{ -public: - void doMatch(const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result) override; -}; - -/*! - Removes a using directive (using namespace xyz). - -*/ -class RemoveUsingNamespace : public CppQuickFixFactory -{ -public: - RemoveUsingNamespace() { setClangdReplacement({10}); } - -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 -{ -private: - void doMatch(const CppQuickFixInterface &interface, - TextEditor::QuickFixOperations &result) override; -}; - -//! Moves function documentation between declaration and implementation. -class MoveFunctionComments : public CppQuickFixFactory -{ -private: - void doMatch(const CppQuickFixInterface &interface, - TextEditor::QuickFixOperations &result) override; -}; - -//! Converts a normal function call into a meta method invocation, if the functions is -//! marked as invokable. -class ConvertToMetaMethodCall : public CppQuickFixFactory -{ -private: - void doMatch(const CppQuickFixInterface &interface, - TextEditor::QuickFixOperations &result) override; -}; - -} // namespace Internal -} // namespace CppEditor diff --git a/src/plugins/cppeditor/cpprenaming_test.cpp b/src/plugins/cppeditor/cpprenaming_test.cpp index 2827434be5..a31d00fa07 100644 --- a/src/plugins/cppeditor/cpprenaming_test.cpp +++ b/src/plugins/cppeditor/cpprenaming_test.cpp @@ -5,7 +5,7 @@ #include "cppeditorwidget.h" #include "cppmodelmanager.h" -#include "cppquickfix_test.h" +#include "quickfixes/cppquickfix_test.h" #include <texteditor/texteditor.h> diff --git a/src/plugins/cppeditor/cpptoolsreuse.cpp b/src/plugins/cppeditor/cpptoolsreuse.cpp index 947ecdd4c6..23d8495a74 100644 --- a/src/plugins/cppeditor/cpptoolsreuse.cpp +++ b/src/plugins/cppeditor/cpptoolsreuse.cpp @@ -15,9 +15,9 @@ #include "cppfilesettingspage.h" #include "cpphighlighter.h" #include "cppqtstyleindenter.h" -#include "cppquickfixassistant.h" #include "cpprefactoringchanges.h" #include "projectinfo.h" +#include "quickfixes/cppquickfixassistant.h" #include <coreplugin/documentmanager.h> #include <coreplugin/editormanager/editormanager.h> diff --git a/src/plugins/cppeditor/cpptoolstestcase.cpp b/src/plugins/cppeditor/cpptoolstestcase.cpp index 869bbf9b84..70435abe9f 100644 --- a/src/plugins/cppeditor/cpptoolstestcase.cpp +++ b/src/plugins/cppeditor/cpptoolstestcase.cpp @@ -510,4 +510,19 @@ int clangdIndexingTimeout() return intervalAsInt; } +SourceFilesRefreshGuard::SourceFilesRefreshGuard() +{ + connect(CppModelManager::instance(), &CppModelManager::sourceFilesRefreshed, this, [this] { + m_refreshed = true; + }); +} + +bool SourceFilesRefreshGuard::wait() +{ + for (int i = 0; i < 10 && !m_refreshed; ++i) { + CppEditor::Tests::waitForSignalOrTimeout( + CppModelManager::instance(), &CppModelManager::sourceFilesRefreshed, 1000); + } + return m_refreshed; +} } // namespace CppEditor::Tests diff --git a/src/plugins/cppeditor/cpptoolstestcase.h b/src/plugins/cppeditor/cpptoolstestcase.h index 3bbeff0bb6..6e9f0133ba 100644 --- a/src/plugins/cppeditor/cpptoolstestcase.h +++ b/src/plugins/cppeditor/cpptoolstestcase.h @@ -205,5 +205,17 @@ private: TemporaryCopiedDir(); }; +class SourceFilesRefreshGuard : public QObject +{ +public: + SourceFilesRefreshGuard(); + + void reset() { m_refreshed = false; } + bool wait(); + +private: + bool m_refreshed = false; +}; + } // namespace Tests } // namespace CppEditor diff --git a/src/plugins/cppeditor/cursorineditor.h b/src/plugins/cppeditor/cursorineditor.h index 46dd4b2b2a..77c853f97b 100644 --- a/src/plugins/cppeditor/cursorineditor.h +++ b/src/plugins/cppeditor/cursorineditor.h @@ -3,6 +3,7 @@ #pragma once +#include <cplusplus/CppDocument.h> #include <utils/fileutils.h> #include <utils/link.h> @@ -22,15 +23,18 @@ class CursorInEditor { public: CursorInEditor(const QTextCursor &cursor, const Utils::FilePath &filePath, - CppEditorWidget *editorWidget = nullptr, - TextEditor::TextDocument *textDocument = nullptr) + CppEditorWidget *editorWidget = nullptr, + TextEditor::TextDocument *textDocument = nullptr, + const CPlusPlus::Document::Ptr &cppDocument = {}) : m_cursor(cursor) , m_filePath(filePath) , m_editorWidget(editorWidget) , m_textDocument(textDocument) + , m_cppDocument(cppDocument) {} CppEditorWidget *editorWidget() const { return m_editorWidget; } TextEditor::TextDocument *textDocument() const { return m_textDocument; } + CPlusPlus::Document::Ptr cppDocument() const { return m_cppDocument; } const QTextCursor &cursor() const { return m_cursor; } const Utils::FilePath &filePath() const { return m_filePath; } private: @@ -38,6 +42,7 @@ private: Utils::FilePath m_filePath; CppEditorWidget *m_editorWidget = nullptr; TextEditor::TextDocument * const m_textDocument; + const CPlusPlus::Document::Ptr m_cppDocument; }; } // namespace CppEditor diff --git a/src/plugins/cppeditor/fileandtokenactions_test.cpp b/src/plugins/cppeditor/fileandtokenactions_test.cpp index 5acca7fa11..7c8175fa18 100644 --- a/src/plugins/cppeditor/fileandtokenactions_test.cpp +++ b/src/plugins/cppeditor/fileandtokenactions_test.cpp @@ -4,13 +4,13 @@ #include "fileandtokenactions_test.h" #include "cppeditorwidget.h" -#include "cppquickfix.h" -#include "cppquickfixassistant.h" -#include "cppinsertvirtualmethods.h" #include "cppmodelmanager.h" #include "cpptoolstestcase.h" #include "cppworkingcopy.h" #include "projectinfo.h" +#include "quickfixes/cppquickfix.h" +#include "quickfixes/cppquickfixassistant.h" +#include "quickfixes/cppinsertvirtualmethods.h" #include <coreplugin/editormanager/editormanager.h> #include <projectexplorer/project.h> diff --git a/src/plugins/cppeditor/quickfixes/assigntolocalvariable.cpp b/src/plugins/cppeditor/quickfixes/assigntolocalvariable.cpp new file mode 100644 index 0000000000..ac14597997 --- /dev/null +++ b/src/plugins/cppeditor/quickfixes/assigntolocalvariable.cpp @@ -0,0 +1,513 @@ +// 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 "assigntolocalvariable.h" + +#include "../cppcodestylesettings.h" +#include "../cppeditortr.h" +#include "../cppeditorwidget.h" +#include "../cpprefactoringchanges.h" +#include "cppquickfix.h" +#include "cppquickfixprojectsettings.h" + +#include <cplusplus/CppRewriter.h> +#include <cplusplus/Overview.h> +#include <cplusplus/TypeOfExpression.h> +#include <projectexplorer/projecttree.h> + +#ifdef WITH_TESTS +#include "cppquickfix_test.h" +#include <QtTest> +#endif + +using namespace CPlusPlus; +using namespace Utils; + +namespace CppEditor::Internal { +namespace { + +class AssignToLocalVariableOperation : public CppQuickFixOperation +{ +public: + explicit AssignToLocalVariableOperation(const CppQuickFixInterface &interface, + const int insertPos, const AST *ast, const Name *name) + : CppQuickFixOperation(interface) + , m_insertPos(insertPos) + , m_ast(ast) + , m_name(name) + , m_oo(CppCodeStyleSettings::currentProjectCodeStyleOverview()) + , m_originalName(m_oo.prettyName(m_name)) + , m_file(CppRefactoringChanges(snapshot()).cppFile(filePath())) + { + setDescription(Tr::tr("Assign to Local Variable")); + } + +private: + void perform() override + { + QString type = deduceType(); + if (type.isEmpty()) + return; + const int origNameLength = m_originalName.length(); + const QString varName = constructVarName(); + const QString insertString = type.replace(type.length() - origNameLength, origNameLength, + varName + QLatin1String(" = ")); + ChangeSet changes; + changes.insert(m_insertPos, insertString); + m_file->setChangeSet(changes); + m_file->apply(); + + // move cursor to new variable name + QTextCursor c = m_file->cursor(); + c.setPosition(m_insertPos + insertString.length() - varName.length() - 3); + c.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor); + editor()->setTextCursor(c); + } + + QString deduceType() const + { + const auto settings = CppQuickFixProjectsSettings::getQuickFixSettings( + ProjectExplorer::ProjectTree::currentProject()); + if (m_file->cppDocument()->languageFeatures().cxx11Enabled && settings->useAuto) + return "auto " + m_originalName; + + TypeOfExpression typeOfExpression; + typeOfExpression.init(semanticInfo().doc, snapshot(), context().bindings()); + typeOfExpression.setExpandTemplates(true); + Scope * const scope = m_file->scopeAt(m_ast->firstToken()); + const QList<LookupItem> result = typeOfExpression(m_file->textOf(m_ast).toUtf8(), + scope, TypeOfExpression::Preprocess); + if (result.isEmpty()) + return {}; + + SubstitutionEnvironment env; + env.setContext(context()); + env.switchScope(result.first().scope()); + ClassOrNamespace *con = typeOfExpression.context().lookupType(scope); + if (!con) + con = typeOfExpression.context().globalNamespace(); + UseMinimalNames q(con); + env.enter(&q); + + Control *control = context().bindings()->control().get(); + FullySpecifiedType type = rewriteType(result.first().type(), &env, control); + + return m_oo.prettyType(type, m_name); + } + + QString constructVarName() const + { + QString newName = m_originalName; + if (newName.startsWith(QLatin1String("get"), Qt::CaseInsensitive) + && newName.length() > 3 + && newName.at(3).isUpper()) { + newName.remove(0, 3); + newName.replace(0, 1, newName.at(0).toLower()); + } else if (newName.startsWith(QLatin1String("to"), Qt::CaseInsensitive) + && newName.length() > 2 + && newName.at(2).isUpper()) { + newName.remove(0, 2); + newName.replace(0, 1, newName.at(0).toLower()); + } else { + newName.replace(0, 1, newName.at(0).toUpper()); + newName.prepend(QLatin1String("local")); + } + return newName; + } + + const int m_insertPos; + const AST * const m_ast; + const Name * const m_name; + const Overview m_oo; + const QString m_originalName; + const CppRefactoringFilePtr m_file; +}; + +//! Assigns the return value of a function call or a new expression to a local variable +class AssignToLocalVariable : public CppQuickFixFactory +{ +#ifdef WITH_TESTS +public: + static QObject *createTest(); +#endif + +private: + void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override + { + const QList<AST *> &path = interface.path(); + AST *outerAST = nullptr; + SimpleNameAST *nameAST = nullptr; + + for (int i = path.size() - 3; i >= 0; --i) { + if (CallAST *callAST = path.at(i)->asCall()) { + if (!interface.isCursorOn(callAST)) + return; + if (i - 2 >= 0) { + const int idx = i - 2; + if (path.at(idx)->asSimpleDeclaration()) + return; + if (path.at(idx)->asExpressionStatement()) + return; + if (path.at(idx)->asMemInitializer()) + return; + if (path.at(idx)->asCall()) { // Fallback if we have a->b()->c()... + --i; + continue; + } + } + for (int a = i - 1; a > 0; --a) { + if (path.at(a)->asBinaryExpression()) + return; + if (path.at(a)->asReturnStatement()) + return; + if (path.at(a)->asCall()) + return; + } + + if (MemberAccessAST *member = path.at(i + 1)->asMemberAccess()) { // member + if (NameAST *name = member->member_name) + nameAST = name->asSimpleName(); + } else if (QualifiedNameAST *qname = path.at(i + 2)->asQualifiedName()) { // static or + nameAST = qname->unqualified_name->asSimpleName(); // func in ns + } else { // normal + nameAST = path.at(i + 2)->asSimpleName(); + } + + if (nameAST) { + outerAST = callAST; + break; + } + } else if (NewExpressionAST *newexp = path.at(i)->asNewExpression()) { + if (!interface.isCursorOn(newexp)) + return; + if (i - 2 >= 0) { + const int idx = i - 2; + if (path.at(idx)->asSimpleDeclaration()) + return; + if (path.at(idx)->asExpressionStatement()) + return; + if (path.at(idx)->asMemInitializer()) + return; + } + for (int a = i - 1; a > 0; --a) { + if (path.at(a)->asReturnStatement()) + return; + if (path.at(a)->asCall()) + return; + } + + if (NamedTypeSpecifierAST *ts = path.at(i + 2)->asNamedTypeSpecifier()) { + nameAST = ts->name->asSimpleName(); + outerAST = newexp; + break; + } + } + } + + if (outerAST && nameAST) { + const CppRefactoringFilePtr file = interface.currentFile(); + QList<LookupItem> items; + TypeOfExpression typeOfExpression; + typeOfExpression.init(interface.semanticInfo().doc, interface.snapshot(), + interface.context().bindings()); + typeOfExpression.setExpandTemplates(true); + + // If items are empty, AssignToLocalVariableOperation will fail. + items = typeOfExpression(file->textOf(outerAST).toUtf8(), + file->scopeAt(outerAST->firstToken()), + TypeOfExpression::Preprocess); + if (items.isEmpty()) + return; + + if (CallAST *callAST = outerAST->asCall()) { + items = typeOfExpression(file->textOf(callAST->base_expression).toUtf8(), + file->scopeAt(callAST->base_expression->firstToken()), + TypeOfExpression::Preprocess); + } else { + items = typeOfExpression(file->textOf(nameAST).toUtf8(), + file->scopeAt(nameAST->firstToken()), + TypeOfExpression::Preprocess); + } + + for (const LookupItem &item : std::as_const(items)) { + if (!item.declaration()) + continue; + + if (Function *func = item.declaration()->asFunction()) { + if (func->isSignal() || func->returnType()->asVoidType()) + return; + } else if (Declaration *dec = item.declaration()->asDeclaration()) { + if (Function *func = dec->type()->asFunctionType()) { + if (func->isSignal() || func->returnType()->asVoidType()) + return; + } + } + + const Name *name = nameAST->name; + const int insertPos = interface.currentFile()->startOf(outerAST); + result << new AssignToLocalVariableOperation(interface, insertPos, outerAST, name); + return; + } + } + } +}; + +#ifdef WITH_TESTS +using namespace Tests; + +class AssignToLocalVariableTest : public QObject +{ + Q_OBJECT + +private slots: + void testTemplates() + { + QList<TestDocumentPtr> testDocuments; + QByteArray original; + QByteArray expected; + + // Header File + original = + "template <typename T>\n" + "class List {\n" + "public:\n" + " T first();" + "};\n" + ; + expected = original; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File + original = + "#include \"file.h\"\n" + "void foo() {\n" + " List<int> list;\n" + " li@st.first();\n" + "}\n"; + expected = + "#include \"file.h\"\n" + "void foo() {\n" + " List<int> list;\n" + " auto localFirst = list.first();\n" + "}\n"; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + AssignToLocalVariable factory; + QuickFixOperationTest(testDocuments, &factory); + } + + void test_data() + { + QTest::addColumn<QByteArray>("original"); + QTest::addColumn<QByteArray>("expected"); + + // Check: Add local variable for a free function. + QTest::newRow("freeFunction") + << QByteArray( + "int foo() {return 1;}\n" + "void bar() {fo@o();}\n") + << QByteArray( + "int foo() {return 1;}\n" + "void bar() {auto localFoo = foo();}\n"); + + // Check: Add local variable for a member function. + QTest::newRow("memberFunction") + << QByteArray( + "class Foo {public: int* fooFunc();}\n" + "void bar() {\n" + " Foo *f = new Foo;\n" + " @f->fooFunc();\n" + "}\n") + << QByteArray( + "class Foo {public: int* fooFunc();}\n" + "void bar() {\n" + " Foo *f = new Foo;\n" + " auto localFooFunc = f->fooFunc();\n" + "}\n"); + + // Check: Add local variable for a member function, cursor in the middle (QTCREATORBUG-10355) + QTest::newRow("memberFunction2ndGrade1") + << QByteArray( + "struct Foo {int* func();};\n" + "struct Baz {Foo* foo();};\n" + "void bar() {\n" + " Baz *b = new Baz;\n" + " b->foo@()->func();\n" + "}") + << QByteArray( + "struct Foo {int* func();};\n" + "struct Baz {Foo* foo();};\n" + "void bar() {\n" + " Baz *b = new Baz;\n" + " auto localFunc = b->foo()->func();\n" + "}"); + + // Check: Add local variable for a member function, cursor on function call (QTCREATORBUG-10355) + QTest::newRow("memberFunction2ndGrade2") + << QByteArray( + "struct Foo {int* func();};\n" + "struct Baz {Foo* foo();};\n" + "void bar() {\n" + " Baz *b = new Baz;\n" + " b->foo()->f@unc();\n" + "}") + << QByteArray( + "struct Foo {int* func();};\n" + "struct Baz {Foo* foo();};\n" + "void bar() {\n" + " Baz *b = new Baz;\n" + " auto localFunc = b->foo()->func();\n" + "}"); + + // Check: Add local variable for a static member function. + QTest::newRow("staticMemberFunction") + << QByteArray( + "class Foo {public: static int* fooFunc();}\n" + "void bar() {\n" + " Foo::fooF@unc();\n" + "}") + << QByteArray( + "class Foo {public: static int* fooFunc();}\n" + "void bar() {\n" + " auto localFooFunc = Foo::fooFunc();\n" + "}"); + + // Check: Add local variable for a new Expression. + QTest::newRow("newExpression") + << QByteArray( + "class Foo {}\n" + "void bar() {\n" + " new Fo@o;\n" + "}") + << QByteArray( + "class Foo {}\n" + "void bar() {\n" + " auto localFoo = new Foo;\n" + "}"); + + // Check: No trigger for function inside member initialization list. + QTest::newRow("noInitializationList") + << QByteArray( + "class Foo\n" + "{\n" + " public: Foo : m_i(fooF@unc()) {}\n" + " int fooFunc() {return 2;}\n" + " int m_i;\n" + "};\n") + << QByteArray(); + + // Check: No trigger for void functions. + QTest::newRow("noVoidFunction") + << QByteArray( + "void foo() {}\n" + "void bar() {fo@o();}") + << QByteArray(); + + // Check: No trigger for void member functions. + QTest::newRow("noVoidMemberFunction") + << QByteArray( + "class Foo {public: void fooFunc();}\n" + "void bar() {\n" + " Foo *f = new Foo;\n" + " @f->fooFunc();\n" + "}") + << QByteArray(); + + // Check: No trigger for void static member functions. + QTest::newRow("noVoidStaticMemberFunction") + << QByteArray( + "class Foo {public: static void fooFunc();}\n" + "void bar() {\n" + " Foo::fo@oFunc();\n" + "}") + << QByteArray(); + + // Check: No trigger for functions in expressions. + QTest::newRow("noFunctionInExpression") + << QByteArray( + "int foo(int a) {return a;}\n" + "int bar() {return 1;}" + "void baz() {foo(@bar() + bar());}") + << QByteArray(); + + // Check: No trigger for functions in functions. (QTCREATORBUG-9510) + QTest::newRow("noFunctionInFunction") + << QByteArray( + "int foo(int a, int b) {return a + b;}\n" + "int bar(int a) {return a;}\n" + "void baz() {\n" + " int a = foo(ba@r(), bar());\n" + "}\n") + << QByteArray(); + + // Check: No trigger for functions in return statements (classes). + QTest::newRow("noReturnClass1") + << QByteArray( + "class Foo {public: static void fooFunc();}\n" + "Foo* bar() {\n" + " return new Fo@o;\n" + "}") + << QByteArray(); + + // Check: No trigger for functions in return statements (classes). (QTCREATORBUG-9525) + QTest::newRow("noReturnClass2") + << QByteArray( + "class Foo {public: int fooFunc();}\n" + "int bar() {\n" + " return (new Fo@o)->fooFunc();\n" + "}") + << QByteArray(); + + // Check: No trigger for functions in return statements (functions). + QTest::newRow("noReturnFunc1") + << QByteArray( + "class Foo {public: int fooFunc();}\n" + "int bar() {\n" + " return Foo::fooFu@nc();\n" + "}") + << QByteArray(); + + // Check: No trigger for functions in return statements (functions). (QTCREATORBUG-9525) + QTest::newRow("noReturnFunc2") + << QByteArray( + "int bar() {\n" + " return list.firs@t().foo;\n" + "}\n") + << QByteArray(); + + // Check: No trigger for functions which does not match in signature. + QTest::newRow("noSignatureMatch") + << QByteArray( + "int someFunc(int);\n" + "\n" + "void f()\n" + "{\n" + " some@Func();\n" + "}") + << QByteArray(); + } + + void test() + { + QFETCH(QByteArray, original); + QFETCH(QByteArray, expected); + AssignToLocalVariable factory; + QuickFixOperationTest(singleDocument(original, expected), &factory); + } +}; + +QObject *AssignToLocalVariable::createTest() { return new AssignToLocalVariableTest; } + +#endif // WITH_TESTS +} // namespace + +void registerAssignToLocalVariableQuickfix() +{ + CppQuickFixFactory::registerFactory<AssignToLocalVariable>(); +} + +} // namespace CppEditor::Internal + +#ifdef WITH_TESTS +#include <assigntolocalvariable.moc> +#endif diff --git a/src/plugins/cppeditor/quickfixes/assigntolocalvariable.h b/src/plugins/cppeditor/quickfixes/assigntolocalvariable.h new file mode 100644 index 0000000000..d8cd793c9f --- /dev/null +++ b/src/plugins/cppeditor/quickfixes/assigntolocalvariable.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 registerAssignToLocalVariableQuickfix(); +} // namespace CppEditor::Internal diff --git a/src/plugins/cppeditor/quickfixes/bringidentifierintoscope.cpp b/src/plugins/cppeditor/quickfixes/bringidentifierintoscope.cpp new file mode 100644 index 0000000000..f316ba2ada --- /dev/null +++ b/src/plugins/cppeditor/quickfixes/bringidentifierintoscope.cpp @@ -0,0 +1,1503 @@ +// 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 "bringidentifierintoscope.h" + +#include "../cppeditortr.h" +#include "../cpplocatordata.h" +#include "../cppprojectfile.h" +#include "../cpprefactoringchanges.h" +#include "../indexitem.h" +#include "cppquickfix.h" +#include "cppquickfixhelpers.h" + +#include <cplusplus/Overview.h> +#include <projectexplorer/projectnodes.h> +#include <projectexplorer/projecttree.h> +#include <texteditor/quickfix.h> + +#ifdef WITH_TESTS +#include "../cppsourceprocessertesthelper.h" +#include "../cpptoolstestcase.h" +#include "cppquickfix_test.h" +#include <QtTest> +#endif + +using namespace CPlusPlus; +using namespace ProjectExplorer; +using namespace Utils; + +#ifdef WITH_TESTS +namespace CppEditor::Internal::Tests { +typedef QList<TestDocumentPtr> QuickFixTestDocuments; +} +Q_DECLARE_METATYPE(CppEditor::Internal::Tests::QuickFixTestDocuments) +#endif + +namespace CppEditor::Internal { +namespace { + +static QString findShortestInclude( + const QString currentDocumentFilePath, + const QString candidateFilePath, + const HeaderPaths &headerPaths) +{ + QString result; + + const QFileInfo fileInfo(candidateFilePath); + + if (fileInfo.path() == QFileInfo(currentDocumentFilePath).path()) { + result = QLatin1Char('"') + fileInfo.fileName() + QLatin1Char('"'); + } else { + for (const HeaderPath &headerPath : headerPaths) { + if (!candidateFilePath.startsWith(headerPath.path)) + continue; + QString relativePath = candidateFilePath.mid(headerPath.path.size()); + if (!relativePath.isEmpty() && relativePath.at(0) == QLatin1Char('/')) + relativePath = relativePath.mid(1); + if (result.isEmpty() || relativePath.size() + 2 < result.size()) + result = QLatin1Char('<') + relativePath + QLatin1Char('>'); + } + } + + return result; +} + +static QString findMatchingInclude(const QString &className, const HeaderPaths &headerPaths) +{ + const QStringList candidateFileNames{ + className, + className + ".h", + className + ".hpp", + className.toLower(), + className.toLower() + ".h", + className.toLower() + ".hpp"}; + for (const QString &fileName : candidateFileNames) { + for (const HeaderPath &headerPath : headerPaths) { + const QString headerPathCandidate = headerPath.path + QLatin1Char('/') + fileName; + const QFileInfo fileInfo(headerPathCandidate); + if (fileInfo.exists() && fileInfo.isFile()) + return '<' + fileName + '>'; + } + } + return {}; +} + +static HeaderPaths relevantHeaderPaths(const QString &filePath) +{ + HeaderPaths headerPaths; + + const QList<ProjectPart::ConstPtr> projectParts = CppModelManager::projectPart(filePath); + if (projectParts.isEmpty()) { // Not part of any project, better use all include paths than none + headerPaths += CppModelManager::headerPaths(); + } else { + for (const ProjectPart::ConstPtr &part : projectParts) + headerPaths += part->headerPaths; + } + + return headerPaths; +} + +static NameAST *nameUnderCursor(const QList<AST *> &path) +{ + if (path.isEmpty()) + return nullptr; + + NameAST *nameAst = nullptr; + for (int i = path.size() - 1; i >= 0; --i) { + AST * const ast = path.at(i); + if (SimpleNameAST *simpleName = ast->asSimpleName()) { + nameAst = simpleName; + } else if (TemplateIdAST *templateId = ast->asTemplateId()) { + nameAst = templateId; + } else if (nameAst && ast->asNamedTypeSpecifier()) { + break; // Stop at "Foo" for "N::Bar<@Foo>" + } else if (QualifiedNameAST *qualifiedName = ast->asQualifiedName()) { + nameAst = qualifiedName; + break; + } + } + + return nameAst; +} + +enum class LookupResult { Declared, ForwardDeclared, NotDeclared }; +static LookupResult lookUpDefinition(const CppQuickFixInterface &interface, const NameAST *nameAst) +{ + QTC_ASSERT(nameAst && nameAst->name, return LookupResult::NotDeclared); + + // Find the enclosing scope + int line, column; + const Document::Ptr doc = interface.semanticInfo().doc; + doc->translationUnit()->getTokenPosition(nameAst->firstToken(), &line, &column); + Scope *scope = doc->scopeAt(line, column); + if (!scope) + return LookupResult::NotDeclared; + + // Try to find the class/template definition + const Name *name = nameAst->name; + const QList<LookupItem> results = interface.context().lookup(name, scope); + LookupResult best = LookupResult::NotDeclared; + for (const LookupItem &item : results) { + if (Symbol *declaration = item.declaration()) { + if (declaration->asClass()) + return LookupResult::Declared; + if (declaration->asForwardClassDeclaration()) { + best = LookupResult::ForwardDeclared; + continue; + } + if (Template *templ = declaration->asTemplate()) { + if (Symbol *declaration = templ->declaration()) { + if (declaration->asClass()) + return LookupResult::Declared; + if (declaration->asForwardClassDeclaration()) { + best = LookupResult::ForwardDeclared; + continue; + } + } + } + return LookupResult::Declared; + } + } + + return best; +} + +static QString templateNameAsString(const TemplateNameId *templateName) +{ + const Identifier *id = templateName->identifier(); + return QString::fromUtf8(id->chars(), id->size()); +} + +static Snapshot forwardingHeaders(const CppQuickFixInterface &interface) +{ + Snapshot result; + + const Snapshot docs = interface.snapshot(); + for (Document::Ptr doc : docs) { + if (doc->globalSymbolCount() == 0 && doc->resolvedIncludes().size() == 1) + result.insert(doc); + } + + return result; +} + +static QList<IndexItem::Ptr> matchName(const Name *name, QString *className) +{ + if (!name) + return {}; + + QString simpleName; + QList<IndexItem::Ptr> matches; + CppLocatorData *locatorData = CppModelManager::locatorData(); + const Overview oo; + if (const QualifiedNameId *qualifiedName = name->asQualifiedNameId()) { + const Name *name = qualifiedName->name(); + if (const TemplateNameId *templateName = name->asTemplateNameId()) { + *className = templateNameAsString(templateName); + } else { + simpleName = oo.prettyName(name); + *className = simpleName; + matches = locatorData->findSymbols(IndexItem::Class, *className); + if (matches.isEmpty()) { + if (const Name *name = qualifiedName->base()) { + if (const TemplateNameId *templateName = name->asTemplateNameId()) + *className = templateNameAsString(templateName); + else + *className = oo.prettyName(name); + } + } + } + } else if (const TemplateNameId *templateName = name->asTemplateNameId()) { + *className = templateNameAsString(templateName); + } else { + *className = oo.prettyName(name); + } + + if (matches.isEmpty()) + matches = locatorData->findSymbols(IndexItem::Class, *className); + + if (matches.isEmpty() && !simpleName.isEmpty()) + *className = simpleName; + + return matches; +} + +class AddIncludeForUndefinedIdentifierOp : public CppQuickFixOperation +{ +public: + AddIncludeForUndefinedIdentifierOp(const CppQuickFixInterface &interface, int priority, + const QString &include); + void perform() override; + + QString include() const { return m_include; } + +private: + QString m_include; +}; + +class AddForwardDeclForUndefinedIdentifierOp : public CppQuickFixOperation +{ +public: + AddForwardDeclForUndefinedIdentifierOp(const CppQuickFixInterface &interface, int priority, + const QString &fqClassName, int symbolPos); +private: + void perform() override + { + const QStringList parts = m_className.split("::"); + QTC_ASSERT(!parts.isEmpty(), return); + const QStringList namespaces = parts.mid(0, parts.length() - 1); + + CppRefactoringChanges refactoring(snapshot()); + CppRefactoringFilePtr file = refactoring.cppFile(filePath()); + + NSVisitor visitor(file.data(), namespaces, m_symbolPos); + visitor.accept(file->cppDocument()->translationUnit()->ast()); + const auto stringToInsert = [&visitor, symbol = parts.last()] { + QString s = "\n"; + for (const QString &ns : visitor.remainingNamespaces()) + s += "namespace " + ns + " { "; + s += "class " + symbol + ';'; + for (int i = 0; i < visitor.remainingNamespaces().size(); ++i) + s += " }"; + return s; + }; + + int insertPos = 0; + + // Find the position to insert: + // If we have a matching namespace, we do the insertion there. + // If we don't have a matching namespace, but there is another namespace in the file, + // we assume that to be a good position for our insertion. + // Otherwise, do the insertion after the last include that comes before the use of the symbol. + // If there is no such include, do the insertion before the first token. + if (visitor.enclosingNamespace()) { + insertPos = file->startOf(visitor.enclosingNamespace()->linkage_body) + 1; + } else if (visitor.firstNamespace()) { + insertPos = file->startOf(visitor.firstNamespace()); + } else { + const QTextCursor tc = file->document()->find( + QRegularExpression("^\\s*#include .*$"), + m_symbolPos, + QTextDocument::FindBackward | QTextDocument::FindCaseSensitively); + if (!tc.isNull()) + insertPos = tc.position() + 1; + else if (visitor.firstToken()) + insertPos = file->startOf(visitor.firstToken()); + } + + QString insertion = stringToInsert(); + if (file->charAt(insertPos - 1) != QChar::ParagraphSeparator) + insertion.prepend('\n'); + if (file->charAt(insertPos) != QChar::ParagraphSeparator) + insertion.append('\n'); + ChangeSet s; + s.insert(insertPos, insertion); + file->setChangeSet(s); + file->apply(); + } + + const QString m_className; + const int m_symbolPos; +}; + +AddIncludeForUndefinedIdentifierOp::AddIncludeForUndefinedIdentifierOp( + const CppQuickFixInterface &interface, int priority, const QString &include) + : CppQuickFixOperation(interface, priority) + , m_include(include) +{ + setDescription(Tr::tr("Add #include %1").arg(m_include)); +} + +void AddIncludeForUndefinedIdentifierOp::perform() +{ + CppRefactoringChanges refactoring(snapshot()); + CppRefactoringFilePtr file = refactoring.cppFile(filePath()); + + ChangeSet changes; + insertNewIncludeDirective(m_include, file, semanticInfo().doc, changes); + file->setChangeSet(changes); + file->apply(); +} + +AddForwardDeclForUndefinedIdentifierOp::AddForwardDeclForUndefinedIdentifierOp( + const CppQuickFixInterface &interface, + int priority, + const QString &fqClassName, + int symbolPos) + : CppQuickFixOperation(interface, priority), m_className(fqClassName), m_symbolPos(symbolPos) +{ + setDescription(Tr::tr("Add Forward Declaration for %1").arg(m_className)); +} + +/*! + Adds an include for an undefined identifier or only forward declared identifier. + Activates on: the undefined identifier +*/ +class AddIncludeForUndefinedIdentifier : public CppQuickFixFactory +{ +#ifdef WITH_TESTS +public: + static QObject *createTest(); +#endif + +private: + void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override + { + const NameAST *nameAst = nameUnderCursor(interface.path()); + if (!nameAst || !nameAst->name) + return; + + const LookupResult lookupResult = lookUpDefinition(interface, nameAst); + if (lookupResult == LookupResult::Declared) + return; + + QString className; + const QString currentDocumentFilePath = interface.semanticInfo().doc->filePath().toString(); + const HeaderPaths headerPaths = relevantHeaderPaths(currentDocumentFilePath); + FilePaths headers; + + const QList<IndexItem::Ptr> matches = matchName(nameAst->name, &className); + // Find an include file through the locator + if (!matches.isEmpty()) { + QList<IndexItem::Ptr> indexItems; + const Snapshot forwardHeaders = forwardingHeaders(interface); + for (const IndexItem::Ptr &info : matches) { + if (!info || info->symbolName() != className) + continue; + indexItems << info; + + Snapshot localForwardHeaders = forwardHeaders; + localForwardHeaders.insert(interface.snapshot().document(info->filePath())); + FilePaths headerAndItsForwardingHeaders; + headerAndItsForwardingHeaders << info->filePath(); + headerAndItsForwardingHeaders += localForwardHeaders.filesDependingOn(info->filePath()); + + for (const FilePath &header : std::as_const(headerAndItsForwardingHeaders)) { + const QString include = findShortestInclude(currentDocumentFilePath, + header.toString(), + headerPaths); + if (include.size() > 2) { + const QString headerFileName = info->filePath().fileName(); + QTC_ASSERT(!headerFileName.isEmpty(), break); + + int priority = 0; + if (headerFileName == className) + priority = 2; + else if (headerFileName.at(1).isUpper()) + priority = 1; + + result << new AddIncludeForUndefinedIdentifierOp(interface, priority, + include); + headers << header; + } + } + } + + if (lookupResult == LookupResult::NotDeclared && indexItems.size() == 1) { + QString qualifiedName = Overview().prettyName(nameAst->name); + if (qualifiedName.startsWith("::")) + qualifiedName.remove(0, 2); + if (indexItems.first()->scopedSymbolName().endsWith(qualifiedName)) { + const Node * const node = ProjectTree::nodeForFile(interface.filePath()); + FileType fileType = node && node->asFileNode() ? node->asFileNode()->fileType() + : FileType::Unknown; + if (fileType == FileType::Unknown + && ProjectFile::isHeader(ProjectFile::classify(interface.filePath().toString()))) { + fileType = FileType::Header; + } + if (fileType == FileType::Header) { + result << new AddForwardDeclForUndefinedIdentifierOp( + interface, 0, indexItems.first()->scopedSymbolName(), + interface.currentFile()->startOf(nameAst)); + } + } + } + } + + if (className.isEmpty()) + return; + + // Fallback: Check the include paths for files that look like candidates + // for the given name. + if (!Utils::contains(headers, + [&className](const FilePath &fp) { return fp.fileName() == className; })) { + const QString include = findMatchingInclude(className, headerPaths); + const auto matcher = [&include](const TextEditor::QuickFixOperation::Ptr &o) { + const auto includeOp = o.dynamicCast<AddIncludeForUndefinedIdentifierOp>(); + return includeOp && includeOp->include() == include; + }; + if (!include.isEmpty() && !contains(result, matcher)) + result << new AddIncludeForUndefinedIdentifierOp(interface, 1, include); + } + } +}; + +#ifdef WITH_TESTS +using namespace Tests; +using namespace ::CppEditor::Tests; +using namespace ::CppEditor::Tests::Internal; // FIXME: Get rid of this nonsense namespace + +/// Delegates directly to AddIncludeForUndefinedIdentifierOp for easier testing. +class AddIncludeForUndefinedIdentifierTestFactory : public CppQuickFixFactory +{ +public: + AddIncludeForUndefinedIdentifierTestFactory(const QString &include) + : m_include(include) {} + + void doMatch(const CppQuickFixInterface &cppQuickFixInterface, QuickFixOperations &result) override + { + result << new AddIncludeForUndefinedIdentifierOp(cppQuickFixInterface, 0, m_include); + } + +private: + const QString m_include; +}; + +class AddForwardDeclForUndefinedIdentifierTestFactory : public CppQuickFixFactory +{ +public: + AddForwardDeclForUndefinedIdentifierTestFactory(const QString &className, int symbolPos) + : m_className(className), m_symbolPos(symbolPos) {} + + void doMatch(const CppQuickFixInterface &cppQuickFixInterface, QuickFixOperations &result) override + { + result << new AddForwardDeclForUndefinedIdentifierOp(cppQuickFixInterface, 0, + m_className, m_symbolPos); + } + +private: + const QString m_className; + const int m_symbolPos; +}; + +class BringIdentifierIntoScopeTest : public QObject +{ + Q_OBJECT + +private slots: + void testAddIncludeForUndefinedIdentifier_data() + { + QTest::addColumn<QString>("headerPath"); + QTest::addColumn<QuickFixTestDocuments>("testDocuments"); + QTest::addColumn<int>("refactoringOperationIndex"); + QTest::addColumn<QString>("includeForTestFactory"); + + const int firstRefactoringOperation = 0; + const int secondRefactoringOperation = 1; + + QList<TestDocumentPtr> testDocuments; + QByteArray original; + QByteArray expected; + + // ------------------------------------------------------------------------------------------- + + // Header File + original = "class Foo {};\n"; + expected = original; + testDocuments << CppTestDocument::create("afile.h", original, expected); + + // Source File + original = + "#include \"header.h\"\n" + "\n" + "void f()\n" + "{\n" + " Fo@o foo;\n" + "}\n" + ; + expected = + "#include \"afile.h\"\n" + "#include \"header.h\"\n" + "\n" + "void f()\n" + "{\n" + " Foo foo;\n" + "}\n" + ; + testDocuments << CppTestDocument::create("afile.cpp", original, expected); + QTest::newRow("onSimpleName") + << TestIncludePaths::globalIncludePath() + << testDocuments << firstRefactoringOperation << ""; + testDocuments.clear(); + + // ------------------------------------------------------------------------------------------- + + // Header File + original = "namespace N { class Foo {}; }\n"; + expected = original; + testDocuments << CppTestDocument::create("afile.h", original, expected); + + // Source File + original = + "#include \"header.h\"\n" + "\n" + "void f()\n" + "{\n" + " N::Fo@o foo;\n" + "}\n" + ; + expected = + "#include \"afile.h\"\n" + "#include \"header.h\"\n" + "\n" + "void f()\n" + "{\n" + " N::Foo foo;\n" + "}\n" + ; + testDocuments << CppTestDocument::create("afile.cpp", original, expected); + QTest::newRow("onNameOfQualifiedName") + << TestIncludePaths::globalIncludePath() + << testDocuments << firstRefactoringOperation << ""; + testDocuments.clear(); + + // ------------------------------------------------------------------------------------------- + + // Header File + original = "namespace N { class Foo {}; }\n"; + expected = original; + testDocuments << CppTestDocument::create("afile.h", original, expected); + + // Source File + original = + "#include \"header.h\"\n" + "\n" + "void f()\n" + "{\n" + " @N::Foo foo;\n" + "}\n" + ; + expected = + "#include \"afile.h\"\n" + "#include \"header.h\"\n" + "\n" + "void f()\n" + "{\n" + " N::Foo foo;\n" + "}\n" + ; + testDocuments << CppTestDocument::create("afile.cpp", original, expected); + QTest::newRow("onBaseOfQualifiedName") + << TestIncludePaths::globalIncludePath() + << testDocuments << firstRefactoringOperation << ""; + testDocuments.clear(); + + // ------------------------------------------------------------------------------------------- + + // Header File + original = "class Foo { static void bar() {} };\n"; + expected = original; + testDocuments << CppTestDocument::create("afile.h", original, expected); + + // Source File + original = + "#include \"header.h\"\n" + "\n" + "void f()\n" + "{\n" + " @Foo::bar();\n" + "}\n" + ; + expected = + "#include \"afile.h\"\n" + "#include \"header.h\"\n" + "\n" + "void f()\n" + "{\n" + " Foo::bar();\n" + "}\n" + ; + testDocuments << CppTestDocument::create("afile.cpp", original, expected); + QTest::newRow("onBaseOfQualifiedClassName") + << TestIncludePaths::globalIncludePath() + << testDocuments << firstRefactoringOperation << ""; + testDocuments.clear(); + + // ------------------------------------------------------------------------------------------- + + // Header File + original = "template <typename T> class Foo { static void bar() {} };\n"; + expected = original; + testDocuments << CppTestDocument::create("afile.h", original, expected); + + // Source File + original = + "#include \"header.h\"\n" + "\n" + "void f()\n" + "{\n" + " @Foo<int>::bar();\n" + "}\n" + ; + expected = + "#include \"afile.h\"\n" + "#include \"header.h\"\n" + "\n" + "void f()\n" + "{\n" + " Foo<int>::bar();\n" + "}\n" + ; + testDocuments << CppTestDocument::create("afile.cpp", original, expected); + QTest::newRow("onBaseOfQualifiedTemplateClassName") + << TestIncludePaths::globalIncludePath() + << testDocuments << firstRefactoringOperation << ""; + testDocuments.clear(); + + // ------------------------------------------------------------------------------------------- + + // Header File + original = "namespace N { template <typename T> class Foo {}; }\n"; + expected = original; + testDocuments << CppTestDocument::create("afile.h", original, expected); + + // Source File + original = + "#include \"header.h\"\n" + "\n" + "void f()\n" + "{\n" + " @N::Foo<Bar> foo;\n" + "}\n" + ; + expected = + "#include \"afile.h\"\n" + "#include \"header.h\"\n" + "\n" + "void f()\n" + "{\n" + " N::Foo<Bar> foo;\n" + "}\n" + ; + testDocuments << CppTestDocument::create("afile.cpp", original, expected); + QTest::newRow("onTemplateName") + << TestIncludePaths::globalIncludePath() + << testDocuments << firstRefactoringOperation << ""; + testDocuments.clear(); + + // ------------------------------------------------------------------------------------------- + + // Header File + original = "namespace N { template <typename T> class Foo {}; }\n"; + expected = original; + testDocuments << CppTestDocument::create("afile.h", original, expected); + + // Source File + original = + "#include \"header.h\"\n" + "\n" + "void f()\n" + "{\n" + " N::Bar<@Foo> foo;\n" + "}\n" + ; + expected = + "#include \"afile.h\"\n" + "#include \"header.h\"\n" + "\n" + "void f()\n" + "{\n" + " N::Bar<Foo> foo;\n" + "}\n" + ; + testDocuments << CppTestDocument::create("afile.cpp", original, expected); + QTest::newRow("onTemplateNameInsideArguments") + << TestIncludePaths::globalIncludePath() + << testDocuments << firstRefactoringOperation << ""; + testDocuments.clear(); + + // ------------------------------------------------------------------------------------------- + + // Header File + original = "class Foo {};\n"; + expected = original; + testDocuments << CppTestDocument::create("afile.h", original, expected); + + // Source File + original = + "#include \"header.h\"\n" + "\n" + "class Foo;\n" + "\n" + "void f()\n" + "{\n" + " @Foo foo;\n" + "}\n" + ; + expected = + "#include \"afile.h\"\n" + "#include \"header.h\"\n" + "\n" + "class Foo;\n" + "\n" + "void f()\n" + "{\n" + " Foo foo;\n" + "}\n" + ; + testDocuments << CppTestDocument::create("afile.cpp", original, expected); + QTest::newRow("withForwardDeclaration") + << TestIncludePaths::globalIncludePath() + << testDocuments << firstRefactoringOperation << ""; + testDocuments.clear(); + + // ------------------------------------------------------------------------------------------- + + // Header File + original = "template<class T> class Foo {};\n"; + expected = original; + testDocuments << CppTestDocument::create("afile.h", original, expected); + + // Source File + original = + "#include \"header.h\"\n" + "\n" + "template<class T> class Foo;\n" + "\n" + "void f()\n" + "{\n" + " @Foo foo;\n" + "}\n" + ; + expected = + "#include \"afile.h\"\n" + "#include \"header.h\"\n" + "\n" + "template<class T> class Foo;\n" + "\n" + "void f()\n" + "{\n" + " Foo foo;\n" + "}\n" + ; + testDocuments << CppTestDocument::create("afile.cpp", original, expected); + QTest::newRow("withForwardDeclaration2") + << TestIncludePaths::globalIncludePath() + << testDocuments << firstRefactoringOperation << ""; + testDocuments.clear(); + + // ------------------------------------------------------------------------------------------- + + // Header File + original = "template<class T> class QMyClass {};\n"; + expected = original; + testDocuments << CppTestDocument::create("qmyclass.h", original, expected); + + // Forward Header File + original = "#include \"qmyclass.h\"\n"; + expected = original; + testDocuments << CppTestDocument::create("QMyClass", original, expected); + + // Source File + original = + "#include \"header.h\"\n" + "\n" + "void f()\n" + "{\n" + " @QMyClass c;\n" + "}\n" + ; + expected = + "#include \"QMyClass\"\n" + "#include \"header.h\"\n" + "\n" + "void f()\n" + "{\n" + " QMyClass c;\n" + "}\n" + ; + testDocuments << CppTestDocument::create("afile.cpp", original, expected); + QTest::newRow("withForwardHeader") + << TestIncludePaths::globalIncludePath() + << testDocuments << secondRefactoringOperation << ""; + testDocuments.clear(); + + // ------------------------------------------------------------------------------------------- + + original = + "void @f();\n" + "#include \"file.moc\";\n" + ; + expected = + "#include \"file.h\"\n" + "\n" + "void f();\n" + "#include \"file.moc\";\n" + ; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + QTest::newRow("insertingIgnoreMoc") + << TestIncludePaths::globalIncludePath() + << testDocuments << firstRefactoringOperation << "\"file.h\""; + testDocuments.clear(); + + // ------------------------------------------------------------------------------------------- + + original = + "#include \"y.h\"\n" + "#include \"z.h\"\n" + "\n@" + ; + expected = + "#include \"file.h\"\n" + "#include \"y.h\"\n" + "#include \"z.h\"\n" + "\n" + ; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + QTest::newRow("insertingSortingTop") + << TestIncludePaths::globalIncludePath() + << testDocuments << firstRefactoringOperation << "\"file.h\""; + testDocuments.clear(); + + // ------------------------------------------------------------------------------------------- + + original = + "#include \"a.h\"\n" + "#include \"z.h\"\n" + "\n@" + ; + expected = + "#include \"a.h\"\n" + "#include \"file.h\"\n" + "#include \"z.h\"\n" + "\n" + ; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + QTest::newRow("insertingSortingMiddle") + << TestIncludePaths::globalIncludePath() + << testDocuments << firstRefactoringOperation << "\"file.h\""; + testDocuments.clear(); + + // ------------------------------------------------------------------------------------------- + + original = + "#include \"a.h\"\n" + "#include \"b.h\"\n" + "\n@" + ; + expected = + "#include \"a.h\"\n" + "#include \"b.h\"\n" + "#include \"file.h\"\n" + "\n" + ; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + QTest::newRow("insertingSortingBottom") + << TestIncludePaths::globalIncludePath() + << testDocuments << firstRefactoringOperation << "\"file.h\""; + testDocuments.clear(); + + // ------------------------------------------------------------------------------------------- + + original = + "#include \"b.h\"\n" + "#include \"a.h\"\n" + "\n@" + ; + expected = + "#include \"b.h\"\n" + "#include \"a.h\"\n" + "#include \"file.h\"\n" + "\n" + ; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + QTest::newRow("inserting_appendToUnsorted") + << TestIncludePaths::globalIncludePath() + << testDocuments << firstRefactoringOperation << "\"file.h\""; + testDocuments.clear(); + + // ------------------------------------------------------------------------------------------- + + original = + "#include <a.h>\n" + "#include <b.h>\n" + "\n@" + ; + expected = + "#include \"file.h\"\n" + "\n" + "#include <a.h>\n" + "#include <b.h>\n" + "\n" + ; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + QTest::newRow("inserting_firstLocalIncludeAtFront") + << TestIncludePaths::globalIncludePath() + << testDocuments << firstRefactoringOperation << "\"file.h\""; + testDocuments.clear(); + + // ------------------------------------------------------------------------------------------- + + original = + "#include \"a.h\"\n" + "#include \"b.h\"\n" + "\n" + "void @f();\n" + ; + expected = + "#include \"a.h\"\n" + "#include \"b.h\"\n" + "\n" + "#include <file.h>\n" + "\n" + "void f();\n" + ; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + QTest::newRow("firstGlobalIncludeAtBack") + << TestIncludePaths::globalIncludePath() + << testDocuments << firstRefactoringOperation << "<file.h>"; + testDocuments.clear(); + + // ------------------------------------------------------------------------------------------- + + original = + "#include \"prefixa.h\"\n" + "#include \"prefixb.h\"\n" + "\n" + "#include \"foo.h\"\n" + "\n@" + ; + expected = + "#include \"prefixa.h\"\n" + "#include \"prefixb.h\"\n" + "#include \"prefixc.h\"\n" + "\n" + "#include \"foo.h\"\n" + "\n" + ; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + QTest::newRow("inserting_preferGroupWithLongerMatchingPrefix") + << TestIncludePaths::globalIncludePath() + << testDocuments << firstRefactoringOperation << "\"prefixc.h\""; + testDocuments.clear(); + + // ------------------------------------------------------------------------------------------- + + original = + "#include \"lib/file.h\"\n" + "#include \"lib/fileother.h\"\n" + "\n@" + ; + expected = + "#include \"lib/file.h\"\n" + "#include \"lib/fileother.h\"\n" + "\n" + "#include \"file.h\"\n" + "\n" + ; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + QTest::newRow("inserting_newGroupIfOnlyDifferentIncludeDirs") + << TestIncludePaths::globalIncludePath() + << testDocuments << firstRefactoringOperation << "\"file.h\""; + testDocuments.clear(); + + // ------------------------------------------------------------------------------------------- + + original = + "#include <lib/file.h>\n" + "#include <otherlib/file.h>\n" + "#include <utils/file.h>\n" + "\n@" + ; + expected = + "#include <firstlib/file.h>\n" + "#include <lib/file.h>\n" + "#include <otherlib/file.h>\n" + "#include <utils/file.h>\n" + "\n" + ; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + QTest::newRow("inserting_mixedDirsSorted") + << TestIncludePaths::globalIncludePath() + << testDocuments << firstRefactoringOperation << "<firstlib/file.h>"; + testDocuments.clear(); + + // ------------------------------------------------------------------------------------------- + + original = + "#include <otherlib/file.h>\n" + "#include <lib/file.h>\n" + "#include <utils/file.h>\n" + "\n@" + ; + expected = + "#include <otherlib/file.h>\n" + "#include <lib/file.h>\n" + "#include <utils/file.h>\n" + "#include <lastlib/file.h>\n" + "\n" + ; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + QTest::newRow("inserting_mixedDirsUnsorted") + << TestIncludePaths::globalIncludePath() + << testDocuments << firstRefactoringOperation << "<lastlib/file.h>"; + testDocuments.clear(); + + // ------------------------------------------------------------------------------------------- + + original = + "#include \"a.h\"\n" + "#include <global.h>\n" + "\n@" + ; + expected = + "#include \"a.h\"\n" + "#include \"z.h\"\n" + "#include <global.h>\n" + "\n" + ; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + QTest::newRow("inserting_mixedIncludeTypes1") + << TestIncludePaths::globalIncludePath() + << testDocuments << firstRefactoringOperation << "\"z.h\""; + testDocuments.clear(); + + // ------------------------------------------------------------------------------------------- + + original = + "#include \"z.h\"\n" + "#include <global.h>\n" + "\n@" + ; + expected = + "#include \"a.h\"\n" + "#include \"z.h\"\n" + "#include <global.h>\n" + "\n" + ; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + QTest::newRow("inserting_mixedIncludeTypes2") + << TestIncludePaths::globalIncludePath() + << testDocuments << firstRefactoringOperation << "\"a.h\""; + testDocuments.clear(); + + // ------------------------------------------------------------------------------------------- + + original = + "#include \"z.h\"\n" + "#include <global.h>\n" + "\n@" + ; + expected = + "#include \"z.h\"\n" + "#include \"lib/file.h\"\n" + "#include <global.h>\n" + "\n" + ; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + QTest::newRow("inserting_mixedIncludeTypes3") + << TestIncludePaths::globalIncludePath() + << testDocuments << firstRefactoringOperation << "\"lib/file.h\""; + testDocuments.clear(); + + // ------------------------------------------------------------------------------------------- + + original = + "#include \"z.h\"\n" + "#include <global.h>\n" + "\n@" + ; + expected = + "#include \"z.h\"\n" + "#include <global.h>\n" + "#include <lib/file.h>\n" + "\n" + ; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + QTest::newRow("inserting_mixedIncludeTypes4") + << TestIncludePaths::globalIncludePath() + << testDocuments << firstRefactoringOperation << "<lib/file.h>"; + testDocuments.clear(); + + // ------------------------------------------------------------------------------------------- + + original = + "void @f();\n" + ; + expected = + "#include \"file.h\"\n" + "\n" + "void f();\n" + ; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + QTest::newRow("inserting_noinclude") + << TestIncludePaths::globalIncludePath() + << testDocuments << firstRefactoringOperation << "\"file.h\""; + testDocuments.clear(); + + // ------------------------------------------------------------------------------------------- + + original = + "#ifndef FOO_H\n" + "#define FOO_H\n" + "void @f();\n" + "#endif\n" + ; + expected = + "#ifndef FOO_H\n" + "#define FOO_H\n" + "\n" + "#include \"file.h\"\n" + "\n" + "void f();\n" + "#endif\n" + ; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + QTest::newRow("inserting_onlyIncludeGuard") + << TestIncludePaths::globalIncludePath() + << testDocuments << firstRefactoringOperation << "\"file.h\""; + testDocuments.clear(); + + // ------------------------------------------------------------------------------------------- + + original = + "\n" + "// comment\n" + "\n" + "void @f();\n" + ; + expected = + "\n" + "// comment\n" + "\n" + "#include \"file.h\"\n" + "\n" + "void @f();\n" + ; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + QTest::newRow("inserting_veryFirstIncludeCppStyleCommentOnTop") + << TestIncludePaths::globalIncludePath() + << testDocuments << firstRefactoringOperation << "\"file.h\""; + testDocuments.clear(); + + // ------------------------------------------------------------------------------------------- + + original = + "\n" + "/*\n" + " comment\n" + " */\n" + "\n" + "void @f();\n" + ; + expected = + "\n" + "/*\n" + " comment\n" + " */\n" + "\n" + "#include \"file.h\"\n" + "\n" + "void @f();\n" + ; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + QTest::newRow("inserting_veryFirstIncludeCStyleCommentOnTop") + << TestIncludePaths::globalIncludePath() + << testDocuments << firstRefactoringOperation << "\"file.h\""; + testDocuments.clear(); + + // ------------------------------------------------------------------------------------------- + + original = + "@QDir dir;\n" + ; + expected = + "#include <QDir>\n" + "\n" + "QDir dir;\n" + ; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + QTest::newRow("inserting_checkQSomethingInQtIncludePaths") + << TestIncludePaths::globalQtCoreIncludePath() + << testDocuments << firstRefactoringOperation << ""; + testDocuments.clear(); + + original = + "std::s@tring s;\n" + ; + expected = + "#include <string>\n" + "\n" + "std::string s;\n" + ; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + QTest::newRow("inserting_std::string") + << TestIncludePaths::globalIncludePath() + << testDocuments << firstRefactoringOperation << ""; + testDocuments.clear(); + + original = "class A{};"; + testDocuments << CppTestDocument::create("a.h", original, original); + original = "class B{};"; + testDocuments << CppTestDocument::create("b.h", original, original); + original = + "#include \"b.h\"\n" + "@A a;\n" + "B b;"; + expected = + "#include \"b.h\"\n\n" + "#include \"a.h\"\n" + "A a;\n" + "B b;"; + testDocuments << CppTestDocument::create("b.cpp", original, expected); + QTest::newRow("preserve first header") + << TestIncludePaths::globalIncludePath() + << testDocuments << firstRefactoringOperation << ""; + testDocuments.clear(); + + original = "class C{};"; + testDocuments << CppTestDocument::create("c.h", original, original); + original = "class B{};"; + testDocuments << CppTestDocument::create("b.h", original, original); + original = + "#include \"c.h\"\n" + "C c;\n" + "@B b;"; + expected = + "#include \"b.h\"\n" + "#include \"c.h\"\n" + "C c;\n" + "B b;"; + testDocuments << CppTestDocument::create("x.cpp", original, expected); + QTest::newRow("do not preserve first header") + << TestIncludePaths::globalIncludePath() + << testDocuments << firstRefactoringOperation << ""; + testDocuments.clear(); + } + + void testAddIncludeForUndefinedIdentifier() + { + QFETCH(QString, headerPath); + QFETCH(QuickFixTestDocuments, testDocuments); + QFETCH(int, refactoringOperationIndex); + QFETCH(QString, includeForTestFactory); + + TemporaryDir temporaryDir; + QVERIFY(temporaryDir.isValid()); + for (const TestDocumentPtr &testDocument : std::as_const(testDocuments)) + testDocument->setBaseDirectory(temporaryDir.path()); + + QScopedPointer<CppQuickFixFactory> factory; + if (includeForTestFactory.isEmpty()) + factory.reset(new AddIncludeForUndefinedIdentifier); + else + factory.reset(new AddIncludeForUndefinedIdentifierTestFactory(includeForTestFactory)); + + QuickFixOperationTest::run(testDocuments, factory.data(), headerPath, + refactoringOperationIndex); + } + + void testAddIncludeForUndefinedIdentifierNoDoubleQtHeaderInclude() + { + TemporaryDir temporaryDir; + QVERIFY(temporaryDir.isValid()); + + QList<TestDocumentPtr> testDocuments; + QByteArray original; + QByteArray expected; + + const QByteArray base = temporaryDir.path().toUtf8(); + + // This file makes the QDir definition available so that locator finds it. + original = expected = "#include <QDir>\n" + "void avoidBeingRecognizedAsForwardingHeader();"; + testDocuments << CppTestDocument::create(base + "/fileUsingQDir.cpp", original, expected); + + original = expected = "@QDir dir;\n"; + testDocuments << CppTestDocument::create(base + "/fileWantsToUseQDir.cpp", original, expected); + + AddIncludeForUndefinedIdentifier factory; + const QStringList expectedOperations = QStringList("Add #include <QDir>"); + QuickFixOfferedOperationsTest( + testDocuments, + &factory, + ProjectExplorer::toUserHeaderPaths( + QStringList{TestIncludePaths::globalQtCoreIncludePath()}), + expectedOperations); + } + + void testAddForwardDeclForUndefinedIdentifier_data() + { + QTest::addColumn<QuickFixTestDocuments>("testDocuments"); + QTest::addColumn<QString>("symbol"); + QTest::addColumn<int>("symbolPos"); + + QByteArray original; + QByteArray expected; + + original = + "#pragma once\n" + "\n" + "void f(const Blu@bb &b)\n" + "{\n" + "}\n" + ; + expected = + "#pragma once\n" + "\n" + "\n" + "class Blubb;\n" + "void f(const Blubb &b)\n" + "{\n" + "}\n" + ; + QTest::newRow("unqualified symbol") + << QuickFixTestDocuments{CppTestDocument::create("theheader.h", original, expected)} + << "Blubb" << original.indexOf('@'); + + original = + "#pragma once\n" + "\n" + "namespace NS {\n" + "class C;\n" + "}\n" + "void f(const NS::Blu@bb &b)\n" + "{\n" + "}\n" + ; + expected = + "#pragma once\n" + "\n" + "namespace NS {\n" + "\n" + "class Blubb;\n" + "class C;\n" + "}\n" + "void f(const NS::Blubb &b)\n" + "{\n" + "}\n" + ; + QTest::newRow("qualified symbol, full namespace present") + << QuickFixTestDocuments{CppTestDocument::create("theheader.h", original, expected)} + << "NS::Blubb" << original.indexOf('@'); + + original = + "#pragma once\n" + "\n" + "namespace NS {\n" + "class C;\n" + "}\n" + "void f(const NS::NS2::Blu@bb &b)\n" + "{\n" + "}\n" + ; + expected = + "#pragma once\n" + "\n" + "namespace NS {\n" + "\n" + "namespace NS2 { class Blubb; }\n" + "class C;\n" + "}\n" + "void f(const NS::NS2::Blubb &b)\n" + "{\n" + "}\n" + ; + QTest::newRow("qualified symbol, partial namespace present") + << QuickFixTestDocuments{CppTestDocument::create("theheader.h", original, expected)} + << "NS::NS2::Blubb" << original.indexOf('@'); + + original = + "#pragma once\n" + "\n" + "namespace NS {\n" + "class C;\n" + "}\n" + "void f(const NS2::Blu@bb &b)\n" + "{\n" + "}\n" + ; + expected = + "#pragma once\n" + "\n" + "\n" + "namespace NS2 { class Blubb; }\n" + "namespace NS {\n" + "class C;\n" + "}\n" + "void f(const NS2::Blubb &b)\n" + "{\n" + "}\n" + ; + QTest::newRow("qualified symbol, other namespace present") + << QuickFixTestDocuments{CppTestDocument::create("theheader.h", original, expected)} + << "NS2::Blubb" << original.indexOf('@'); + + original = + "#pragma once\n" + "\n" + "void f(const NS2::Blu@bb &b)\n" + "{\n" + "}\n" + ; + expected = + "#pragma once\n" + "\n" + "\n" + "namespace NS2 { class Blubb; }\n" + "void f(const NS2::Blubb &b)\n" + "{\n" + "}\n" + ; + QTest::newRow("qualified symbol, no namespace present") + << QuickFixTestDocuments{CppTestDocument::create("theheader.h", original, expected)} + << "NS2::Blubb" << original.indexOf('@'); + + original = + "#pragma once\n" + "\n" + "void f(const NS2::Blu@bb &b)\n" + "{\n" + "}\n" + "namespace NS2 {}\n" + ; + expected = + "#pragma once\n" + "\n" + "\n" + "namespace NS2 { class Blubb; }\n" + "void f(const NS2::Blubb &b)\n" + "{\n" + "}\n" + "namespace NS2 {}\n" + ; + QTest::newRow("qualified symbol, existing namespace after symbol") + << QuickFixTestDocuments{CppTestDocument::create("theheader.h", original, expected)} + << "NS2::Blubb" << original.indexOf('@'); + } + + void testAddForwardDeclForUndefinedIdentifier() + { + QFETCH(QuickFixTestDocuments, testDocuments); + QFETCH(QString, symbol); + QFETCH(int, symbolPos); + + TemporaryDir temporaryDir; + QVERIFY(temporaryDir.isValid()); + testDocuments.first()->setBaseDirectory(temporaryDir.path()); + + QScopedPointer<CppQuickFixFactory> factory( + new AddForwardDeclForUndefinedIdentifierTestFactory(symbol, symbolPos)); + QuickFixOperationTest::run({testDocuments}, factory.data(), ".", 0); + } +}; + +QObject *AddIncludeForUndefinedIdentifier::createTest() +{ + return new BringIdentifierIntoScopeTest; +} + +#endif // WITH_TESTS +} // namespace + +void registerBringIdentifierIntoScopeQuickfixes() +{ + CppQuickFixFactory::registerFactory<AddIncludeForUndefinedIdentifier>(); +} + +} // namespace CppEditor::Internal + +#ifdef WITH_TESTS +#include <bringidentifierintoscope.moc> +#endif diff --git a/src/plugins/cppeditor/quickfixes/bringidentifierintoscope.h b/src/plugins/cppeditor/quickfixes/bringidentifierintoscope.h new file mode 100644 index 0000000000..b2f8bbe5d6 --- /dev/null +++ b/src/plugins/cppeditor/quickfixes/bringidentifierintoscope.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 registerBringIdentifierIntoScopeQuickfixes(); +} // namespace CppEditor::Internal diff --git a/src/plugins/cppeditor/quickfixes/completeswitchstatement.cpp b/src/plugins/cppeditor/quickfixes/completeswitchstatement.cpp new file mode 100644 index 0000000000..f92f5bbfd4 --- /dev/null +++ b/src/plugins/cppeditor/quickfixes/completeswitchstatement.cpp @@ -0,0 +1,799 @@ +// 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 "completeswitchstatement.h" + +#include "../cppeditortr.h" +#include "../cpprefactoringchanges.h" +#include "cppquickfix.h" + +#include <cplusplus/Overview.h> +#include <cplusplus/TypeOfExpression.h> + +#ifdef WITH_TESTS +#include "cppquickfix_test.h" +#include <QtTest> +#endif + +using namespace CPlusPlus; +using namespace Utils; + +namespace CppEditor::Internal { +namespace { + +class CaseStatementCollector : public ASTVisitor +{ +public: + CaseStatementCollector(Document::Ptr document, const Snapshot &snapshot, + Scope *scope) + : ASTVisitor(document->translationUnit()), + document(document), + scope(scope) + { + typeOfExpression.init(document, snapshot); + } + + QStringList operator ()(AST *ast) + { + values.clear(); + foundCaseStatementLevel = false; + accept(ast); + return values; + } + + bool preVisit(AST *ast) override { + if (CaseStatementAST *cs = ast->asCaseStatement()) { + foundCaseStatementLevel = true; + if (ExpressionAST *csExpression = cs->expression) { + if (ExpressionAST *expression = csExpression->asIdExpression()) { + QList<LookupItem> candidates = typeOfExpression(expression, document, scope); + if (!candidates.isEmpty() && candidates.first().declaration()) { + Symbol *decl = candidates.first().declaration(); + values << prettyPrint.prettyName(LookupContext::fullyQualifiedName(decl)); + } + } + } + return true; + } else if (foundCaseStatementLevel) { + return false; + } + return true; + } + + Overview prettyPrint; + bool foundCaseStatementLevel = false; + QStringList values; + TypeOfExpression typeOfExpression; + Document::Ptr document; + Scope *scope; +}; + +class CompleteSwitchCaseStatementOp: public CppQuickFixOperation +{ +public: + CompleteSwitchCaseStatementOp(const CppQuickFixInterface &interface, + int priority, CompoundStatementAST *compoundStatement, const QStringList &values) + : CppQuickFixOperation(interface, priority) + , compoundStatement(compoundStatement) + , values(values) + { + setDescription(Tr::tr("Complete Switch Statement")); + } + + void perform() override + { + CppRefactoringChanges refactoring(snapshot()); + CppRefactoringFilePtr currentFile = refactoring.cppFile(filePath()); + + ChangeSet changes; + int start = currentFile->endOf(compoundStatement->lbrace_token); + changes.insert(start, QLatin1String("\ncase ") + + values.join(QLatin1String(":\nbreak;\ncase ")) + + QLatin1String(":\nbreak;")); + currentFile->setChangeSet(changes); + currentFile->apply(); + } + + CompoundStatementAST *compoundStatement; + QStringList values; +}; + +static Enum *findEnum(const QList<LookupItem> &results, const LookupContext &ctxt) +{ + for (const LookupItem &result : results) { + const FullySpecifiedType fst = result.type(); + + Type *type = result.declaration() ? result.declaration()->type().type() + : fst.type(); + + if (!type) + continue; + if (Enum *e = type->asEnumType()) + return e; + if (const NamedType *namedType = type->asNamedType()) { + if (ClassOrNamespace *con = ctxt.lookupType(namedType->name(), result.scope())) { + QList<Enum *> enums = con->unscopedEnums(); + const QList<Symbol *> symbols = con->symbols(); + for (Symbol * const s : symbols) { + if (const auto e = s->asEnum()) + enums << e; + } + const Name *referenceName = namedType->name(); + if (const QualifiedNameId *qualifiedName = referenceName->asQualifiedNameId()) + referenceName = qualifiedName->name(); + for (Enum *e : std::as_const(enums)) { + if (const Name *candidateName = e->name()) { + if (candidateName->match(referenceName)) + return e; + } + } + } + } + } + + return nullptr; +} + +Enum *conditionEnum(const CppQuickFixInterface &interface, SwitchStatementAST *statement) +{ + Block *block = statement->symbol; + Scope *scope = interface.semanticInfo().doc->scopeAt(block->line(), block->column()); + TypeOfExpression typeOfExpression; + typeOfExpression.setExpandTemplates(true); + typeOfExpression.init(interface.semanticInfo().doc, interface.snapshot()); + const QList<LookupItem> results = typeOfExpression(statement->condition, + interface.semanticInfo().doc, + scope); + + return findEnum(results, typeOfExpression.context()); +} + +//! Adds missing case statements for "switch (enumVariable)" +class CompleteSwitchStatement: public CppQuickFixFactory +{ +public: + CompleteSwitchStatement() { setClangdReplacement({12}); } +#ifdef WITH_TESTS + static QObject *createTest(); +#endif + +private: + void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override + { + const QList<AST *> &path = interface.path(); + + if (path.isEmpty()) + return; + + // look for switch statement + for (int depth = path.size() - 1; depth >= 0; --depth) { + AST *ast = path.at(depth); + SwitchStatementAST *switchStatement = ast->asSwitchStatement(); + if (switchStatement) { + if (!switchStatement->statement || !switchStatement->symbol) + return; + CompoundStatementAST *compoundStatement = switchStatement->statement->asCompoundStatement(); + if (!compoundStatement) // we ignore pathologic case "switch (t) case A: ;" + return; + // look if the condition's type is an enum + if (Enum *e = conditionEnum(interface, switchStatement)) { + // check the possible enum values + QStringList values; + Overview prettyPrint; + for (int i = 0; i < e->memberCount(); ++i) { + if (Declaration *decl = e->memberAt(i)->asDeclaration()) + values << prettyPrint.prettyName(LookupContext::fullyQualifiedName(decl)); + } + // Get the used values + Block *block = switchStatement->symbol; + CaseStatementCollector caseValues(interface.semanticInfo().doc, interface.snapshot(), + interface.semanticInfo().doc->scopeAt(block->line(), block->column())); + const QStringList usedValues = caseValues(switchStatement); + // save the values that would be added + for (const QString &usedValue : usedValues) + values.removeAll(usedValue); + if (!values.isEmpty()) + result << new CompleteSwitchCaseStatementOp(interface, depth, + compoundStatement, values); + return; + } + + return; + } + } + } +}; + +#ifdef WITH_TESTS +using namespace Tests; + +class CompleteSwitchStatementTest : public QObject +{ + Q_OBJECT + +private slots: + void test_data() + { + QTest::addColumn<QByteArray>("original"); + QTest::addColumn<QByteArray>("expected"); + + using QByteArray = QByteArray; + + // Checks: All enum values are added as case statements for a blank switch. + QTest::newRow("basic1") + << QByteArray( + "enum EnumType { V1, V2 };\n" + "\n" + "void f()\n" + "{\n" + " EnumType t;\n" + " @switch (t) {\n" + " }\n" + "}\n") + << QByteArray( + "enum EnumType { V1, V2 };\n" + "\n" + "void f()\n" + "{\n" + " EnumType t;\n" + " switch (t) {\n" + " case V1:\n" + " break;\n" + " case V2:\n" + " break;\n" + " }\n" + "}\n"); + + // Same as above for enum class. + QTest::newRow("basic1_enum class") + << QByteArray( + "enum class EnumType { V1, V2 };\n" + "\n" + "void f()\n" + "{\n" + " EnumType t;\n" + " @switch (t) {\n" + " }\n" + "}\n") + << QByteArray( + "enum class EnumType { V1, V2 };\n" + "\n" + "void f()\n" + "{\n" + " EnumType t;\n" + " switch (t) {\n" + " case EnumType::V1:\n" + " break;\n" + " case EnumType::V2:\n" + " break;\n" + " }\n" + "}\n"); + + // Same as above with the cursor somewhere in the body. + QTest::newRow("basic1_enum class, cursor in the body") + << QByteArray( + "enum class EnumType { V1, V2 };\n" + "\n" + "void f()\n" + "{\n" + " EnumType t;\n" + " switch (t) {\n" + " @}\n" + "}\n") + << QByteArray( + "enum class EnumType { V1, V2 };\n" + "\n" + "void f()\n" + "{\n" + " EnumType t;\n" + " switch (t) {\n" + " case EnumType::V1:\n" + " break;\n" + " case EnumType::V2:\n" + " break;\n" + " }\n" + "}\n"); + + // Checks: All enum values are added as case statements for a blank switch when + // the variable is declared alongside the enum definition. + QTest::newRow("basic1_enum_with_declaration") + << QByteArray( + "enum EnumType { V1, V2 } t;\n" + "\n" + "void f()\n" + "{\n" + " @switch (t) {\n" + " }\n" + "}\n") + << QByteArray( + "enum EnumType { V1, V2 } t;\n" + "\n" + "void f()\n" + "{\n" + " switch (t) {\n" + " case V1:\n" + " break;\n" + " case V2:\n" + " break;\n" + " }\n" + "}\n"); + + // Same as above for enum class. + QTest::newRow("basic1_enum_with_declaration_enumClass") + << QByteArray( + "enum class EnumType { V1, V2 } t;\n" + "\n" + "void f()\n" + "{\n" + " @switch (t) {\n" + " }\n" + "}\n") + << QByteArray( + "enum class EnumType { V1, V2 } t;\n" + "\n" + "void f()\n" + "{\n" + " switch (t) {\n" + " case EnumType::V1:\n" + " break;\n" + " case EnumType::V2:\n" + " break;\n" + " }\n" + "}\n"); + + // Checks: All enum values are added as case statements for a blank switch + // for anonymous enums. + QTest::newRow("basic1_anonymous_enum") + << QByteArray( + "enum { V1, V2 } t;\n" + "\n" + "void f()\n" + "{\n" + " @switch (t) {\n" + " }\n" + "}\n") + << QByteArray( + "enum { V1, V2 } t;\n" + "\n" + "void f()\n" + "{\n" + " switch (t) {\n" + " case V1:\n" + " break;\n" + " case V2:\n" + " break;\n" + " }\n" + "}\n"); + + // Checks: All enum values are added as case statements for a blank switch with a default case. + QTest::newRow("basic2") + << QByteArray( + "enum EnumType { V1, V2 };\n" + "\n" + "void f()\n" + "{\n" + " EnumType t;\n" + " @switch (t) {\n" + " default:\n" + " break;\n" + " }\n" + "}\n") + << QByteArray( + "enum EnumType { V1, V2 };\n" + "\n" + "void f()\n" + "{\n" + " EnumType t;\n" + " switch (t) {\n" + " case V1:\n" + " break;\n" + " case V2:\n" + " break;\n" + " default:\n" + " break;\n" + " }\n" + "}\n"); + + // Same as above for enum class. + QTest::newRow("basic2_enumClass") + << QByteArray( + "enum class EnumType { V1, V2 };\n" + "\n" + "void f()\n" + "{\n" + " EnumType t;\n" + " @switch (t) {\n" + " default:\n" + " break;\n" + " }\n" + "}\n") + << QByteArray( + "enum class EnumType { V1, V2 };\n" + "\n" + "void f()\n" + "{\n" + " EnumType t;\n" + " switch (t) {\n" + " case EnumType::V1:\n" + " break;\n" + " case EnumType::V2:\n" + " break;\n" + " default:\n" + " break;\n" + " }\n" + "}\n"); + + // Checks: Enum type in class is found. + QTest::newRow("enumTypeInClass") + << QByteArray( + "struct C { enum EnumType { V1, V2 }; };\n" + "\n" + "void f(C::EnumType t) {\n" + " @switch (t) {\n" + " }\n" + "}\n") + << QByteArray( + "struct C { enum EnumType { V1, V2 }; };\n" + "\n" + "void f(C::EnumType t) {\n" + " switch (t) {\n" + " case C::V1:\n" + " break;\n" + " case C::V2:\n" + " break;\n" + " }\n" + "}\n"); + + // Same as above for enum class. + QTest::newRow("enumClassInClass") + << QByteArray( + "struct C { enum class EnumType { V1, V2 }; };\n" + "\n" + "void f(C::EnumType t) {\n" + " @switch (t) {\n" + " }\n" + "}\n") + << QByteArray( + "struct C { enum class EnumType { V1, V2 }; };\n" + "\n" + "void f(C::EnumType t) {\n" + " switch (t) {\n" + " case C::EnumType::V1:\n" + " break;\n" + " case C::EnumType::V2:\n" + " break;\n" + " }\n" + "}\n"); + + // Checks: Enum type in namespace is found. + QTest::newRow("enumTypeInNamespace") + << QByteArray( + "namespace N { enum EnumType { V1, V2 }; };\n" + "\n" + "void f(N::EnumType t) {\n" + " @switch (t) {\n" + " }\n" + "}\n") + << QByteArray( + "namespace N { enum EnumType { V1, V2 }; };\n" + "\n" + "void f(N::EnumType t) {\n" + " switch (t) {\n" + " case N::V1:\n" + " break;\n" + " case N::V2:\n" + " break;\n" + " }\n" + "}\n"); + + // Same as above for enum class. + QTest::newRow("enumClassInNamespace") + << QByteArray( + "namespace N { enum class EnumType { V1, V2 }; };\n" + "\n" + "void f(N::EnumType t) {\n" + " @switch (t) {\n" + " }\n" + "}\n") + << QByteArray( + "namespace N { enum class EnumType { V1, V2 }; };\n" + "\n" + "void f(N::EnumType t) {\n" + " switch (t) {\n" + " case N::EnumType::V1:\n" + " break;\n" + " case N::EnumType::V2:\n" + " break;\n" + " }\n" + "}\n"); + + // Checks: The missing enum value is added. + QTest::newRow("oneValueMissing") + << QByteArray( + "enum EnumType { V1, V2 };\n" + "\n" + "void f()\n" + "{\n" + " EnumType t;\n" + " @switch (t) {\n" + " case V2:\n" + " break;\n" + " default:\n" + " break;\n" + " }\n" + "}\n") + << QByteArray( + "enum EnumType { V1, V2 };\n" + "\n" + "void f()\n" + "{\n" + " EnumType t;\n" + " switch (t) {\n" + " case V1:\n" + " break;\n" + " case V2:\n" + " break;\n" + " default:\n" + " break;\n" + " }\n" + "}\n"); + + // Checks: Same as above for enum class. + QTest::newRow("oneValueMissing_enumClass") + << QByteArray( + "enum class EnumType { V1, V2 };\n" + "\n" + "void f()\n" + "{\n" + " EnumType t;\n" + " @switch (t) {\n" + " case EnumType::V2:\n" + " break;\n" + " default:\n" + " break;\n" + " }\n" + "}\n") + << QByteArray( + "enum class EnumType { V1, V2 };\n" + "\n" + "void f()\n" + "{\n" + " EnumType t;\n" + " switch (t) {\n" + " case EnumType::V1:\n" + " break;\n" + " case EnumType::V2:\n" + " break;\n" + " default:\n" + " break;\n" + " }\n" + "}\n"); + + // Checks: Find the correct enum type despite there being a declaration with the same name. + QTest::newRow("QTCREATORBUG10366_1") + << QByteArray( + "enum test { TEST_1, TEST_2 };\n" + "\n" + "void f() {\n" + " enum test test;\n" + " @switch (test) {\n" + " }\n" + "}\n") + << QByteArray( + "enum test { TEST_1, TEST_2 };\n" + "\n" + "void f() {\n" + " enum test test;\n" + " switch (test) {\n" + " case TEST_1:\n" + " break;\n" + " case TEST_2:\n" + " break;\n" + " }\n" + "}\n"); + + // Same as above for enum class. + QTest::newRow("QTCREATORBUG10366_1_enumClass") + << QByteArray( + "enum class test { TEST_1, TEST_2 };\n" + "\n" + "void f() {\n" + " enum test test;\n" + " @switch (test) {\n" + " }\n" + "}\n") + << QByteArray( + "enum class test { TEST_1, TEST_2 };\n" + "\n" + "void f() {\n" + " enum test test;\n" + " switch (test) {\n" + " case test::TEST_1:\n" + " break;\n" + " case test::TEST_2:\n" + " break;\n" + " }\n" + "}\n"); + + // Checks: Find the correct enum type despite there being a declaration with the same name. + QTest::newRow("QTCREATORBUG10366_2") + << QByteArray( + "enum test1 { Wrong11, Wrong12 };\n" + "enum test { Right1, Right2 };\n" + "enum test2 { Wrong21, Wrong22 };\n" + "\n" + "int main() {\n" + " enum test test;\n" + " @switch (test) {\n" + " }\n" + "}\n") + << QByteArray( + "enum test1 { Wrong11, Wrong12 };\n" + "enum test { Right1, Right2 };\n" + "enum test2 { Wrong21, Wrong22 };\n" + "\n" + "int main() {\n" + " enum test test;\n" + " switch (test) {\n" + " case Right1:\n" + " break;\n" + " case Right2:\n" + " break;\n" + " }\n" + "}\n"); + + // Same as above for enum class. + QTest::newRow("QTCREATORBUG10366_2_enumClass") + << QByteArray( + "enum class test1 { Wrong11, Wrong12 };\n" + "enum class test { Right1, Right2 };\n" + "enum class test2 { Wrong21, Wrong22 };\n" + "\n" + "int main() {\n" + " enum test test;\n" + " @switch (test) {\n" + " }\n" + "}\n") + << QByteArray( + "enum class test1 { Wrong11, Wrong12 };\n" + "enum class test { Right1, Right2 };\n" + "enum class test2 { Wrong21, Wrong22 };\n" + "\n" + "int main() {\n" + " enum test test;\n" + " switch (test) {\n" + " case test::Right1:\n" + " break;\n" + " case test::Right2:\n" + " break;\n" + " }\n" + "}\n"); + + // Checks: Do not crash on incomplete case statetement. + QTest::newRow("doNotCrashOnIncompleteCase") + << QByteArray( + "enum E {};\n" + "void f(E o)\n" + "{\n" + " @switch (o)\n" + " {\n" + " case\n" + " }\n" + "}\n") + << QByteArray(); + + // Same as above for enum class. + QTest::newRow("doNotCrashOnIncompleteCase_enumClass") + << QByteArray( + "enum class E {};\n" + "void f(E o)\n" + "{\n" + " @switch (o)\n" + " {\n" + " case\n" + " }\n" + "}\n") + << QByteArray(); + + // Checks: complete switch statement where enum is goes via a template type parameter + QTest::newRow("QTCREATORBUG-24752") + << QByteArray( + "enum E {EA, EB};\n" + "template<typename T> struct S {\n" + " static T theType() { return T(); }\n" + "};\n" + "int main() {\n" + " @switch (S<E>::theType()) {\n" + " }\n" + "}\n") + << QByteArray( + "enum E {EA, EB};\n" + "template<typename T> struct S {\n" + " static T theType() { return T(); }\n" + "};\n" + "int main() {\n" + " switch (S<E>::theType()) {\n" + " case EA:\n" + " break;\n" + " case EB:\n" + " break;\n" + " }\n" + "}\n"); + + // Same as above for enum class. + QTest::newRow("QTCREATORBUG-24752_enumClass") + << QByteArray( + "enum class E {A, B};\n" + "template<typename T> struct S {\n" + " static T theType() { return T(); }\n" + "};\n" + "int main() {\n" + " @switch (S<E>::theType()) {\n" + " }\n" + "}\n") + << QByteArray( + "enum class E {A, B};\n" + "template<typename T> struct S {\n" + " static T theType() { return T(); }\n" + "};\n" + "int main() {\n" + " switch (S<E>::theType()) {\n" + " case E::A:\n" + " break;\n" + " case E::B:\n" + " break;\n" + " }\n" + "}\n"); + + // Checks: Complete switch statement where enum is return type of a template function + // which is outside the scope of the return value. + // TODO: Type minimization. + QTest::newRow("QTCREATORBUG-25998") + << QByteArray( + "template <typename T> T enumCast(int value) { return static_cast<T>(value); }\n" + "class Test {\n" + " enum class E { V1, V2 };" + " void func(int i) {\n" + " @switch (enumCast<E>(i)) {\n" + " }\n" + " }\n" + "};\n") + << QByteArray( + "template <typename T> T enumCast(int value) { return static_cast<T>(value); }\n" + "class Test {\n" + " enum class E { V1, V2 };" + " void func(int i) {\n" + " switch (enumCast<E>(i)) {\n" + " case Test::E::V1:\n" + " break;\n" + " case Test::E::V2:\n" + " break;\n" + " }\n" + " }\n" + "};\n"); + } + + void test() + { + QFETCH(QByteArray, original); + QFETCH(QByteArray, expected); + CompleteSwitchStatement factory; + QuickFixOperationTest(singleDocument(original, expected), &factory); + } +}; + +QObject *CompleteSwitchStatement::createTest() { return new CompleteSwitchStatementTest; } + +#endif // WITH_TESTS +} // namespace + +void registerCompleteSwitchStatementQuickfix() +{ + CppQuickFixFactory::registerFactory<CompleteSwitchStatement>(); +} + +} // namespace CppEditor::Internal + +#ifdef WITH_TESTS +#include <completeswitchstatement.moc> +#endif diff --git a/src/plugins/cppeditor/quickfixes/completeswitchstatement.h b/src/plugins/cppeditor/quickfixes/completeswitchstatement.h new file mode 100644 index 0000000000..dc2293b1b2 --- /dev/null +++ b/src/plugins/cppeditor/quickfixes/completeswitchstatement.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 registerCompleteSwitchStatementQuickfix(); +} // namespace CppEditor::Internal diff --git a/src/plugins/cppeditor/quickfixes/convertfromandtopointer.cpp b/src/plugins/cppeditor/quickfixes/convertfromandtopointer.cpp new file mode 100644 index 0000000000..3b5345f688 --- /dev/null +++ b/src/plugins/cppeditor/quickfixes/convertfromandtopointer.cpp @@ -0,0 +1,705 @@ +// 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 "convertfromandtopointer.h" + +#include "../cppeditortr.h" +#include "../cpprefactoringchanges.h" +#include "cppquickfix.h" + +#include <cplusplus/ASTPath.h> +#include <cplusplus/Overview.h> +#include <cplusplus/TypeOfExpression.h> + +#ifdef WITH_TESTS +#include "cppquickfix_test.h" +#include <QtTest> +#endif + +using namespace CPlusPlus; +using namespace Utils; + +namespace CppEditor::Internal { +namespace { + +class ConvertFromAndToPointerOp : public CppQuickFixOperation +{ +public: + enum Mode { FromPointer, FromVariable, FromReference }; + + ConvertFromAndToPointerOp(const CppQuickFixInterface &interface, int priority, Mode mode, + bool isAutoDeclaration, + const SimpleDeclarationAST *simpleDeclaration, + const DeclaratorAST *declaratorAST, + const SimpleNameAST *identifierAST, + Symbol *symbol) + : CppQuickFixOperation(interface, priority) + , m_mode(mode) + , m_isAutoDeclaration(isAutoDeclaration) + , m_simpleDeclaration(simpleDeclaration) + , m_declaratorAST(declaratorAST) + , m_identifierAST(identifierAST) + , m_symbol(symbol) + , m_refactoring(snapshot()) + , m_file(m_refactoring.cppFile(filePath())) + , m_document(interface.semanticInfo().doc) + { + setDescription( + mode == FromPointer + ? Tr::tr("Convert to Stack Variable") + : Tr::tr("Convert to Pointer")); + } + + void perform() override + { + ChangeSet changes; + + switch (m_mode) { + case FromPointer: + removePointerOperator(changes); + convertToStackVariable(changes); + break; + case FromReference: + removeReferenceOperator(changes); + Q_FALLTHROUGH(); + case FromVariable: + convertToPointer(changes); + break; + } + + m_file->setChangeSet(changes); + m_file->apply(); + } + +private: + void removePointerOperator(ChangeSet &changes) const + { + if (!m_declaratorAST->ptr_operator_list) + return; + PointerAST *ptrAST = m_declaratorAST->ptr_operator_list->value->asPointer(); + QTC_ASSERT(ptrAST, return); + const int pos = m_file->startOf(ptrAST->star_token); + changes.remove(pos, pos + 1); + } + + void removeReferenceOperator(ChangeSet &changes) const + { + ReferenceAST *refAST = m_declaratorAST->ptr_operator_list->value->asReference(); + QTC_ASSERT(refAST, return); + const int pos = m_file->startOf(refAST->reference_token); + changes.remove(pos, pos + 1); + } + + void removeNewExpression(ChangeSet &changes, NewExpressionAST *newExprAST) const + { + ExpressionListAST *exprlist = nullptr; + if (newExprAST->new_initializer) { + if (ExpressionListParenAST *ast = newExprAST->new_initializer->asExpressionListParen()) + exprlist = ast->expression_list; + else if (BracedInitializerAST *ast = newExprAST->new_initializer->asBracedInitializer()) + exprlist = ast->expression_list; + } + + if (exprlist) { + // remove 'new' keyword and type before initializer + changes.remove(m_file->startOf(newExprAST->new_token), + m_file->startOf(newExprAST->new_initializer)); + + changes.remove(m_file->endOf(m_declaratorAST->equal_token - 1), + m_file->startOf(m_declaratorAST->equal_token + 1)); + } else { + // remove the whole new expression + changes.remove(m_file->endOf(m_identifierAST->firstToken()), + m_file->startOf(newExprAST->lastToken())); + } + } + + void removeNewKeyword(ChangeSet &changes, NewExpressionAST *newExprAST) const + { + // remove 'new' keyword before initializer + changes.remove(m_file->startOf(newExprAST->new_token), + m_file->startOf(newExprAST->new_type_id)); + } + + void convertToStackVariable(ChangeSet &changes) const + { + // Handle the initializer. + if (m_declaratorAST->initializer) { + if (NewExpressionAST *newExpression = m_declaratorAST->initializer->asNewExpression()) { + if (m_isAutoDeclaration) { + if (!newExpression->new_initializer) + changes.insert(m_file->endOf(newExpression), QStringLiteral("()")); + removeNewKeyword(changes, newExpression); + } else { + removeNewExpression(changes, newExpression); + } + } + } + + // Fix all occurrences of the identifier in this function. + ASTPath astPath(m_document); + const QList<SemanticInfo::Use> uses = semanticInfo().localUses.value(m_symbol); + for (const SemanticInfo::Use &use : uses) { + const QList<AST *> path = astPath(use.line, use.column); + AST *idAST = path.last(); + bool declarationFound = false; + bool starFound = false; + int ampersandPos = 0; + bool memberAccess = false; + bool deleteCall = false; + + for (int i = path.count() - 2; i >= 0; --i) { + if (path.at(i) == m_declaratorAST) { + declarationFound = true; + break; + } + if (MemberAccessAST *memberAccessAST = path.at(i)->asMemberAccess()) { + if (m_file->tokenAt(memberAccessAST->access_token).kind() != T_ARROW) + continue; + int pos = m_file->startOf(memberAccessAST->access_token); + changes.replace(pos, pos + 2, QLatin1String(".")); + memberAccess = true; + break; + } else if (DeleteExpressionAST *deleteAST = path.at(i)->asDeleteExpression()) { + const int pos = m_file->startOf(deleteAST->delete_token); + changes.insert(pos, QLatin1String("// ")); + deleteCall = true; + break; + } else if (UnaryExpressionAST *unaryExprAST = path.at(i)->asUnaryExpression()) { + const Token tk = m_file->tokenAt(unaryExprAST->unary_op_token); + if (tk.kind() == T_STAR) { + if (!starFound) { + int pos = m_file->startOf(unaryExprAST->unary_op_token); + changes.remove(pos, pos + 1); + } + starFound = true; + } else if (tk.kind() == T_AMPER) { + ampersandPos = m_file->startOf(unaryExprAST->unary_op_token); + } + } else if (PointerAST *ptrAST = path.at(i)->asPointer()) { + if (!starFound) { + const int pos = m_file->startOf(ptrAST->star_token); + changes.remove(pos, pos); + } + starFound = true; + } else if (path.at(i)->asFunctionDefinition()) { + break; + } + } + if (!declarationFound && !starFound && !memberAccess && !deleteCall) { + if (ampersandPos) { + changes.insert(ampersandPos, QLatin1String("&(")); + changes.insert(m_file->endOf(idAST->firstToken()), QLatin1String(")")); + } else { + changes.insert(m_file->startOf(idAST), QLatin1String("&")); + } + } + } + } + + QString typeNameOfDeclaration() const + { + if (!m_simpleDeclaration + || !m_simpleDeclaration->decl_specifier_list + || !m_simpleDeclaration->decl_specifier_list->value) { + return QString(); + } + NamedTypeSpecifierAST *namedType + = m_simpleDeclaration->decl_specifier_list->value->asNamedTypeSpecifier(); + if (!namedType) + return QString(); + + Overview overview; + return overview.prettyName(namedType->name->name); + } + + void insertNewExpression(ChangeSet &changes, ExpressionAST *ast) const + { + const QString typeName = typeNameOfDeclaration(); + if (CallAST *callAST = ast->asCall()) { + if (typeName.isEmpty()) { + changes.insert(m_file->startOf(callAST), QLatin1String("new ")); + } else { + changes.insert(m_file->startOf(callAST), + QLatin1String("new ") + typeName + QLatin1Char('(')); + changes.insert(m_file->startOf(callAST->lastToken()), QLatin1String(")")); + } + } else { + if (typeName.isEmpty()) + return; + changes.insert(m_file->startOf(ast), QLatin1String(" = new ") + typeName); + } + } + + void insertNewExpression(ChangeSet &changes) const + { + const QString typeName = typeNameOfDeclaration(); + if (typeName.isEmpty()) + return; + changes.insert(m_file->endOf(m_identifierAST->firstToken()), + QLatin1String(" = new ") + typeName); + } + + void convertToPointer(ChangeSet &changes) const + { + // Handle initializer. + if (m_declaratorAST->initializer) { + if (IdExpressionAST *idExprAST = m_declaratorAST->initializer->asIdExpression()) { + changes.insert(m_file->startOf(idExprAST), QLatin1String("&")); + } else if (CallAST *callAST = m_declaratorAST->initializer->asCall()) { + insertNewExpression(changes, callAST); + } else if (ExpressionListParenAST *exprListAST = m_declaratorAST->initializer + ->asExpressionListParen()) { + insertNewExpression(changes, exprListAST); + } else if (BracedInitializerAST *bracedInitializerAST = m_declaratorAST->initializer + ->asBracedInitializer()) { + insertNewExpression(changes, bracedInitializerAST); + } + } else { + insertNewExpression(changes); + } + + // Fix all occurrences of the identifier in this function. + ASTPath astPath(m_document); + const QList<SemanticInfo::Use> uses = semanticInfo().localUses.value(m_symbol); + for (const SemanticInfo::Use &use : uses) { + const QList<AST *> path = astPath(use.line, use.column); + AST *idAST = path.last(); + bool insertStar = true; + for (int i = path.count() - 2; i >= 0; --i) { + if (m_isAutoDeclaration && path.at(i) == m_declaratorAST) { + insertStar = false; + break; + } + if (MemberAccessAST *memberAccessAST = path.at(i)->asMemberAccess()) { + const int pos = m_file->startOf(memberAccessAST->access_token); + changes.replace(pos, pos + 1, QLatin1String("->")); + insertStar = false; + break; + } else if (UnaryExpressionAST *unaryExprAST = path.at(i)->asUnaryExpression()) { + if (m_file->tokenAt(unaryExprAST->unary_op_token).kind() == T_AMPER) { + const int pos = m_file->startOf(unaryExprAST->unary_op_token); + changes.remove(pos, pos + 1); + insertStar = false; + break; + } + } else if (path.at(i)->asFunctionDefinition()) { + break; + } + } + if (insertStar) + changes.insert(m_file->startOf(idAST), QLatin1String("*")); + } + } + + const Mode m_mode; + const bool m_isAutoDeclaration; + const SimpleDeclarationAST * const m_simpleDeclaration; + const DeclaratorAST * const m_declaratorAST; + const SimpleNameAST * const m_identifierAST; + Symbol * const m_symbol; + const CppRefactoringChanges m_refactoring; + const CppRefactoringFilePtr m_file; + const Document::Ptr m_document; +}; + +/*! + Converts the selected variable to a pointer if it is a stack variable or reference, or vice versa. + Activates on variable declarations. + */ +class ConvertFromAndToPointer : public CppQuickFixFactory +{ +#ifdef WITH_TESTS +public: + static QObject *createTest(); +#endif + +private: + void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override + { + const QList<AST *> &path = interface.path(); + if (path.count() < 2) + return; + SimpleNameAST *identifier = path.last()->asSimpleName(); + if (!identifier) + return; + SimpleDeclarationAST *simpleDeclaration = nullptr; + DeclaratorAST *declarator = nullptr; + bool isFunctionLocal = false; + bool isClassLocal = false; + ConvertFromAndToPointerOp::Mode mode = ConvertFromAndToPointerOp::FromVariable; + for (int i = path.count() - 2; i >= 0; --i) { + AST *ast = path.at(i); + if (!declarator && (declarator = ast->asDeclarator())) + continue; + if (!simpleDeclaration && (simpleDeclaration = ast->asSimpleDeclaration())) + continue; + if (declarator && simpleDeclaration) { + if (ast->asClassSpecifier()) { + isClassLocal = true; + } else if (ast->asFunctionDefinition() && !isClassLocal) { + isFunctionLocal = true; + break; + } + } + } + if (!isFunctionLocal || !simpleDeclaration || !declarator) + return; + + Symbol *symbol = nullptr; + for (List<Symbol *> *lst = simpleDeclaration->symbols; lst; lst = lst->next) { + if (lst->value->name() == identifier->name) { + symbol = lst->value; + break; + } + } + if (!symbol) + return; + + bool isAutoDeclaration = false; + if (symbol->storage() == Symbol::Auto) { + // For auto variables we must deduce the type from the initializer. + if (!declarator->initializer) + return; + + isAutoDeclaration = true; + TypeOfExpression typeOfExpression; + typeOfExpression.init(interface.semanticInfo().doc, interface.snapshot()); + typeOfExpression.setExpandTemplates(true); + CppRefactoringFilePtr file = interface.currentFile(); + Scope *scope = file->scopeAt(declarator->firstToken()); + QList<LookupItem> result = typeOfExpression(file->textOf(declarator->initializer).toUtf8(), + scope, TypeOfExpression::Preprocess); + if (!result.isEmpty() && result.first().type()->asPointerType()) + mode = ConvertFromAndToPointerOp::FromPointer; + } else if (declarator->ptr_operator_list) { + for (PtrOperatorListAST *ops = declarator->ptr_operator_list; ops; ops = ops->next) { + if (ops != declarator->ptr_operator_list) { + // Bail out on more complex pointer types (e.g. pointer of pointer, + // or reference of pointer). + return; + } + if (ops->value->asPointer()) + mode = ConvertFromAndToPointerOp::FromPointer; + else if (ops->value->asReference()) + mode = ConvertFromAndToPointerOp::FromReference; + } + } + + const int priority = path.size() - 1; + result << new ConvertFromAndToPointerOp(interface, priority, mode, isAutoDeclaration, + simpleDeclaration, declarator, identifier, symbol); + } +}; + +#ifdef WITH_TESTS +using namespace Tests; + +class ConvertFromAndToPointerTest : public QObject +{ + Q_OBJECT + +private slots: + void test_data() + { + QTest::addColumn<QByteArray>("original"); + QTest::addColumn<QByteArray>("expected"); + + QTest::newRow("ConvertFromPointer") + << QByteArray("void foo() {\n" + " QString *@str;\n" + " if (!str->isEmpty())\n" + " str->clear();\n" + " f1(*str);\n" + " f2(str);\n" + "}\n") + << QByteArray("void foo() {\n" + " QString str;\n" + " if (!str.isEmpty())\n" + " str.clear();\n" + " f1(str);\n" + " f2(&str);\n" + "}\n"); + + QTest::newRow("ConvertToPointer") + << QByteArray("void foo() {\n" + " QString @str;\n" + " if (!str.isEmpty())\n" + " str.clear();\n" + " f1(str);\n" + " f2(&str);\n" + "}\n") + << QByteArray("void foo() {\n" + " QString *str = new QString;\n" + " if (!str->isEmpty())\n" + " str->clear();\n" + " f1(*str);\n" + " f2(str);\n" + "}\n"); + + QTest::newRow("ConvertReferenceToPointer") + << QByteArray("void foo() {\n" + " QString narf;" + " QString &@str = narf;\n" + " if (!str.isEmpty())\n" + " str.clear();\n" + " f1(str);\n" + " f2(&str);\n" + "}\n") + << QByteArray("void foo() {\n" + " QString narf;" + " QString *str = &narf;\n" + " if (!str->isEmpty())\n" + " str->clear();\n" + " f1(*str);\n" + " f2(str);\n" + "}\n"); + + QTest::newRow("ConvertFromPointer_withInitializer") + << QByteArray("void foo() {\n" + " QString *@str = new QString(QLatin1String(\"schnurz\"));\n" + " if (!str->isEmpty())\n" + " str->clear();\n" + "}\n") + << QByteArray("void foo() {\n" + " QString str(QLatin1String(\"schnurz\"));\n" + " if (!str.isEmpty())\n" + " str.clear();\n" + "}\n"); + + QTest::newRow("ConvertFromPointer_withBareInitializer") + << QByteArray("void foo() {\n" + " QString *@str = new QString;\n" + " if (!str->isEmpty())\n" + " str->clear();\n" + "}\n") + << QByteArray("void foo() {\n" + " QString str;\n" + " if (!str.isEmpty())\n" + " str.clear();\n" + "}\n"); + + QTest::newRow("ConvertFromPointer_withEmptyInitializer") + << QByteArray("void foo() {\n" + " QString *@str = new QString();\n" + " if (!str->isEmpty())\n" + " str->clear();\n" + "}\n") + << QByteArray("void foo() {\n" + " QString str;\n" + " if (!str.isEmpty())\n" + " str.clear();\n" + "}\n"); + + QTest::newRow("ConvertFromPointer_structWithPointer") + << QByteArray("struct Bar{ QString *str; };\n" + "void foo() {\n" + " Bar *@bar = new Bar;\n" + " bar->str = new QString;\n" + " delete bar->str;\n" + " delete bar;\n" + "}\n") + << QByteArray("struct Bar{ QString *str; };\n" + "void foo() {\n" + " Bar bar;\n" + " bar.str = new QString;\n" + " delete bar.str;\n" + " // delete bar;\n" + "}\n"); + + QTest::newRow("ConvertToPointer_withInitializer") + << QByteArray("void foo() {\n" + " QString @str = QLatin1String(\"narf\");\n" + " if (!str.isEmpty())\n" + " str.clear();\n" + "}\n") + << QByteArray("void foo() {\n" + " QString *str = new QString(QLatin1String(\"narf\"));\n" + " if (!str->isEmpty())\n" + " str->clear();\n" + "}\n"); + + QTest::newRow("ConvertToPointer_withParenInitializer") + << QByteArray("void foo() {\n" + " QString @str(QLatin1String(\"narf\"));\n" + " if (!str.isEmpty())\n" + " str.clear();\n" + "}\n") + << QByteArray("void foo() {\n" + " QString *str = new QString(QLatin1String(\"narf\"));\n" + " if (!str->isEmpty())\n" + " str->clear();\n" + "}\n"); + + QTest::newRow("ConvertToPointer_noTriggerRValueRefs") + << QByteArray("void foo(Narf &&@narf) {}\n") + << QByteArray(); + + QTest::newRow("ConvertToPointer_noTriggerGlobal") + << QByteArray("int @global;\n") + << QByteArray(); + + QTest::newRow("ConvertToPointer_noTriggerClassMember") + << QByteArray("struct C { int @member; };\n") + << QByteArray(); + + QTest::newRow("ConvertToPointer_noTriggerClassMember2") + << QByteArray("void f() { struct C { int @member; }; }\n") + << QByteArray(); + + QTest::newRow("ConvertToPointer_functionOfFunctionLocalClass") + << QByteArray("void f() {\n" + " struct C {\n" + " void g() { int @member; }\n" + " };\n" + "}\n") + << QByteArray("void f() {\n" + " struct C {\n" + " void g() { int *member; }\n" + " };\n" + "}\n"); + + QTest::newRow("ConvertToPointer_redeclaredVariable_block") + << QByteArray("void foo() {\n" + " QString @str;\n" + " str.clear();\n" + " {\n" + " QString str;\n" + " str.clear();\n" + " }\n" + " f1(str);\n" + "}\n") + << QByteArray("void foo() {\n" + " QString *str = new QString;\n" + " str->clear();\n" + " {\n" + " QString str;\n" + " str.clear();\n" + " }\n" + " f1(*str);\n" + "}\n"); + + QTest::newRow("ConvertAutoFromPointer") + << QByteArray("void foo() {\n" + " auto @str = new QString(QLatin1String(\"foo\"));\n" + " if (!str->isEmpty())\n" + " str->clear();\n" + " f1(*str);\n" + " f2(str);\n" + "}\n") + << QByteArray("void foo() {\n" + " auto str = QString(QLatin1String(\"foo\"));\n" + " if (!str.isEmpty())\n" + " str.clear();\n" + " f1(str);\n" + " f2(&str);\n" + "}\n"); + + QTest::newRow("ConvertAutoFromPointer2") + << QByteArray("void foo() {\n" + " auto *@str = new QString;\n" + " if (!str->isEmpty())\n" + " str->clear();\n" + " f1(*str);\n" + " f2(str);\n" + "}\n") + << QByteArray("void foo() {\n" + " auto str = QString();\n" + " if (!str.isEmpty())\n" + " str.clear();\n" + " f1(str);\n" + " f2(&str);\n" + "}\n"); + + QTest::newRow("ConvertAutoToPointer") + << QByteArray("void foo() {\n" + " auto @str = QString(QLatin1String(\"foo\"));\n" + " if (!str.isEmpty())\n" + " str.clear();\n" + " f1(str);\n" + " f2(&str);\n" + "}\n") + << QByteArray("void foo() {\n" + " auto @str = new QString(QLatin1String(\"foo\"));\n" + " if (!str->isEmpty())\n" + " str->clear();\n" + " f1(*str);\n" + " f2(str);\n" + "}\n"); + + QTest::newRow("ConvertToPointerWithMacro") + << QByteArray("#define BAR bar\n" + "void func()\n" + "{\n" + " int @foo = 42;\n" + " int bar;\n" + " BAR = foo;\n" + "}\n") + << QByteArray("#define BAR bar\n" + "void func()\n" + "{\n" + " int *foo = 42;\n" + " int bar;\n" + " BAR = *foo;\n" + "}\n"); + + QString testObjAndFunc = "struct Object\n" + "{\n" + " Object(%1){}\n" + "};\n" + "void func()\n" + "{\n" + " %2\n" + "}\n"; + + QTest::newRow("ConvertToStack1_QTCREATORBUG23181") + << QByteArray(testObjAndFunc.arg("int").arg("Object *@obj = new Object(0);").toUtf8()) + << QByteArray(testObjAndFunc.arg("int").arg("Object obj(0);").toUtf8()); + + QTest::newRow("ConvertToStack2_QTCREATORBUG23181") + << QByteArray(testObjAndFunc.arg("int").arg("Object *@obj = new Object{0};").toUtf8()) + << QByteArray(testObjAndFunc.arg("int").arg("Object obj{0};").toUtf8()); + + QTest::newRow("ConvertToPointer1_QTCREATORBUG23181") + << QByteArray(testObjAndFunc.arg("").arg("Object @obj;").toUtf8()) + << QByteArray(testObjAndFunc.arg("").arg("Object *obj = new Object;").toUtf8()); + + QTest::newRow("ConvertToPointer2_QTCREATORBUG23181") + << QByteArray(testObjAndFunc.arg("").arg("Object @obj();").toUtf8()) + << QByteArray(testObjAndFunc.arg("").arg("Object *obj = new Object();").toUtf8()); + + QTest::newRow("ConvertToPointer3_QTCREATORBUG23181") + << QByteArray(testObjAndFunc.arg("").arg("Object @obj{};").toUtf8()) + << QByteArray(testObjAndFunc.arg("").arg("Object *obj = new Object{};").toUtf8()); + + QTest::newRow("ConvertToPointer4_QTCREATORBUG23181") + << QByteArray(testObjAndFunc.arg("int").arg("Object @obj(0);").toUtf8()) + << QByteArray(testObjAndFunc.arg("int").arg("Object *obj = new Object(0);").toUtf8()); + + + } + + void test() + { + QFETCH(QByteArray, original); + QFETCH(QByteArray, expected); + ConvertFromAndToPointer factory; + QuickFixOperationTest(singleDocument(original, expected), &factory); + } +}; + +QObject *ConvertFromAndToPointer::createTest() { return new ConvertFromAndToPointerTest; } + +#endif // WITH_TESTS +} // namespace + +void registerConvertFromAndToPointerQuickfix() +{ + CppQuickFixFactory::registerFactory<ConvertFromAndToPointer>(); +} + +} // namespace CppEditor::Internal + +#ifdef WITH_TESTS +#include <convertfromandtopointer.moc> +#endif diff --git a/src/plugins/cppeditor/quickfixes/convertfromandtopointer.h b/src/plugins/cppeditor/quickfixes/convertfromandtopointer.h new file mode 100644 index 0000000000..0deb6c0da9 --- /dev/null +++ b/src/plugins/cppeditor/quickfixes/convertfromandtopointer.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 registerConvertFromAndToPointerQuickfix(); +} // namespace CppEditor::Internal diff --git a/src/plugins/cppeditor/quickfixes/convertnumericliteral.cpp b/src/plugins/cppeditor/quickfixes/convertnumericliteral.cpp new file mode 100644 index 0000000000..5f62291e05 --- /dev/null +++ b/src/plugins/cppeditor/quickfixes/convertnumericliteral.cpp @@ -0,0 +1,205 @@ +// 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 "convertnumericliteral.h" + +#include "../cppeditortr.h" +#include "../cpprefactoringchanges.h" +#include "cppquickfix.h" + +#include <bitset> + +using namespace CPlusPlus; +using namespace Utils; + +namespace CppEditor::Internal { +namespace { + +class ConvertNumericLiteralOp: public CppQuickFixOperation +{ +public: + ConvertNumericLiteralOp(const CppQuickFixInterface &interface, int start, int end, + const QString &replacement) + : CppQuickFixOperation(interface) + , start(start) + , end(end) + , replacement(replacement) + {} + + void perform() override + { + CppRefactoringChanges refactoring(snapshot()); + CppRefactoringFilePtr currentFile = refactoring.cppFile(filePath()); + + ChangeSet changes; + changes.replace(start, end, replacement); + currentFile->setChangeSet(changes); + currentFile->apply(); + } + +private: + int start, end; + QString replacement; +}; + +/*! + Base class for converting numeric literals between decimal, octal and hex. + Does the base check for the specific ones and parses the number. + + Test cases: + 0xFA0Bu; + 0X856A; + 298.3; + 199; + 074; + 199L; + 074L; + -199; + -017; + 0783; // invalid octal + 0; // border case, allow only hex<->decimal + + Activates on: numeric literals +*/ +class ConvertNumericLiteral : public CppQuickFixFactory +{ +#ifdef WITH_TESTS +public: + static QObject *createTest() { return new QObject; } +#endif + +private: + void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override + { + const QList<AST *> &path = interface.path(); + CppRefactoringFilePtr file = interface.currentFile(); + + if (path.isEmpty()) + return; + + NumericLiteralAST *literal = path.last()->asNumericLiteral(); + + if (!literal) + return; + + Token token = file->tokenAt(literal->asNumericLiteral()->literal_token); + if (!token.is(T_NUMERIC_LITERAL)) + return; + const NumericLiteral *numeric = token.number; + if (numeric->isDouble() || numeric->isFloat()) + return; + + // remove trailing L or U and stuff + const char * const spell = numeric->chars(); + int numberLength = numeric->size(); + while (numberLength > 0 && !std::isxdigit(spell[numberLength - 1])) + --numberLength; + if (numberLength < 1) + return; + + // convert to number + bool valid; + ulong value = 0; + const QString x = QString::fromUtf8(spell).left(numberLength); + if (x.startsWith("0b", Qt::CaseInsensitive)) + value = x.mid(2).toULong(&valid, 2); + else + value = x.toULong(&valid, 0); + + if (!valid) + return; + + const int priority = path.size() - 1; // very high priority + const int start = file->startOf(literal); + const char * const str = numeric->chars(); + + const bool isBinary = numberLength > 2 && str[0] == '0' && (str[1] == 'b' || str[1] == 'B'); + const bool isOctal = numberLength >= 2 && str[0] == '0' && str[1] >= '0' && str[1] <= '7'; + const bool isDecimal = !(isBinary || isOctal || numeric->isHex()); + + if (!numeric->isHex()) { + /* + Convert integer literal to hex representation. + Replace + 0b100000 + 32 + 040 + With + 0x20 + + */ + const QString replacement = QString::asprintf("0x%lX", value); + auto op = new ConvertNumericLiteralOp(interface, start, start + numberLength, replacement); + op->setDescription(Tr::tr("Convert to Hexadecimal")); + op->setPriority(priority); + result << op; + } + + if (!isOctal) { + /* + Convert integer literal to octal representation. + Replace + 0b100000 + 32 + 0x20 + With + 040 + */ + const QString replacement = QString::asprintf("0%lo", value); + auto op = new ConvertNumericLiteralOp(interface, start, start + numberLength, replacement); + op->setDescription(Tr::tr("Convert to Octal")); + op->setPriority(priority); + result << op; + } + + if (!isDecimal) { + /* + Convert integer literal to decimal representation. + Replace + 0b100000 + 0x20 + 040 + With + 32 + */ + const QString replacement = QString::asprintf("%lu", value); + auto op = new ConvertNumericLiteralOp(interface, start, start + numberLength, replacement); + op->setDescription(Tr::tr("Convert to Decimal")); + op->setPriority(priority); + result << op; + } + + if (!isBinary) { + /* + Convert integer literal to binary representation. + Replace + 32 + 0x20 + 040 + With + 0b100000 + */ + QString replacement = "0b"; + if (value == 0) { + replacement.append('0'); + } else { + std::bitset<std::numeric_limits<decltype (value)>::digits> b(value); + QRegularExpression re("^[0]*"); + replacement.append(QString::fromStdString(b.to_string()).remove(re)); + } + auto op = new ConvertNumericLiteralOp(interface, start, start + numberLength, replacement); + op->setDescription(Tr::tr("Convert to Binary")); + op->setPriority(priority); + result << op; + } + } +}; + +} // namespace + +void registerConvertNumericLiteralQuickfix() +{ + CppQuickFixFactory::registerFactory<ConvertNumericLiteral>(); +} + +} // namespace CppEditor::Internal diff --git a/src/plugins/cppeditor/quickfixes/convertnumericliteral.h b/src/plugins/cppeditor/quickfixes/convertnumericliteral.h new file mode 100644 index 0000000000..4158807c68 --- /dev/null +++ b/src/plugins/cppeditor/quickfixes/convertnumericliteral.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 registerConvertNumericLiteralQuickfix(); +} // namespace CppEditor::Internal diff --git a/src/plugins/cppeditor/quickfixes/convertqt4connect.cpp b/src/plugins/cppeditor/quickfixes/convertqt4connect.cpp new file mode 100644 index 0000000000..a003ab6a5c --- /dev/null +++ b/src/plugins/cppeditor/quickfixes/convertqt4connect.cpp @@ -0,0 +1,508 @@ +// 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 "convertqt4connect.h" + +#include "../cppcodestylesettings.h" +#include "../cppeditortr.h" +#include "../cpprefactoringchanges.h" +#include "cppquickfix.h" + +#include <cplusplus/Overview.h> +#include <cplusplus/TypeOfExpression.h> + +#ifdef WITH_TESTS +#include "cppquickfix_test.h" +#include <QtTest> +#endif + +using namespace CPlusPlus; +using namespace TextEditor; +using namespace Utils; + +namespace CppEditor::Internal { +namespace { + +class ConvertQt4ConnectOperation: public CppQuickFixOperation +{ +public: + ConvertQt4ConnectOperation(const CppQuickFixInterface &interface, const ChangeSet &changes) + : CppQuickFixOperation(interface, 1), m_changes(changes) + { + setDescription(Tr::tr("Convert connect() to Qt 5 Style")); + } + +private: + void perform() override + { + CppRefactoringChanges refactoring(snapshot()); + CppRefactoringFilePtr currentFile = refactoring.cppFile(filePath()); + currentFile->setChangeSet(m_changes); + currentFile->apply(); + } + + const ChangeSet m_changes; +}; + +static Symbol *skipForwardDeclarations(const QList<Symbol *> &symbols) +{ + for (Symbol *symbol : symbols) { + if (!symbol->type()->asForwardClassDeclarationType()) + return symbol; + } + + return nullptr; +} + +static bool findRawAccessFunction(Class *klass, PointerType *pointerType, QString *objAccessFunction) +{ + QList<Function *> candidates; + for (auto it = klass->memberBegin(), end = klass->memberEnd(); it != end; ++it) { + if (Function *func = (*it)->asFunction()) { + const Name *funcName = func->name(); + if (!funcName->asOperatorNameId() + && !funcName->asConversionNameId() + && func->returnType().type() == pointerType + && func->isConst() + && func->argumentCount() == 0) { + candidates << func; + } + } + } + const Name *funcName = nullptr; + switch (candidates.size()) { + case 0: + return false; + case 1: + funcName = candidates.first()->name(); + break; + default: + // Multiple candidates - prefer a function named data + for (Function *func : std::as_const(candidates)) { + if (!strcmp(func->name()->identifier()->chars(), "data")) { + funcName = func->name(); + break; + } + } + if (!funcName) + funcName = candidates.first()->name(); + } + const Overview oo = CppCodeStyleSettings::currentProjectCodeStyleOverview(); + *objAccessFunction = QLatin1Char('.') + oo.prettyName(funcName) + QLatin1String("()"); + return true; +} + +static PointerType *determineConvertedType( + NamedType *namedType, const LookupContext &context, Scope *scope, QString *objAccessFunction) +{ + if (!namedType) + return nullptr; + if (ClassOrNamespace *binding = context.lookupType(namedType->name(), scope)) { + if (Symbol *objectClassSymbol = skipForwardDeclarations(binding->symbols())) { + if (Class *klass = objectClassSymbol->asClass()) { + for (auto it = klass->memberBegin(), end = klass->memberEnd(); it != end; ++it) { + if (Function *func = (*it)->asFunction()) { + if (const ConversionNameId *conversionName = + func->name()->asConversionNameId()) { + if (PointerType *type = conversionName->type()->asPointerType()) { + if (findRawAccessFunction(klass, type, objAccessFunction)) + return type; + } + } + } + } + } + } + } + + return nullptr; +} + +static Class *senderOrReceiverClass( + const CppQuickFixInterface &interface, + const CppRefactoringFilePtr &file, + const ExpressionAST *objectPointerAST, + Scope *objectPointerScope, + QString *objAccessFunction) +{ + const LookupContext &context = interface.context(); + + QByteArray objectPointerExpression; + if (objectPointerAST) + objectPointerExpression = file->textOf(objectPointerAST).toUtf8(); + else + objectPointerExpression = "this"; + + TypeOfExpression toe; + toe.setExpandTemplates(true); + toe.init(interface.semanticInfo().doc, interface.snapshot(), context.bindings()); + const QList<LookupItem> objectPointerExpressions = toe(objectPointerExpression, + objectPointerScope, TypeOfExpression::Preprocess); + QTC_ASSERT(!objectPointerExpressions.isEmpty(), return nullptr); + + Type *objectPointerTypeBase = objectPointerExpressions.first().type().type(); + QTC_ASSERT(objectPointerTypeBase, return nullptr); + + PointerType *objectPointerType = objectPointerTypeBase->asPointerType(); + if (!objectPointerType) { + objectPointerType = determineConvertedType(objectPointerTypeBase->asNamedType(), context, + objectPointerScope, objAccessFunction); + } + QTC_ASSERT(objectPointerType, return nullptr); + + Type *objectTypeBase = objectPointerType->elementType().type(); // Dereference + QTC_ASSERT(objectTypeBase, return nullptr); + + NamedType *objectType = objectTypeBase->asNamedType(); + QTC_ASSERT(objectType, return nullptr); + + ClassOrNamespace *objectClassCON = context.lookupType(objectType->name(), objectPointerScope); + if (!objectClassCON) { + objectClassCON = objectPointerExpressions.first().binding(); + QTC_ASSERT(objectClassCON, return nullptr); + } + QTC_ASSERT(!objectClassCON->symbols().isEmpty(), return nullptr); + + Symbol *objectClassSymbol = skipForwardDeclarations(objectClassCON->symbols()); + QTC_ASSERT(objectClassSymbol, return nullptr); + + return objectClassSymbol->asClass(); +} + +static bool findConnectReplacement( + const CppQuickFixInterface &interface, + const ExpressionAST *objectPointerAST, + const QtMethodAST *methodAST, + const CppRefactoringFilePtr &file, + QString *replacement, + QString *objAccessFunction) +{ + // Get name of method + if (!methodAST->declarator || !methodAST->declarator->core_declarator) + return false; + + DeclaratorIdAST *methodDeclIdAST = methodAST->declarator->core_declarator->asDeclaratorId(); + if (!methodDeclIdAST) + return false; + + NameAST *methodNameAST = methodDeclIdAST->name; + if (!methodNameAST) + return false; + + // Lookup object pointer type + Scope *scope = file->scopeAt(methodAST->firstToken()); + Class *objectClass = senderOrReceiverClass(interface, file, objectPointerAST, scope, + objAccessFunction); + QTC_ASSERT(objectClass, return false); + + // Look up member function in call, including base class members. + const LookupContext &context = interface.context(); + const QList<LookupItem> methodResults = context.lookup(methodNameAST->name, objectClass); + if (methodResults.isEmpty()) + return false; // Maybe mis-spelled signal/slot name + + Scope *baseClassScope = methodResults.at(0).scope(); // FIXME: Handle overloads + QTC_ASSERT(baseClassScope, return false); + + Class *classOfMethod = baseClassScope->asClass(); // Declaration point of signal/slot + QTC_ASSERT(classOfMethod, return false); + + Symbol *method = methodResults.at(0).declaration(); + QTC_ASSERT(method, return false); + + // Minimize qualification + Control *control = context.bindings()->control().get(); + ClassOrNamespace *functionCON = context.lookupParent(scope); + const Name *shortName = LookupContext::minimalName(method, functionCON, control); + if (!shortName->asQualifiedNameId()) + shortName = control->qualifiedNameId(classOfMethod->name(), shortName); + + const Overview oo = CppCodeStyleSettings::currentProjectCodeStyleOverview(); + *replacement = QLatin1Char('&') + oo.prettyName(shortName); + return true; +} + +static bool onConnectOrDisconnectCall(AST *ast, const ExpressionListAST **arguments) +{ + if (!ast) + return false; + + CallAST *call = ast->asCall(); + if (!call) + return false; + + if (!call->base_expression) + return false; + + const IdExpressionAST *idExpr = call->base_expression->asIdExpression(); + if (!idExpr || !idExpr->name || !idExpr->name->name) + return false; + + const ExpressionListAST *args = call->expression_list; + if (!arguments) + return false; + + const Identifier *id = idExpr->name->name->identifier(); + if (!id) + return false; + + const QByteArray name(id->chars(), id->size()); + if (name != "connect" && name != "disconnect") + return false; + + if (arguments) + *arguments = args; + return true; +} + +// Might modify arg* output arguments even if false is returned. +static bool collectConnectArguments( + const ExpressionListAST *arguments, + const ExpressionAST **arg1, + const QtMethodAST **arg2, + const ExpressionAST **arg3, + const QtMethodAST **arg4) +{ + if (!arguments || !arg1 || !arg2 || !arg3 || !arg4) + return false; + + *arg1 = arguments->value; + arguments = arguments->next; + if (!arg1 || !arguments) + return false; + + *arg2 = arguments->value->asQtMethod(); + arguments = arguments->next; + if (!*arg2 || !arguments) + return false; + + *arg3 = arguments->value; + if (!*arg3) + return false; + + // Take care of three-arg version, with 'this' receiver. + if (QtMethodAST *receiverMethod = arguments->value->asQtMethod()) { + *arg3 = nullptr; // Means 'this' + *arg4 = receiverMethod; + return true; + } + + arguments = arguments->next; + if (!arguments) + return false; + + *arg4 = arguments->value->asQtMethod(); + if (!*arg4) + return false; + + return true; +} + +//! Converts a Qt 4 QObject::connect() to Qt 5 style. +class ConvertQt4Connect : public CppQuickFixFactory +{ +public: +#ifdef WITH_TESTS + static QObject *createTest(); +#endif + +private: + void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override + { + const QList<AST *> &path = interface.path(); + + for (int i = path.size(); --i >= 0; ) { + const ExpressionListAST *arguments; + if (!onConnectOrDisconnectCall(path.at(i), &arguments)) + continue; + + const ExpressionAST *arg1, *arg3; + const QtMethodAST *arg2, *arg4; + if (!collectConnectArguments(arguments, &arg1, &arg2, &arg3, &arg4)) + continue; + + const CppRefactoringFilePtr file = interface.currentFile(); + + QString newSignal; + QString senderAccessFunc; + if (!findConnectReplacement(interface, arg1, arg2, file, &newSignal, &senderAccessFunc)) + continue; + + QString newMethod; + QString receiverAccessFunc; + if (!findConnectReplacement(interface, arg3, arg4, file, &newMethod, &receiverAccessFunc)) + continue; + + ChangeSet changes; + changes.replace(file->endOf(arg1), file->endOf(arg1), senderAccessFunc); + changes.replace(file->startOf(arg2), file->endOf(arg2), newSignal); + if (!arg3) + newMethod.prepend(QLatin1String("this, ")); + else + changes.replace(file->endOf(arg3), file->endOf(arg3), receiverAccessFunc); + changes.replace(file->startOf(arg4), file->endOf(arg4), newMethod); + + result << new ConvertQt4ConnectOperation(interface, changes); + return; + } + } +}; + +#ifdef WITH_TESTS +using namespace Tests; +class ConvertQt4ConnectTest : public QObject +{ + Q_OBJECT + +private slots: + void testOutOfClass() + { + QByteArray prefix = + "class QObject {};\n" + "class TestClass : public QObject\n" + "{\n" + "public:\n" + " void setProp(int) {}\n" + " void sigFoo(int) {}\n" + "};\n" + "\n" + "int foo()\n" + "{\n"; + + QByteArray suffix = "\n}\n"; + + QByteArray original = prefix + + " TestClass obj;\n" + " conne@ct(&obj, SIGNAL(sigFoo(int)), &obj, SLOT(setProp(int)));" + + suffix; + + QByteArray expected = prefix + + " TestClass obj;\n" + " connect(&obj, &TestClass::sigFoo, &obj, &TestClass::setProp);" + + suffix; + + QList<TestDocumentPtr> testDocuments; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + ConvertQt4Connect factory; + QuickFixOperationTest(testDocuments, &factory); + } + + void testWithinClass_data() + { + QTest::addColumn<QByteArray>("original"); + QTest::addColumn<QByteArray>("expected"); + + QTest::newRow("four-args-connect") + << QByteArray("conne@ct(this, SIGNAL(sigFoo(int)), this, SLOT(setProp(int)));") + << QByteArray("connect(this, &TestClass::sigFoo, this, &TestClass::setProp);"); + + QTest::newRow("four-args-disconnect") + << QByteArray("disconne@ct(this, SIGNAL(sigFoo(int)), this, SLOT(setProp(int)));") + << QByteArray("disconnect(this, &TestClass::sigFoo, this, &TestClass::setProp);"); + + QTest::newRow("three-args-connect") + << QByteArray("conne@ct(this, SIGNAL(sigFoo(int)), SLOT(setProp(int)));") + << QByteArray("connect(this, &TestClass::sigFoo, this, &TestClass::setProp);"); + + QTest::newRow("template-value") + << QByteArray("Pointer<TestClass> p;\n" + "conne@ct(p.t, SIGNAL(sigFoo(int)), p.t, SLOT(setProp(int)));") + << QByteArray("Pointer<TestClass> p;\n" + "connect(p.t, &TestClass::sigFoo, p.t, &TestClass::setProp);"); + + QTest::newRow("implicit-pointer") + << QByteArray("Pointer<TestClass> p;\n" + "conne@ct(p, SIGNAL(sigFoo(int)), p, SLOT(setProp(int)));") + << QByteArray("Pointer<TestClass> p;\n" + "connect(p.data(), &TestClass::sigFoo, p.data(), &TestClass::setProp);"); + } + + void testWithinClass() + { + QFETCH(QByteArray, original); + QFETCH(QByteArray, expected); + + QByteArray prefix = + "template<class T>\n" + "struct Pointer\n" + "{\n" + " T *t;\n" + " operator T*() const { return t; }\n" + " T *data() const { return t; }\n" + "};\n" + "class QObject {};\n" + "class TestClass : public QObject\n" + "{\n" + "public:\n" + " void setProp(int) {}\n" + " void sigFoo(int) {}\n" + " void setupSignals();\n" + "};\n" + "\n" + "int TestClass::setupSignals()\n" + "{\n"; + + QByteArray suffix = "\n}\n"; + + QList<TestDocumentPtr> testDocuments; + testDocuments << CppTestDocument::create("file.cpp", + prefix + original + suffix, + prefix + expected + suffix); + + ConvertQt4Connect factory; + QuickFixOperationTest(testDocuments, &factory); + } + + void testDifferentNamespace() + { + const QByteArray prefix = + "namespace NsA {\n" + "class ClassA : public QObject\n" + "{\n" + " static ClassA *instance();\n" + "signals:\n" + " void sig();\n" + "};\n" + "}\n" + "\n" + "namespace NsB {\n" + "class ClassB : public QObject\n" + "{\n" + " void slot();\n" + " void connector() {\n"; + + const QByteArray suffix = " }\n};\n}"; + + const QByteArray original = "co@nnect(NsA::ClassA::instance(), SIGNAL(sig()),\n" + " this, SLOT(slot()));\n"; + const QByteArray expected = "connect(NsA::ClassA::instance(), &NsA::ClassA::sig,\n" + " this, &ClassB::slot);\n"; + QList<TestDocumentPtr> testDocuments; + testDocuments << CppTestDocument::create("file.cpp", + prefix + original + suffix, + prefix + expected + suffix); + + ConvertQt4Connect factory; + QuickFixOperationTest(testDocuments, &factory); + } +}; + +QObject *ConvertQt4Connect::createTest() +{ + return new ConvertQt4ConnectTest; +} +#endif // WITH_TESTS + +} // namespace + +void registerConvertQt4ConnectQuickfix() +{ + CppQuickFixFactory::registerFactory<ConvertQt4Connect>(); +} + +} // namespace CppEditor::Internal + +#ifdef WITH_TESTS +#include <convertqt4connect.moc> +#endif diff --git a/src/plugins/cppeditor/quickfixes/convertqt4connect.h b/src/plugins/cppeditor/quickfixes/convertqt4connect.h new file mode 100644 index 0000000000..e96bd8f106 --- /dev/null +++ b/src/plugins/cppeditor/quickfixes/convertqt4connect.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 registerConvertQt4ConnectQuickfix(); +} // namespace CppEditor::Internal diff --git a/src/plugins/cppeditor/quickfixes/convertstringliteral.cpp b/src/plugins/cppeditor/quickfixes/convertstringliteral.cpp new file mode 100644 index 0000000000..c65ff5107b --- /dev/null +++ b/src/plugins/cppeditor/quickfixes/convertstringliteral.cpp @@ -0,0 +1,758 @@ +// 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 "convertstringliteral.h" + +#include "../cppeditordocument.h" +#include "../cppeditortr.h" +#include "../cppeditorwidget.h" +#include "../cpprefactoringchanges.h" +#include "cppquickfix.h" + +#include <QTextDecoder> + +#ifdef WITH_TESTS +#include "cppquickfix_test.h" +#include <QtTest> +#endif + +using namespace CPlusPlus; +using namespace Utils; + +namespace CppEditor::Internal { +namespace { + +enum StringLiteralType { TypeString, TypeObjCString, TypeChar, TypeNone }; + +enum ActionFlags { + EncloseInQLatin1CharAction = 0x1, + EncloseInQLatin1StringAction = 0x2, + EncloseInQStringLiteralAction = 0x4, + EncloseInQByteArrayLiteralAction = 0x8, + EncloseActionMask = EncloseInQLatin1CharAction | EncloseInQLatin1StringAction + | EncloseInQStringLiteralAction | EncloseInQByteArrayLiteralAction, + TranslateTrAction = 0x10, + TranslateQCoreApplicationAction = 0x20, + TranslateNoopAction = 0x40, + TranslationMask = TranslateTrAction | TranslateQCoreApplicationAction | TranslateNoopAction, + RemoveObjectiveCAction = 0x100, + ConvertEscapeSequencesToCharAction = 0x200, + ConvertEscapeSequencesToStringAction = 0x400, + SingleQuoteAction = 0x800, + DoubleQuoteAction = 0x1000 +}; + +static bool isQtStringLiteral(const QByteArray &id) +{ + return id == "QLatin1String" || id == "QLatin1Literal" || id == "QStringLiteral" + || id == "QByteArrayLiteral"; +} + +static bool isQtStringTranslation(const QByteArray &id) +{ + return id == "tr" || id == "trUtf8" || id == "translate" || id == "QT_TRANSLATE_NOOP"; +} + +/* Convert single-character string literals into character literals with some + * special cases "a" --> 'a', "'" --> '\'', "\n" --> '\n', "\"" --> '"'. */ +static QByteArray stringToCharEscapeSequences(const QByteArray &content) +{ + if (content.size() == 1) + return content.at(0) == '\'' ? QByteArray("\\'") : content; + if (content.size() == 2 && content.at(0) == '\\') + return content == "\\\"" ? QByteArray(1, '"') : content; + return QByteArray(); +} + +/* Convert character literal into a string literal with some special cases + * 'a' -> "a", '\n' -> "\n", '\'' --> "'", '"' --> "\"". */ +static QByteArray charToStringEscapeSequences(const QByteArray &content) +{ + if (content.size() == 1) + return content.at(0) == '"' ? QByteArray("\\\"") : content; + if (content.size() == 2) + return content == "\\'" ? QByteArray("'") : content; + return QByteArray(); +} + +static QString msgQtStringLiteralDescription(const QString &replacement) +{ + return Tr::tr("Enclose in %1(...)").arg(replacement); +} + +static QString stringLiteralReplacement(unsigned actions) +{ + if (actions & EncloseInQLatin1CharAction) + return QLatin1String("QLatin1Char"); + if (actions & EncloseInQLatin1StringAction) + return QLatin1String("QLatin1String"); + if (actions & EncloseInQStringLiteralAction) + return QLatin1String("QStringLiteral"); + if (actions & EncloseInQByteArrayLiteralAction) + return QLatin1String("QByteArrayLiteral"); + if (actions & TranslateTrAction) + return QLatin1String("tr"); + if (actions & TranslateQCoreApplicationAction) + return QLatin1String("QCoreApplication::translate"); + if (actions & TranslateNoopAction) + return QLatin1String("QT_TRANSLATE_NOOP"); + return QString(); +} + +static ExpressionAST *analyzeStringLiteral(const QList<AST *> &path, + const CppRefactoringFilePtr &file, StringLiteralType *type, + QByteArray *enclosingFunction = nullptr, + CallAST **enclosingFunctionCall = nullptr) +{ + *type = TypeNone; + if (enclosingFunction) + enclosingFunction->clear(); + if (enclosingFunctionCall) + *enclosingFunctionCall = nullptr; + + if (path.isEmpty()) + return nullptr; + + ExpressionAST *literal = path.last()->asExpression(); + if (literal) { + if (literal->asStringLiteral()) { + // Check for Objective C string (@"bla") + const QChar firstChar = file->charAt(file->startOf(literal)); + *type = firstChar == QLatin1Char('@') ? TypeObjCString : TypeString; + } else if (NumericLiteralAST *numericLiteral = literal->asNumericLiteral()) { + // character ('c') constants are numeric. + if (file->tokenAt(numericLiteral->literal_token).is(T_CHAR_LITERAL)) + *type = TypeChar; + } + } + + if (*type != TypeNone && enclosingFunction && path.size() > 1) { + if (CallAST *call = path.at(path.size() - 2)->asCall()) { + if (call->base_expression) { + if (IdExpressionAST *idExpr = call->base_expression->asIdExpression()) { + if (SimpleNameAST *functionName = idExpr->name->asSimpleName()) { + *enclosingFunction = file->tokenAt(functionName->identifier_token).identifier->chars(); + if (enclosingFunctionCall) + *enclosingFunctionCall = call; + } + } + } + } + } + return literal; +} + +class EscapeStringLiteralOperation: public CppQuickFixOperation +{ +public: + EscapeStringLiteralOperation(const CppQuickFixInterface &interface, + ExpressionAST *literal, bool escape) + : CppQuickFixOperation(interface) + , m_literal(literal) + , m_escape(escape) + { + if (m_escape) { + setDescription(Tr::tr("Escape String Literal as UTF-8")); + } else { + setDescription(Tr::tr("Unescape String Literal as UTF-8")); + } + } + +private: + static inline bool isDigit(quint8 ch, int base) + { + if (base == 8) + return ch >= '0' && ch < '8'; + if (base == 16) + return isxdigit(ch); + return false; + } + + static QByteArrayList escapeString(const QByteArray &contents) + { + QByteArrayList newContents; + QByteArray chunk; + bool wasEscaped = false; + for (const quint8 c : contents) { + const bool needsEscape = !isascii(c) || !isprint(c); + if (!needsEscape && wasEscaped && std::isxdigit(c) && !chunk.isEmpty()) { + newContents << chunk; + chunk.clear(); + } + if (needsEscape) + chunk += QByteArray("\\x") + QByteArray::number(c, 16).rightJustified(2, '0'); + else + chunk += c; + wasEscaped = needsEscape; + } + if (!chunk.isEmpty()) + newContents << chunk; + return newContents; + } + + static QByteArray unescapeString(const QByteArray &contents) + { + QByteArray newContents; + const int len = contents.length(); + for (int i = 0; i < len; ++i) { + quint8 c = contents.at(i); + if (c == '\\' && i < len - 1) { + int idx = i + 1; + quint8 ch = contents.at(idx); + int base = 0; + int maxlen = 0; + if (isDigit(ch, 8)) { + base = 8; + maxlen = 3; + } else if ((ch == 'x' || ch == 'X') && idx < len - 1) { + base = 16; + maxlen = 2; + ch = contents.at(++idx); + } + if (base > 0) { + QByteArray buf; + while (isDigit(ch, base) && idx < len && buf.length() < maxlen) { + buf += ch; + ++idx; + if (idx == len) + break; + ch = contents.at(idx); + } + if (!buf.isEmpty()) { + bool ok; + uint value = buf.toUInt(&ok, base); + // Don't unescape isascii() && !isprint() + if (ok && (!isascii(value) || isprint(value))) { + newContents += value; + i = idx - 1; + continue; + } + } + } + newContents += c; + c = contents.at(++i); + } + newContents += c; + } + return newContents; + } + + // QuickFixOperation interface +public: + void perform() override + { + CppRefactoringChanges refactoring(snapshot()); + CppRefactoringFilePtr currentFile = refactoring.cppFile(filePath()); + + const int startPos = currentFile->startOf(m_literal); + const int endPos = currentFile->endOf(m_literal); + + StringLiteralAST *stringLiteral = m_literal->asStringLiteral(); + QTC_ASSERT(stringLiteral, return); + const QByteArray oldContents(currentFile->tokenAt(stringLiteral->literal_token). + identifier->chars()); + QByteArrayList newContents; + if (m_escape) + newContents = escapeString(oldContents); + else + newContents = {unescapeString(oldContents)}; + + if (newContents.isEmpty() + || (newContents.size() == 1 && newContents.first() == oldContents)) { + return; + } + + QTextCodec *utf8codec = QTextCodec::codecForName("UTF-8"); + QScopedPointer<QTextDecoder> decoder(utf8codec->makeDecoder()); + ChangeSet changes; + + bool replace = true; + for (const QByteArray &chunk : std::as_const(newContents)) { + const QString str = decoder->toUnicode(chunk); + const QByteArray utf8buf = str.toUtf8(); + if (!utf8codec->canEncode(str) || chunk != utf8buf) + return; + if (replace) + changes.replace(startPos + 1, endPos - 1, str); + else + changes.insert(endPos, "\"" + str + "\""); + replace = false; + } + currentFile->setChangeSet(changes); + currentFile->apply(); + } + +private: + ExpressionAST *m_literal; + bool m_escape; +}; + +/// Operation performs the operations of type ActionFlags passed in as actions. +class WrapStringLiteralOp : public CppQuickFixOperation +{ +public: + WrapStringLiteralOp(const CppQuickFixInterface &interface, int priority, + unsigned actions, const QString &description, ExpressionAST *literal, + const QString &translationContext = QString()) + : CppQuickFixOperation(interface, priority), m_actions(actions), m_literal(literal), + m_translationContext(translationContext) + { + setDescription(description); + } + + void perform() override + { + CppRefactoringChanges refactoring(snapshot()); + CppRefactoringFilePtr currentFile = refactoring.cppFile(filePath()); + + ChangeSet changes; + + const int startPos = currentFile->startOf(m_literal); + const int endPos = currentFile->endOf(m_literal); + + // kill leading '@'. No need to adapt endPos, that is done by ChangeSet + if (m_actions & RemoveObjectiveCAction) + changes.remove(startPos, startPos + 1); + + // Fix quotes + if (m_actions & (SingleQuoteAction | DoubleQuoteAction)) { + const QString newQuote((m_actions & SingleQuoteAction) + ? QLatin1Char('\'') : QLatin1Char('"')); + changes.replace(startPos, startPos + 1, newQuote); + changes.replace(endPos - 1, endPos, newQuote); + } + + // Convert single character strings into character constants + if (m_actions & ConvertEscapeSequencesToCharAction) { + StringLiteralAST *stringLiteral = m_literal->asStringLiteral(); + QTC_ASSERT(stringLiteral, return ;); + const QByteArray oldContents(currentFile->tokenAt(stringLiteral->literal_token).identifier->chars()); + const QByteArray newContents = stringToCharEscapeSequences(oldContents); + QTC_ASSERT(!newContents.isEmpty(), return ;); + if (oldContents != newContents) + changes.replace(startPos + 1, endPos -1, QString::fromLatin1(newContents)); + } + + // Convert character constants into strings constants + if (m_actions & ConvertEscapeSequencesToStringAction) { + NumericLiteralAST *charLiteral = m_literal->asNumericLiteral(); // char 'c' constants are numerical. + QTC_ASSERT(charLiteral, return ;); + const QByteArray oldContents(currentFile->tokenAt(charLiteral->literal_token).identifier->chars()); + const QByteArray newContents = charToStringEscapeSequences(oldContents); + QTC_ASSERT(!newContents.isEmpty(), return ;); + if (oldContents != newContents) + changes.replace(startPos + 1, endPos -1, QString::fromLatin1(newContents)); + } + + // Enclose in literal or translation function, macro. + if (m_actions & (EncloseActionMask | TranslationMask)) { + changes.insert(endPos, QString(QLatin1Char(')'))); + QString leading = stringLiteralReplacement(m_actions); + leading += QLatin1Char('('); + if (m_actions + & (TranslateQCoreApplicationAction | TranslateNoopAction)) { + leading += QLatin1Char('"'); + leading += m_translationContext; + leading += QLatin1String("\", "); + } + changes.insert(startPos, leading); + } + + currentFile->setChangeSet(changes); + currentFile->apply(); + } + +private: + const unsigned m_actions; + ExpressionAST *m_literal; + const QString m_translationContext; +}; + +class ConvertCStringToNSStringOp: public CppQuickFixOperation +{ +public: + ConvertCStringToNSStringOp(const CppQuickFixInterface &interface, int priority, + StringLiteralAST *stringLiteral, CallAST *qlatin1Call) + : CppQuickFixOperation(interface, priority) + , stringLiteral(stringLiteral) + , qlatin1Call(qlatin1Call) + { + setDescription(Tr::tr("Convert to Objective-C String Literal")); + } + + void perform() override + { + CppRefactoringChanges refactoring(snapshot()); + CppRefactoringFilePtr currentFile = refactoring.cppFile(filePath()); + + ChangeSet changes; + + if (qlatin1Call) { + changes.replace(currentFile->startOf(qlatin1Call), currentFile->startOf(stringLiteral), + QLatin1String("@")); + changes.remove(currentFile->endOf(stringLiteral), currentFile->endOf(qlatin1Call)); + } else { + changes.insert(currentFile->startOf(stringLiteral), QLatin1String("@")); + } + + currentFile->setChangeSet(changes); + currentFile->apply(); + } + +private: + StringLiteralAST *stringLiteral; + CallAST *qlatin1Call; +}; + +/*! + Replace + "abcd" + QLatin1String("abcd") + QLatin1Literal("abcd") + + With + @"abcd" + + Activates on: the string literal, if the file type is a Objective-C(++) file. +*/ +class ConvertCStringToNSString: public CppQuickFixFactory +{ +#ifdef WITH_TESTS +public: + static QObject *createTest(); +#endif + +private: + void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override + { + CppRefactoringFilePtr file = interface.currentFile(); + + if (!interface.editor()->cppEditorDocument()->isObjCEnabled()) + return; + + StringLiteralType type = TypeNone; + QByteArray enclosingFunction; + CallAST *qlatin1Call; + const QList<AST *> &path = interface.path(); + ExpressionAST *literal = analyzeStringLiteral(path, file, &type, &enclosingFunction, + &qlatin1Call); + if (!literal || type != TypeString) + return; + if (!isQtStringLiteral(enclosingFunction)) + qlatin1Call = nullptr; + + result << new ConvertCStringToNSStringOp(interface, path.size() - 1, literal->asStringLiteral(), + qlatin1Call); + } +}; + +/*! + Replace + "abcd" + + With + tr("abcd") or + QCoreApplication::translate("CONTEXT", "abcd") or + QT_TRANSLATE_NOOP("GLOBAL", "abcd") + + depending on what is available. + + Activates on: the string literal +*/ +class TranslateStringLiteral: public CppQuickFixFactory +{ +#ifdef WITH_TESTS +public: + static QObject *createTest(); +#endif + +private: + void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override + { + // Initialize + StringLiteralType type = TypeNone; + QByteArray enclosingFunction; + const QList<AST *> &path = interface.path(); + CppRefactoringFilePtr file = interface.currentFile(); + ExpressionAST *literal = analyzeStringLiteral(path, file, &type, &enclosingFunction); + if (!literal || type != TypeString + || isQtStringLiteral(enclosingFunction) || isQtStringTranslation(enclosingFunction)) + return; + + QString trContext; + + std::shared_ptr<Control> control = interface.context().bindings()->control(); + const Name *trName = control->identifier("tr"); + + // Check whether we are in a function: + const QString description = Tr::tr("Mark as Translatable"); + for (int i = path.size() - 1; i >= 0; --i) { + if (FunctionDefinitionAST *definition = path.at(i)->asFunctionDefinition()) { + Function *function = definition->symbol; + ClassOrNamespace *b = interface.context().lookupType(function); + if (b) { + // Do we have a tr function? + const QList<LookupItem> items = b->find(trName); + for (const LookupItem &r : items) { + Symbol *s = r.declaration(); + if (s->type()->asFunctionType()) { + // no context required for tr + result << new WrapStringLiteralOp(interface, path.size() - 1, + TranslateTrAction, + description, literal); + return; + } + } + } + // We need to do a QCA::translate, so we need a context. + // Use fully qualified class name: + Overview oo; + const QList<const Name *> names = LookupContext::path(function); + for (const Name *n : names) { + if (!trContext.isEmpty()) + trContext.append(QLatin1String("::")); + trContext.append(oo.prettyName(n)); + } + // ... or global if none available! + if (trContext.isEmpty()) + trContext = QLatin1String("GLOBAL"); + result << new WrapStringLiteralOp(interface, path.size() - 1, + TranslateQCoreApplicationAction, + description, literal, trContext); + return; + } + } + + // We need to use Q_TRANSLATE_NOOP + result << new WrapStringLiteralOp(interface, path.size() - 1, + TranslateNoopAction, + description, literal, trContext); + } +}; + +/*! + Replace + "abcd" -> QLatin1String("abcd") + @"abcd" -> QLatin1String("abcd") (Objective C) + 'a' -> QLatin1Char('a') + 'a' -> "a" + "a" -> 'a' or QLatin1Char('a') (Single character string constants) + "\n" -> '\n', QLatin1Char('\n') + + Except if they are already enclosed in + QLatin1Char, QT_TRANSLATE_NOOP, tr, + trUtf8, QLatin1Literal, QLatin1String + + Activates on: the string or character literal +*/ + +class WrapStringLiteral: public CppQuickFixFactory +{ +#ifdef WITH_TESTS +public: + static QObject *createTest(); +#endif + +private: + void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override + { + StringLiteralType type = TypeNone; + QByteArray enclosingFunction; + const QList<AST *> &path = interface.path(); + CppRefactoringFilePtr file = interface.currentFile(); + ExpressionAST *literal = analyzeStringLiteral(path, file, &type, &enclosingFunction); + if (!literal || type == TypeNone) + return; + if ((type == TypeChar && enclosingFunction == "QLatin1Char") + || isQtStringLiteral(enclosingFunction) + || isQtStringTranslation(enclosingFunction)) + return; + + const int priority = path.size() - 1; // very high priority + if (type == TypeChar) { + unsigned actions = EncloseInQLatin1CharAction; + QString description = msgQtStringLiteralDescription(stringLiteralReplacement(actions)); + result << new WrapStringLiteralOp(interface, priority, actions, description, literal); + if (NumericLiteralAST *charLiteral = literal->asNumericLiteral()) { + const QByteArray contents(file->tokenAt(charLiteral->literal_token).identifier->chars()); + if (!charToStringEscapeSequences(contents).isEmpty()) { + actions = DoubleQuoteAction | ConvertEscapeSequencesToStringAction; + description = Tr::tr("Convert to String Literal"); + result << new WrapStringLiteralOp(interface, priority, actions, + description, literal); + } + } + } else { + const unsigned objectiveCActions = type == TypeObjCString ? + unsigned(RemoveObjectiveCAction) : 0u; + unsigned actions = 0; + if (StringLiteralAST *stringLiteral = literal->asStringLiteral()) { + const QByteArray contents(file->tokenAt(stringLiteral->literal_token).identifier->chars()); + if (!stringToCharEscapeSequences(contents).isEmpty()) { + actions = EncloseInQLatin1CharAction | SingleQuoteAction + | ConvertEscapeSequencesToCharAction | objectiveCActions; + QString description = + Tr::tr("Convert to Character Literal and Enclose in QLatin1Char(...)"); + result << new WrapStringLiteralOp(interface, priority, actions, + description, literal); + actions &= ~EncloseInQLatin1CharAction; + description = Tr::tr("Convert to Character Literal"); + result << new WrapStringLiteralOp(interface, priority, actions, + description, literal); + } + } + actions = EncloseInQLatin1StringAction | objectiveCActions; + result << new WrapStringLiteralOp(interface, priority, actions, + msgQtStringLiteralDescription(stringLiteralReplacement(actions)), literal); + actions = EncloseInQStringLiteralAction | objectiveCActions; + result << new WrapStringLiteralOp(interface, priority, actions, + msgQtStringLiteralDescription(stringLiteralReplacement(actions)), literal); + actions = EncloseInQByteArrayLiteralAction | objectiveCActions; + result << new WrapStringLiteralOp(interface, priority, actions, + msgQtStringLiteralDescription(stringLiteralReplacement(actions)), literal); + } + } +}; + +/*! + Escapes or unescapes a string literal as UTF-8. + + Escapes non-ASCII characters in a string literal to hexadecimal escape sequences. + Unescapes octal or hexadecimal escape sequences in a string literal. + String literals are handled as UTF-8 even if file's encoding is not UTF-8. + */ +class EscapeStringLiteral : public CppQuickFixFactory +{ +#ifdef WITH_TESTS +public: + static QObject *createTest(); +#endif + +private: + void doMatch(const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result) override + { + const QList<AST *> &path = interface.path(); + if (path.isEmpty()) + return; + + AST * const lastAst = path.last(); + ExpressionAST *literal = lastAst->asStringLiteral(); + if (!literal) + return; + + StringLiteralAST *stringLiteral = literal->asStringLiteral(); + CppRefactoringFilePtr file = interface.currentFile(); + const QByteArray contents(file->tokenAt(stringLiteral->literal_token).identifier->chars()); + + bool canEscape = false; + bool canUnescape = false; + for (int i = 0; i < contents.length(); ++i) { + quint8 c = contents.at(i); + if (!isascii(c) || !isprint(c)) { + canEscape = true; + } else if (c == '\\' && i < contents.length() - 1) { + c = contents.at(++i); + if ((c >= '0' && c < '8') || c == 'x' || c == 'X') + canUnescape = true; + } + } + + if (canEscape) + result << new EscapeStringLiteralOperation(interface, literal, true); + + if (canUnescape) + result << new EscapeStringLiteralOperation(interface, literal, false); + } +}; + +#ifdef WITH_TESTS +using namespace Tests; + +class EscapeStringLiteralTest : public QObject +{ + Q_OBJECT + +private slots: + void test_data() + { + QTest::addColumn<QByteArray>("original"); + QTest::addColumn<QByteArray>("expected"); + + // Escape String Literal as UTF-8 (no-trigger) + QTest::newRow("EscapeStringLiteral_notrigger") + << QByteArray("const char *notrigger = \"@abcdef \\a\\n\\\\\";\n") + << QByteArray(); + + // Escape String Literal as UTF-8 + QTest::newRow("EscapeStringLiteral") + << QByteArray("const char *utf8 = \"@\xe3\x81\x82\xe3\x81\x84\";\n") + << QByteArray("const char *utf8 = \"\\xe3\\x81\\x82\\xe3\\x81\\x84\";\n"); + + // Unescape String Literal as UTF-8 (from hexdecimal escape sequences) + QTest::newRow("UnescapeStringLiteral_hex") + << QByteArray("const char *hex_escaped = \"@\\xe3\\x81\\x82\\xe3\\x81\\x84\";\n") + << QByteArray("const char *hex_escaped = \"\xe3\x81\x82\xe3\x81\x84\";\n"); + + // Unescape String Literal as UTF-8 (from octal escape sequences) + QTest::newRow("UnescapeStringLiteral_oct") + << QByteArray("const char *oct_escaped = \"@\\343\\201\\202\\343\\201\\204\";\n") + << QByteArray("const char *oct_escaped = \"\xe3\x81\x82\xe3\x81\x84\";\n"); + + // Unescape String Literal as UTF-8 (triggered but no change) + QTest::newRow("UnescapeStringLiteral_noconv") + << QByteArray("const char *escaped_ascii = \"@\\x1b\";\n") + << QByteArray("const char *escaped_ascii = \"\\x1b\";\n"); + + // Unescape String Literal as UTF-8 (no conversion because of invalid utf-8) + QTest::newRow("UnescapeStringLiteral_invalid") + << QByteArray("const char *escaped = \"@\\xe3\\x81\";\n") + << QByteArray("const char *escaped = \"\\xe3\\x81\";\n"); + + QTest::newRow("escape string literal: simple case") + << QByteArray(R"(const char *str = @"à xyz";)") + << QByteArray(R"(const char *str = "\xc3\xa0xyz";)"); + QTest::newRow("escape string literal: simple case reverse") + << QByteArray(R"(const char *str = @"\xc3\xa0xyz";)") + << QByteArray(R"(const char *str = "à xyz";)"); + QTest::newRow("escape string literal: raw string literal") + << QByteArray(R"x(const char *str = @R"(à xyz)";)x") + << QByteArray(R"x(const char *str = R"(\xc3\xa0xyz)";)x"); + QTest::newRow("escape string literal: splitting required") + << QByteArray(R"(const char *str = @"à f23бgб1";)") + << QByteArray(R"(const char *str = "\xc3\xa0""f23\xd0\xb1g\xd0\xb1""1";)"); + QTest::newRow("escape string literal: unescape adjacent literals") + << QByteArray(R"(const char *str = @"\xc3\xa0""f23\xd0\xb1g\xd0\xb1""1";)") + << QByteArray(R"(const char *str = "à f23бgб1";)"); + } + + void test() + { + QFETCH(QByteArray, original); + QFETCH(QByteArray, expected); + + EscapeStringLiteral factory; + QuickFixOperationTest(singleDocument(original, expected), &factory); + } +}; + +QObject *EscapeStringLiteral::createTest() { return new EscapeStringLiteralTest; } +QObject *ConvertCStringToNSString::createTest() { return new QObject; } +QObject *WrapStringLiteral::createTest() { return new QObject; } +QObject *TranslateStringLiteral::createTest() { return new QObject; } + +#endif // WITH_TESTS +} // namespace + +void registerConvertStringLiteralQuickfixes() +{ + CppQuickFixFactory::registerFactory<ConvertCStringToNSString>(); + CppQuickFixFactory::registerFactory<EscapeStringLiteral>(); + CppQuickFixFactory::registerFactory<TranslateStringLiteral>(); + CppQuickFixFactory::registerFactory<WrapStringLiteral>(); +} + +} // namespace CppEditor::Internal + +#ifdef WITH_TESTS +#include <convertstringliteral.moc> +#endif diff --git a/src/plugins/cppeditor/quickfixes/convertstringliteral.h b/src/plugins/cppeditor/quickfixes/convertstringliteral.h new file mode 100644 index 0000000000..16cefacae0 --- /dev/null +++ b/src/plugins/cppeditor/quickfixes/convertstringliteral.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 registerConvertStringLiteralQuickfixes(); +} // namespace CppEditor::Internal diff --git a/src/plugins/cppeditor/quickfixes/converttocamelcase.cpp b/src/plugins/cppeditor/quickfixes/converttocamelcase.cpp new file mode 100644 index 0000000000..5d79997297 --- /dev/null +++ b/src/plugins/cppeditor/quickfixes/converttocamelcase.cpp @@ -0,0 +1,191 @@ +// 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 "converttocamelcase.h" + +#include "../cppeditortr.h" +#include "../cppeditorwidget.h" +#include "../cpprefactoringchanges.h" +#include "cppquickfix.h" + +#ifdef WITH_TESTS +#include "cppquickfix_test.h" +#include <QtTest> +#endif + +using namespace CPlusPlus; +using namespace Utils; + +namespace CppEditor::Internal { +namespace { + +class ConvertToCamelCaseOp: public CppQuickFixOperation +{ +public: + ConvertToCamelCaseOp(const CppQuickFixInterface &interface, const QString &name, + const AST *nameAst, bool test) + : CppQuickFixOperation(interface, -1) + , m_name(name) + , m_nameAst(nameAst) + , m_isAllUpper(name.isUpper()) + , m_test(test) + { + setDescription(Tr::tr("Convert to Camel Case")); + } + + static bool isConvertibleUnderscore(const QString &name, int pos) + { + return name.at(pos) == QLatin1Char('_') && name.at(pos+1).isLetter() + && !(pos == 1 && name.at(0) == QLatin1Char('m')); + } + +private: + void perform() override + { + CppRefactoringChanges refactoring(snapshot()); + CppRefactoringFilePtr currentFile = refactoring.cppFile(filePath()); + + QString newName = m_isAllUpper ? m_name.toLower() : m_name; + for (int i = 1; i < newName.length(); ++i) { + const QChar c = newName.at(i); + if (c.isUpper() && m_isAllUpper) { + newName[i] = c.toLower(); + } else if (i < newName.length() - 1 && isConvertibleUnderscore(newName, i)) { + newName.remove(i, 1); + newName[i] = newName.at(i).toUpper(); + } + } + if (m_test) { + ChangeSet changeSet; + changeSet.replace(currentFile->range(m_nameAst), newName); + currentFile->setChangeSet(changeSet); + currentFile->apply(); + } else { + editor()->renameUsages(newName); + } + } + + const QString m_name; + const AST * const m_nameAst; + const bool m_isAllUpper; + const bool m_test; +}; + +/*! + Turns "an_example_symbol" into "anExampleSymbol" and + "AN_EXAMPLE_SYMBOL" into "AnExampleSymbol". + + Activates on: identifiers +*/ +class ConvertToCamelCase : public CppQuickFixFactory +{ +public: + ConvertToCamelCase(bool test = false) : m_test(test) {} + +#ifdef WITH_TESTS + static QObject *createTest(); +#endif + +private: + void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override + { + const QList<AST *> &path = interface.path(); + + if (path.isEmpty()) + return; + + AST * const ast = path.last(); + const Name *name = nullptr; + const AST *astForName = nullptr; + if (const NameAST * const nameAst = ast->asName()) { + if (nameAst->name && nameAst->name->asNameId()) { + astForName = nameAst; + name = nameAst->name; + } + } else if (const NamespaceAST * const namespaceAst = ast->asNamespace()) { + astForName = namespaceAst; + name = namespaceAst->symbol->name(); + } + + if (!name) + return; + + QString nameString = QString::fromUtf8(name->identifier()->chars()); + if (nameString.length() < 3) + return; + for (int i = 1; i < nameString.length() - 1; ++i) { + if (ConvertToCamelCaseOp::isConvertibleUnderscore(nameString, i)) { + result << new ConvertToCamelCaseOp(interface, nameString, astForName, m_test); + return; + } + } + } + + const bool m_test; +}; + +#ifdef WITH_TESTS +using namespace Tests; + +class ConvertToCamelCaseTest : public QObject +{ + Q_OBJECT + +private slots: + void test_data() + { + QTest::addColumn<QByteArray>("original"); + QTest::addColumn<QByteArray>("expected"); + + using QByteArray = QByteArray; + + QTest::newRow("convert to camel case: normal") + << QByteArray("void @lower_case_function();\n") + << QByteArray("void lowerCaseFunction();\n"); + QTest::newRow("convert to camel case: already camel case") + << QByteArray("void @camelCaseFunction();\n") + << QByteArray(); + QTest::newRow("convert to camel case: no underscores (lower case)") + << QByteArray("void @lowercasefunction();\n") + << QByteArray(); + QTest::newRow("convert to camel case: no underscores (upper case)") + << QByteArray("void @UPPERCASEFUNCTION();\n") + << QByteArray(); + QTest::newRow("convert to camel case: non-applicable underscore") + << QByteArray("void @m_a_member;\n") + << QByteArray("void m_aMember;\n"); + QTest::newRow("convert to camel case: upper case") + << QByteArray("void @UPPER_CASE_FUNCTION();\n") + << QByteArray("void upperCaseFunction();\n"); + QTest::newRow("convert to camel case: partially camel case already") + << QByteArray("void mixed@_andCamelCase();\n") + << QByteArray("void mixedAndCamelCase();\n"); + QTest::newRow("convert to camel case: wild mix") + << QByteArray("void @WhAt_TODO_hErE();\n") + << QByteArray("void WhAtTODOHErE();\n"); + } + + void test() + { + QFETCH(QByteArray, original); + QFETCH(QByteArray, expected); + ConvertToCamelCase factory(true); + QuickFixOperationTest(singleDocument(original, expected), &factory); + } +}; + +QObject *ConvertToCamelCase::createTest() { return new ConvertToCamelCaseTest; } + +#endif // WITH_TESTS +} // namespace + +void registerConvertToCamelCaseQuickfix() +{ + CppQuickFixFactory::registerFactory<ConvertToCamelCase>(); +} + +} // namespace CppEditor::Internal + +#ifdef WITH_TESTS +#include <converttocamelcase.moc> +#endif diff --git a/src/plugins/cppeditor/quickfixes/converttocamelcase.h b/src/plugins/cppeditor/quickfixes/converttocamelcase.h new file mode 100644 index 0000000000..2d7acb08f7 --- /dev/null +++ b/src/plugins/cppeditor/quickfixes/converttocamelcase.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 registerConvertToCamelCaseQuickfix(); +} // namespace CppEditor::Internal diff --git a/src/plugins/cppeditor/quickfixes/converttometamethodcall.cpp b/src/plugins/cppeditor/quickfixes/converttometamethodcall.cpp new file mode 100644 index 0000000000..95c250595b --- /dev/null +++ b/src/plugins/cppeditor/quickfixes/converttometamethodcall.cpp @@ -0,0 +1,274 @@ +// 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 "converttometamethodcall.h" + +#include "../cppeditortr.h" +#include "../cpprefactoringchanges.h" +#include "cppquickfix.h" +#include "cppquickfixhelpers.h" + +#include <cplusplus/ASTPath.h> +#include <cplusplus/Overview.h> +#include <cplusplus/TypeOfExpression.h> + +#ifdef WITH_TESTS +#include "cppquickfix_test.h" +#include <QtTest> +#endif + +using namespace CPlusPlus; +using namespace Utils; + +namespace CppEditor::Internal { +namespace { + +class ConvertToMetaMethodCallOp : public CppQuickFixOperation +{ +public: + ConvertToMetaMethodCallOp(const CppQuickFixInterface &interface, CallAST *callAst) + : CppQuickFixOperation(interface), m_callAst(callAst) + { + setDescription(Tr::tr("Convert Function Call to Qt Meta-Method Invocation")); + } + +private: + void perform() override + { + // Construct the argument list. + Overview ov; + QStringList arguments; + for (ExpressionListAST *it = m_callAst->expression_list; it; it = it->next) { + if (!it->value) + return; + const FullySpecifiedType argType + = typeOfExpr(it->value, currentFile(), snapshot(), context()); + if (!argType.isValid()) + return; + arguments << QString::fromUtf8("Q_ARG(%1, %2)") + .arg(ov.prettyType(argType), currentFile()->textOf(it->value)); + } + QString argsString = arguments.join(", "); + if (!argsString.isEmpty()) + argsString.prepend(", "); + + // Construct the replace string. + const auto memberAccessAst = m_callAst->base_expression->asMemberAccess(); + QTC_ASSERT(memberAccessAst, return); + QString baseExpr = currentFile()->textOf(memberAccessAst->base_expression); + const FullySpecifiedType baseExprType + = typeOfExpr(memberAccessAst->base_expression, currentFile(), snapshot(), context()); + if (!baseExprType.isValid()) + return; + if (!baseExprType->asPointerType()) + baseExpr.prepend('&'); + const QString functionName = currentFile()->textOf(memberAccessAst->member_name); + const QString qMetaObject = "QMetaObject"; + const QString newCall = QString::fromUtf8("%1::invokeMethod(%2, \"%3\"%4)") + .arg(qMetaObject, baseExpr, functionName, argsString); + + // Determine the start and end positions of the replace operation. + // If the call is preceded by an "emit" keyword, that one has to be removed as well. + int firstToken = m_callAst->firstToken(); + if (firstToken > 0) + switch (semanticInfo().doc->translationUnit()->tokenKind(firstToken - 1)) { + case T_EMIT: case T_Q_EMIT: --firstToken; break; + default: break; + } + const TranslationUnit *const tu = semanticInfo().doc->translationUnit(); + const int startPos = tu->getTokenPositionInDocument(firstToken, textDocument()); + const int endPos = tu->getTokenPositionInDocument(m_callAst->lastToken(), textDocument()); + + // Replace the old call with the new one. + ChangeSet changes; + changes.replace(startPos, endPos, newCall); + + // Insert include for QMetaObject, if necessary. + const Identifier qMetaObjectId(qPrintable(qMetaObject), qMetaObject.size()); + Scope * const scope = currentFile()->scopeAt(firstToken); + const QList<LookupItem> results = context().lookup(&qMetaObjectId, scope); + bool isDeclared = false; + for (const LookupItem &item : results) { + if (Symbol *declaration = item.declaration(); declaration && declaration->asClass()) { + isDeclared = true; + break; + } + } + if (!isDeclared) { + insertNewIncludeDirective('<' + qMetaObject + '>', currentFile(), semanticInfo().doc, + changes); + } + + // Apply the changes. + currentFile()->setChangeSet(changes); + currentFile()->apply(); + } + + const CallAST * const m_callAst; +}; + +//! Converts a normal function call into a meta method invocation, if the functions is +//! marked as invokable. +class ConvertToMetaMethodCall : public CppQuickFixFactory +{ +#ifdef WITH_TESTS +public: + static QObject *createTest(); +#endif +private: + void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override + { + const Document::Ptr &cppDoc = interface.currentFile()->cppDocument(); + const QList<AST *> path = ASTPath(cppDoc)(interface.cursor()); + if (path.isEmpty()) + return; + + // Are we on a member function call? + CallAST *callAst = nullptr; + for (auto it = path.crbegin(); it != path.crend(); ++it) { + if ((callAst = (*it)->asCall())) + break; + } + if (!callAst || !callAst->base_expression) + return; + ExpressionAST *baseExpr = nullptr; + const NameAST *nameAst = nullptr; + if (const MemberAccessAST * const ast = callAst->base_expression->asMemberAccess()) { + baseExpr = ast->base_expression; + nameAst = ast->member_name; + } + if (!baseExpr || !nameAst || !nameAst->name) + return; + + // Locate called function and check whether it is invokable. + Scope *scope = cppDoc->globalNamespace(); + for (auto it = path.crbegin(); it != path.crend(); ++it) { + if (const CompoundStatementAST * const stmtAst = (*it)->asCompoundStatement()) { + scope = stmtAst->symbol; + break; + } + } + const LookupContext context(cppDoc, interface.snapshot()); + TypeOfExpression exprType; + exprType.setExpandTemplates(true); + exprType.init(cppDoc, interface.snapshot()); + const QList<LookupItem> typeMatches = exprType(callAst->base_expression, cppDoc, scope); + for (const LookupItem &item : typeMatches) { + if (const auto func = item.type()->asFunctionType(); func && func->methodKey()) { + result << new ConvertToMetaMethodCallOp(interface, callAst); + return; + } + } + } +}; + +#ifdef WITH_TESTS +using namespace Tests; + +class ConvertToMetaMethodCallTest : public QObject +{ + Q_OBJECT + +private slots: + void test_data() + { + QTest::addColumn<QByteArray>("input"); + QTest::addColumn<QByteArray>("expected"); + + // ^ marks the cursor locations. + // $ marks the replacement regions. + // The quoted string in the comment is the data tag. + // The rest of the comment is the replacement string. + const QByteArray allCases = R"( +class C { +public: + C() { + $this->^aSignal()$; // "signal from region on pointer to object" QMetaObject::invokeMethod(this, "aSignal") + C c; + $c.^aSignal()$; // "signal from region on object value" QMetaObject::invokeMethod(&c, "aSignal") + $(new C)->^aSignal()$; // "signal from region on expression" QMetaObject::invokeMethod((new C), "aSignal") + $emit this->^aSignal()$; // "signal from region, with emit" QMetaObject::invokeMethod(this, "aSignal") + $Q_EMIT this->^aSignal()$; // "signal from region, with Q_EMIT" QMetaObject::invokeMethod(this, "aSignal") + $this->^aSlot()$; // "slot from region" QMetaObject::invokeMethod(this, "aSlot") + $this->^noArgs()$; // "Q_SIGNAL, no arguments" QMetaObject::invokeMethod(this, "noArgs") + $this->^oneArg(0)$; // "Q_SLOT, one argument" QMetaObject::invokeMethod(this, "oneArg", Q_ARG(int, 0)) + $this->^twoArgs(0, c)$; // "Q_INVOKABLE, two arguments" QMetaObject::invokeMethod(this, "twoArgs", Q_ARG(int, 0), Q_ARG(C, c)) + this->^notInvokable(); // "not invokable" + } + +signals: + void aSignal(); + +private slots: + void aSlot(); + +private: + Q_SIGNAL void noArgs(); + Q_SLOT void oneArg(int index); + Q_INVOKABLE void twoArgs(int index, const C &value); + void notInvokable(); +}; +)"; + + qsizetype nextCursor = allCases.indexOf('^'); + while (nextCursor != -1) { + const int commentStart = allCases.indexOf("//", nextCursor); + QVERIFY(commentStart != -1); + const int tagStart = allCases.indexOf('"', commentStart + 2); + QVERIFY(tagStart != -1); + const int tagEnd = allCases.indexOf('"', tagStart + 1); + QVERIFY(tagEnd != -1); + QByteArray input = allCases; + QByteArray output = allCases; + input.replace(nextCursor, 1, "@"); + const QByteArray tag = allCases.mid(tagStart + 1, tagEnd - tagStart - 1); + const int prevNewline = allCases.lastIndexOf('\n', nextCursor); + const int regionStart = allCases.lastIndexOf('$', nextCursor); + bool hasReplacement = false; + if (regionStart != -1 && regionStart > prevNewline) { + const int regionEnd = allCases.indexOf('$', regionStart + 1); + QVERIFY(regionEnd != -1); + const int nextNewline = allCases.indexOf('\n', tagEnd); + QVERIFY(nextNewline != -1); + const QByteArray replacement + = allCases.mid(tagEnd + 1, nextNewline - tagEnd - 1).trimmed(); + output.replace(regionStart, regionEnd - regionStart, replacement); + hasReplacement = true; + } + static const auto matcher = [](char c) { return c == '^' || c == '$'; }; + input.removeIf(matcher); + if (hasReplacement) { + output.removeIf(matcher); + output.prepend("#include <QMetaObject>\n\n"); + } else { + output.clear(); + } + QTest::newRow(tag.data()) << input << output; + nextCursor = allCases.indexOf('^', nextCursor + 1); + } + } + + void test() + { + QFETCH(QByteArray, input); + QFETCH(QByteArray, expected); + ConvertToMetaMethodCall factory; + QuickFixOperationTest({CppTestDocument::create("file.cpp", input, expected)}, &factory); + } +}; + +QObject *ConvertToMetaMethodCall::createTest() { return new ConvertToMetaMethodCallTest; } + +#endif // WITH_TESTS +} // namespace + +void registerConvertToMetaMethodCallQuickfix() +{ + CppQuickFixFactory::registerFactory<ConvertToMetaMethodCall>(); +} + +} // namespace CppEditor::Internal + +#ifdef WITH_TESTS +#include <converttometamethodcall.moc> +#endif diff --git a/src/plugins/cppeditor/quickfixes/converttometamethodcall.h b/src/plugins/cppeditor/quickfixes/converttometamethodcall.h new file mode 100644 index 0000000000..e6c309ad13 --- /dev/null +++ b/src/plugins/cppeditor/quickfixes/converttometamethodcall.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 registerConvertToMetaMethodCallQuickfix(); +} // namespace CppEditor::Internal diff --git a/src/plugins/cppeditor/quickfixes/cppcodegenerationquickfixes.cpp b/src/plugins/cppeditor/quickfixes/cppcodegenerationquickfixes.cpp new file mode 100644 index 0000000000..350646ee4d --- /dev/null +++ b/src/plugins/cppeditor/quickfixes/cppcodegenerationquickfixes.cpp @@ -0,0 +1,5078 @@ +// 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 "../insertionpointlocator.h" +#include "cppquickfix.h" +#include "cppquickfixhelpers.h" +#include "cppquickfixprojectsettings.h" +#include "cppquickfixsettings.h" + +#include <cplusplus/Overview.h> +#include <cplusplus/CppRewriter.h> +#include <projectexplorer/projecttree.h> +#include <utils/basetreeview.h> +#include <utils/treemodel.h> + +#include <QApplication> +#include <QCheckBox> +#include <QDialog> +#include <QDialogButtonBox> +#include <QHBoxLayout> +#include <QHeaderView> +#include <QMimeData> +#include <QPushButton> +#include <QProxyStyle> +#include <QStyledItemDelegate> +#include <QTableView> +#include <QTreeView> + +#ifdef WITH_TESTS +#include "cppquickfix_test.h" +#include <QAbstractItemModelTester> +#include <QtTest> +#endif + +using namespace CPlusPlus; +using namespace ProjectExplorer; +using namespace TextEditor; +using namespace Utils; + +namespace CppEditor::Internal { +namespace { + +static QStringList toStringList(const QList<Symbol *> 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<Symbol *> getMemberFunctions(const Class *clazz) +{ + QList<Symbol *> 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<FullySpecifiedType> 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<FullySpecifiedType> getFirstTemplateParameter(Type *type) +{ + if (NamedType *namedType = type->asNamedType()) + return getFirstTemplateParameter(namedType->name()); + + return {}; +} + +static std::optional<FullySpecifiedType> 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<LookupItem> 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<InsertionPointLocator::AccessSpec, InsertionLocation> m_headerInsertionPoints; + InsertionLocation m_sourceFileInsertionPoint; + QString m_sourceFileCode; + QMap<InsertionPointLocator::AccessSpec, QString> 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<ConstructorMemberInfo> candidates; + std::vector<ConstructorMemberInfo *> 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<typename... _Args> + void emplaceBackParameter(_Args &&...__args) + { + candidates.emplace_back(std::forward<_Args>(__args)...); + infos.push_back(&candidates.back()); + } + const std::vector<ConstructorMemberInfo *> &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<ParentClassConstructorParameter> 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<ParentClassConstructorInfo>; + +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<ParentClassConstructorInfo *>(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<int>(constructors.size()); + auto info = static_cast<ParentClassConstructorInfo *>(parent.internalPointer()); + if (!info) + return static_cast<int>(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<ParentClassConstructorInfo *>(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<ParentClassConstructorInfo *>(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<ParentClassConstructorInfo *>(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<int>(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<A>(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<MemberInfo>; + +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<LookupItem> 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<ConstructorMemberInfo *> 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<int>(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<int>(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<int>(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<int>(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<TreeItem, CandidateTreeItem>(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<QCheckBox *, 4> 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<Symbol *> dataMembers; + QList<Symbol *> 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<int>' + // 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<FullySpecifiedType> 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<LookupItem> 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 <parameterName> 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<GenerateConstructorOperation>::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<AST *> &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<Symbol *> *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<GenerateGettersSettersOperation>::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<AST *> &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<QByteArrayList>("headers"); + QTest::addColumn<QByteArrayList>("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<TestDocumentPtr> 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<Name>"; + s->setterInCppFileFrom = 1; + s->getterInCppFileFrom = 1; + GenerateGetterSetter factory; + QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), 2); + } + + void testNamespaceHandlingAddUsing_data() + { + QTest::addColumn<QByteArrayList>("headers"); + QTest::addColumn<QByteArrayList>("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<TestDocumentPtr> 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<QByteArrayList>("headers"); + QTest::addColumn<QByteArrayList>("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<TestDocumentPtr> 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<QByteArrayList>("headers"); + QTest::addColumn<int>("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<TestDocumentPtr> testDocuments( + {CppTestDocument::create("file.h", headers.at(0), headers.at(1))}); + + QuickFixSettings s; + s->setterInCppFileFrom = 0; + s->getterInCppFileFrom = 0; + s->setterNameTemplate = "Seet_<Name>"; + s->getterNameTemplate = "give_me_<snake>"; + s->signalNameTemplate = "new<Camel>Value"; + s->setterParameterNameTemplate = "New_<Snake>"; + s->resetNameTemplate = "set_<camel>_toDefault"; + s->memberVariableNameTemplate = "mem_<name>"; + 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<QByteArrayList>("headers"); + QTest::addColumn<int>("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<typename T> + class Value{}; + class Test { + Value<int> v@; + }; +)-"; + expectedSource = R"-( + template<typename T> + class Value{}; + class Test { + Value<int> v; + + public: + Value<int> getV() const + { + return v; + } + }; +)-"; + QTest::addRow("value template class") << QByteArrayList{originalSource, expectedSource} << 1; + } + + void testValueTypes() + { + QFETCH(QByteArrayList, headers); + QFETCH(int, operation); + + QList<TestDocumentPtr> testDocuments( + {CppTestDocument::create("file.h", headers.at(0), headers.at(1))}); + + QuickFixSettings s; + s->setterInCppFileFrom = 0; + s->getterInCppFileFrom = 0; + s->getterNameTemplate = "get<Name>"; + s->valueTypes << "Value"; + s->returnByConstRef = true; + + GenerateGetterSetter factory; + QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), operation); + } + + /// Checks: Use template for a custom type + void testCustomTemplate() + { + QList<TestDocumentPtr> testDocuments; + QByteArray original; + QByteArray expected; + + const QByteArray customTypeDecl = R"--( +namespace N1 { +namespace N2 { +struct test{}; +} +template<typename T> +struct custom { + void assign(const custom<T>&); + bool equals(const custom<T>&); + T* get(); +}; +)--"; + // Header File + original = customTypeDecl + R"--( +class Foo +{ +public: + custom<N2::test> bar@; +}; +})--"; + expected = customTypeDecl + R"--( +class Foo +{ +public: + custom<N2::test> bar@; + N2::test *getBar() const; + void setBar(const custom<N2::test> &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<N2::test> &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<Name>"; + s->getterInCppFileFrom = 1; + s->signalWithNewValue = true; + CppQuickFixSettings::CustomTemplate t; + t.types.append("custom"); + t.equalComparison = "<cur>.equals(<new>)"; + t.returnExpression = "<cur>.get()"; + t.returnType = "<T> *"; + t.assignment = "<cur>.assign(<new>)"; + 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<TestDocumentPtr> 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 = "<name>"; + s->setterInCppFileFrom = 0; + + GenerateGetterSetter factory; + QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), 0); + } + + void testOfferedFixes_data() + { + QTest::addColumn<QByteArray>("header"); + QTest::addColumn<QStringList>("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<TestDocumentPtr> testDocuments( + {CppTestDocument::create("file.h", header, header)}); + + GenerateGetterSetter factory; + QuickFixOfferedOperationsTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), offered); + } + + void testGeneral_data() + { + QTest::addColumn<int>("operation"); + QTest::addColumn<QByteArray>("original"); + QTest::addColumn<QByteArray>("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 = "<name>"; + s->getterInCppFileFrom = 1; + s->setterInCppFileFrom = 1; + + GenerateGetterSetter factory; + QuickFixOperationTest(singleDocument(original, expected), + &factory, + ProjectExplorer::HeaderPaths(), + operation); + } + + /// Checks: Only generate getter + void testOnlyGetter() + { + QList<TestDocumentPtr> 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<Name>"; + GenerateGetterSetter factory; + QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), 1); + } + + /// Checks: Only generate setter + void testOnlySetter() + { + QList<TestDocumentPtr> 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<TestDocumentPtr> 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<TestDocumentPtr> 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<Name>"; + + GenerateGetterSetter factory; + QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), 4); + } + + void testOnlySetterHeaderFileWithIncludeGuard() + { + QList<TestDocumentPtr> 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<TestDocumentPtr> testDocuments; + const QByteArray original = R"( + template<typename T> class TS {}; + template<typename T, typename U> class TS<T(U)> {}; + + class S2 { + TS<int(int)> @member; + }; +)"; + const QByteArray expected = R"( + template<typename T> class TS {}; + template<typename T, typename U> class TS<T(U)> {}; + + class S2 { + TS<int(int)> member; + + public: + const TS<int (int)> &getMember() const + { + return member; + } + }; +)"; + + testDocuments << CppTestDocument::create("file.h", original, expected); + + QuickFixSettings s; + s->getterOutsideClassFrom = 0; + s->getterInCppFileFrom = 0; + s->getterNameTemplate = "get<Name>"; + 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<QByteArray>("original"); + QTest::addColumn<QByteArray>("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<Name>"; + 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<QByteArray>("original"); + QTest::addColumn<QByteArray>("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 = "<name>"; + 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<QByteArray>("original_header"); + QTest::addColumn<QByteArray>("expected_header"); + QTest::addColumn<QByteArray>("original_source"); + QTest::addColumn<QByteArray>("expected_source"); + QTest::addColumn<int>("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<typename T> + struct vector{ + }; +} +)--"; + header = common + R"--( +namespace M{ +enum G{g}; +class@ Foo{ + N::vector<G> g; + enum E{e}e; +public: +}; +} +)--"; + + expected = common + R"--( +namespace M{ +enum G{g}; +class@ Foo{ + N::vector<G> g; + enum E{e}e; +public: + Foo(const N::vector<G> &g, E e); +}; + +Foo::Foo(const N::vector<G> &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> g; + enum E{e}e; +public: + Foo(const N::vector<G> &g, E e); +}; +} + + +inline M::Foo::Foo(const N::vector<M::G> &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> g; + enum E{e}e; +public: + Foo(const N::vector<G> &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> &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> &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<M::G> &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<TestDocumentPtr> 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<GenerateGetterSetter>(); + CppQuickFixFactory::registerFactory<GenerateGettersSettersForClass>(); + CppQuickFixFactory::registerFactory<GenerateConstructor>(); + CppQuickFixFactory::registerFactory<InsertQtPropertyMembers>(); +} + +} // namespace CppEditor::Internal + +#include <cppcodegenerationquickfixes.moc> diff --git a/src/plugins/cppeditor/quickfixes/cppcodegenerationquickfixes.h b/src/plugins/cppeditor/quickfixes/cppcodegenerationquickfixes.h new file mode 100644 index 0000000000..3cef95c7c1 --- /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/cppinsertvirtualmethods.cpp b/src/plugins/cppeditor/quickfixes/cppinsertvirtualmethods.cpp index b34977cfd8..0ea79b9dd9 100644 --- a/src/plugins/cppeditor/cppinsertvirtualmethods.cpp +++ b/src/plugins/cppeditor/quickfixes/cppinsertvirtualmethods.cpp @@ -3,12 +3,13 @@ #include "cppinsertvirtualmethods.h" -#include "cppcodestylesettings.h" -#include "cppeditortr.h" +#include "../cppcodestylesettings.h" +#include "../cppeditortr.h" +#include "../cpptoolsreuse.h" +#include "../functionutils.h" +#include "../insertionpointlocator.h" #include "cppquickfixassistant.h" -#include "cpptoolsreuse.h" -#include "functionutils.h" -#include "insertionpointlocator.h" +#include "cppquickfixhelpers.h" #include <coreplugin/icore.h> #include <texteditor/fontsettings.h> @@ -116,6 +117,11 @@ public: QSortFilterProxyModel *classFunctionFilterModel; }; +void registerInsertVirtualMethodsQuickfix() +{ + CppQuickFixFactory::registerFactory<InsertVirtualMethods>(); +} + } // namespace Internal } // namespace CppEditor @@ -1269,6 +1275,17 @@ public: void saveSettings() override { } }; +class InsertVirtualMethodsTest : public QObject +{ + Q_OBJECT + +private slots: + void test_data(); + void test(); + void testImplementationFile(); + void testBaseClassInNamespace(); +}; + void InsertVirtualMethodsTest::test_data() { QTest::addColumn<InsertVirtualMethodsDialog::ImplementationMode>("implementationMode"); @@ -1969,6 +1986,11 @@ InsertVirtualMethods *InsertVirtualMethods::createTestFactory() InsertVirtualMethodsDialog::ModeOutsideClass, true, false)); } +QObject *InsertVirtualMethods::createTest() +{ + return new Tests::InsertVirtualMethodsTest; +} + #endif // WITH_TESTS } // namespace Internal diff --git a/src/plugins/cppeditor/cppinsertvirtualmethods.h b/src/plugins/cppeditor/quickfixes/cppinsertvirtualmethods.h index 7116efde1d..ffb749a71e 100644 --- a/src/plugins/cppeditor/cppinsertvirtualmethods.h +++ b/src/plugins/cppeditor/quickfixes/cppinsertvirtualmethods.h @@ -19,6 +19,7 @@ public: void doMatch(const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result) override; #ifdef WITH_TESTS + static QObject *createTest(); static InsertVirtualMethods *createTestFactory(); #endif @@ -26,20 +27,7 @@ private: InsertVirtualMethodsDialog *m_dialog; }; -#ifdef WITH_TESTS -namespace Tests { -class InsertVirtualMethodsTest : public QObject -{ - Q_OBJECT - -private slots: - void test_data(); - void test(); - void testImplementationFile(); - void testBaseClassInNamespace(); -}; -} // namespace Tests -#endif // WITH_TESTS +void registerInsertVirtualMethodsQuickfix(); } // namespace Internal } // namespace CppEditor diff --git a/src/plugins/cppeditor/cppquickfix.cpp b/src/plugins/cppeditor/quickfixes/cppquickfix.cpp index 0741c842ba..964c0752fc 100644 --- a/src/plugins/cppeditor/cppquickfix.cpp +++ b/src/plugins/cppeditor/quickfixes/cppquickfix.cpp @@ -4,12 +4,32 @@ #include "cppquickfix.h" #include "cppquickfixassistant.h" -#include "cpprefactoringchanges.h" + +#include <extensionsystem/pluginmanager.h> +#include <extensionsystem/pluginspec.h> using namespace CPlusPlus; using namespace TextEditor; -namespace CppEditor::Internal { +namespace CppEditor { + +static ExtensionSystem::IPlugin *getCppEditor() +{ + using namespace ExtensionSystem; + for (PluginSpec * const spec : PluginManager::plugins()) { + if (spec->name() == "CppEditor") + return spec->plugin(); + } + QTC_ASSERT(false, return nullptr); +} + +ExtensionSystem::IPlugin *CppQuickFixFactory::cppEditor() +{ + static ExtensionSystem::IPlugin *plugin = getCppEditor(); + return plugin; +} + +namespace Internal { const QStringList magicQObjectFunctions() { @@ -23,4 +43,5 @@ CppQuickFixOperation::CppQuickFixOperation(const CppQuickFixInterface &interface CppQuickFixOperation::~CppQuickFixOperation() = default; -} // namespace CppEditor::Internal +} // namespace Internal +} // namespace CppEditor diff --git a/src/plugins/cppeditor/cppquickfix.h b/src/plugins/cppeditor/quickfixes/cppquickfix.h index e901b2bd30..a93db1f5f8 100644 --- a/src/plugins/cppeditor/cppquickfix.h +++ b/src/plugins/cppeditor/quickfixes/cppquickfix.h @@ -3,9 +3,10 @@ #pragma once -#include "cppeditor_global.h" +#include "../cppeditor_global.h" #include "cppquickfixassistant.h" +#include <extensionsystem/iplugin.h> #include <texteditor/quickfix.h> #include <QVersionNumber> @@ -16,9 +17,6 @@ namespace CppEditor { namespace Internal { class CppQuickFixInterface; -// These are generated functions that should not be offered in quickfixes. -const QStringList magicQObjectFunctions(); - class CppQuickFixOperation : public TextEditor::QuickFixOperation, public Internal::CppQuickFixInterface @@ -59,6 +57,14 @@ public: std::optional<QVersionNumber> clangdReplacement() const { return m_clangdReplacement; } void setClangdReplacement(const QVersionNumber &version) { m_clangdReplacement = version; } + template<class Factory> static void registerFactory() + { + new Factory; +#ifdef WITH_TESTS + cppEditor()->addTestCreator(Factory::createTest); +#endif + } + private: /*! Implement this function to doMatch and create the appropriate @@ -67,6 +73,8 @@ private: virtual void doMatch(const Internal::CppQuickFixInterface &interface, QuickFixOperations &result) = 0; + static ExtensionSystem::IPlugin *cppEditor(); + std::optional<QVersionNumber> m_clangdReplacement; }; diff --git a/src/plugins/cppeditor/quickfixes/cppquickfix_test.cpp b/src/plugins/cppeditor/quickfixes/cppquickfix_test.cpp new file mode 100644 index 0000000000..efb2d7fa64 --- /dev/null +++ b/src/plugins/cppeditor/quickfixes/cppquickfix_test.cpp @@ -0,0 +1,320 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "cppquickfix_test.h" + +#include "../cppcodestylepreferences.h" +#include "../cppeditorwidget.h" +#include "../cppmodelmanager.h" +#include "../cppsourceprocessertesthelper.h" +#include "../cpptoolssettings.h" +#include "cppquickfixassistant.h" +#include "cppquickfixes.h" + +#include <projectexplorer/kitmanager.h> +#include <projectexplorer/projectexplorer.h> +#include <texteditor/textdocument.h> +#include <utils/fileutils.h> + +#include <QDebug> +#include <QDir> +#include <QtTest> + +/*! + Tests for quick-fixes. + */ +using namespace Core; +using namespace CPlusPlus; +using namespace ProjectExplorer; +using namespace TextEditor; +using namespace Utils; + +using CppEditor::Tests::TemporaryDir; +using CppEditor::Tests::Internal::TestIncludePaths; + +typedef QByteArray _; + +namespace CppEditor { +namespace Internal { +namespace Tests { + +QList<TestDocumentPtr> singleDocument(const QByteArray &original, + const QByteArray &expected) +{ + return {CppTestDocument::create("file.cpp", original, expected)}; +} + +BaseQuickFixTestCase::BaseQuickFixTestCase(const QList<TestDocumentPtr> &testDocuments, + const ProjectExplorer::HeaderPaths &headerPaths, + const QByteArray &clangFormatSettings) + : m_testDocuments(testDocuments) + , m_cppCodeStylePreferences(0) + , m_restoreHeaderPaths(false) +{ + QVERIFY(succeededSoFar()); + m_succeededSoFar = false; + + // Check if there is exactly one cursor marker + unsigned cursorMarkersCount = 0; + for (const TestDocumentPtr &document : std::as_const(m_testDocuments)) { + if (document->hasCursorMarker()) + ++cursorMarkersCount; + } + QVERIFY2(cursorMarkersCount == 1, "Exactly one cursor marker is allowed."); + + // Write documents to disk + m_temporaryDirectory.reset(new TemporaryDir); + QVERIFY(m_temporaryDirectory->isValid()); + for (const TestDocumentPtr &document : std::as_const(m_testDocuments)) { + if (QFileInfo(document->m_fileName).isRelative()) + document->setBaseDirectory(m_temporaryDirectory->path()); + document->writeToDisk(); + } + + // Create .clang-format file + if (!clangFormatSettings.isEmpty()) + m_temporaryDirectory->createFile(".clang-format", clangFormatSettings); + + // Set appropriate include paths + if (!headerPaths.isEmpty()) { + m_restoreHeaderPaths = true; + m_headerPathsToRestore = CppModelManager::headerPaths(); + CppModelManager::setHeaderPaths(headerPaths); + } + + // Update Code Model + QSet<FilePath> filePaths; + for (const TestDocumentPtr &document : std::as_const(m_testDocuments)) + filePaths << document->filePath(); + QVERIFY(parseFiles(filePaths)); + + // Open Files + for (const TestDocumentPtr &document : std::as_const(m_testDocuments)) { + QVERIFY(openCppEditor(document->filePath(), &document->m_editor, + &document->m_editorWidget)); + closeEditorAtEndOfTestCase(document->m_editor); + + // Set cursor position + if (document->hasCursorMarker()) { + if (document->hasAnchorMarker()) { + document->m_editor->setCursorPosition(document->m_anchorPosition); + document->m_editor->select(document->m_cursorPosition); + } else { + document->m_editor->setCursorPosition(document->m_cursorPosition); + } + } else { + document->m_editor->setCursorPosition(0); + } + + // Rehighlight + waitForRehighlightedSemanticDocument(document->m_editorWidget); + } + + // Enforce the default cpp code style, so we are independent of config file settings. + // This is needed by e.g. the GenerateGetterSetter quick fix. + m_cppCodeStylePreferences = CppToolsSettings::cppCodeStyle(); + QVERIFY(m_cppCodeStylePreferences); + m_cppCodeStylePreferencesOriginalDelegateId = m_cppCodeStylePreferences->currentDelegateId(); + m_cppCodeStylePreferences->setCurrentDelegate("qt"); + + // Find the document having the cursor marker + for (const TestDocumentPtr &document : std::as_const(m_testDocuments)) { + if (document->hasCursorMarker()){ + m_documentWithMarker = document; + break; + } + } + + QVERIFY(m_documentWithMarker); + m_succeededSoFar = true; +} + +BaseQuickFixTestCase::~BaseQuickFixTestCase() +{ + // Restore default cpp code style + if (m_cppCodeStylePreferences) + m_cppCodeStylePreferences->setCurrentDelegate(m_cppCodeStylePreferencesOriginalDelegateId); + + // Restore include paths + if (m_restoreHeaderPaths) + CppModelManager::setHeaderPaths(m_headerPathsToRestore); + + // Remove created files from file system + for (const TestDocumentPtr &testDocument : std::as_const(m_testDocuments)) + QVERIFY(testDocument->filePath().removeFile()); +} + +QuickFixOfferedOperationsTest::QuickFixOfferedOperationsTest( + const QList<TestDocumentPtr> &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) +{ + const QStringList lines = input.split(QLatin1Char('\n')); + input.resize(0); + for (int i = 0, total = lines.size(); i < total; ++i) { + QString line = lines.at(i); + while (line.length() > 0) { + QChar lastChar = line[line.length() - 1]; + if (lastChar == QLatin1Char(' ') || lastChar == QLatin1Char('\t')) + line.chop(1); + else + break; + } + input.append(line); + + const bool isLastLine = i == lines.size() - 1; + if (!isLastLine) + input.append(QLatin1Char('\n')); + } + return input; +} + +QuickFixOperationTest::QuickFixOperationTest(const QList<TestDocumentPtr> &testDocuments, + CppQuickFixFactory *factory, + const ProjectExplorer::HeaderPaths &headerPaths, + int operationIndex, + const QByteArray &expectedFailMessage, + const QByteArray &clangFormatSettings) + : BaseQuickFixTestCase(testDocuments, headerPaths, clangFormatSettings) +{ + if (factory->clangdReplacement() && CppModelManager::isClangCodeModelActive()) + return; + + QVERIFY(succeededSoFar()); + + // Perform operation if there is one + CppQuickFixInterface quickFixInterface(m_documentWithMarker->m_editorWidget, ExplicitlyInvoked); + QuickFixOperations operations; + factory->match(quickFixInterface, operations); + if (operations.isEmpty()) { + QEXPECT_FAIL("QTCREATORBUG-25998", "FIXME", Abort); + QVERIFY(testDocuments.first()->m_expectedSource.isEmpty()); + return; + } + + QVERIFY(operationIndex < operations.size()); + const QuickFixOperation::Ptr operation = operations.at(operationIndex); + operation->perform(); + + // Compare all files + for (const TestDocumentPtr &testDocument : std::as_const(m_testDocuments)) { + // Check + QString result = testDocument->m_editorWidget->document()->toPlainText(); + removeTrailingWhitespace(result); + QEXPECT_FAIL("escape string literal: raw string literal", "FIXME", Continue); + QEXPECT_FAIL("escape string literal: unescape adjacent literals", "FIXME", Continue); + if (!expectedFailMessage.isEmpty()) + QEXPECT_FAIL("", expectedFailMessage.data(), Continue); + else if (result != testDocument->m_expectedSource) { + qDebug() << "---" << testDocument->m_expectedSource; + qDebug() << "+++" << result; + } + QCOMPARE(result, testDocument->m_expectedSource); + + // Undo the change + for (int i = 0; i < 100; ++i) + testDocument->m_editorWidget->undo(); + result = testDocument->m_editorWidget->document()->toPlainText(); + QCOMPARE(result, testDocument->m_source); + } +} + +void QuickFixOperationTest::run(const QList<TestDocumentPtr> &testDocuments, + CppQuickFixFactory *factory, + const QString &headerPath, + int operationIndex) +{ + ProjectExplorer::HeaderPaths headerPaths; + headerPaths.push_back(ProjectExplorer::HeaderPath::makeUser(headerPath)); + QuickFixOperationTest(testDocuments, factory, headerPaths, operationIndex); +} + +} // namespace Tests +} // namespace Internal + +typedef QSharedPointer<CppQuickFixFactory> CppQuickFixFactoryPtr; + +} // namespace CppEditor + +namespace CppEditor::Internal::Tests { + +void QuickfixTest::testGeneric_data() +{ + QTest::addColumn<CppQuickFixFactoryPtr>("factory"); + QTest::addColumn<QByteArray>("original"); + QTest::addColumn<QByteArray>("expected"); + + // Check: Just a basic test since the main functionality is tested in + // cpppointerdeclarationformatter_test.cpp + QTest::newRow("ReformatPointerDeclaration") + << CppQuickFixFactoryPtr(new ReformatPointerDeclaration) + << _("char@*s;") + << _("char *s;"); +} + +void QuickfixTest::testGeneric() +{ + QFETCH(CppQuickFixFactoryPtr, factory); + QFETCH(QByteArray, original); + QFETCH(QByteArray, expected); + + QuickFixOperationTest(singleDocument(original, expected), factory.data()); +} + +class CppCodeStyleSettingsChanger { +public: + CppCodeStyleSettingsChanger(const CppCodeStyleSettings &settings); + ~CppCodeStyleSettingsChanger(); // Restore original + + static CppCodeStyleSettings currentSettings(); + +private: + void setSettings(const CppCodeStyleSettings &settings); + + CppCodeStyleSettings m_originalSettings; +}; + +CppCodeStyleSettingsChanger::CppCodeStyleSettingsChanger(const CppCodeStyleSettings &settings) +{ + m_originalSettings = currentSettings(); + setSettings(settings); +} + +CppCodeStyleSettingsChanger::~CppCodeStyleSettingsChanger() +{ + setSettings(m_originalSettings); +} + +void CppCodeStyleSettingsChanger::setSettings(const CppCodeStyleSettings &settings) +{ + QVariant variant; + variant.setValue(settings); + + CppToolsSettings::cppCodeStyle()->currentDelegate()->setValue(variant); +} + +CppCodeStyleSettings CppCodeStyleSettingsChanger::currentSettings() +{ + return CppToolsSettings::cppCodeStyle()->currentDelegate()->value().value<CppCodeStyleSettings>(); +} + +} // namespace CppEditor::Internal::Tests diff --git a/src/plugins/cppeditor/quickfixes/cppquickfix_test.h b/src/plugins/cppeditor/quickfixes/cppquickfix_test.h new file mode 100644 index 0000000000..0bead0a5b1 --- /dev/null +++ b/src/plugins/cppeditor/quickfixes/cppquickfix_test.h @@ -0,0 +1,103 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "../cpptoolstestcase.h" +#include "cppquickfix.h" +#include "cppquickfixsettings.h" + +#include <projectexplorer/headerpath.h> + +#include <QByteArray> +#include <QList> +#include <QObject> +#include <QSharedPointer> +#include <QStringList> + +namespace TextEditor { class QuickFixOperation; } + +namespace CppEditor { +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: + /// Exactly one QuickFixTestDocument must contain the cursor position marker '@' + /// or "@{start}" and "@{end}" + BaseQuickFixTestCase(const QList<TestDocumentPtr> &testDocuments, + const ProjectExplorer::HeaderPaths &headerPaths, + const QByteArray &clangFormatSettings = {}); + + ~BaseQuickFixTestCase(); + +protected: + TestDocumentPtr m_documentWithMarker; + QList<TestDocumentPtr> m_testDocuments; + +private: + QScopedPointer<CppEditor::Tests::TemporaryDir> m_temporaryDirectory; + + CppCodeStylePreferences *m_cppCodeStylePreferences; + QByteArray m_cppCodeStylePreferencesOriginalDelegateId; + + ProjectExplorer::HeaderPaths m_headerPathsToRestore; + bool m_restoreHeaderPaths; +}; + +/// Tests the offered operations provided by a given CppQuickFixFactory +class QuickFixOfferedOperationsTest : public BaseQuickFixTestCase +{ +public: + QuickFixOfferedOperationsTest(const QList<TestDocumentPtr> &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 +{ +public: + QuickFixOperationTest(const QList<TestDocumentPtr> &testDocuments, + CppQuickFixFactory *factory, + const ProjectExplorer::HeaderPaths &headerPaths + = ProjectExplorer::HeaderPaths(), + int operationIndex = 0, + const QByteArray &expectedFailMessage = {}, + const QByteArray &clangFormatSettings = {}); + + static void run(const QList<TestDocumentPtr> &testDocuments, + CppQuickFixFactory *factory, + const QString &headerPath, + int operationIndex = 0); +}; + +QList<TestDocumentPtr> singleDocument(const QByteArray &original, + const QByteArray &expected); + +class QuickfixTest : public QObject +{ + Q_OBJECT + +private slots: + void testGeneric_data(); + void testGeneric(); +}; + +} // namespace Tests +} // namespace Internal +} // namespace CppEditor diff --git a/src/plugins/cppeditor/cppquickfixassistant.cpp b/src/plugins/cppeditor/quickfixes/cppquickfixassistant.cpp index 1456ce5604..834abaed8a 100644 --- a/src/plugins/cppeditor/cppquickfixassistant.cpp +++ b/src/plugins/cppeditor/quickfixes/cppquickfixassistant.cpp @@ -3,10 +3,10 @@ #include "cppquickfixassistant.h" -#include "cppeditorwidget.h" -#include "cppmodelmanager.h" +#include "../cppeditorwidget.h" +#include "../cppmodelmanager.h" +#include "../cpprefactoringchanges.h" #include "cppquickfix.h" -#include "cpprefactoringchanges.h" #include <texteditor/codeassist/genericproposal.h> #include <texteditor/codeassist/iassistprocessor.h> diff --git a/src/plugins/cppeditor/cppquickfixassistant.h b/src/plugins/cppeditor/quickfixes/cppquickfixassistant.h index 63a37f715a..db818c923c 100644 --- a/src/plugins/cppeditor/cppquickfixassistant.h +++ b/src/plugins/cppeditor/quickfixes/cppquickfixassistant.h @@ -3,7 +3,7 @@ #pragma once -#include "cppsemanticinfo.h" +#include "../cppsemanticinfo.h" #include <texteditor/codeassist/assistinterface.h> #include <texteditor/codeassist/iassistprovider.h> diff --git a/src/plugins/cppeditor/quickfixes/cppquickfixes.cpp b/src/plugins/cppeditor/quickfixes/cppquickfixes.cpp new file mode 100644 index 0000000000..46af51d38c --- /dev/null +++ b/src/plugins/cppeditor/quickfixes/cppquickfixes.cpp @@ -0,0 +1,425 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "cppquickfixes.h" + +#include "../baseeditordocumentprocessor.h" +#include "../cppcodestylesettings.h" +#include "../cppeditortr.h" +#include "../cppeditorwidget.h" +#include "../cppfunctiondecldeflink.h" +#include "../cpppointerdeclarationformatter.h" +#include "../cpprefactoringchanges.h" +#include "../cpptoolsreuse.h" +#include "assigntolocalvariable.h" +#include "bringidentifierintoscope.h" +#include "completeswitchstatement.h" +#include "convertfromandtopointer.h" +#include "convertnumericliteral.h" +#include "convertqt4connect.h" +#include "convertstringliteral.h" +#include "converttocamelcase.h" +#include "converttometamethodcall.h" +#include "cppcodegenerationquickfixes.h" +#include "cppinsertvirtualmethods.h" +#include "cppquickfixassistant.h" +#include "cppquickfixhelpers.h" +#include "createdeclarationfromuse.h" +#include "extractfunction.h" +#include "extractliteralasparameter.h" +#include "insertfunctiondefinition.h" +#include "logicaloperationquickfixes.h" +#include "moveclasstoownfile.h" +#include "movefunctiondefinition.h" +#include "removeusingnamespace.h" +#include "rewritecomment.h" +#include "rewritecontrolstatements.h" +#include "splitsimpledeclaration.h" + +#include <coreplugin/icore.h> +#include <coreplugin/messagemanager.h> + +#include <cplusplus/ASTPath.h> +#include <cplusplus/CPlusPlusForwardDeclarations.h> +#include <cplusplus/CppRewriter.h> +#include <cplusplus/declarationcomments.h> +#include <cplusplus/NamePrettyPrinter.h> +#include <cplusplus/Overview.h> +#include <cplusplus/TypeOfExpression.h> +#include <cplusplus/TypePrettyPrinter.h> + +#include <projectexplorer/editorconfiguration.h> +#include <projectexplorer/projectnodes.h> +#include <projectexplorer/projecttree.h> +#include <projectexplorer/projectmanager.h> + +#include <texteditor/tabsettings.h> + +#include <utils/algorithm.h> +#include <utils/basetreeview.h> +#include <utils/codegeneration.h> +#include <utils/layoutbuilder.h> +#include <utils/fancylineedit.h> +#include <utils/fileutils.h> +#include <utils/pathchooser.h> +#include <utils/qtcassert.h> +#include <utils/treemodel.h> +#include <utils/treeviewcombobox.h> + +#include <QApplication> +#include <QCheckBox> +#include <QComboBox> +#include <QDialog> +#include <QDialogButtonBox> +#include <QDir> +#include <QFileInfo> +#include <QFormLayout> +#include <QGridLayout> +#include <QHash> +#include <QHeaderView> +#include <QInputDialog> +#include <QMimeData> +#include <QPair> +#include <QProxyStyle> +#include <QPushButton> +#include <QRegularExpression> +#include <QSharedPointer> +#include <QStack> +#include <QStyledItemDelegate> +#include <QTableView> +#include <QTextCodec> +#include <QTextCursor> +#include <QVBoxLayout> + +#include <cctype> + +using namespace CPlusPlus; +using namespace ProjectExplorer; +using namespace TextEditor; +using namespace Utils; + +namespace CppEditor { + +static QList<CppQuickFixFactory *> g_cppQuickFixFactories; + +CppQuickFixFactory::CppQuickFixFactory() +{ + g_cppQuickFixFactories.append(this); +} + +CppQuickFixFactory::~CppQuickFixFactory() +{ + g_cppQuickFixFactories.removeOne(this); +} + +void CppQuickFixFactory::match(const Internal::CppQuickFixInterface &interface, + QuickFixOperations &result) +{ + if (m_clangdReplacement) { + if (const auto clangdVersion = CppModelManager::usesClangd( + interface.currentFile()->editor()->textDocument()); + clangdVersion && clangdVersion >= m_clangdReplacement) { + return; + } + } + + doMatch(interface, result); +} + +const QList<CppQuickFixFactory *> &CppQuickFixFactory::cppQuickFixFactories() +{ + return g_cppQuickFixFactories; +} + +namespace Internal { + +namespace { + +class RearrangeParamDeclarationListOp: public CppQuickFixOperation +{ +public: + enum Target { TargetPrevious, TargetNext }; + + RearrangeParamDeclarationListOp(const CppQuickFixInterface &interface, AST *currentParam, + AST *targetParam, Target target) + : CppQuickFixOperation(interface) + , m_currentParam(currentParam) + , m_targetParam(targetParam) + { + QString targetString; + if (target == TargetPrevious) + targetString = Tr::tr("Switch with Previous Parameter"); + else + targetString = Tr::tr("Switch with Next Parameter"); + setDescription(targetString); + } + + void perform() override + { + CppRefactoringChanges refactoring(snapshot()); + CppRefactoringFilePtr currentFile = refactoring.cppFile(filePath()); + + int targetEndPos = currentFile->endOf(m_targetParam); + ChangeSet changes; + changes.flip(currentFile->startOf(m_currentParam), currentFile->endOf(m_currentParam), + currentFile->startOf(m_targetParam), targetEndPos); + currentFile->setChangeSet(changes); + currentFile->setOpenEditor(false, targetEndPos); + currentFile->apply(); + } + +private: + AST *m_currentParam; + AST *m_targetParam; +}; + +} // anonymous namespace + +void RearrangeParamDeclarationList::doMatch(const CppQuickFixInterface &interface, + QuickFixOperations &result) +{ + const QList<AST *> path = interface.path(); + + ParameterDeclarationAST *paramDecl = nullptr; + int index = path.size() - 1; + for (; index != -1; --index) { + paramDecl = path.at(index)->asParameterDeclaration(); + if (paramDecl) + break; + } + + if (index < 1) + return; + + ParameterDeclarationClauseAST *paramDeclClause = path.at(index-1)->asParameterDeclarationClause(); + QTC_ASSERT(paramDeclClause && paramDeclClause->parameter_declaration_list, return); + + ParameterDeclarationListAST *paramListNode = paramDeclClause->parameter_declaration_list; + ParameterDeclarationListAST *prevParamListNode = nullptr; + while (paramListNode) { + if (paramDecl == paramListNode->value) + break; + prevParamListNode = paramListNode; + paramListNode = paramListNode->next; + } + + if (!paramListNode) + return; + + if (prevParamListNode) + result << new RearrangeParamDeclarationListOp(interface, paramListNode->value, + prevParamListNode->value, RearrangeParamDeclarationListOp::TargetPrevious); + if (paramListNode->next) + result << new RearrangeParamDeclarationListOp(interface, paramListNode->value, + paramListNode->next->value, RearrangeParamDeclarationListOp::TargetNext); +} + +namespace { + +class ReformatPointerDeclarationOp: public CppQuickFixOperation +{ +public: + ReformatPointerDeclarationOp(const CppQuickFixInterface &interface, const ChangeSet change) + : CppQuickFixOperation(interface) + , m_change(change) + { + QString description; + if (m_change.operationList().size() == 1) { + description = Tr::tr( + "Reformat to \"%1\"").arg(m_change.operationList().constFirst().text()); + } else { // > 1 + description = Tr::tr("Reformat Pointers or References"); + } + setDescription(description); + } + + void perform() override + { + CppRefactoringChanges refactoring(snapshot()); + CppRefactoringFilePtr currentFile = refactoring.cppFile(filePath()); + currentFile->setChangeSet(m_change); + currentFile->apply(); + } + +private: + ChangeSet m_change; +}; + +/// Filter the results of ASTPath. +/// The resulting list contains the supported AST types only once. +/// For this, the results of ASTPath are iterated in reverse order. +class ReformatPointerDeclarationASTPathResultsFilter +{ +public: + QList<AST*> filter(const QList<AST*> &astPathList) + { + QList<AST*> filtered; + + for (int i = astPathList.size() - 1; i >= 0; --i) { + AST *ast = astPathList.at(i); + + if (!m_hasSimpleDeclaration && ast->asSimpleDeclaration()) { + m_hasSimpleDeclaration = true; + filtered.append(ast); + } else if (!m_hasFunctionDefinition && ast->asFunctionDefinition()) { + m_hasFunctionDefinition = true; + filtered.append(ast); + } else if (!m_hasParameterDeclaration && ast->asParameterDeclaration()) { + m_hasParameterDeclaration = true; + filtered.append(ast); + } else if (!m_hasIfStatement && ast->asIfStatement()) { + m_hasIfStatement = true; + filtered.append(ast); + } else if (!m_hasWhileStatement && ast->asWhileStatement()) { + m_hasWhileStatement = true; + filtered.append(ast); + } else if (!m_hasForStatement && ast->asForStatement()) { + m_hasForStatement = true; + filtered.append(ast); + } else if (!m_hasForeachStatement && ast->asForeachStatement()) { + m_hasForeachStatement = true; + filtered.append(ast); + } + } + + return filtered; + } + +private: + bool m_hasSimpleDeclaration = false; + bool m_hasFunctionDefinition = false; + bool m_hasParameterDeclaration = false; + bool m_hasIfStatement = false; + bool m_hasWhileStatement = false; + bool m_hasForStatement = false; + bool m_hasForeachStatement = false; +}; + +} // anonymous namespace + +void ReformatPointerDeclaration::doMatch(const CppQuickFixInterface &interface, + QuickFixOperations &result) +{ + const QList<AST *> &path = interface.path(); + CppRefactoringFilePtr file = interface.currentFile(); + + Overview overview = CppCodeStyleSettings::currentProjectCodeStyleOverview(); + overview.showArgumentNames = true; + overview.showReturnTypes = true; + + const QTextCursor cursor = file->cursor(); + ChangeSet change; + PointerDeclarationFormatter formatter(file, overview, + PointerDeclarationFormatter::RespectCursor); + + if (cursor.hasSelection()) { + // This will no work always as expected since this function is only called if + // interface-path() is not empty. If the user selects the whole document via + // ctrl-a and there is an empty line in the end, then the cursor is not on + // any AST and therefore no quick fix will be triggered. + change = formatter.format(file->cppDocument()->translationUnit()->ast()); + if (!change.isEmpty()) + result << new ReformatPointerDeclarationOp(interface, change); + } else { + const QList<AST *> suitableASTs + = ReformatPointerDeclarationASTPathResultsFilter().filter(path); + for (AST *ast : suitableASTs) { + change = formatter.format(ast); + if (!change.isEmpty()) { + result << new ReformatPointerDeclarationOp(interface, change); + return; + } + } + } +} + +namespace { + +class ApplyDeclDefLinkOperation : public CppQuickFixOperation +{ +public: + explicit ApplyDeclDefLinkOperation(const CppQuickFixInterface &interface, + const std::shared_ptr<FunctionDeclDefLink> &link) + : CppQuickFixOperation(interface, 100) + , m_link(link) + {} + + void perform() override + { + if (editor()->declDefLink() == m_link) + editor()->applyDeclDefLinkChanges(/*don't jump*/false); + } + +protected: + virtual void performChanges(const CppRefactoringFilePtr &, const CppRefactoringChanges &) + { /* never called since perform is overridden */ } + +private: + std::shared_ptr<FunctionDeclDefLink> m_link; +}; + +} // anonymous namespace + +void ApplyDeclDefLinkChanges::doMatch(const CppQuickFixInterface &interface, + QuickFixOperations &result) +{ + std::shared_ptr<FunctionDeclDefLink> link = interface.editor()->declDefLink(); + if (!link || !link->isMarkerVisible()) + return; + + auto op = new ApplyDeclDefLinkOperation(interface, link); + op->setDescription(Tr::tr("Apply Function Signature Changes")); + result << op; +} + +void ExtraRefactoringOperations::doMatch(const CppQuickFixInterface &interface, + QuickFixOperations &result) +{ + const auto processor = CppModelManager::cppEditorDocumentProcessor(interface.filePath()); + if (processor) { + const auto clangFixItOperations = processor->extraRefactoringOperations(interface); + result.append(clangFixItOperations); + } +} + +void createCppQuickFixes() +{ + new RearrangeParamDeclarationList; + new ReformatPointerDeclaration; + + new ApplyDeclDefLinkChanges; + + registerInsertVirtualMethodsQuickfix(); + registerMoveClassToOwnFileQuickfix(); + registerRemoveUsingNamespaceQuickfix(); + registerCodeGenerationQuickfixes(); + registerConvertQt4ConnectQuickfix(); + registerMoveFunctionDefinitionQuickfixes(); + registerInsertFunctionDefinitionQuickfixes(); + registerBringIdentifierIntoScopeQuickfixes(); + registerConvertStringLiteralQuickfixes(); + registerCreateDeclarationFromUseQuickfixes(); + registerLogicalOperationQuickfixes(); + registerRewriteControlStatementQuickfixes(); + registerRewriteCommentQuickfixes(); + registerExtractFunctionQuickfix(); + registerExtractLiteralAsParameterQuickfix(); + registerConvertFromAndToPointerQuickfix(); + registerAssignToLocalVariableQuickfix(); + registerCompleteSwitchStatementQuickfix(); + registerConvertToMetaMethodCallQuickfix(); + registerSplitSimpleDeclarationQuickfix(); + registerConvertNumericLiteralQuickfix(); + registerConvertToCamelCaseQuickfix(); + + new ExtraRefactoringOperations; +} + +void destroyCppQuickFixes() +{ + for (int i = g_cppQuickFixFactories.size(); --i >= 0; ) + delete g_cppQuickFixFactories.at(i); +} + +} // namespace Internal +} // namespace CppEditor diff --git a/src/plugins/cppeditor/quickfixes/cppquickfixes.h b/src/plugins/cppeditor/quickfixes/cppquickfixes.h new file mode 100644 index 0000000000..c53c66bd84 --- /dev/null +++ b/src/plugins/cppeditor/quickfixes/cppquickfixes.h @@ -0,0 +1,68 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "cppquickfix.h" + +#include <variant> + +/// +/// Adding New Quick Fixes +/// +/// When adding new Quick Fixes, make sure that the doMatch() function is "cheap". +/// Otherwise, since the match() functions are also called to generate context menu +/// entries, the user might experience a delay opening the context menu. +/// + +namespace CppEditor { +namespace Internal { +using TypeOrExpr = std::variant<const CPlusPlus::ExpressionAST *, CPlusPlus::FullySpecifiedType>; + +void createCppQuickFixes(); +void destroyCppQuickFixes(); + +class ExtraRefactoringOperations : public CppQuickFixFactory +{ +public: + void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override; +}; + +/*! + Switches places of the parameter declaration under cursor + with the next or the previous one in the parameter declaration list + + Activates on: parameter declarations +*/ +class RearrangeParamDeclarationList : public CppQuickFixFactory +{ +public: + void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override; +}; + +/*! + Reformats a pointer, reference or rvalue reference type/declaration. + + Works also with selections (except when the cursor is not on any AST). + + Activates on: simple declarations, parameters and return types of function + declarations and definitions, control flow statements (if, + while, for, foreach) with declarations. +*/ +class ReformatPointerDeclaration : public CppQuickFixFactory +{ +public: + void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override; +}; + +/*! + Applies function signature changes + */ +class ApplyDeclDefLinkChanges: public CppQuickFixFactory +{ +public: + void doMatch(const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result) override; +}; + +} // namespace Internal +} // namespace CppEditor diff --git a/src/plugins/cppeditor/quickfixes/cppquickfixhelpers.cpp b/src/plugins/cppeditor/quickfixes/cppquickfixhelpers.cpp new file mode 100644 index 0000000000..40d19e8194 --- /dev/null +++ b/src/plugins/cppeditor/quickfixes/cppquickfixhelpers.cpp @@ -0,0 +1,193 @@ +// 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 "cppquickfixhelpers.h" + +#include "../cppcodestylesettings.h" +#include "../cppprojectfile.h" +#include "../includeutils.h" +#include "cppquickfixassistant.h" + +#include <cplusplus/CppRewriter.h> +#include <cplusplus/Overview.h> +#include <cplusplus/TypeOfExpression.h> + +using namespace CPlusPlus; +using namespace Utils; + +namespace CppEditor::Internal { + +void insertNewIncludeDirective( + const QString &include, + CppRefactoringFilePtr file, + const Document::Ptr &cppDocument, + ChangeSet &changes) +{ + // Find optimal position + unsigned newLinesToPrepend = 0; + unsigned newLinesToAppend = 0; + const int insertLine = lineForNewIncludeDirective( + file->filePath(), + file->document(), + cppDocument, + IgnoreMocIncludes, + AutoDetect, + include, + &newLinesToPrepend, + &newLinesToAppend); + QTC_ASSERT(insertLine >= 1, return); + const int insertPosition = file->position(insertLine, 1); + QTC_ASSERT(insertPosition >= 0, return); + + // Construct text to insert + const QString includeLine = QLatin1String("#include ") + include + QLatin1Char('\n'); + QString prependedNewLines, appendedNewLines; + while (newLinesToAppend--) + appendedNewLines += QLatin1String("\n"); + while (newLinesToPrepend--) + prependedNewLines += QLatin1String("\n"); + const QString textToInsert = prependedNewLines + includeLine + appendedNewLines; + + // Insert + changes.insert(insertPosition, textToInsert); +} + +ClassSpecifierAST *astForClassOperations(const CppQuickFixInterface &interface) +{ + const QList<AST *> &path = interface.path(); + if (path.isEmpty()) + return nullptr; + if (const auto classSpec = path.last()->asClassSpecifier()) // Cursor inside class decl? + return classSpec; + + // Cursor on a class name? + if (path.size() < 2) + return nullptr; + const SimpleNameAST * const nameAST = path.at(path.size() - 1)->asSimpleName(); + if (!nameAST || !interface.isCursorOn(nameAST)) + return nullptr; + if (const auto classSpec = path.at(path.size() - 2)->asClassSpecifier()) + return classSpec; + return nullptr; +} + +bool nameIncludesOperatorName(const Name *name) +{ + return name->asOperatorNameId() + || (name->asQualifiedNameId() && name->asQualifiedNameId()->name()->asOperatorNameId()); +} + +QString inlinePrefix(const FilePath &targetFile, const std::function<bool()> &extraCondition) +{ + if (ProjectFile::isHeader(ProjectFile::classify(targetFile.path())) + && (!extraCondition || extraCondition())) { + return "inline "; + } + return {}; +} + +Class *isMemberFunction(const CPlusPlus::LookupContext &context, CPlusPlus::Function *function) +{ + QTC_ASSERT(function, return nullptr); + + Scope *enclosingScope = function->enclosingScope(); + while (!(enclosingScope->asNamespace() || enclosingScope->asClass())) + enclosingScope = enclosingScope->enclosingScope(); + QTC_ASSERT(enclosingScope != nullptr, return nullptr); + + const Name *functionName = function->name(); + if (!functionName) + return nullptr; + + if (!functionName->asQualifiedNameId()) + return nullptr; // trying to add a declaration for a global function + + const QualifiedNameId *q = functionName->asQualifiedNameId(); + if (!q->base()) + return nullptr; + + if (ClassOrNamespace *binding = context.lookupType(q->base(), enclosingScope)) { + const QList<Symbol *> symbols = binding->symbols(); + for (Symbol *s : symbols) { + if (Class *matchingClass = s->asClass()) + return matchingClass; + } + } + + return nullptr; +} + +CPlusPlus::Namespace *isNamespaceFunction( + const CPlusPlus::LookupContext &context, CPlusPlus::Function *function) +{ + QTC_ASSERT(function, return nullptr); + if (isMemberFunction(context, function)) + return nullptr; + + Scope *enclosingScope = function->enclosingScope(); + while (!(enclosingScope->asNamespace() || enclosingScope->asClass())) + enclosingScope = enclosingScope->enclosingScope(); + QTC_ASSERT(enclosingScope != nullptr, return nullptr); + + const Name *functionName = function->name(); + if (!functionName) + return nullptr; + + // global namespace + if (!functionName->asQualifiedNameId()) { + const QList<Symbol *> symbols = context.globalNamespace()->symbols(); + for (Symbol *s : symbols) { + if (Namespace *matchingNamespace = s->asNamespace()) + return matchingNamespace; + } + return nullptr; + } + + const QualifiedNameId *q = functionName->asQualifiedNameId(); + if (!q->base()) + return nullptr; + + if (ClassOrNamespace *binding = context.lookupType(q->base(), enclosingScope)) { + const QList<Symbol *> symbols = binding->symbols(); + for (Symbol *s : symbols) { + if (Namespace *matchingNamespace = s->asNamespace()) + return matchingNamespace; + } + } + + return nullptr; +} + +QString nameString(const CPlusPlus::NameAST *name) +{ + return CppCodeStyleSettings::currentProjectCodeStyleOverview().prettyName(name->name); +} + +CPlusPlus::FullySpecifiedType typeOfExpr( + const ExpressionAST *expr, + const CppRefactoringFilePtr &file, + const Snapshot &snapshot, + const LookupContext &context) +{ + TypeOfExpression typeOfExpression; + typeOfExpression.init(file->cppDocument(), snapshot, context.bindings()); + Scope *scope = file->scopeAt(expr->firstToken()); + const QList<LookupItem> result + = typeOfExpression(file->textOf(expr).toUtf8(), scope, TypeOfExpression::Preprocess); + if (result.isEmpty()) + return {}; + + SubstitutionEnvironment env; + env.setContext(context); + env.switchScope(result.first().scope()); + ClassOrNamespace *con = typeOfExpression.context().lookupType(scope); + if (!con) + con = typeOfExpression.context().globalNamespace(); + UseMinimalNames q(con); + env.enter(&q); + + Control *control = context.bindings()->control().get(); + return rewriteType(result.first().type(), &env, control); +} + +} // namespace CppEditor::Internal diff --git a/src/plugins/cppeditor/quickfixes/cppquickfixhelpers.h b/src/plugins/cppeditor/quickfixes/cppquickfixhelpers.h new file mode 100644 index 0000000000..fd655d489f --- /dev/null +++ b/src/plugins/cppeditor/quickfixes/cppquickfixhelpers.h @@ -0,0 +1,48 @@ +// 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 + +#include "../cpprefactoringchanges.h" +#include "cppquickfixes.h" + +#include <QStringList> + +namespace CppEditor::Internal { +class CppQuickFixInterface; + +// These are generated functions that should not be offered in quickfixes. +const QStringList magicQObjectFunctions(); + +// Given include is e.g. "afile.h" or <afile.h> (quotes/angle brackets included!). +void insertNewIncludeDirective( + const QString &include, + CppRefactoringFilePtr file, + const CPlusPlus::Document::Ptr &cppDocument, + Utils::ChangeSet &changes); + +// Returns a non-null value if and only if the cursor is on the name of a (proper) class +// declaration or at some place inside the body of a class declaration that does not +// correspond to an AST of its own, i.e. on "empty space". +CPlusPlus::ClassSpecifierAST *astForClassOperations(const CppQuickFixInterface &interface); + +bool nameIncludesOperatorName(const CPlusPlus::Name *name); + +QString inlinePrefix(const Utils::FilePath &targetFile, + const std::function<bool()> &extraCondition = {}); + +CPlusPlus::Class *isMemberFunction( + const CPlusPlus::LookupContext &context, CPlusPlus::Function *function); + +CPlusPlus::Namespace *isNamespaceFunction( + const CPlusPlus::LookupContext &context, CPlusPlus::Function *function); + +QString nameString(const CPlusPlus::NameAST *name); + +CPlusPlus::FullySpecifiedType typeOfExpr( + const CPlusPlus::ExpressionAST *expr, + const CppRefactoringFilePtr &file, + const CPlusPlus::Snapshot &snapshot, + const CPlusPlus::LookupContext &context); + +} // namespace CppEditor::Internal diff --git a/src/plugins/cppeditor/cppquickfixprojectsettings.cpp b/src/plugins/cppeditor/quickfixes/cppquickfixprojectsettings.cpp index 3f3f4bc366..0313103433 100644 --- a/src/plugins/cppeditor/cppquickfixprojectsettings.cpp +++ b/src/plugins/cppeditor/quickfixes/cppquickfixprojectsettings.cpp @@ -3,8 +3,8 @@ #include "cppquickfixprojectsettings.h" -#include "cppeditorconstants.h" -#include "cppeditortr.h" +#include "../cppeditorconstants.h" +#include "../cppeditortr.h" #include <coreplugin/icore.h> diff --git a/src/plugins/cppeditor/cppquickfixprojectsettings.h b/src/plugins/cppeditor/quickfixes/cppquickfixprojectsettings.h index d8e871515f..d8e871515f 100644 --- a/src/plugins/cppeditor/cppquickfixprojectsettings.h +++ b/src/plugins/cppeditor/quickfixes/cppquickfixprojectsettings.h diff --git a/src/plugins/cppeditor/cppquickfixprojectsettingswidget.cpp b/src/plugins/cppeditor/quickfixes/cppquickfixprojectsettingswidget.cpp index feb9777e45..8046d80a60 100644 --- a/src/plugins/cppeditor/cppquickfixprojectsettingswidget.cpp +++ b/src/plugins/cppeditor/quickfixes/cppquickfixprojectsettingswidget.cpp @@ -3,8 +3,8 @@ #include "cppquickfixprojectsettingswidget.h" -#include "cppeditorconstants.h" -#include "cppeditortr.h" +#include "../cppeditorconstants.h" +#include "../cppeditortr.h" #include "cppquickfixprojectsettings.h" #include "cppquickfixsettingswidget.h" diff --git a/src/plugins/cppeditor/cppquickfixprojectsettingswidget.h b/src/plugins/cppeditor/quickfixes/cppquickfixprojectsettingswidget.h index a88395cc73..a88395cc73 100644 --- a/src/plugins/cppeditor/cppquickfixprojectsettingswidget.h +++ b/src/plugins/cppeditor/quickfixes/cppquickfixprojectsettingswidget.h diff --git a/src/plugins/cppeditor/cppquickfixsettings.cpp b/src/plugins/cppeditor/quickfixes/cppquickfixsettings.cpp index 4ce1082267..e08de44675 100644 --- a/src/plugins/cppeditor/cppquickfixsettings.cpp +++ b/src/plugins/cppeditor/quickfixes/cppquickfixsettings.cpp @@ -3,8 +3,8 @@ #include "cppquickfixsettings.h" -#include "cppcodestylesettings.h" -#include "cppeditorconstants.h" +#include "../cppcodestylesettings.h" +#include "../cppeditorconstants.h" #include <coreplugin/icore.h> diff --git a/src/plugins/cppeditor/cppquickfixsettings.h b/src/plugins/cppeditor/quickfixes/cppquickfixsettings.h index d033516276..d033516276 100644 --- a/src/plugins/cppeditor/cppquickfixsettings.h +++ b/src/plugins/cppeditor/quickfixes/cppquickfixsettings.h diff --git a/src/plugins/cppeditor/cppquickfixsettingspage.cpp b/src/plugins/cppeditor/quickfixes/cppquickfixsettingspage.cpp index 4e1297f5c2..5933694bbc 100644 --- a/src/plugins/cppeditor/cppquickfixsettingspage.cpp +++ b/src/plugins/cppeditor/quickfixes/cppquickfixsettingspage.cpp @@ -3,8 +3,8 @@ #include "cppquickfixsettingspage.h" -#include "cppeditorconstants.h" -#include "cppeditortr.h" +#include "../cppeditorconstants.h" +#include "../cppeditortr.h" #include "cppquickfixsettingswidget.h" #include <coreplugin/dialogs/ioptionspage.h> diff --git a/src/plugins/cppeditor/cppquickfixsettingspage.h b/src/plugins/cppeditor/quickfixes/cppquickfixsettingspage.h index 6288c9f9f2..6288c9f9f2 100644 --- a/src/plugins/cppeditor/cppquickfixsettingspage.h +++ b/src/plugins/cppeditor/quickfixes/cppquickfixsettingspage.h diff --git a/src/plugins/cppeditor/cppquickfixsettingswidget.cpp b/src/plugins/cppeditor/quickfixes/cppquickfixsettingswidget.cpp index c589a26b9d..9445ed3c54 100644 --- a/src/plugins/cppeditor/cppquickfixsettingswidget.cpp +++ b/src/plugins/cppeditor/quickfixes/cppquickfixsettingswidget.cpp @@ -3,7 +3,7 @@ #include "cppquickfixsettingswidget.h" -#include "cppeditortr.h" +#include "../cppeditortr.h" #include "cppquickfixsettings.h" #include <utils/layoutbuilder.h> diff --git a/src/plugins/cppeditor/cppquickfixsettingswidget.h b/src/plugins/cppeditor/quickfixes/cppquickfixsettingswidget.h index e11d81a9f2..e11d81a9f2 100644 --- a/src/plugins/cppeditor/cppquickfixsettingswidget.h +++ b/src/plugins/cppeditor/quickfixes/cppquickfixsettingswidget.h diff --git a/src/plugins/cppeditor/quickfixes/createdeclarationfromuse.cpp b/src/plugins/cppeditor/quickfixes/createdeclarationfromuse.cpp new file mode 100644 index 0000000000..c753b61c7e --- /dev/null +++ b/src/plugins/cppeditor/quickfixes/createdeclarationfromuse.cpp @@ -0,0 +1,1213 @@ +// 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 "createdeclarationfromuse.h" + +#include "../cppcodestylesettings.h" +#include "../cppeditortr.h" +#include "../cppeditorwidget.h" +#include "../cpprefactoringchanges.h" +#include "../insertionpointlocator.h" +#include "../symbolfinder.h" +#include "cppquickfixes.h" +#include "cppquickfixhelpers.h" +#include "cppquickfixprojectsettings.h" + +#include <coreplugin/icore.h> +#include <cplusplus/Overview.h> +#include <cplusplus/TypeOfExpression.h> +#include <projectexplorer/projecttree.h> + +#include <QInputDialog> + +#ifdef WITH_TESTS +#include "cppquickfix_test.h" +#include <QtTest> +#endif + +using namespace CPlusPlus; +using namespace ProjectExplorer; +using namespace TextEditor; +using namespace Utils; + +namespace CppEditor::Internal { +namespace { + +// FIXME: Needs to consider the scope at the insertion site. +static QString declFromExpr( + const TypeOrExpr &typeOrExpr, + const CallAST *call, + const NameAST *varName, + const Snapshot &snapshot, + const LookupContext &context, + const CppRefactoringFilePtr &file, + bool makeConst) +{ + const auto getTypeFromUser = [varName, call]() -> QString { + if (call) + return {}; + const QString typeFromUser = QInputDialog::getText( + Core::ICore::dialogParent(), + Tr::tr("Provide the type"), + Tr::tr("Data type:"), + QLineEdit::Normal); + if (!typeFromUser.isEmpty()) + return typeFromUser + ' ' + nameString(varName); + return {}; + }; + const auto getTypeOfExpr = [&](const ExpressionAST *expr) -> FullySpecifiedType { + return typeOfExpr(expr, file, snapshot, context); + }; + + const Overview oo = CppCodeStyleSettings::currentProjectCodeStyleOverview(); + const FullySpecifiedType type = std::holds_alternative<FullySpecifiedType>(typeOrExpr) + ? std::get<FullySpecifiedType>(typeOrExpr) + : getTypeOfExpr(std::get<const ExpressionAST *>(typeOrExpr)); + if (!call) + return type.isValid() ? oo.prettyType(type, varName->name) : getTypeFromUser(); + + Function func(file->cppDocument()->translationUnit(), 0, varName->name); + func.setConst(makeConst); + for (ExpressionListAST *it = call->expression_list; it; it = it->next) { + Argument *const arg = new Argument(nullptr, 0, nullptr); + arg->setType(getTypeOfExpr(it->value)); + func.addMember(arg); + } + return oo.prettyType(type) + ' ' + oo.prettyType(func.type(), varName->name); +} + + + + +class InsertDeclOperation: public CppQuickFixOperation +{ +public: + InsertDeclOperation(const CppQuickFixInterface &interface, + const FilePath &targetFilePath, const Class *targetSymbol, + InsertionPointLocator::AccessSpec xsSpec, const QString &decl, int priority) + : CppQuickFixOperation(interface, priority) + , m_targetFilePath(targetFilePath) + , m_targetSymbol(targetSymbol) + , m_xsSpec(xsSpec) + , m_decl(decl) + { + setDescription(Tr::tr("Add %1 Declaration") + .arg(InsertionPointLocator::accessSpecToString(xsSpec))); + } + + void perform() override + { + CppRefactoringChanges refactoring(snapshot()); + + InsertionPointLocator locator(refactoring); + const InsertionLocation loc = locator.methodDeclarationInClass( + m_targetFilePath, m_targetSymbol, m_xsSpec); + QTC_ASSERT(loc.isValid(), return); + + CppRefactoringFilePtr targetFile = refactoring.cppFile(m_targetFilePath); + int targetPosition = targetFile->position(loc.line(), loc.column()); + + ChangeSet target; + target.insert(targetPosition, loc.prefix() + m_decl); + targetFile->setChangeSet(target); + targetFile->setOpenEditor(true, targetPosition); + targetFile->apply(); + } + + static QString generateDeclaration(const Function *function) + { + Overview oo = CppCodeStyleSettings::currentProjectCodeStyleOverview(); + oo.showFunctionSignatures = true; + oo.showReturnTypes = true; + oo.showArgumentNames = true; + oo.showEnclosingTemplate = true; + + QString decl; + decl += oo.prettyType(function->type(), function->unqualifiedName()); + decl += QLatin1String(";\n"); + + return decl; + } + +private: + FilePath m_targetFilePath; + const Class *m_targetSymbol; + InsertionPointLocator::AccessSpec m_xsSpec; + QString m_decl; +}; + +class DeclOperationFactory +{ +public: + DeclOperationFactory(const CppQuickFixInterface &interface, const FilePath &filePath, + const Class *matchingClass, const QString &decl) + : m_interface(interface) + , m_filePath(filePath) + , m_matchingClass(matchingClass) + , m_decl(decl) + {} + + QuickFixOperation *operator()(InsertionPointLocator::AccessSpec xsSpec, int priority) + { + return new InsertDeclOperation(m_interface, m_filePath, m_matchingClass, xsSpec, m_decl, priority); + } + +private: + const CppQuickFixInterface &m_interface; + const FilePath &m_filePath; + const Class *m_matchingClass; + const QString &m_decl; +}; + +class InsertMemberFromInitializationOp : public CppQuickFixOperation +{ +public: + InsertMemberFromInitializationOp( + const CppQuickFixInterface &interface, + const Class *theClass, + const NameAST *memberName, + const TypeOrExpr &typeOrExpr, + const CallAST *call, + InsertionPointLocator::AccessSpec accessSpec, + bool makeStatic, + bool makeConst) + : CppQuickFixOperation(interface), + m_class(theClass), m_memberName(memberName), m_typeOrExpr(typeOrExpr), m_call(call), + m_accessSpec(accessSpec), m_makeStatic(makeStatic), m_makeConst(makeConst) + { + if (call) + setDescription(Tr::tr("Add Member Function \"%1\"").arg(nameString(memberName))); + else + setDescription(Tr::tr("Add Class Member \"%1\"").arg(nameString(memberName))); + } + +private: + void perform() override + { + QString decl = declFromExpr(m_typeOrExpr, m_call, m_memberName, snapshot(), context(), + currentFile(), m_makeConst); + if (decl.isEmpty()) + return; + if (m_makeStatic) + decl.prepend("static "); + + const CppRefactoringChanges refactoring(snapshot()); + const InsertionPointLocator locator(refactoring); + const FilePath filePath = FilePath::fromUtf8(m_class->fileName()); + const InsertionLocation loc = locator.methodDeclarationInClass( + filePath, m_class, m_accessSpec); + QTC_ASSERT(loc.isValid(), return); + + CppRefactoringFilePtr targetFile = refactoring.cppFile(filePath); + const int targetPosition = targetFile->position(loc.line(), loc.column()); + ChangeSet target; + target.insert(targetPosition, loc.prefix() + decl + ";\n"); + targetFile->setChangeSet(target); + targetFile->apply(); + } + + const Class * const m_class; + const NameAST * const m_memberName; + const TypeOrExpr m_typeOrExpr; + const CallAST * m_call; + const InsertionPointLocator::AccessSpec m_accessSpec; + const bool m_makeStatic; + const bool m_makeConst; +}; + +class AddLocalDeclarationOp: public CppQuickFixOperation +{ +public: + AddLocalDeclarationOp(const CppQuickFixInterface &interface, + int priority, + const BinaryExpressionAST *binaryAST, + const SimpleNameAST *simpleNameAST) + : CppQuickFixOperation(interface, priority) + , binaryAST(binaryAST) + , simpleNameAST(simpleNameAST) + { + setDescription(Tr::tr("Add Local Declaration")); + } + + void perform() override + { + CppRefactoringChanges refactoring(snapshot()); + CppRefactoringFilePtr currentFile = refactoring.cppFile(filePath()); + QString declaration = getDeclaration(); + + if (!declaration.isEmpty()) { + ChangeSet changes; + changes.replace(currentFile->startOf(binaryAST), + currentFile->endOf(simpleNameAST), + declaration); + currentFile->setChangeSet(changes); + currentFile->apply(); + } + } + +private: + QString getDeclaration() + { + CppRefactoringChanges refactoring(snapshot()); + CppRefactoringFilePtr currentFile = refactoring.cppFile(filePath()); + Overview oo = CppCodeStyleSettings::currentProjectCodeStyleOverview(); + const auto settings = CppQuickFixProjectsSettings::getQuickFixSettings( + ProjectTree::currentProject()); + + if (currentFile->cppDocument()->languageFeatures().cxx11Enabled && settings->useAuto) + return "auto " + oo.prettyName(simpleNameAST->name); + return declFromExpr(binaryAST->right_expression, nullptr, simpleNameAST, snapshot(), + context(), currentFile, false); + } + + const BinaryExpressionAST *binaryAST; + const SimpleNameAST *simpleNameAST; +}; + +//! Adds a declarations to a definition +class InsertDeclFromDef: public CppQuickFixFactory +{ +#ifdef WITH_TESTS +public: + static QObject *createTest(); +#endif + +private: + void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override + { + const QList<AST *> &path = interface.path(); + CppRefactoringFilePtr file = interface.currentFile(); + + FunctionDefinitionAST *funDef = nullptr; + int idx = 0; + for (; idx < path.size(); ++idx) { + AST *node = path.at(idx); + if (idx > 1) { + if (DeclaratorIdAST *declId = node->asDeclaratorId()) { + if (file->isCursorOn(declId)) { + if (FunctionDefinitionAST *candidate = path.at(idx - 2)->asFunctionDefinition()) { + funDef = candidate; + break; + } + } + } + } + + if (node->asClassSpecifier()) + return; + } + + if (!funDef || !funDef->symbol) + return; + + Function *fun = funDef->symbol; + if (Class *matchingClass = isMemberFunction(interface.context(), fun)) { + const QualifiedNameId *qName = fun->name()->asQualifiedNameId(); + for (Symbol *symbol = matchingClass->find(qName->identifier()); + symbol; symbol = symbol->next()) { + Symbol *s = symbol; + if (fun->enclosingScope()->asTemplate()) { + if (const Template *templ = s->type()->asTemplateType()) { + if (Symbol *decl = templ->declaration()) { + if (decl->type()->asFunctionType()) + s = decl; + } + } + } + if (!s->name() + || !qName->identifier()->match(s->identifier()) + || !s->type()->asFunctionType()) + continue; + + if (s->type().match(fun->type())) { + // Declaration exists. + return; + } + } + const FilePath fileName = matchingClass->filePath(); + const QString decl = InsertDeclOperation::generateDeclaration(fun); + + // Add several possible insertion locations for declaration + DeclOperationFactory operation(interface, fileName, matchingClass, decl); + + result << operation(InsertionPointLocator::Public, 5) + << operation(InsertionPointLocator::PublicSlot, 4) + << operation(InsertionPointLocator::Protected, 3) + << operation(InsertionPointLocator::ProtectedSlot, 2) + << operation(InsertionPointLocator::Private, 1) + << operation(InsertionPointLocator::PrivateSlot, 0); + } + } +}; + +class AddDeclarationForUndeclaredIdentifier : public CppQuickFixFactory +{ +public: +#ifdef WITH_TESTS + static QObject *createTest(); + void setMembersOnly() { m_membersOnly = true; } +#endif + +private: + void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override + { + // Are we on a name? + const QList<AST *> &path = interface.path(); + if (path.isEmpty()) + return; + if (!path.last()->asSimpleName()) + return; + + // Special case: Member initializer. + if (!checkForMemberInitializer(interface, result)) + return; + + // Are we inside a function? + const FunctionDefinitionAST *func = nullptr; + for (auto it = path.rbegin(); !func && it != path.rend(); ++it) + func = (*it)->asFunctionDefinition(); + if (!func) + return; + + // Is this name declared somewhere already? + const CursorInEditor cursorInEditor(interface.cursor(), interface.filePath(), + interface.editor(), interface.editor()->textDocument()); + const auto followSymbolFallback = [&](const Link &link) { + if (!link.hasValidTarget()) + collectOperations(interface, result); + }; + CppModelManager::followSymbol(cursorInEditor, followSymbolFallback, false, false, + FollowSymbolMode::Exact, + CppModelManager::Backend::Builtin); + } + + void collectOperations(const CppQuickFixInterface &interface, + QuickFixOperations &result) + { + const QList<AST *> &path = interface.path(); + const CppRefactoringFilePtr &file = interface.currentFile(); + for (int index = path.size() - 1; index != -1; --index) { + if (const auto call = path.at(index)->asCall()) + return handleCall(call, interface, result); + + // We only trigger if the identifier appears on the left-hand side of an + // assignment expression. + const auto binExpr = path.at(index)->asBinaryExpression(); + if (!binExpr) + continue; + if (!binExpr->left_expression || !binExpr->right_expression + || file->tokenAt(binExpr->binary_op_token).kind() != T_EQUAL + || !interface.isCursorOn(binExpr->left_expression)) { + return; + } + + // In the case of "a.|b = c", find out the type of a, locate the class declaration + // and add a member b there. + if (const auto memberAccess = binExpr->left_expression->asMemberAccess()) { + if (interface.isCursorOn(memberAccess->member_name) + && memberAccess->member_name == path.last()) { + maybeAddMember(interface, file->scopeAt(memberAccess->firstToken()), + file->textOf(memberAccess->base_expression).toUtf8(), + binExpr->right_expression, nullptr, result); + } + return; + } + + const auto idExpr = binExpr->left_expression->asIdExpression(); + if (!idExpr || !idExpr->name) + return; + + // In the case of "A::|b = c", add a static member b to A. + if (const auto qualName = idExpr->name->asQualifiedName()) { + return maybeAddStaticMember(interface, qualName, binExpr->right_expression, nullptr, + result); + } + + // For an unqualified access, offer a local declaration and, if we are + // in a member function, a member declaration. + if (const auto simpleName = idExpr->name->asSimpleName()) { + if (!m_membersOnly) + result << new AddLocalDeclarationOp(interface, index, binExpr, simpleName); + maybeAddMember(interface, file->scopeAt(idExpr->firstToken()), "this", + binExpr->right_expression, nullptr, result); + return; + } + } + } + + void handleCall(const CPlusPlus::CallAST *call, const CppQuickFixInterface &interface, + QuickFixOperations &result) + { + if (!call->base_expression) + return; + + // In order to find out the return type, we need to check the context of the call. + // If it is a statement expression, the type is void, if it's a binary expression, + // we assume the type of the other side of the expression, if it's a return statement, + // we use the return type of the surrounding function, and if it's a declaration, + // we use the type of the variable. Other cases are not supported. + const QList<AST *> &path = interface.path(); + const CppRefactoringFilePtr &file = interface.currentFile(); + TypeOrExpr returnTypeOrExpr; + for (auto it = path.rbegin(); it != path.rend(); ++it) { + if ((*it)->asCompoundStatement()) + return; + if ((*it)->asExpressionStatement()) { + returnTypeOrExpr = FullySpecifiedType(new VoidType); + break; + } + if (const auto binExpr = (*it)->asBinaryExpression()) { + returnTypeOrExpr = interface.isCursorOn(binExpr->left_expression) + ? binExpr->right_expression : binExpr->left_expression; + break; + } + if ((*it)->asReturnStatement()) { + for (auto it2 = std::next(it); it2 != path.rend(); ++it2) { + if (const auto func = (*it2)->asFunctionDefinition()) { + if (!func->symbol) + return; + returnTypeOrExpr = func->symbol->returnType(); + break; + } + } + break; + } + if (const auto declarator = (*it)->asDeclarator()) { + if (!interface.isCursorOn(declarator->initializer)) + return; + const auto decl = (*std::next(it))->asSimpleDeclaration(); + if (!decl || !decl->symbols) + return; + if (!decl->symbols->value->type().isValid()) + return; + returnTypeOrExpr = decl->symbols->value->type(); + break; + } + } + + if (std::holds_alternative<const ExpressionAST *>(returnTypeOrExpr) + && !std::get<const ExpressionAST *>(returnTypeOrExpr)) { + return; + } + + // a.f() + if (const auto memberAccess = call->base_expression->asMemberAccess()) { + if (!interface.isCursorOn(memberAccess->member_name)) + return; + maybeAddMember( + interface, file->scopeAt(call->firstToken()), + file->textOf(memberAccess->base_expression).toUtf8(), returnTypeOrExpr, call, result); + } + + const auto idExpr = call->base_expression->asIdExpression(); + if (!idExpr || !idExpr->name) + return; + + // A::f() + if (const auto qualName = idExpr->name->asQualifiedName()) + return maybeAddStaticMember(interface, qualName, returnTypeOrExpr, call, result); + + // f() + if (idExpr->name->asSimpleName()) { + maybeAddMember(interface, file->scopeAt(idExpr->firstToken()), "this", + returnTypeOrExpr, call, result); + } + } + + // Returns whether to still do other checks. + bool checkForMemberInitializer(const CppQuickFixInterface &interface, + QuickFixOperations &result) + { + const QList<AST *> &path = interface.path(); + const int size = path.size(); + if (size < 4) + return true; + const MemInitializerAST * const memInitializer = path.at(size - 2)->asMemInitializer(); + if (!memInitializer) + return true; + if (!path.at(size - 3)->asCtorInitializer()) + return true; + const FunctionDefinitionAST * ctor = path.at(size - 4)->asFunctionDefinition(); + if (!ctor) + return false; + + // Now find the class. + const Class *theClass = nullptr; + if (size > 4) { + const ClassSpecifierAST * const classSpec = path.at(size - 5)->asClassSpecifier(); + if (classSpec) // Inline constructor. We get the class directly. + theClass = classSpec->symbol; + } + if (!theClass) { + // Out-of-line constructor. We need to find the class. + SymbolFinder finder; + const QList<Declaration *> matches = finder.findMatchingDeclaration( + LookupContext(interface.currentFile()->cppDocument(), interface.snapshot()), + ctor->symbol); + if (!matches.isEmpty()) + theClass = matches.first()->enclosingClass(); + } + + if (!theClass) + return false; + + const SimpleNameAST * const name = path.at(size - 1)->asSimpleName(); + QTC_ASSERT(name, return false); + + // Check whether the member exists already. + if (theClass->find(interface.currentFile()->cppDocument()->translationUnit()->identifier( + name->identifier_token))) { + return false; + } + + result << new InsertMemberFromInitializationOp( + interface, theClass, memInitializer->name->asSimpleName(), memInitializer->expression, + nullptr, InsertionPointLocator::Private, false, false); + return false; + } + + void maybeAddMember(const CppQuickFixInterface &interface, CPlusPlus::Scope *scope, + const QByteArray &classTypeExpr, const TypeOrExpr &typeOrExpr, + const CPlusPlus::CallAST *call, QuickFixOperations &result) + { + const QList<AST *> &path = interface.path(); + + TypeOfExpression typeOfExpression; + typeOfExpression.init(interface.semanticInfo().doc, interface.snapshot(), + interface.context().bindings()); + const QList<LookupItem> lhsTypes = typeOfExpression( + classTypeExpr, scope, + TypeOfExpression::Preprocess); + if (lhsTypes.isEmpty()) + return; + + const Type *type = lhsTypes.first().type().type(); + if (!type) + return; + if (type->asPointerType()) { + type = type->asPointerType()->elementType().type(); + if (!type) + return; + } + const auto namedType = type->asNamedType(); + if (!namedType) + return; + const ClassOrNamespace * const classOrNamespace + = interface.context().lookupType(namedType->name(), scope); + if (!classOrNamespace || !classOrNamespace->rootClass()) + return; + + const Class * const theClass = classOrNamespace->rootClass(); + bool needsStatic = lhsTypes.first().type().isStatic(); + + // If the base expression refers to the same class that the member function is in, + // then we want to insert a private member, otherwise a public one. + const FunctionDefinitionAST *func = nullptr; + for (auto it = path.rbegin(); !func && it != path.rend(); ++it) + func = (*it)->asFunctionDefinition(); + QTC_ASSERT(func, return); + InsertionPointLocator::AccessSpec accessSpec = InsertionPointLocator::Public; + for (int i = 0; i < theClass->memberCount(); ++i) { + if (theClass->memberAt(i) == func->symbol) { + accessSpec = InsertionPointLocator::Private; + needsStatic = func->symbol->isStatic(); + break; + } + } + if (accessSpec == InsertionPointLocator::Public) { + QList<Declaration *> decls; + QList<Declaration *> dummy; + SymbolFinder().findMatchingDeclaration(interface.context(), func->symbol, &decls, + &dummy, &dummy); + for (const Declaration * const decl : std::as_const(decls)) { + for (int i = 0; i < theClass->memberCount(); ++i) { + if (theClass->memberAt(i) == decl) { + accessSpec = InsertionPointLocator::Private; + needsStatic = decl->isStatic(); + break; + } + } + if (accessSpec == InsertionPointLocator::Private) + break; + } + } + result << new InsertMemberFromInitializationOp(interface, theClass, path.last()->asName(), + typeOrExpr, call, accessSpec, needsStatic, + func->symbol->isConst()); + } + + void maybeAddStaticMember( + const CppQuickFixInterface &interface, const CPlusPlus::QualifiedNameAST *qualName, + const TypeOrExpr &typeOrExpr, const CPlusPlus::CallAST *call, + QuickFixOperations &result) + { + const QList<AST *> &path = interface.path(); + + if (!interface.isCursorOn(qualName->unqualified_name)) + return; + if (qualName->unqualified_name != path.last()) + return; + if (!qualName->nested_name_specifier_list) + return; + + const NameAST * const topLevelName + = qualName->nested_name_specifier_list->value->class_or_namespace_name; + if (!topLevelName) + return; + ClassOrNamespace * const classOrNamespace = interface.context().lookupType( + topLevelName->name, interface.currentFile()->scopeAt(qualName->firstToken())); + if (!classOrNamespace) + return; + QList<const Name *> otherNames; + for (auto it = qualName->nested_name_specifier_list->next; it; it = it->next) { + if (!it->value || !it->value->class_or_namespace_name) + return; + otherNames << it->value->class_or_namespace_name->name; + } + + const Class *theClass = nullptr; + if (!otherNames.isEmpty()) { + const Symbol * const symbol = classOrNamespace->lookupInScope(otherNames); + if (!symbol) + return; + theClass = symbol->asClass(); + } else { + theClass = classOrNamespace->rootClass(); + } + if (theClass) { + result << new InsertMemberFromInitializationOp( + interface, theClass, path.last()->asName(), typeOrExpr, call, + InsertionPointLocator::Public, true, false); + } + } + + bool m_membersOnly = false; +}; + +#ifdef WITH_TESTS +using namespace Tests; + +class AddDeclarationForUndeclaredIdentifierTest : public QObject +{ + Q_OBJECT + +private slots: + // QTCREATORBUG-26004 + void testLocalDeclFromUse() + { + const QByteArray original = "void func() {\n" + " QStringList list;\n" + " @it = list.cbegin();\n" + "}\n"; + const QByteArray expected = "void func() {\n" + " QStringList list;\n" + " auto it = list.cbegin();\n" + "}\n"; + AddDeclarationForUndeclaredIdentifier factory; + QuickFixOperationTest(singleDocument(original, expected), &factory); + } + + void testInsertMemberFromUse_data() + { + QTest::addColumn<QByteArray>("original"); + QTest::addColumn<QByteArray>("expected"); + + QByteArray original; + QByteArray expected; + + original = + "class C {\n" + "public:\n" + " C(int x) : @m_x(x) {}\n" + "private:\n" + " int m_y;\n" + "};\n"; + expected = + "class C {\n" + "public:\n" + " C(int x) : m_x(x) {}\n" + "private:\n" + " int m_y;\n" + " int m_x;\n" + "};\n"; + QTest::addRow("inline constructor") << original << expected; + + original = + "class C {\n" + "public:\n" + " C(int x, double d);\n" + "private:\n" + " int m_x;\n" + "};\n" + "C::C(int x, double d) : m_x(x), @m_d(d)\n"; + expected = + "class C {\n" + "public:\n" + " C(int x, double d);\n" + "private:\n" + " int m_x;\n" + " double m_d;\n" + "};\n" + "C::C(int x, double d) : m_x(x), m_d(d)\n"; + QTest::addRow("out-of-line constructor") << original << expected; + + original = + "class C {\n" + "public:\n" + " C(int x) : @m_x(x) {}\n" + "private:\n" + " int m_x;\n" + "};\n"; + expected = ""; + QTest::addRow("member already present") << original << expected; + + original = + "int func() { return 0; }\n" + "class C {\n" + "public:\n" + " C() : @m_x(func()) {}\n" + "private:\n" + " int m_y;\n" + "};\n"; + expected = + "int func() { return 0; }\n" + "class C {\n" + "public:\n" + " C() : m_x(func()) {}\n" + "private:\n" + " int m_y;\n" + " int m_x;\n" + "};\n"; + QTest::addRow("initialization via function call") << original << expected; + + original = + "struct S {\n\n};\n" + "class C {\n" + "public:\n" + " void setValue(int v) { m_s.@value = v; }\n" + "private:\n" + " S m_s;\n" + "};\n"; + expected = + "struct S {\n\n" + " int value;\n" + "};\n" + "class C {\n" + "public:\n" + " void setValue(int v) { m_s.value = v; }\n" + "private:\n" + " S m_s;\n" + "};\n"; + QTest::addRow("add member to other struct") << original << expected; + + original = + "struct S {\n\n};\n" + "class C {\n" + "public:\n" + " void setValue(int v) { S::@value = v; }\n" + "};\n"; + expected = + "struct S {\n\n" + " static int value;\n" + "};\n" + "class C {\n" + "public:\n" + " void setValue(int v) { S::value = v; }\n" + "};\n"; + QTest::addRow("add static member to other struct (explicit)") << original << expected; + + original = + "struct S {\n\n};\n" + "class C {\n" + "public:\n" + " void setValue(int v) { m_s.@value = v; }\n" + "private:\n" + " static S m_s;\n" + "};\n"; + expected = + "struct S {\n\n" + " static int value;\n" + "};\n" + "class C {\n" + "public:\n" + " void setValue(int v) { m_s.value = v; }\n" + "private:\n" + " static S m_s;\n" + "};\n"; + QTest::addRow("add static member to other struct (implicit)") << original << expected; + + original = + "class C {\n" + "public:\n" + " void setValue(int v);\n" + "};\n" + "void C::setValue(int v) { this->@m_value = v; }\n"; + expected = + "class C {\n" + "public:\n" + " void setValue(int v);\n" + "private:\n" + " int m_value;\n" + "};\n" + "void C::setValue(int v) { this->@m_value = v; }\n"; + QTest::addRow("add member to this (explicit)") << original << expected; + + original = + "class C {\n" + "public:\n" + " void setValue(int v) { @m_value = v; }\n" + "};\n"; + expected = + "class C {\n" + "public:\n" + " void setValue(int v) { m_value = v; }\n" + "private:\n" + " int m_value;\n" + "};\n"; + QTest::addRow("add member to this (implicit)") << original << expected; + + original = + "class C {\n" + "public:\n" + " static void setValue(int v) { @m_value = v; }\n" + "};\n"; + expected = + "class C {\n" + "public:\n" + " static void setValue(int v) { m_value = v; }\n" + "private:\n" + " static int m_value;\n" + "};\n"; + QTest::addRow("add static member to this (inline)") << original << expected; + + original = + "class C {\n" + "public:\n" + " static void setValue(int v);\n" + "};\n" + "void C::setValue(int v) { @m_value = v; }\n"; + expected = + "class C {\n" + "public:\n" + " static void setValue(int v);\n" + "private:\n" + " static int m_value;\n" + "};\n" + "void C::setValue(int v) { @m_value = v; }\n"; + QTest::addRow("add static member to this (non-inline)") << original << expected; + + original = + "struct S {\n\n};\n" + "class C {\n" + "public:\n" + " void setValue(int v) { m_s.@setValue(v); }\n" + "private:\n" + " S m_s;\n" + "};\n"; + expected = + "struct S {\n\n" + " void setValue(int);\n" + "};\n" + "class C {\n" + "public:\n" + " void setValue(int v) { m_s.setValue(v); }\n" + "private:\n" + " S m_s;\n" + "};\n"; + QTest::addRow("add member function to other struct") << original << expected; + + original = + "struct S {\n\n};\n" + "class C {\n" + "public:\n" + " void setValue(int v) { S::@setValue(v); }\n" + "};\n"; + expected = + "struct S {\n\n" + " static void setValue(int);\n" + "};\n" + "class C {\n" + "public:\n" + " void setValue(int v) { S::setValue(v); }\n" + "};\n"; + QTest::addRow("add static member function to other struct (explicit)") << original << expected; + + original = + "struct S {\n\n};\n" + "class C {\n" + "public:\n" + " void setValue(int v) { m_s.@setValue(v); }\n" + "private:\n" + " static S m_s;\n" + "};\n"; + expected = + "struct S {\n\n" + " static void setValue(int);\n" + "};\n" + "class C {\n" + "public:\n" + " void setValue(int v) { m_s.setValue(v); }\n" + "private:\n" + " static S m_s;\n" + "};\n"; + QTest::addRow("add static member function to other struct (implicit)") << original << expected; + + original = + "class C {\n" + "public:\n" + " void setValue(int v);\n" + "};\n" + "void C::setValue(int v) { this->@setValueInternal(v); }\n"; + expected = + "class C {\n" + "public:\n" + " void setValue(int v);\n" + "private:\n" + " void setValueInternal(int);\n" + "};\n" + "void C::setValue(int v) { this->setValueInternal(v); }\n"; + QTest::addRow("add member function to this (explicit)") << original << expected; + + original = + "class C {\n" + "public:\n" + " void setValue(int v) { @setValueInternal(v); }\n" + "};\n"; + expected = + "class C {\n" + "public:\n" + " void setValue(int v) { setValueInternal(v); }\n" + "private:\n" + " void setValueInternal(int);\n" + "};\n"; + QTest::addRow("add member function to this (implicit)") << original << expected; + + original = + "class C {\n" + "public:\n" + " int value() const { return @valueInternal(); }\n" + "};\n"; + expected = + "class C {\n" + "public:\n" + " int value() const { return valueInternal(); }\n" + "private:\n" + " int valueInternal() const;\n" + "};\n"; + QTest::addRow("add const member function to this (implicit)") << original << expected; + + original = + "class C {\n" + "public:\n" + " static int value() { int i = @valueInternal(); return i; }\n" + "};\n"; + expected = + "class C {\n" + "public:\n" + " static int value() { int i = @valueInternal(); return i; }\n" + "private:\n" + " static int valueInternal();\n" + "};\n"; + QTest::addRow("add static member function to this (inline)") << original << expected; + + original = + "class C {\n" + "public:\n" + " static int value();\n" + "};\n" + "int C::value() { return @valueInternal(); }\n"; + expected = + "class C {\n" + "public:\n" + " static int value();\n" + "private:\n" + " static int valueInternal();\n" + "};\n" + "int C::value() { return valueInternal(); }\n"; + QTest::addRow("add static member function to this (non-inline)") << original << expected; + } + + void testInsertMemberFromUse() + { + QFETCH(QByteArray, original); + QFETCH(QByteArray, expected); + + QList<TestDocumentPtr> testDocuments({ + CppTestDocument::create("file.h", original, expected) + }); + + AddDeclarationForUndeclaredIdentifier factory; + factory.setMembersOnly(); + QuickFixOperationTest(testDocuments, &factory); + } +}; + +class InsertDeclFromDefTest : public QObject +{ + Q_OBJECT + +private slots: + /// Check from source file: Insert in header file. + void test() + { + insertToSectionDeclFromDef("public", 0); + insertToSectionDeclFromDef("public slots", 1); + insertToSectionDeclFromDef("protected", 2); + insertToSectionDeclFromDef("protected slots", 3); + insertToSectionDeclFromDef("private", 4); + insertToSectionDeclFromDef("private slots", 5); + } + + void testTemplateFuncTypename() + { + QByteArray original = + "class Foo\n" + "{\n" + "};\n" + "\n" + "template<class T>\n" + "void Foo::fu@nc() {}\n"; + + QByteArray expected = + "class Foo\n" + "{\n" + "public:\n" + " template<class T>\n" + " void func();\n" + "};\n" + "\n" + "template<class T>\n" + "void Foo::fu@nc() {}\n"; + + InsertDeclFromDef factory; + QuickFixOperationTest(singleDocument(original, expected), &factory, {}, 0); + } + + void testTemplateFuncInt() + { + QByteArray original = + "class Foo\n" + "{\n" + "};\n" + "\n" + "template<int N>\n" + "void Foo::fu@nc() {}\n"; + + QByteArray expected = + "class Foo\n" + "{\n" + "public:\n" + " template<int N>\n" + " void func();\n" + "};\n" + "\n" + "template<int N>\n" + "void Foo::fu@nc() {}\n"; + + InsertDeclFromDef factory; + QuickFixOperationTest(singleDocument(original, expected), &factory, {}, 0); + } + + void testTemplateReturnType() + { + QByteArray original = + "class Foo\n" + "{\n" + "};\n" + "\n" + "std::vector<int> Foo::fu@nc() const {}\n"; + + QByteArray expected = + "class Foo\n" + "{\n" + "public:\n" + " std::vector<int> func() const;\n" + "};\n" + "\n" + "std::vector<int> Foo::func() const {}\n"; + + InsertDeclFromDef factory; + QuickFixOperationTest(singleDocument(original, expected), &factory, {}, 0); + } + + void testNotTriggeredForTemplateFunc() + { + QByteArray contents = + "class Foo\n" + "{\n" + " template<class T>\n" + " void func();\n" + "};\n" + "\n" + "template<class T>\n" + "void Foo::fu@nc() {}\n"; + + InsertDeclFromDef factory; + QuickFixOperationTest(singleDocument(contents, ""), &factory); + } + +private: + // Function for one of InsertDeclDef section cases + void insertToSectionDeclFromDef(const QByteArray §ion, int sectionIndex) + { + QList<TestDocumentPtr> testDocuments; + + QByteArray original; + QByteArray expected; + QByteArray sectionString = section + ":\n"; + if (sectionIndex == 4) + sectionString.clear(); + + // Header File + original = + "class Foo\n" + "{\n" + "};\n"; + expected = + "class Foo\n" + "{\n" + + sectionString + + " Foo();\n" + "@};\n"; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File + original = + "#include \"file.h\"\n" + "\n" + "Foo::Foo@()\n" + "{\n" + "}\n" + ; + expected = original; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + InsertDeclFromDef factory; + QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), sectionIndex); + } +}; + +QObject *AddDeclarationForUndeclaredIdentifier::createTest() +{ + return new AddDeclarationForUndeclaredIdentifierTest; +} + +QObject *InsertDeclFromDef::createTest() +{ + return new InsertDeclFromDefTest; +} + +#endif // WITH_TESTS +} // namespace + +void registerCreateDeclarationFromUseQuickfixes() +{ + CppQuickFixFactory::registerFactory<InsertDeclFromDef>(); + CppQuickFixFactory::registerFactory<AddDeclarationForUndeclaredIdentifier>(); +} + +} // namespace CppEditor::Internal + +#ifdef WITH_TESTS +#include <createdeclarationfromuse.moc> +#endif diff --git a/src/plugins/cppeditor/quickfixes/createdeclarationfromuse.h b/src/plugins/cppeditor/quickfixes/createdeclarationfromuse.h new file mode 100644 index 0000000000..d8452f5850 --- /dev/null +++ b/src/plugins/cppeditor/quickfixes/createdeclarationfromuse.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 registerCreateDeclarationFromUseQuickfixes(); +} // namespace CppEditor::Internal diff --git a/src/plugins/cppeditor/quickfixes/extractfunction.cpp b/src/plugins/cppeditor/quickfixes/extractfunction.cpp new file mode 100644 index 0000000000..994323e596 --- /dev/null +++ b/src/plugins/cppeditor/quickfixes/extractfunction.cpp @@ -0,0 +1,766 @@ +// 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 "extractfunction.h" + +#include "../cppcodestylesettings.h" +#include "../cppeditortr.h" +#include "../cpprefactoringchanges.h" +#include "../insertionpointlocator.h" +#include "cppquickfix.h" +#include "cppquickfixhelpers.h" + +#include <coreplugin/icore.h> +#include <cplusplus/CppRewriter.h> +#include <cplusplus/declarationcomments.h> +#include <cplusplus/Overview.h> + +#include <QDialogButtonBox> +#include <QFormLayout> +#include <QPushButton> + +#include <functional> + +#ifdef WITH_TESTS +#include "cppquickfix_test.h" +#include <QTest> +#endif + +using namespace CPlusPlus; +using namespace Utils; + +namespace CppEditor::Internal { +namespace { +using FunctionNameGetter = std::function<QString()>; + +class ExtractFunctionOptions +{ +public: + static bool isValidFunctionName(const QString &name) + { + return !name.isEmpty() && isValidIdentifier(name); + } + + bool hasValidFunctionName() const + { + return isValidFunctionName(funcName); + } + + QString funcName; + InsertionPointLocator::AccessSpec access = InsertionPointLocator::Public; +}; + +class ExtractFunctionOperation : public CppQuickFixOperation +{ +public: + ExtractFunctionOperation( + const CppQuickFixInterface &interface, + int extractionStart, + int extractionEnd, + FunctionDefinitionAST *refFuncDef, + Symbol *funcReturn, + QList<QPair<QString, QString>> relevantDecls, + FunctionNameGetter functionNameGetter = {}) + : CppQuickFixOperation(interface) + , m_extractionStart(extractionStart) + , m_extractionEnd(extractionEnd) + , m_refFuncDef(refFuncDef) + , m_funcReturn(funcReturn) + , m_relevantDecls(relevantDecls) + , m_functionNameGetter(functionNameGetter) + { + setDescription(Tr::tr("Extract Function")); + } + + void perform() override + { + QTC_ASSERT(!m_funcReturn || !m_relevantDecls.isEmpty(), return); + CppRefactoringChanges refactoring(snapshot()); + CppRefactoringFilePtr currentFile = refactoring.cppFile(filePath()); + + ExtractFunctionOptions options; + if (m_functionNameGetter) + options.funcName = m_functionNameGetter(); + else + options = getOptions(); + + if (!options.hasValidFunctionName()) + return; + const QString &funcName = options.funcName; + + Function *refFunc = m_refFuncDef->symbol; + + // We don't need to rewrite the type for declarations made inside the reference function, + // since their scope will remain the same. Then we preserve the original spelling style. + // However, we must do so for the return type in the definition. + SubstitutionEnvironment env; + env.setContext(context()); + env.switchScope(refFunc); + ClassOrNamespace *targetCoN = context().lookupType(refFunc->enclosingScope()); + if (!targetCoN) + targetCoN = context().globalNamespace(); + UseMinimalNames subs(targetCoN); + env.enter(&subs); + + Overview printer = CppCodeStyleSettings::currentProjectCodeStyleOverview(); + Control *control = context().bindings()->control().get(); + QString funcDef; + QString funcDecl; // We generate a declaration only in the case of a member function. + QString funcCall; + + Class *matchingClass = isMemberFunction(context(), refFunc); + + // Write return type. + if (!m_funcReturn) { + funcDef.append(QLatin1String("void ")); + if (matchingClass) + funcDecl.append(QLatin1String("void ")); + } else { + const FullySpecifiedType &fullType = rewriteType(m_funcReturn->type(), &env, control); + funcDef.append(printer.prettyType(fullType) + QLatin1Char(' ')); + funcDecl.append(printer.prettyType(m_funcReturn->type()) + QLatin1Char(' ')); + } + + // Write class qualification, if any. + if (matchingClass) { + const Scope *current = matchingClass; + QVector<const Name *> classes{matchingClass->name()}; + while (current->enclosingScope()->asClass()) { + current = current->enclosingScope()->asClass(); + classes.prepend(current->name()); + } + while (current->enclosingScope() && current->enclosingScope()->asNamespace()) { + current = current->enclosingScope()->asNamespace(); + if (current->name()) + classes.prepend(current->name()); + } + for (const Name *n : classes) { + const Name *name = rewriteName(n, &env, control); + funcDef.append(printer.prettyName(name)); + funcDef.append(QLatin1String("::")); + } + } + + // Write the extracted function itself and its call. + funcDef.append(funcName); + if (matchingClass) + funcDecl.append(funcName); + funcCall.append(funcName); + funcDef.append(QLatin1Char('(')); + if (matchingClass) + funcDecl.append(QLatin1Char('(')); + funcCall.append(QLatin1Char('(')); + for (int i = m_funcReturn ? 1 : 0; i < m_relevantDecls.length(); ++i) { + QPair<QString, QString> p = m_relevantDecls.at(i); + funcCall.append(p.first); + funcDef.append(p.second); + if (matchingClass) + funcDecl.append(p.second); + if (i < m_relevantDecls.length() - 1) { + funcCall.append(QLatin1String(", ")); + funcDef.append(QLatin1String(", ")); + if (matchingClass) + funcDecl.append(QLatin1String(", ")); + } + } + funcDef.append(QLatin1Char(')')); + if (matchingClass) + funcDecl.append(QLatin1Char(')')); + funcCall.append(QLatin1Char(')')); + if (refFunc->isConst()) { + funcDef.append(QLatin1String(" const")); + funcDecl.append(QLatin1String(" const")); + } + funcDef.append(QLatin1String("\n{\n")); + QString extract = currentFile->textOf(m_extractionStart, m_extractionEnd); + extract.replace(QChar::ParagraphSeparator, QLatin1String("\n")); + if (!extract.endsWith(QLatin1Char('\n')) && m_funcReturn) + extract.append(QLatin1Char('\n')); + funcDef.append(extract); + if (matchingClass) + funcDecl.append(QLatin1String(";\n")); + if (m_funcReturn) { + funcDef.append(QLatin1String("\nreturn ") + + m_relevantDecls.at(0).first + + QLatin1Char(';')); + funcCall.prepend(m_relevantDecls.at(0).second + QLatin1String(" = ")); + } + funcDef.append(QLatin1String("\n}\n\n")); + funcDef.replace(QChar::ParagraphSeparator, QLatin1String("\n")); + funcDef.prepend(inlinePrefix(currentFile->filePath())); + funcCall.append(QLatin1Char(';')); + + // Do not insert right between the function and an associated comment. + int position = currentFile->startOf(m_refFuncDef); + const QList<Token> functionDoc = commentsForDeclaration( + m_refFuncDef->symbol, m_refFuncDef, *currentFile->document(), + currentFile->cppDocument()); + if (!functionDoc.isEmpty()) { + position = currentFile->cppDocument()->translationUnit()->getTokenPositionInDocument( + functionDoc.first(), currentFile->document()); + } + + ChangeSet change; + change.insert(position, funcDef); + change.replace(m_extractionStart, m_extractionEnd, funcCall); + currentFile->setChangeSet(change); + currentFile->apply(); + + // Write declaration, if necessary. + if (matchingClass) { + InsertionPointLocator locator(refactoring); + const FilePath filePath = FilePath::fromUtf8(matchingClass->fileName()); + const InsertionLocation &location = + locator.methodDeclarationInClass(filePath, matchingClass, options.access); + CppRefactoringFilePtr declFile = refactoring.cppFile(filePath); + change.clear(); + position = declFile->position(location.line(), location.column()); + change.insert(position, location.prefix() + funcDecl + location.suffix()); + declFile->setChangeSet(change); + declFile->apply(); + } + } + + ExtractFunctionOptions getOptions() const + { + QDialog dlg(Core::ICore::dialogParent()); + dlg.setWindowTitle(Tr::tr("Extract Function Refactoring")); + auto layout = new QFormLayout(&dlg); + + auto funcNameEdit = new FancyLineEdit; + funcNameEdit->setValidationFunction([](FancyLineEdit *edit, QString *) { + return ExtractFunctionOptions::isValidFunctionName(edit->text()); + }); + layout->addRow(Tr::tr("Function name"), funcNameEdit); + + auto accessCombo = new QComboBox; + accessCombo->addItem( + InsertionPointLocator::accessSpecToString(InsertionPointLocator::Public), + InsertionPointLocator::Public); + accessCombo->addItem( + InsertionPointLocator::accessSpecToString(InsertionPointLocator::PublicSlot), + InsertionPointLocator::PublicSlot); + accessCombo->addItem( + InsertionPointLocator::accessSpecToString(InsertionPointLocator::Protected), + InsertionPointLocator::Protected); + accessCombo->addItem( + InsertionPointLocator::accessSpecToString(InsertionPointLocator::ProtectedSlot), + InsertionPointLocator::ProtectedSlot); + accessCombo->addItem( + InsertionPointLocator::accessSpecToString(InsertionPointLocator::Private), + InsertionPointLocator::Private); + accessCombo->addItem( + InsertionPointLocator::accessSpecToString(InsertionPointLocator::PrivateSlot), + InsertionPointLocator::PrivateSlot); + layout->addRow(Tr::tr("Access"), accessCombo); + + auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + QObject::connect(buttonBox, &QDialogButtonBox::accepted, &dlg, &QDialog::accept); + QObject::connect(buttonBox, &QDialogButtonBox::rejected, &dlg, &QDialog::reject); + QPushButton *ok = buttonBox->button(QDialogButtonBox::Ok); + ok->setEnabled(false); + QObject::connect(funcNameEdit, &Utils::FancyLineEdit::validChanged, + ok, &QPushButton::setEnabled); + layout->addWidget(buttonBox); + + if (dlg.exec() == QDialog::Accepted) { + ExtractFunctionOptions options; + options.funcName = funcNameEdit->text(); + options.access = static_cast<InsertionPointLocator::AccessSpec>(accessCombo-> + currentData().toInt()); + return options; + } + return ExtractFunctionOptions(); + } + + int m_extractionStart; + int m_extractionEnd; + FunctionDefinitionAST *m_refFuncDef; + Symbol *m_funcReturn; + QList<QPair<QString, QString> > m_relevantDecls; + FunctionNameGetter m_functionNameGetter; +}; + +static QPair<QString, QString> assembleDeclarationData( + const QString &specifiers, + DeclaratorAST *decltr, + const CppRefactoringFilePtr &file, + const Overview &printer) +{ + QTC_ASSERT(decltr, return (QPair<QString, QString>())); + if (decltr->core_declarator + && decltr->core_declarator->asDeclaratorId() + && decltr->core_declarator->asDeclaratorId()->name) { + QString decltrText = file->textOf(file->startOf(decltr), + file->endOf(decltr->core_declarator)); + if (!decltrText.isEmpty()) { + const QString &name = printer.prettyName( + decltr->core_declarator->asDeclaratorId()->name->name); + QString completeDecl = specifiers; + if (!decltrText.contains(QLatin1Char(' '))) + completeDecl.append(QLatin1Char(' ') + decltrText); + else + completeDecl.append(decltrText); + return {name, completeDecl}; + } + } + return QPair<QString, QString>(); +} + +class FunctionExtractionAnalyser : public ASTVisitor +{ +public: + FunctionExtractionAnalyser(TranslationUnit *unit, + const int selStart, + const int selEnd, + const CppRefactoringFilePtr &file, + const Overview &printer) + : ASTVisitor(unit) + , m_done(false) + , m_failed(false) + , m_selStart(selStart) + , m_selEnd(selEnd) + , m_extractionStart(0) + , m_extractionEnd(0) + , m_file(file) + , m_printer(printer) + {} + + bool operator()(FunctionDefinitionAST *refFunDef) + { + accept(refFunDef); + + if (!m_failed && m_extractionStart == m_extractionEnd) + m_failed = true; + + return !m_failed; + } + + bool preVisit(AST *) override + { + return !m_done; + } + + void statement(StatementAST *stmt) + { + if (!stmt) + return; + + const int stmtStart = m_file->startOf(stmt); + const int stmtEnd = m_file->endOf(stmt); + + if (stmtStart >= m_selEnd + || (m_extractionStart && stmtEnd > m_selEnd)) { + m_done = true; + return; + } + + if (stmtStart >= m_selStart && !m_extractionStart) + m_extractionStart = stmtStart; + if (stmtEnd > m_extractionEnd && m_extractionStart) + m_extractionEnd = stmtEnd; + + accept(stmt); + } + + bool visit(CaseStatementAST *stmt) override + { + statement(stmt->statement); + return false; + } + + bool visit(CompoundStatementAST *stmt) override + { + for (StatementListAST *it = stmt->statement_list; it; it = it->next) { + statement(it->value); + if (m_done) + break; + } + return false; + } + + bool visit(DoStatementAST *stmt) override + { + statement(stmt->statement); + return false; + } + + bool visit(ForeachStatementAST *stmt) override + { + statement(stmt->statement); + return false; + } + + bool visit(RangeBasedForStatementAST *stmt) override + { + statement(stmt->statement); + return false; + } + + bool visit(ForStatementAST *stmt) override + { + statement(stmt->initializer); + if (!m_done) + statement(stmt->statement); + return false; + } + + bool visit(IfStatementAST *stmt) override + { + statement(stmt->statement); + if (!m_done) + statement(stmt->else_statement); + return false; + } + + bool visit(TryBlockStatementAST *stmt) override + { + statement(stmt->statement); + for (CatchClauseListAST *it = stmt->catch_clause_list; it; it = it->next) { + statement(it->value); + if (m_done) + break; + } + return false; + } + + bool visit(WhileStatementAST *stmt) override + { + statement(stmt->statement); + return false; + } + + bool visit(DeclarationStatementAST *declStmt) override + { + // We need to collect the declarations we see before the extraction or even inside it. + // They might need to be used as either a parameter or return value. Actually, we could + // still obtain their types from the local uses, but it's good to preserve the original + // typing style. + if (declStmt + && declStmt->declaration + && declStmt->declaration->asSimpleDeclaration()) { + SimpleDeclarationAST *simpleDecl = declStmt->declaration->asSimpleDeclaration(); + if (simpleDecl->decl_specifier_list + && simpleDecl->declarator_list) { + const QString &specifiers = + m_file->textOf(m_file->startOf(simpleDecl), + m_file->endOf(simpleDecl->decl_specifier_list->lastValue())); + for (DeclaratorListAST *decltrList = simpleDecl->declarator_list; + decltrList; + decltrList = decltrList->next) { + const QPair<QString, QString> p = + assembleDeclarationData(specifiers, decltrList->value, m_file, m_printer); + if (!p.first.isEmpty()) + m_knownDecls.insert(p.first, p.second); + } + } + } + + return false; + } + + bool visit(ReturnStatementAST *) override + { + if (m_extractionStart) { + m_done = true; + m_failed = true; + } + + return false; + } + + bool m_done; + bool m_failed; + const int m_selStart; + const int m_selEnd; + int m_extractionStart; + int m_extractionEnd; + QHash<QString, QString> m_knownDecls; + CppRefactoringFilePtr m_file; + const Overview &m_printer; +}; + +//! Extracts the selected code and puts it to a function +class ExtractFunction : public CppQuickFixFactory +{ +public: + ExtractFunction(FunctionNameGetter functionNameGetter = FunctionNameGetter()) + : m_functionNameGetter(functionNameGetter) + {} + +#ifdef WITH_TESTS + static QObject *createTest(); +#endif + +private: + void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override + { + const CppRefactoringFilePtr file = interface.currentFile(); + + // TODO: Fix upstream and uncomment; see QTCREATORBUG-28030. + // if (CppModelManager::usesClangd(file->editor()->textDocument()) + // && file->cppDocument()->languageFeatures().cxxEnabled) { + // return; + // } + + QTextCursor cursor = file->cursor(); + if (!cursor.hasSelection()) + return; + + const QList<AST *> &path = interface.path(); + FunctionDefinitionAST *refFuncDef = nullptr; // The "reference" function, which we will extract from. + for (int i = path.size() - 1; i >= 0; --i) { + refFuncDef = path.at(i)->asFunctionDefinition(); + if (refFuncDef) + break; + } + + if (!refFuncDef + || !refFuncDef->function_body + || !refFuncDef->function_body->asCompoundStatement() + || !refFuncDef->function_body->asCompoundStatement()->statement_list + || !refFuncDef->symbol + || !refFuncDef->symbol->name() + || refFuncDef->symbol->enclosingScope()->asTemplate() /* TODO: Templates... */) { + return; + } + + // Adjust selection ends. + int selStart = cursor.selectionStart(); + int selEnd = cursor.selectionEnd(); + if (selStart > selEnd) + std::swap(selStart, selEnd); + + Overview printer; + + // Analyze the content to be extracted, which consists of determining the statements + // which are complete and collecting the declarations seen. + FunctionExtractionAnalyser analyser(interface.semanticInfo().doc->translationUnit(), + selStart, selEnd, + file, + printer); + if (!analyser(refFuncDef)) + return; + + // We also need to collect the declarations of the parameters from the reference function. + QSet<QString> refFuncParams; + if (refFuncDef->declarator->postfix_declarator_list + && refFuncDef->declarator->postfix_declarator_list->value + && refFuncDef->declarator->postfix_declarator_list->value->asFunctionDeclarator()) { + FunctionDeclaratorAST *funcDecltr = + refFuncDef->declarator->postfix_declarator_list->value->asFunctionDeclarator(); + if (funcDecltr->parameter_declaration_clause + && funcDecltr->parameter_declaration_clause->parameter_declaration_list) { + for (ParameterDeclarationListAST *it = + funcDecltr->parameter_declaration_clause->parameter_declaration_list; + it; + it = it->next) { + ParameterDeclarationAST *paramDecl = it->value->asParameterDeclaration(); + if (paramDecl->declarator) { + const QString &specifiers = + file->textOf(file->startOf(paramDecl), + file->endOf(paramDecl->type_specifier_list->lastValue())); + const QPair<QString, QString> &p = + assembleDeclarationData(specifiers, paramDecl->declarator, + file, printer); + if (!p.first.isEmpty()) { + analyser.m_knownDecls.insert(p.first, p.second); + refFuncParams.insert(p.first); + } + } + } + } + } + + // Identify what would be parameters for the new function and its return value, if any. + Symbol *funcReturn = nullptr; + QList<QPair<QString, QString> > relevantDecls; + const SemanticInfo::LocalUseMap localUses = interface.semanticInfo().localUses; + for (auto it = localUses.cbegin(), end = localUses.cend(); it != end; ++it) { + bool usedBeforeExtraction = false; + bool usedAfterExtraction = false; + bool usedInsideExtraction = false; + const QList<SemanticInfo::Use> &uses = it.value(); + for (const SemanticInfo::Use &use : uses) { + if (use.isInvalid()) + continue; + + const int position = file->position(use.line, use.column); + if (position < analyser.m_extractionStart) + usedBeforeExtraction = true; + else if (position >= analyser.m_extractionEnd) + usedAfterExtraction = true; + else + usedInsideExtraction = true; + } + + const QString &name = printer.prettyName(it.key()->name()); + + if ((usedBeforeExtraction && usedInsideExtraction) + || (usedInsideExtraction && refFuncParams.contains(name))) { + QTC_ASSERT(analyser.m_knownDecls.contains(name), return); + relevantDecls.push_back({name, analyser.m_knownDecls.value(name)}); + } + + // We assume that the first use of a local corresponds to its declaration. + if (usedInsideExtraction && usedAfterExtraction && !usedBeforeExtraction) { + if (!funcReturn) { + QTC_ASSERT(analyser.m_knownDecls.contains(name), return); + // The return, if any, is stored as the first item in the list. + relevantDecls.push_front({name, analyser.m_knownDecls.value(name)}); + funcReturn = it.key(); + } else { + // Would require multiple returns. (Unless we do fancy things, as pointed below.) + return; + } + } + } + + // The current implementation doesn't try to be too smart since it preserves the original form + // of the declarations. This might be or not the desired effect. An improvement would be to + // let the user somehow customize the function interface. + result << new ExtractFunctionOperation(interface, + analyser.m_extractionStart, + analyser.m_extractionEnd, + refFuncDef, funcReturn, relevantDecls, + m_functionNameGetter); + } + +private: + FunctionNameGetter m_functionNameGetter; // For tests to avoid GUI pop-up. +}; + +#ifdef WITH_TESTS +using namespace Tests; + +class ExtractFunctionTest : public QObject +{ + Q_OBJECT + +private slots: + void test_data() + { + QTest::addColumn<QByteArray>("original"); + QTest::addColumn<QByteArray>("expected"); + + QTest::newRow("basic") + << QByteArray("// Documentation for f\n" + "void f()\n" + "{\n" + " @{start}g();@{end}\n" + "}\n") + << QByteArray("inline void extracted()\n" + "{\n" + " g();\n" + "}\n" + "\n" + "// Documentation for f\n" + "void f()\n" + "{\n" + " extracted();\n" + "}\n"); + + QTest::newRow("class function") + << QByteArray("class Foo\n" + "{\n" + "private:\n" + " void bar();\n" + "};\n\n" + "void Foo::bar()\n" + "{\n" + " @{start}g();@{end}\n" + "}\n") + << QByteArray("class Foo\n" + "{\n" + "public:\n" + " void extracted();\n\n" + "private:\n" + " void bar();\n" + "};\n\n" + "inline void Foo::extracted()\n" + "{\n" + " g();\n" + "}\n\n" + "void Foo::bar()\n" + "{\n" + " extracted();\n" + "}\n"); + + QTest::newRow("class in namespace") + << QByteArray("namespace NS {\n" + "class C {\n" + " void f(C &c);\n" + "};\n" + "}\n" + "void NS::C::f(NS::C &c)\n" + "{\n" + " @{start}C *c2 = &c;@{end}\n" + "}\n") + << QByteArray("namespace NS {\n" + "class C {\n" + " void f(C &c);\n" + "\n" + "public:\n" + " void extracted(NS::C &c);\n" // TODO: Remove non-required qualification + "};\n" + "}\n" + "inline void NS::C::extracted(NS::C &c)\n" + "{\n" + " C *c2 = &c;\n" + "}\n" + "\n" + "void NS::C::f(NS::C &c)\n" + "{\n" + " extracted(c);\n" + "}\n"); + + QTest::newRow("if-block") + << QByteArray("inline void func()\n" + "{\n" + " int dummy = 0;\n" + " @{start}if@{end} (dummy < 10) {\n" + " ++dummy;\n" + " }\n" + "}\n") + << QByteArray("inline void extracted(int dummy)\n" + "{\n" + " if (dummy < 10) {\n" + " ++dummy;\n" + " }\n" + "}\n\n" + "inline void func()\n" + "{\n" + " int dummy = 0;\n" + " extracted(dummy);\n" + "}\n"); + } + + void test() + { + QFETCH(QByteArray, original); + QFETCH(QByteArray, expected); + + QList<TestDocumentPtr> testDocuments; + testDocuments << CppTestDocument::create("file.h", original, expected); + + ExtractFunction factory([]() { return QLatin1String("extracted"); }); + QuickFixOperationTest(testDocuments, &factory); + } +}; + +QObject *ExtractFunction::createTest() { return new ExtractFunctionTest; } + +#endif // WITH_TESTS + +} // namespace + +void registerExtractFunctionQuickfix() +{ + CppQuickFixFactory::registerFactory<ExtractFunction>(); +} + +} // namespace CppEditor::Internal + +#ifdef WITH_TESTS +#include <extractfunction.moc> +#endif diff --git a/src/plugins/cppeditor/quickfixes/extractfunction.h b/src/plugins/cppeditor/quickfixes/extractfunction.h new file mode 100644 index 0000000000..b41cef59c2 --- /dev/null +++ b/src/plugins/cppeditor/quickfixes/extractfunction.h @@ -0,0 +1,7 @@ +// 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 registerExtractFunctionQuickfix(); +} // namespace CppEditor::Internal diff --git a/src/plugins/cppeditor/quickfixes/extractliteralasparameter.cpp b/src/plugins/cppeditor/quickfixes/extractliteralasparameter.cpp new file mode 100644 index 0000000000..a1f0760dfb --- /dev/null +++ b/src/plugins/cppeditor/quickfixes/extractliteralasparameter.cpp @@ -0,0 +1,563 @@ +// 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 "extractliteralasparameter.h" + +#include "../cppeditortr.h" +#include "../cppeditorwidget.h" +#include "../cpprefactoringchanges.h" +#include "cppquickfix.h" +#include "cppquickfixhelpers.h" + +#include <cplusplus/ASTPath.h> +#include <cplusplus/Overview.h> +#include <cplusplus/TypeOfExpression.h> + +#ifdef WITH_TESTS +#include "cppquickfix_test.h" +#include <QtTest> +#endif + +using namespace CPlusPlus; +using namespace Utils; + +namespace CppEditor::Internal { +namespace { + +struct ReplaceLiteralsResult +{ + Token token; + QString literalText; +}; + +template <class T> +class ReplaceLiterals : private ASTVisitor +{ +public: + ReplaceLiterals(const CppRefactoringFilePtr &file, ChangeSet *changes, T *literal) + : ASTVisitor(file->cppDocument()->translationUnit()), m_file(file), m_changes(changes), + m_literal(literal) + { + m_result.token = m_file->tokenAt(literal->firstToken()); + m_literalTokenText = m_result.token.spell(); + m_result.literalText = QLatin1String(m_literalTokenText); + if (m_result.token.isCharLiteral()) { + m_result.literalText.prepend(QLatin1Char('\'')); + m_result.literalText.append(QLatin1Char('\'')); + if (m_result.token.kind() == T_WIDE_CHAR_LITERAL) + m_result.literalText.prepend(QLatin1Char('L')); + else if (m_result.token.kind() == T_UTF16_CHAR_LITERAL) + m_result.literalText.prepend(QLatin1Char('u')); + else if (m_result.token.kind() == T_UTF32_CHAR_LITERAL) + m_result.literalText.prepend(QLatin1Char('U')); + } else if (m_result.token.isStringLiteral()) { + m_result.literalText.prepend(QLatin1Char('"')); + m_result.literalText.append(QLatin1Char('"')); + if (m_result.token.kind() == T_WIDE_STRING_LITERAL) + m_result.literalText.prepend(QLatin1Char('L')); + else if (m_result.token.kind() == T_UTF16_STRING_LITERAL) + m_result.literalText.prepend(QLatin1Char('u')); + else if (m_result.token.kind() == T_UTF32_STRING_LITERAL) + m_result.literalText.prepend(QLatin1Char('U')); + } + } + + ReplaceLiteralsResult apply(AST *ast) + { + ast->accept(this); + return m_result; + } + +private: + bool visit(T *ast) override + { + if (ast != m_literal + && strcmp(m_file->tokenAt(ast->firstToken()).spell(), m_literalTokenText) != 0) { + return true; + } + int start, end; + m_file->startAndEndOf(ast->firstToken(), &start, &end); + m_changes->replace(start, end, QLatin1String("newParameter")); + return true; + } + + const CppRefactoringFilePtr &m_file; + ChangeSet *m_changes; + T *m_literal; + const char *m_literalTokenText; + ReplaceLiteralsResult m_result; +}; + +class ExtractLiteralAsParameterOp : public CppQuickFixOperation +{ +public: + ExtractLiteralAsParameterOp(const CppQuickFixInterface &interface, int priority, + ExpressionAST *literal, FunctionDefinitionAST *function) + : CppQuickFixOperation(interface, priority), + m_literal(literal), + m_functionDefinition(function) + { + setDescription(Tr::tr("Extract Constant as Function Parameter")); + } + + struct FoundDeclaration + { + FunctionDeclaratorAST *ast = nullptr; + CppRefactoringFilePtr file; + }; + + FoundDeclaration findDeclaration(const CppRefactoringChanges &refactoring, + FunctionDefinitionAST *ast) + { + FoundDeclaration result; + Function *func = ast->symbol; + if (Class *matchingClass = isMemberFunction(context(), func)) { + // Dealing with member functions + const QualifiedNameId *qName = func->name()->asQualifiedNameId(); + for (Symbol *s = matchingClass->find(qName->identifier()); s; s = s->next()) { + if (!s->name() + || !qName->identifier()->match(s->identifier()) + || !s->type()->asFunctionType() + || !s->type().match(func->type()) + || s->asFunction()) { + continue; + } + + const FilePath declFilePath = matchingClass->filePath(); + result.file = refactoring.cppFile(declFilePath); + ASTPath astPath(result.file->cppDocument()); + const QList<AST *> path = astPath(s->line(), s->column()); + SimpleDeclarationAST *simpleDecl = nullptr; + for (AST *node : path) { + simpleDecl = node->asSimpleDeclaration(); + if (simpleDecl) { + if (simpleDecl->symbols && !simpleDecl->symbols->next) { + result.ast = functionDeclarator(simpleDecl); + return result; + } + } + } + + if (simpleDecl) + break; + } + } else if (Namespace *matchingNamespace = isNamespaceFunction(context(), func)) { + // Dealing with free functions and inline member functions. + bool isHeaderFile; + FilePath declFilePath = correspondingHeaderOrSource(filePath(), &isHeaderFile); + if (!declFilePath.exists()) + return FoundDeclaration(); + result.file = refactoring.cppFile(declFilePath); + if (!result.file) + return FoundDeclaration(); + const LookupContext lc(result.file->cppDocument(), snapshot()); + const QList<LookupItem> candidates = lc.lookup(func->name(), matchingNamespace); + for (const LookupItem &candidate : candidates) { + if (Symbol *s = candidate.declaration()) { + if (s->asDeclaration()) { + ASTPath astPath(result.file->cppDocument()); + const QList<AST *> path = astPath(s->line(), s->column()); + for (AST *node : path) { + SimpleDeclarationAST *simpleDecl = node->asSimpleDeclaration(); + if (simpleDecl) { + result.ast = functionDeclarator(simpleDecl); + return result; + } + } + } + } + } + } + return result; + } + + void perform() override + { + FunctionDeclaratorAST *functionDeclaratorOfDefinition + = functionDeclarator(m_functionDefinition); + const CppRefactoringChanges refactoring(snapshot()); + const CppRefactoringFilePtr currentFile = refactoring.cppFile(filePath()); + deduceTypeNameOfLiteral(currentFile->cppDocument()); + + ChangeSet changes; + if (NumericLiteralAST *concreteLiteral = m_literal->asNumericLiteral()) { + m_literalInfo = ReplaceLiterals<NumericLiteralAST>(currentFile, &changes, + concreteLiteral) + .apply(m_functionDefinition->function_body); + } else if (StringLiteralAST *concreteLiteral = m_literal->asStringLiteral()) { + m_literalInfo = ReplaceLiterals<StringLiteralAST>(currentFile, &changes, + concreteLiteral) + .apply(m_functionDefinition->function_body); + } else if (BoolLiteralAST *concreteLiteral = m_literal->asBoolLiteral()) { + m_literalInfo = ReplaceLiterals<BoolLiteralAST>(currentFile, &changes, + concreteLiteral) + .apply(m_functionDefinition->function_body); + } + const FoundDeclaration functionDeclaration + = findDeclaration(refactoring, m_functionDefinition); + appendFunctionParameter(functionDeclaratorOfDefinition, currentFile, &changes, + !functionDeclaration.ast); + if (functionDeclaration.ast) { + if (currentFile->filePath() != functionDeclaration.file->filePath()) { + ChangeSet declChanges; + appendFunctionParameter(functionDeclaration.ast, functionDeclaration.file, &declChanges, + true); + functionDeclaration.file->setChangeSet(declChanges); + functionDeclaration.file->apply(); + } else { + appendFunctionParameter(functionDeclaration.ast, currentFile, &changes, + true); + } + } + currentFile->setChangeSet(changes); + currentFile->apply(); + QTextCursor c = currentFile->cursor(); + c.setPosition(c.position() - parameterName().length()); + editor()->setTextCursor(c); + editor()->renameSymbolUnderCursor(); + } + +private: + bool hasParameters(FunctionDeclaratorAST *ast) const + { + return ast->parameter_declaration_clause + && ast->parameter_declaration_clause->parameter_declaration_list + && ast->parameter_declaration_clause->parameter_declaration_list->value; + } + + void deduceTypeNameOfLiteral(const Document::Ptr &document) + { + TypeOfExpression typeOfExpression; + typeOfExpression.init(document, snapshot()); + Overview overview; + Scope *scope = m_functionDefinition->symbol->enclosingScope(); + const QList<LookupItem> items = typeOfExpression(m_literal, document, scope); + if (!items.isEmpty()) + m_typeName = overview.prettyType(items.first().type()); + } + + static QString parameterName() { return QLatin1String("newParameter"); } + + QString parameterDeclarationTextToInsert(FunctionDeclaratorAST *ast) const + { + QString str; + if (hasParameters(ast)) + str = QLatin1String(", "); + str += m_typeName; + if (!m_typeName.endsWith(QLatin1Char('*'))) + str += QLatin1Char(' '); + str += parameterName(); + return str; + } + + FunctionDeclaratorAST *functionDeclarator(SimpleDeclarationAST *ast) const + { + for (DeclaratorListAST *decls = ast->declarator_list; decls; decls = decls->next) { + FunctionDeclaratorAST * const functionDeclaratorAST = functionDeclarator(decls->value); + if (functionDeclaratorAST) + return functionDeclaratorAST; + } + return nullptr; + } + + FunctionDeclaratorAST *functionDeclarator(DeclaratorAST *ast) const + { + for (PostfixDeclaratorListAST *pds = ast->postfix_declarator_list; pds; pds = pds->next) { + FunctionDeclaratorAST *funcdecl = pds->value->asFunctionDeclarator(); + if (funcdecl) + return funcdecl; + } + return nullptr; + } + + FunctionDeclaratorAST *functionDeclarator(FunctionDefinitionAST *ast) const + { + return functionDeclarator(ast->declarator); + } + + void appendFunctionParameter(FunctionDeclaratorAST *ast, const CppRefactoringFileConstPtr &file, + ChangeSet *changes, bool addDefaultValue) + { + if (!ast) + return; + if (m_declarationInsertionString.isEmpty()) + m_declarationInsertionString = parameterDeclarationTextToInsert(ast); + QString insertion = m_declarationInsertionString; + if (addDefaultValue) + insertion += QLatin1String(" = ") + m_literalInfo.literalText; + changes->insert(file->startOf(ast->rparen_token), insertion); + } + + ExpressionAST *m_literal; + FunctionDefinitionAST *m_functionDefinition; + QString m_typeName; + QString m_declarationInsertionString; + ReplaceLiteralsResult m_literalInfo; +}; + +/*! + Extracts the selected constant and converts it to a parameter of the current function. + Activates on numeric, bool, character, or string literal in the function body. + */ +class ExtractLiteralAsParameter : public CppQuickFixFactory +{ +#ifdef WITH_TESTS +public: + static QObject *createTest(); +#endif + +private: + void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override + { + const QList<AST *> &path = interface.path(); + if (path.count() < 2) + return; + + AST * const lastAst = path.last(); + ExpressionAST *literal; + if (!((literal = lastAst->asNumericLiteral()) + || (literal = lastAst->asStringLiteral()) + || (literal = lastAst->asBoolLiteral()))) { + return; + } + + FunctionDefinitionAST *function; + int i = path.count() - 2; + while (!(function = path.at(i)->asFunctionDefinition())) { + // Ignore literals in lambda expressions for now. + if (path.at(i)->asLambdaExpression()) + return; + if (--i < 0) + return; + } + + PostfixDeclaratorListAST * const declaratorList = function->declarator->postfix_declarator_list; + if (!declaratorList) + return; + if (FunctionDeclaratorAST *declarator = declaratorList->value->asFunctionDeclarator()) { + if (declarator->parameter_declaration_clause + && declarator->parameter_declaration_clause->dot_dot_dot_token) { + // Do not handle functions with ellipsis parameter. + return; + } + } + + const int priority = path.size() - 1; + result << new ExtractLiteralAsParameterOp(interface, priority, literal, function); + } +}; + +#ifdef WITH_TESTS +using namespace Tests; + +class ExtractLiteralAsParameterTest : public QObject +{ + Q_OBJECT + +private slots: + void testTypeDeduction_data() + { + QTest::addColumn<QByteArray>("typeString"); + QTest::addColumn<QByteArray>("literal"); + QTest::newRow("int") + << QByteArray("int ") << QByteArray("156"); + QTest::newRow("unsigned int") + << QByteArray("unsigned int ") << QByteArray("156u"); + QTest::newRow("long") + << QByteArray("long ") << QByteArray("156l"); + QTest::newRow("unsigned long") + << QByteArray("unsigned long ") << QByteArray("156ul"); + QTest::newRow("long long") + << QByteArray("long long ") << QByteArray("156ll"); + QTest::newRow("unsigned long long") + << QByteArray("unsigned long long ") << QByteArray("156ull"); + QTest::newRow("float") + << QByteArray("float ") << QByteArray("3.14159f"); + QTest::newRow("double") + << QByteArray("double ") << QByteArray("3.14159"); + QTest::newRow("long double") + << QByteArray("long double ") << QByteArray("3.14159L"); + QTest::newRow("bool") + << QByteArray("bool ") << QByteArray("true"); + QTest::newRow("bool") + << QByteArray("bool ") << QByteArray("false"); + QTest::newRow("char") + << QByteArray("char ") << QByteArray("'X'"); + QTest::newRow("wchar_t") + << QByteArray("wchar_t ") << QByteArray("L'X'"); + QTest::newRow("char16_t") + << QByteArray("char16_t ") << QByteArray("u'X'"); + QTest::newRow("char32_t") + << QByteArray("char32_t ") << QByteArray("U'X'"); + QTest::newRow("const char *") + << QByteArray("const char *") << QByteArray("\"narf\""); + QTest::newRow("const wchar_t *") + << QByteArray("const wchar_t *") << QByteArray("L\"narf\""); + QTest::newRow("const char16_t *") + << QByteArray("const char16_t *") << QByteArray("u\"narf\""); + QTest::newRow("const char32_t *") + << QByteArray("const char32_t *") << QByteArray("U\"narf\""); + } + + void testTypeDeduction() + { + QFETCH(QByteArray, typeString); + QFETCH(QByteArray, literal); + const QByteArray original = QByteArray("void foo() {return @") + literal + QByteArray(";}\n"); + const QByteArray expected = QByteArray("void foo(") + typeString + QByteArray("newParameter = ") + + literal + QByteArray(") {return newParameter;}\n"); + + if (literal == "3.14159") { + qWarning("Literal 3.14159 is wrongly reported as int. Skipping."); + return; + } else if (literal == "3.14159L") { + qWarning("Literal 3.14159L is wrongly reported as long. Skipping."); + return; + } + + ExtractLiteralAsParameter factory; + QuickFixOperationTest(singleDocument(original, expected), &factory); + } + + void testFreeFunctionSeparateFiles() + { + QList<TestDocumentPtr> testDocuments; + QByteArray original; + QByteArray expected; + + // Header File + original = + "void foo(const char *a, long b = 1);\n"; + expected = + "void foo(const char *a, long b = 1, int newParameter = 156);\n"; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File + original = + "void foo(const char *a, long b)\n" + "{return 1@56 + 123 + 156;}\n"; + expected = + "void foo(const char *a, long b, int newParameter)\n" + "{return newParameter + 123 + newParameter;}\n"; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + ExtractLiteralAsParameter factory; + QuickFixOperationTest(testDocuments, &factory); + } + + void testMemberFunctionSeparateFiles() + { + QList<TestDocumentPtr> testDocuments; + QByteArray original; + QByteArray expected; + + // Header File + original = + "class Narf {\n" + "public:\n" + " int zort();\n" + "};\n"; + expected = + "class Narf {\n" + "public:\n" + " int zort(int newParameter = 155);\n" + "};\n"; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File + original = + "#include \"file.h\"\n\n" + "int Narf::zort()\n" + "{ return 15@5 + 1; }\n"; + expected = + "#include \"file.h\"\n\n" + "int Narf::zort(int newParameter)\n" + "{ return newParameter + 1; }\n"; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + ExtractLiteralAsParameter factory; + QuickFixOperationTest(testDocuments, &factory); + } + + void testNotTriggeringForInvalidCode() + { + QList<TestDocumentPtr> testDocuments; + QByteArray original; + original = + "T(\"test\")\n" + "{\n" + " const int i = @14;\n" + "}\n"; + testDocuments << CppTestDocument::create("file.cpp", original, ""); + + ExtractLiteralAsParameter factory; + QuickFixOperationTest(testDocuments, &factory); + } + + void test_data() + { + QTest::addColumn<QByteArray>("original"); + QTest::addColumn<QByteArray>("expected"); + + QTest::newRow("ExtractLiteralAsParameter_freeFunction") + << QByteArray( + "void foo(const char *a, long b = 1)\n" + "{return 1@56 + 123 + 156;}\n") + << QByteArray( + "void foo(const char *a, long b = 1, int newParameter = 156)\n" + "{return newParameter + 123 + newParameter;}\n"); + QTest::newRow("ExtractLiteralAsParameter_memberFunction") + << QByteArray( + "class Narf {\n" + "public:\n" + " int zort();\n" + "};\n\n" + "int Narf::zort()\n" + "{ return 15@5 + 1; }\n") + << QByteArray( + "class Narf {\n" + "public:\n" + " int zort(int newParameter = 155);\n" + "};\n\n" + "int Narf::zort(int newParameter)\n" + "{ return newParameter + 1; }\n"); + QTest::newRow("ExtractLiteralAsParameter_memberFunctionInline") + << QByteArray( + "class Narf {\n" + "public:\n" + " int zort()\n" + " { return 15@5 + 1; }\n" + "};\n") + << QByteArray( + "class Narf {\n" + "public:\n" + " int zort(int newParameter = 155)\n" + " { return newParameter + 1; }\n" + "};\n"); + } + + void test() + { + QFETCH(QByteArray, original); + QFETCH(QByteArray, expected); + ExtractLiteralAsParameter factory; + QuickFixOperationTest(singleDocument(original, expected), &factory); + } + +}; + +QObject *ExtractLiteralAsParameter::createTest() { return new ExtractLiteralAsParameterTest; } + +#endif // WITH_TESTS +} // namespace + +void registerExtractLiteralAsParameterQuickfix() +{ + CppQuickFixFactory::registerFactory<ExtractLiteralAsParameter>(); +} + +} // namespace CppEditor::Internal + +#ifdef WITH_TESTS +#include <extractliteralasparameter.moc> +#endif diff --git a/src/plugins/cppeditor/quickfixes/extractliteralasparameter.h b/src/plugins/cppeditor/quickfixes/extractliteralasparameter.h new file mode 100644 index 0000000000..e6359f1602 --- /dev/null +++ b/src/plugins/cppeditor/quickfixes/extractliteralasparameter.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 registerExtractLiteralAsParameterQuickfix(); +} // namespace CppEditor::Internal diff --git a/src/plugins/cppeditor/quickfixes/insertfunctiondefinition.cpp b/src/plugins/cppeditor/quickfixes/insertfunctiondefinition.cpp new file mode 100644 index 0000000000..3c0d1a5182 --- /dev/null +++ b/src/plugins/cppeditor/quickfixes/insertfunctiondefinition.cpp @@ -0,0 +1,2129 @@ +// 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 "insertfunctiondefinition.h" + +#include "../cppcodestylepreferences.h" +#include "../cppcodestylesettings.h" +#include "../cppeditortr.h" +#include "../cppeditorwidget.h" +#include "../cpprefactoringchanges.h" +#include "../cpptoolssettings.h" +#include "../insertionpointlocator.h" +#include "../symbolfinder.h" +#include "cppquickfix.h" +#include "cppquickfixhelpers.h" + +#include <coreplugin/icore.h> +#include <cplusplus/CppRewriter.h> +#include <cplusplus/Overview.h> +#include <utils/layoutbuilder.h> + +#include <QDialogButtonBox> +#include <QGridLayout> +#include <QHBoxLayout> + +#ifdef WITH_TESTS +#include "cppquickfix_test.h" +#include <QtTest> +#endif + +using namespace CPlusPlus; +using namespace TextEditor; +using namespace Utils; + +namespace CppEditor::Internal { +namespace { + +enum DefPos { + DefPosInsideClass, + DefPosOutsideClass, + DefPosImplementationFile +}; + +enum class InsertDefsFromDeclsMode { + Off, // Testing: simulates user canceling the dialog + Impl, // Testing: simulates user choosing cpp file for every function + Alternating, // Testing: simulates user choosing a different DefPos for every function + User // Normal interactive mode +}; + +class InsertDefOperation: public CppQuickFixOperation +{ +public: + // Make sure that either loc is valid or targetFileName is not empty. + InsertDefOperation(const CppQuickFixInterface &interface, + Declaration *decl, DeclaratorAST *declAST, const InsertionLocation &loc, + const DefPos defpos, const FilePath &targetFileName = {}, + bool freeFunction = false) + : CppQuickFixOperation(interface, 0) + , m_decl(decl) + , m_declAST(declAST) + , m_loc(loc) + , m_defpos(defpos) + , m_targetFilePath(targetFileName) + { + if (m_defpos == DefPosImplementationFile) { + const FilePath declFile = decl->filePath(); + const FilePath targetFile = m_loc.isValid() ? m_loc.filePath() : m_targetFilePath; + const FilePath resolved = targetFile.relativePathFrom(declFile.parentDir()); + setPriority(2); + setDescription(Tr::tr("Add Definition in %1").arg(resolved.displayName())); + } else if (freeFunction) { + setDescription(Tr::tr("Add Definition Here")); + } else if (m_defpos == DefPosInsideClass) { + setDescription(Tr::tr("Add Definition Inside Class")); + } else if (m_defpos == DefPosOutsideClass) { + setPriority(1); + setDescription(Tr::tr("Add Definition Outside Class")); + } + } + + static void insertDefinition( + const CppQuickFixOperation *op, + InsertionLocation loc, + DefPos defPos, + DeclaratorAST *declAST, + Declaration *decl, + const FilePath &targetFilePath, + ChangeSet *changeSet = nullptr) + { + CppRefactoringChanges refactoring(op->snapshot()); + if (!loc.isValid()) + loc = insertLocationForMethodDefinition(decl, true, NamespaceHandling::Ignore, + refactoring, targetFilePath); + QTC_ASSERT(loc.isValid(), return); + + CppRefactoringFilePtr targetFile = refactoring.cppFile(loc.filePath()); + Overview oo = CppCodeStyleSettings::currentProjectCodeStyleOverview(); + oo.showFunctionSignatures = true; + oo.showReturnTypes = true; + oo.showArgumentNames = true; + oo.showEnclosingTemplate = true; + + // What we really want is to show template parameters for the class, but not for the + // function, but we cannot express that. This is an approximation that will work + // as long as either the surrounding class or the function is not a template. + oo.showTemplateParameters = decl->enclosingClass() + && decl->enclosingClass()->enclosingTemplate(); + + if (defPos == DefPosInsideClass) { + const int targetPos = targetFile->position(loc.line(), loc.column()); + ChangeSet localChangeSet; + ChangeSet * const target = changeSet ? changeSet : &localChangeSet; + target->replace(targetPos - 1, targetPos, QLatin1String("\n {\n\n}")); // replace ';' + + if (!changeSet) { + targetFile->setChangeSet(*target); + targetFile->setOpenEditor(true, targetPos); + targetFile->apply(); + + // Move cursor inside definition + QTextCursor c = targetFile->cursor(); + c.setPosition(targetPos); + c.movePosition(QTextCursor::Down); + c.movePosition(QTextCursor::EndOfLine); + op->editor()->setTextCursor(c); + } + } else { + // make target lookup context + Document::Ptr targetDoc = targetFile->cppDocument(); + Scope *targetScope = targetDoc->scopeAt(loc.line(), loc.column()); + + // Correct scope in case of a function try-block. See QTCREATORBUG-14661. + if (targetScope && targetScope->asBlock()) { + if (Class * const enclosingClass = targetScope->enclosingClass()) + targetScope = enclosingClass; + else + targetScope = targetScope->enclosingNamespace(); + } + + LookupContext targetContext(targetDoc, op->snapshot()); + ClassOrNamespace *targetCoN = targetContext.lookupType(targetScope); + if (!targetCoN) + targetCoN = targetContext.globalNamespace(); + + // setup rewriting to get minimally qualified names + SubstitutionEnvironment env; + env.setContext(op->context()); + env.switchScope(decl->enclosingScope()); + UseMinimalNames q(targetCoN); + env.enter(&q); + Control *control = op->context().bindings()->control().get(); + + // rewrite the function type + const FullySpecifiedType tn = rewriteType(decl->type(), &env, control); + + // rewrite the function name + if (nameIncludesOperatorName(decl->name())) { + CppRefactoringFilePtr file = refactoring.cppFile(op->filePath()); + const QString operatorNameText = file->textOf(declAST->core_declarator); + oo.includeWhiteSpaceInOperatorName = operatorNameText.contains(QLatin1Char(' ')); + } + const QString name = oo.prettyName(LookupContext::minimalName(decl, targetCoN, + control)); + + const QString inlinePref = inlinePrefix(targetFilePath, [defPos] { + return defPos == DefPosOutsideClass; + }); + + const QString prettyType = oo.prettyType(tn, name); + + QString input = prettyType; + int index = 0; + while (input.startsWith("template")) { + QRegularExpression templateRegex("template\\s*<[^>]*>"); + QRegularExpressionMatch match = templateRegex.match(input); + if (match.hasMatch()) { + index += match.captured().size() + 1; + input = input.mid(match.captured().size() + 1); + } + } + + QString defText = prettyType; + defText.insert(index, inlinePref); + defText += QLatin1String("\n{\n\n}"); + + ChangeSet localChangeSet; + ChangeSet * const target = changeSet ? changeSet : &localChangeSet; + const int targetPos = targetFile->position(loc.line(), loc.column()); + target->insert(targetPos, loc.prefix() + defText + loc.suffix()); + + if (!changeSet) { + targetFile->setChangeSet(*target); + targetFile->setOpenEditor(true, targetPos); + targetFile->apply(); + + // Move cursor inside definition + QTextCursor c = targetFile->cursor(); + c.setPosition(targetPos); + c.movePosition(QTextCursor::Down, QTextCursor::MoveAnchor, + loc.prefix().count(QLatin1String("\n")) + 2); + c.movePosition(QTextCursor::EndOfLine); + if (defPos == DefPosImplementationFile) { + if (targetFile->editor()) + targetFile->editor()->setTextCursor(c); + } else { + op->editor()->setTextCursor(c); + } + } + } + } + +private: + void perform() override + { + insertDefinition(this, m_loc, m_defpos, m_declAST, m_decl, m_targetFilePath); + } + + Declaration *m_decl; + DeclaratorAST *m_declAST; + InsertionLocation m_loc; + const DefPos m_defpos; + const FilePath m_targetFilePath; +}; + +class MemberFunctionImplSetting +{ +public: + Symbol *func = nullptr; + DefPos defPos = DefPosImplementationFile; +}; +using MemberFunctionImplSettings = QList<MemberFunctionImplSetting>; + +class AddImplementationsDialog : public QDialog +{ +public: + AddImplementationsDialog(const QList<Symbol *> &candidates, const FilePath &implFile) + : QDialog(Core::ICore::dialogParent()), m_candidates(candidates) + { + setWindowTitle(Tr::tr("Member Function Implementations")); + + const auto defaultImplTargetComboBox = new QComboBox; + QStringList implTargetStrings{Tr::tr("None"), Tr::tr("Inline"), Tr::tr("Outside Class")}; + if (!implFile.isEmpty()) + implTargetStrings.append(implFile.fileName()); + defaultImplTargetComboBox->insertItems(0, implTargetStrings); + connect(defaultImplTargetComboBox, &QComboBox::currentIndexChanged, this, + [this](int index) { + for (int i = 0; i < m_implTargetBoxes.size(); ++i) { + if (!m_candidates.at(i)->type()->asFunctionType()->isPureVirtual()) + static_cast<QComboBox *>(m_implTargetBoxes.at(i))->setCurrentIndex(index); + } + }); + const auto defaultImplTargetLayout = new QHBoxLayout; + defaultImplTargetLayout->addWidget(new QLabel(Tr::tr("Default implementation location:"))); + defaultImplTargetLayout->addWidget(defaultImplTargetComboBox); + + const auto candidatesLayout = new QGridLayout; + Overview oo = CppCodeStyleSettings::currentProjectCodeStyleOverview(); + oo.showFunctionSignatures = true; + oo.showReturnTypes = true; + for (int i = 0; i < m_candidates.size(); ++i) { + const Function * const func = m_candidates.at(i)->type()->asFunctionType(); + QTC_ASSERT(func, continue); + const auto implTargetComboBox = new QComboBox; + m_implTargetBoxes.append(implTargetComboBox); + implTargetComboBox->insertItems(0, implTargetStrings); + if (func->isPureVirtual()) + implTargetComboBox->setCurrentIndex(0); + candidatesLayout->addWidget(new QLabel(oo.prettyType(func->type(), func->name())), + i, 0); + candidatesLayout->addWidget(implTargetComboBox, i, 1); + } + + const auto buttonBox + = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); + connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); + + defaultImplTargetComboBox->setCurrentIndex(implTargetStrings.size() - 1); + const auto mainLayout = new QVBoxLayout(this); + mainLayout->addLayout(defaultImplTargetLayout); + mainLayout->addWidget(Layouting::createHr(this)); + mainLayout->addLayout(candidatesLayout); + mainLayout->addWidget(buttonBox); + } + + MemberFunctionImplSettings settings() const + { + QTC_ASSERT(m_candidates.size() == m_implTargetBoxes.size(), return {}); + MemberFunctionImplSettings settings; + for (int i = 0; i < m_candidates.size(); ++i) { + MemberFunctionImplSetting setting; + const int index = m_implTargetBoxes.at(i)->currentIndex(); + const bool addImplementation = index != 0; + if (!addImplementation) + continue; + setting.func = m_candidates.at(i); + setting.defPos = static_cast<DefPos>(index - 1); + settings << setting; + } + return settings; + } + +private: + const QList<Symbol *> m_candidates; + QList<QComboBox *> m_implTargetBoxes; +}; + +class InsertDefsOperation: public CppQuickFixOperation +{ +public: + InsertDefsOperation(const CppQuickFixInterface &interface) : CppQuickFixOperation(interface) + { + setDescription(Tr::tr("Create Implementations for Member Functions")); + + m_classAST = astForClassOperations(interface); + if (!m_classAST) + return; + const Class * const theClass = m_classAST->symbol; + if (!theClass) + return; + + // Collect all member functions. + for (auto it = theClass->memberBegin(); it != theClass->memberEnd(); ++it) { + Symbol * const s = *it; + if (!s->identifier() || !s->type() || !s->asDeclaration() || s->asFunction()) + continue; + Function * const func = s->type()->asFunctionType(); + if (!func || func->isSignal() || func->isFriend()) + continue; + Overview oo = CppCodeStyleSettings::currentProjectCodeStyleOverview(); + oo.showFunctionSignatures = true; + if (magicQObjectFunctions().contains(oo.prettyName(func->name()))) + continue; + m_declarations << s; + } + } + + bool isApplicable() const { return !m_declarations.isEmpty(); } + void setMode(InsertDefsFromDeclsMode mode) { m_mode = mode; } + +private: + void perform() override + { + QList<Symbol *> unimplemented; + SymbolFinder symbolFinder; + for (Symbol * const s : std::as_const(m_declarations)) { + if (!symbolFinder.findMatchingDefinition(s, snapshot())) + unimplemented << s; + } + if (unimplemented.isEmpty()) + return; + + CppRefactoringChanges refactoring(snapshot()); + const bool isHeaderFile = ProjectFile::isHeader(ProjectFile::classify(filePath().toString())); + FilePath cppFile; // Only set if the class is defined in a header file. + if (isHeaderFile) { + InsertionPointLocator locator(refactoring); + for (const InsertionLocation &location + : locator.methodDefinition(unimplemented.first(), false, {})) { + if (!location.isValid()) + continue; + const FilePath filePath = location.filePath(); + if (ProjectFile::isHeader(ProjectFile::classify(filePath.path()))) { + const FilePath source = correspondingHeaderOrSource(filePath); + if (!source.isEmpty()) + cppFile = source; + } else { + cppFile = filePath; + } + break; + } + } + + MemberFunctionImplSettings settings; + switch (m_mode) { + case InsertDefsFromDeclsMode::User: { + AddImplementationsDialog dlg(unimplemented, cppFile); + if (dlg.exec() == QDialog::Accepted) + settings = dlg.settings(); + break; + } + case InsertDefsFromDeclsMode::Impl: { + for (Symbol * const func : std::as_const(unimplemented)) { + MemberFunctionImplSetting setting; + setting.func = func; + setting.defPos = DefPosImplementationFile; + settings << setting; + } + break; + } + case InsertDefsFromDeclsMode::Alternating: { + int defPos = DefPosImplementationFile; + const auto incDefPos = [&defPos] { + defPos = (defPos + 1) % (DefPosImplementationFile + 2); + }; + for (Symbol * const func : std::as_const(unimplemented)) { + incDefPos(); + if (defPos > DefPosImplementationFile) + continue; + MemberFunctionImplSetting setting; + setting.func = func; + setting.defPos = static_cast<DefPos>(defPos); + settings << setting; + } + break; + } + case InsertDefsFromDeclsMode::Off: + break; + } + + if (settings.isEmpty()) + return; + + class DeclFinder : public ASTVisitor + { + public: + DeclFinder(const CppRefactoringFile *file, const Symbol *func) + : ASTVisitor(file->cppDocument()->translationUnit()), m_func(func) {} + + SimpleDeclarationAST *decl() const { return m_decl; } + + private: + bool visit(SimpleDeclarationAST *decl) override + { + if (m_decl) + return false; + if (decl->symbols && decl->symbols->value == m_func) + m_decl = decl; + return !m_decl; + } + + const Symbol * const m_func; + SimpleDeclarationAST *m_decl = nullptr; + }; + + QHash<FilePath, ChangeSet> changeSets; + for (const MemberFunctionImplSetting &setting : std::as_const(settings)) { + DeclFinder finder(currentFile().data(), setting.func); + finder.accept(m_classAST); + QTC_ASSERT(finder.decl(), continue); + InsertionLocation loc; + const FilePath targetFilePath = setting.defPos == DefPosImplementationFile + ? cppFile : filePath(); + QTC_ASSERT(!targetFilePath.isEmpty(), continue); + if (setting.defPos == DefPosInsideClass) { + int line, column; + currentFile()->lineAndColumn(currentFile()->endOf(finder.decl()), &line, &column); + loc = InsertionLocation(filePath(), QString(), QString(), line, column); + } + ChangeSet &changeSet = changeSets[targetFilePath]; + InsertDefOperation::insertDefinition( + this, loc, setting.defPos, finder.decl()->declarator_list->value, + setting.func->asDeclaration(),targetFilePath, &changeSet); + } + for (auto it = changeSets.cbegin(); it != changeSets.cend(); ++it) { + const CppRefactoringFilePtr file = refactoring.cppFile(it.key()); + file->setChangeSet(it.value()); + file->apply(); + } + } + + ClassSpecifierAST *m_classAST = nullptr; + InsertDefsFromDeclsMode m_mode; + QList<Symbol *> m_declarations; +}; + +class InsertDefFromDecl: public CppQuickFixFactory +{ +public: +#ifdef WITH_TESTS + static QObject *createTest(); +#endif + + void setOutside() { m_defPosOutsideClass = true; } + +private: + void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override + { + const QList<AST *> &path = interface.path(); + + int idx = path.size() - 1; + for (; idx >= 0; --idx) { + AST *node = path.at(idx); + if (SimpleDeclarationAST *simpleDecl = node->asSimpleDeclaration()) { + if (idx > 0 && path.at(idx - 1)->asStatement()) + return; + if (simpleDecl->symbols && !simpleDecl->symbols->next) { + if (Symbol *symbol = simpleDecl->symbols->value) { + if (Declaration *decl = symbol->asDeclaration()) { + if (Function *func = decl->type()->asFunctionType()) { + if (func->isSignal() || func->isPureVirtual() || func->isFriend()) + return; + + // Check if there is already a definition + SymbolFinder symbolFinder; + if (symbolFinder.findMatchingDefinition(decl, interface.snapshot(), + true)) { + return; + } + + // Insert Position: Implementation File + DeclaratorAST *declAST = simpleDecl->declarator_list->value; + InsertDefOperation *op = nullptr; + ProjectFile::Kind kind = ProjectFile::classify(interface.filePath().toString()); + const bool isHeaderFile = ProjectFile::isHeader(kind); + if (isHeaderFile) { + CppRefactoringChanges refactoring(interface.snapshot()); + InsertionPointLocator locator(refactoring); + // find appropriate implementation file, but do not use this + // location, because insertLocationForMethodDefinition() should + // be used in perform() to get consistent insert positions. + for (const InsertionLocation &location : + locator.methodDefinition(decl, false, {})) { + if (!location.isValid()) + continue; + + const FilePath filePath = location.filePath(); + if (ProjectFile::isHeader(ProjectFile::classify(filePath.path()))) { + const FilePath source = correspondingHeaderOrSource(filePath); + if (!source.isEmpty()) { + op = new InsertDefOperation(interface, decl, declAST, + InsertionLocation(), + DefPosImplementationFile, + source); + } + } else { + op = new InsertDefOperation(interface, decl, declAST, + InsertionLocation(), + DefPosImplementationFile, + filePath); + } + + if (op) + result << op; + break; + } + } + + // Determine if we are dealing with a free function + const bool isFreeFunction = func->enclosingClass() == nullptr; + + // Insert Position: Outside Class + if (!isFreeFunction || m_defPosOutsideClass) { + result << new InsertDefOperation(interface, decl, declAST, + InsertionLocation(), + DefPosOutsideClass, + interface.filePath()); + } + + // Insert Position: Inside Class + // Determine insert location direct after the declaration. + int line, column; + const CppRefactoringFilePtr file = interface.currentFile(); + file->lineAndColumn(file->endOf(simpleDecl), &line, &column); + const InsertionLocation loc + = InsertionLocation(interface.filePath(), QString(), + QString(), line, column); + result << new InsertDefOperation(interface, decl, declAST, loc, + DefPosInsideClass, FilePath(), + isFreeFunction); + + return; + } + } + } + } + break; + } + } + } + + bool m_defPosOutsideClass = false; +}; + +//! Adds a definition for any number of member function declarations. +class InsertDefsFromDecls : public CppQuickFixFactory +{ +public: +#ifdef WITH_TESTS + static QObject *createTest(); +#endif + + void setMode(InsertDefsFromDeclsMode mode) { m_mode = mode; } + +private: + void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override + { + const auto op = QSharedPointer<InsertDefsOperation>::create(interface); + op->setMode(m_mode); + if (op->isApplicable()) + result << op; + } + +private: + InsertDefsFromDeclsMode m_mode = InsertDefsFromDeclsMode::User; +}; + +#ifdef WITH_TESTS +using namespace Tests; + +static QList<TestDocumentPtr> singleHeader(const QByteArray &original, const QByteArray &expected) +{ + return {CppTestDocument::create("file.h", original, expected)}; +} + +class InsertDefFromDeclTest : public QObject +{ + Q_OBJECT + +private slots: + /// Check if definition is inserted right after class for insert definition outside + void testAfterClass() + { + QList<TestDocumentPtr> testDocuments; + QByteArray original; + QByteArray expected; + + // Header File + original = + "class Foo\n" + "{\n" + " Foo();\n" + " void a@();\n" + "};\n" + "\n" + "class Bar {};\n"; + expected = + "class Foo\n" + "{\n" + " Foo();\n" + " void a();\n" + "};\n" + "\n" + "inline void Foo::a()\n" + "{\n\n}\n" + "\n" + "class Bar {};\n"; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File + original = + "#include \"file.h\"\n" + "\n" + "Foo::Foo()\n" + "{\n\n" + "}\n"; + expected = original; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + InsertDefFromDecl factory; + QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), 1); + } + + /// Check from header file: If there is a source file, insert the definition in the source file. + /// Case: Source file is empty. + void testHeaderSourceBasic1() + { + QList<TestDocumentPtr> testDocuments; + + QByteArray original; + QByteArray expected; + + // Header File + original = + "struct Foo\n" + "{\n" + " Foo()@;\n" + "};\n"; + expected = original; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File + original.resize(0); + expected = + "\n" + "Foo::Foo()\n" + "{\n\n" + "}\n" + ; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + InsertDefFromDecl factory; + QuickFixOperationTest(testDocuments, &factory); + } + + /// Check from header file: If there is a source file, insert the definition in the source file. + /// Case: Source file is not empty. + void testHeaderSourceBasic2() + { + QList<TestDocumentPtr> testDocuments; + + QByteArray original; + QByteArray expected; + + // Header File + original = "void f(const std::vector<int> &v)@;\n"; + expected = original; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File + original = + "#include \"file.h\"\n" + "\n" + "int x;\n" + ; + expected = + "#include \"file.h\"\n" + "\n" + "int x;\n" + "\n" + "void f(const std::vector<int> &v)\n" + "{\n" + "\n" + "}\n" + ; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + InsertDefFromDecl factory; + QuickFixOperationTest(testDocuments, &factory); + } + + /// Check from source file: Insert in source file, not header file. + void testHeaderSourceBasic3() + { + QList<TestDocumentPtr> testDocuments; + + QByteArray original; + QByteArray expected; + + // Empty Header File + testDocuments << CppTestDocument::create("file.h", "", ""); + + // Source File + original = + "struct Foo\n" + "{\n" + " Foo()@;\n" + "};\n"; + expected = original + + "\n" + "Foo::Foo()\n" + "{\n\n" + "}\n" + ; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + InsertDefFromDecl factory; + QuickFixOperationTest(testDocuments, &factory); + } + + /// Check from header file: If the class is in a namespace, the added function definition + /// name must be qualified accordingly. + void testHeaderSourceNamespace1() + { + QList<TestDocumentPtr> testDocuments; + + QByteArray original; + QByteArray expected; + + // Header File + original = + "namespace N {\n" + "struct Foo\n" + "{\n" + " Foo()@;\n" + "};\n" + "}\n"; + expected = original; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File + original.resize(0); + expected = + "\n" + "N::Foo::Foo()\n" + "{\n\n" + "}\n" + ; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + InsertDefFromDecl factory; + QuickFixOperationTest(testDocuments, &factory); + } + + /// Check from header file: If the class is in namespace N and the source file has a + /// "using namespace N" line, the function definition name must be qualified accordingly. + void testHeaderSourceNamespace2() + { + QList<TestDocumentPtr> testDocuments; + + QByteArray original; + QByteArray expected; + + // Header File + original = + "namespace N {\n" + "struct Foo\n" + "{\n" + " Foo()@;\n" + "};\n" + "}\n"; + expected = original; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File + original = + "#include \"file.h\"\n" + "using namespace N;\n" + ; + expected = original + + "\n" + "Foo::Foo()\n" + "{\n\n" + "}\n" + ; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + InsertDefFromDecl factory; + QuickFixOperationTest(testDocuments, &factory); + } + + /// Check definition insert inside class + void testInsideClass() + { + const QByteArray original = + "class Foo {\n" + " void b@ar();\n" + "};"; + const QByteArray expected = + "class Foo {\n" + " void bar()\n" + " {\n\n" + " }\n" + "};"; + + InsertDefFromDecl factory; + QuickFixOperationTest(singleDocument(original, expected), &factory, ProjectExplorer::HeaderPaths(), + 1); + } + + /// Check not triggering when definition exists + void testNotTriggeringWhenDefinitionExists() + { + const QByteArray original = + "class Foo {\n" + " void b@ar();\n" + "};\n" + "void Foo::bar() {}\n"; + + InsertDefFromDecl factory; + QuickFixOperationTest(singleDocument(original, ""), &factory, ProjectExplorer::HeaderPaths(), 1); + } + + /// Find right implementation file. + void testFindRightImplementationFile() + { + QList<TestDocumentPtr> testDocuments; + + QByteArray original; + QByteArray expected; + + // Header File + original = + "struct Foo\n" + "{\n" + " Foo();\n" + " void a();\n" + " void b@();\n" + "};\n" + "}\n"; + expected = original; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File #1 + original = + "#include \"file.h\"\n" + "\n" + "Foo::Foo()\n" + "{\n\n" + "}\n"; + expected = original; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + + // Source File #2 + original = + "#include \"file.h\"\n" + "\n" + "void Foo::a()\n" + "{\n\n" + "}\n"; + expected = original + + "\n" + "void Foo::b()\n" + "{\n\n" + "}\n"; + testDocuments << CppTestDocument::create("file2.cpp", original, expected); + + InsertDefFromDecl factory; + QuickFixOperationTest(testDocuments, &factory); + } + + /// Ignore generated functions declarations when looking at the surrounding + /// functions declarations in order to find the right implementation file. + void testIgnoreSurroundingGeneratedDeclarations() + { + QList<TestDocumentPtr> testDocuments; + + QByteArray original; + QByteArray expected; + + // Header File + original = + "#define DECLARE_HIDDEN_FUNCTION void hidden();\n" + "struct Foo\n" + "{\n" + " void a();\n" + " DECLARE_HIDDEN_FUNCTION\n" + " void b@();\n" + "};\n" + "}\n"; + expected = original; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File #1 + original = + "#include \"file.h\"\n" + "\n" + "void Foo::a()\n" + "{\n\n" + "}\n"; + expected = + "#include \"file.h\"\n" + "\n" + "void Foo::a()\n" + "{\n\n" + "}\n" + "\n" + "void Foo::b()\n" + "{\n\n" + "}\n"; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + // Source File #2 + original = + "#include \"file.h\"\n" + "\n" + "void Foo::hidden()\n" + "{\n\n" + "}\n"; + expected = original; + testDocuments << CppTestDocument::create("file2.cpp", original, expected); + + InsertDefFromDecl factory; + QuickFixOperationTest(testDocuments, &factory); + } + + /// Check if whitespace is respected for operator functions + void testRespectWsInOperatorNames1() + { + QByteArray original = + "class Foo\n" + "{\n" + " Foo &opera@tor =();\n" + "};\n"; + QByteArray expected = + "class Foo\n" + "{\n" + " Foo &operator =();\n" + "};\n" + "\n" + "Foo &Foo::operator =()\n" + "{\n" + "\n" + "}\n"; + + InsertDefFromDecl factory; + QuickFixOperationTest(singleDocument(original, expected), &factory); + } + + /// Check if whitespace is respected for operator functions + void testRespectWsInOperatorNames2() + { + QByteArray original = + "class Foo\n" + "{\n" + " Foo &opera@tor=();\n" + "};\n"; + QByteArray expected = + "class Foo\n" + "{\n" + " Foo &operator=();\n" + "};\n" + "\n" + "Foo &Foo::operator=()\n" + "{\n" + "\n" + "}\n"; + + InsertDefFromDecl factory; + QuickFixOperationTest(singleDocument(original, expected), &factory); + } + + /// Check that the noexcept exception specifier is transferred + void testNoexceptSpecifier() + { + QByteArray original = + "class Foo\n" + "{\n" + " void @foo() noexcept(false);\n" + "};\n"; + QByteArray expected = + "class Foo\n" + "{\n" + " void foo() noexcept(false);\n" + "};\n" + "\n" + "void Foo::foo() noexcept(false)\n" + "{\n" + "\n" + "}\n"; + + InsertDefFromDecl factory; + QuickFixOperationTest(singleDocument(original, expected), &factory); + } + + /// Check if a function like macro use is not separated by the function to insert + /// Case: Macro preceded by preproceesor directives and declaration. + void testMacroUsesAtEndOfFile1() + { + QList<TestDocumentPtr> testDocuments; + + QByteArray original; + QByteArray expected; + + // Header File + original = "void f()@;\n"; + expected = original; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File + original = + "#include \"file.h\"\n" + "#define MACRO(X) X x;\n" + "int lala;\n" + "\n" + "MACRO(int)\n" + ; + expected = + "#include \"file.h\"\n" + "#define MACRO(X) X x;\n" + "int lala;\n" + "\n" + "\n" + "\n" + "void f()\n" + "{\n" + "\n" + "}\n" + "\n" + "MACRO(int)\n" + ; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + InsertDefFromDecl factory; + QuickFixOperationTest(testDocuments, &factory); + } + + /// Check if a function like macro use is not separated by the function to insert + /// Case: Marco preceded only by preprocessor directives. + void testMacroUsesAtEndOfFile2() + { + QList<TestDocumentPtr> testDocuments; + + QByteArray original; + QByteArray expected; + + // Header File + original = "void f()@;\n"; + expected = original; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File + original = + "#include \"file.h\"\n" + "#define MACRO(X) X x;\n" + "\n" + "MACRO(int)\n" + ; + expected = + "#include \"file.h\"\n" + "#define MACRO(X) X x;\n" + "\n" + "\n" + "\n" + "void f()\n" + "{\n" + "\n" + "}\n" + "\n" + "MACRO(int)\n" + ; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + InsertDefFromDecl factory; + QuickFixOperationTest(testDocuments, &factory); + } + + /// Check if insertion happens before syntactically erroneous statements at end of file. + void testErroneousStatementAtEndOfFile() + { + QList<TestDocumentPtr> testDocuments; + + QByteArray original; + QByteArray expected; + + // Header File + original = "void f()@;\n"; + expected = original; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File + original = + "#include \"file.h\"\n" + "\n" + "MissingSemicolon(int)\n" + ; + expected = + "#include \"file.h\"\n" + "\n" + "\n" + "\n" + "void f()\n" + "{\n" + "\n" + "}\n" + "\n" + "MissingSemicolon(int)\n" + ; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + InsertDefFromDecl factory; + QuickFixOperationTest(testDocuments, &factory); + } + + /// Check: Respect rvalue references + void testRvalueReference() + { + QList<TestDocumentPtr> testDocuments; + + QByteArray original; + QByteArray expected; + + // Header File + original = "void f(Foo &&)@;\n"; + expected = original; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File + original = ""; + expected = + "\n" + "void f(Foo &&)\n" + "{\n" + "\n" + "}\n" + ; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + InsertDefFromDecl factory; + QuickFixOperationTest(testDocuments, &factory); + } + + void testFunctionTryBlock() + { + QList<TestDocumentPtr> testDocuments; + + QByteArray original; + QByteArray expected; + + // Header File + original = R"( +struct Foo { + void tryCatchFunc(); + void @otherFunc(); +}; +)"; + expected = original; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File + original = R"( +#include "file.h" + +void Foo::tryCatchFunc() try {} catch (...) {} +)"; + expected = R"( +#include "file.h" + +void Foo::tryCatchFunc() try {} catch (...) {} + +void Foo::otherFunc() +{ + +} +)"; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + InsertDefFromDecl factory; + QuickFixOperationTest(testDocuments, &factory); + } + + void testUsingDecl() + { + QList<TestDocumentPtr> testDocuments; + + QByteArray original; + QByteArray expected; + + // Header File + original = R"( +namespace N { struct S; } +using N::S; + +void @func(const S &s); +)"; + expected = original; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File + original = R"( +#include "file.h" +)"; + expected = R"( +#include "file.h" + +void func(const S &s) +{ + +} +)"; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + InsertDefFromDecl factory; + QuickFixOperationTest(testDocuments, &factory); + + testDocuments.clear(); + original = R"( +namespace N1 { +namespace N2 { struct S; } +using N2::S; +} + +void @func(const N1::S &s); +)"; + expected = original; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File + original = R"( +#include "file.h" +)"; + expected = R"( +#include "file.h" + +void func(const N1::S &s) +{ + +} +)"; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + QuickFixOperationTest(testDocuments, &factory); + + // No using declarations here, but the code model has one. No idea why. + testDocuments.clear(); + original = R"( +class B {}; +class D : public B { + @D(); +}; +)"; + expected = original; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File + original = R"( +#include "file.h" +)"; + expected = R"( +#include "file.h" + +D::D() +{ + +} +)"; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + QuickFixOperationTest(testDocuments, &factory); + + testDocuments.clear(); + original = R"( +namespace ns1 { template<typename T> class span {}; } + +namespace ns { +using ns1::span; +class foo +{ + void @bar(ns::span<int>); +}; +} +)"; + expected = R"( +namespace ns1 { template<typename T> class span {}; } + +namespace ns { +using ns1::span; +class foo +{ + void bar(ns::span<int>); +}; + +void foo::bar(ns::span<int>) +{ + +} + +} +)"; + // TODO: Unneeded namespace gets inserted in RewriteName::visit(const QualifiedNameId *) + testDocuments << CppTestDocument::create("file.cpp", original, expected); + QuickFixOperationTest(testDocuments, &factory); + } + + /// Find right implementation file. (QTCREATORBUG-10728) + void testFindImplementationFile() + { + QList<TestDocumentPtr> testDocuments; + + QByteArray original; + QByteArray expected; + + // Header File + original = + "class Foo {\n" + " void bar();\n" + " void ba@z();\n" + "};\n" + "\n" + "void Foo::bar()\n" + "{}\n"; + expected = original; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File + original = + "#include \"file.h\"\n" + ; + expected = + "#include \"file.h\"\n" + "\n" + "void Foo::baz()\n" + "{\n" + "\n" + "}\n" + ; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + InsertDefFromDecl factory; + QuickFixOperationTest(testDocuments, &factory); + } + + void testUnicodeIdentifier() + { + QList<TestDocumentPtr> testDocuments; + + QByteArray original; + QByteArray expected; + +// +// The following "non-latin1" code points are used in the tests: +// +// U+00FC - 2 code units in UTF8, 1 in UTF16 - LATIN SMALL LETTER U WITH DIAERESIS +// U+4E8C - 3 code units in UTF8, 1 in UTF16 - CJK UNIFIED IDEOGRAPH-4E8C +// U+10302 - 4 code units in UTF8, 2 in UTF16 - OLD ITALIC LETTER KE +// + +#define UNICODE_U00FC "\xc3\xbc" +#define UNICODE_U4E8C "\xe4\xba\x8c" +#define UNICODE_U10302 "\xf0\x90\x8c\x82" +#define TEST_UNICODE_IDENTIFIER UNICODE_U00FC UNICODE_U4E8C UNICODE_U10302 + + original = + "class Foo {\n" + " void @" TEST_UNICODE_IDENTIFIER "();\n" + "};\n"; + ; + expected = original; + expected += + "\n" + "void Foo::" TEST_UNICODE_IDENTIFIER "()\n" + "{\n" + "\n" + "}\n"; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + +#undef UNICODE_U00FC +#undef UNICODE_U4E8C +#undef UNICODE_U10302 +#undef TEST_UNICODE_IDENTIFIER + + InsertDefFromDecl factory; + QuickFixOperationTest(testDocuments, &factory); + } + + void testTemplateClass() + { + QByteArray original = + "template<class T>\n" + "class Foo\n" + "{\n" + " void fun@c1();\n" + " void func2();\n" + "};\n\n" + "template<class T>\n" + "void Foo<T>::func2() {}\n"; + QByteArray expected = + "template<class T>\n" + "class Foo\n" + "{\n" + " void func1();\n" + " void func2();\n" + "};\n\n" + "template<class T>\n" + "void Foo<T>::func1()\n" + "{\n" + "\n" + "}\n\n" + "template<class T>\n" + "void Foo<T>::func2() {}\n"; + + InsertDefFromDecl factory; + QuickFixOperationTest(singleDocument(original, expected), &factory); + } + + void testTemplateClassWithValueParam() + { + QList<TestDocumentPtr> testDocuments; + QByteArray original = + "template<typename T, int size> struct MyArray {};\n" + "MyArray<int, 1> @foo();"; + QByteArray expected = original; + testDocuments << CppTestDocument::create("file.h", original, expected); + + original = "#include \"file.h\"\n"; + expected = + "#include \"file.h\"\n\n" + "MyArray<int, 1> foo()\n" + "{\n\n" + "}\n"; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + InsertDefFromDecl factory; + QuickFixOperationTest(testDocuments, &factory); + } + + void testTemplateFunction() + { + QByteArray original = + "class Foo\n" + "{\n" + " template<class T>\n" + " void fun@c();\n" + "};\n"; + QByteArray expected = + "class Foo\n" + "{\n" + " template<class T>\n" + " void fun@c();\n" + "};\n" + "\n" + "template<class T>\n" + "void Foo::func()\n" + "{\n" + "\n" + "}\n"; + + InsertDefFromDecl factory; + QuickFixOperationTest(singleDocument(original, expected), &factory); + } + + void testTemplateClassAndTemplateFunction() + { + QByteArray original = + "template<class T>" + "class Foo\n" + "{\n" + " template<class U>\n" + " T fun@c(U u);\n" + "};\n"; + QByteArray expected = + "template<class T>" + "class Foo\n" + "{\n" + " template<class U>\n" + " T fun@c(U u);\n" + "};\n" + "\n" + "template<class T>\n" + "template<class U>\n" + "T Foo<T>::func(U u)\n" + "{\n" + "\n" + "}\n"; + + InsertDefFromDecl factory; + QuickFixOperationTest(singleDocument(original, expected), &factory); + } + + void testTemplateClassAndFunctionInsideNamespace() + { + QByteArray original = + "namespace N {\n" + "template<class T>" + "class Foo\n" + "{\n" + " template<class U>\n" + " T fun@c(U u);\n" + "};\n" + "}\n"; + QByteArray expected = + "namespace N {\n" + "template<class T>" + "class Foo\n" + "{\n" + " template<class U>\n" + " T fun@c(U u);\n" + "};\n" + "\n" + "template<class T>\n" + "template<class U>\n" + "T Foo<T>::func(U u)\n" + "{\n" + "\n" + "}\n" + "\n" + "}\n"; + + InsertDefFromDecl factory; + QuickFixOperationTest(singleDocument(original, expected), &factory); + } + + void testFunctionWithSignedUnsignedArgument() + { + QByteArray original; + QByteArray expected; + InsertDefFromDecl factory; + + original =R"--( +class myclass +{ + myc@lass(QVector<signed> g); + myclass(QVector<unsigned> g); +} +)--"; + expected =R"--( +class myclass +{ + myclass(QVector<signed> g); + myclass(QVector<unsigned> g); +} + +myclass::myclass(QVector<signed int> g) +{ + +} +)--"; + + QuickFixOperationTest(singleDocument(original, expected), &factory); + + original =R"--( +class myclass +{ + myclass(QVector<signed> g); + myc@lass(QVector<unsigned> g); +} +)--"; + expected =R"--( +class myclass +{ + myclass(QVector<signed> g); + myclass(QVector<unsigned> g); +} + +myclass::myclass(QVector<unsigned int> g) +{ + +} +)--"; + + QuickFixOperationTest(singleDocument(original, expected), &factory); + + original =R"--( +class myclass +{ + unsigned f@oo(unsigned); +} +)--"; + expected =R"--( +class myclass +{ + unsigned foo(unsigned); +} + +unsigned int myclass::foo(unsigned int) +{ + +} +)--"; + QuickFixOperationTest(singleDocument(original, expected), &factory); + + original =R"--( +class myclass +{ + signed f@oo(signed); +} +)--"; + expected =R"--( +class myclass +{ + signed foo(signed); +} + +signed int myclass::foo(signed int) +{ + +} +)--"; + QuickFixOperationTest(singleDocument(original, expected), &factory); + } + + void testNotTriggeredForFriendFunc() + { + const QByteArray contents = + "class Foo\n" + "{\n" + " friend void f@unc();\n" + "};\n" + "\n"; + + InsertDefFromDecl factory; + QuickFixOperationTest(singleDocument(contents, ""), &factory); + } + + void testMinimalFunctionParameterType() + { + QList<TestDocumentPtr> testDocuments; + + QByteArray original; + QByteArray expected; + + // Header File + original = R"( +class C { + typedef int A; + A @foo(A); +}; +)"; + expected = original; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File + original = R"( +#include "file.h" +)"; + expected = R"( +#include "file.h" + +C::A C::foo(A) +{ + +} +)"; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + InsertDefFromDecl factory; + QuickFixOperationTest(testDocuments, &factory); + + testDocuments.clear(); + // Header File + original = R"( +namespace N { + struct S; + S @foo(const S &s); +}; +)"; + expected = original; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File + original = R"( +#include "file.h" +)"; + expected = R"( +#include "file.h" + +N::S N::foo(const S &s) +{ + +} +)"; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + QuickFixOperationTest(testDocuments, &factory); + } + + void testAliasTemplateAsReturnType() + { + QList<TestDocumentPtr> testDocuments; + + QByteArray original; + QByteArray expected; + + // Header File + original = R"( +struct foo { + struct foo2 { + template <typename T> using MyType = T; + MyType<int> @bar(); + }; +}; +)"; + expected = original; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File + original = R"( +#include "file.h" +)"; + expected = R"( +#include "file.h" + +foo::foo2::MyType<int> foo::foo2::bar() +{ + +} +)"; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + InsertDefFromDecl factory; + QuickFixOperationTest(testDocuments, &factory); + } + + void test_data() + { + QTest::addColumn<QByteArray>("original"); + QTest::addColumn<QByteArray>("expected"); + + // Check from source file: If there is no header file, insert the definition after the class. + QByteArray original = + "struct Foo\n" + "{\n" + " Foo();@\n" + "};\n"; + QByteArray expected = original + + "\n" + "Foo::Foo()\n" + "{\n\n" + "}\n"; + QTest::newRow("basic") << original << expected; + + original = "void free()@;\n"; + expected = "void free()\n{\n\n}\n"; + QTest::newRow("freeFunction") << original << expected; + + original = "class Foo {\n" + "public:\n" + " Foo() {}\n" + "};\n" + "void freeFunc() {\n" + " Foo @f();" + "}\n"; + + // Check not triggering when it is a statement + QTest::newRow("notTriggeringStatement") << original << QByteArray(); + } + + void test() + { + QFETCH(QByteArray, original); + QFETCH(QByteArray, expected); + + InsertDefFromDecl factory; + QuickFixOperationTest(singleDocument(original, expected), &factory); + } + + void testOutsideTemplateClassAndTemplateFunction() + { + QByteArray original = + "template<class T>" + "class Foo\n" + "{\n" + " template<class U>\n" + " void fun@c();\n" + "};\n"; + QByteArray expected = + "template<class T>" + "class Foo\n" + "{\n" + " template<class U>\n" + " void fun@c();\n" + "};\n" + "\n" + "template<class T>\n" + "template<class U>\n" + "inline void Foo<T>::func()\n" + "{\n" + "\n" + "}\n"; + + InsertDefFromDecl factory; + factory.setOutside(); + QuickFixOperationTest(singleHeader(original, expected), &factory); + } + + void testOutsideTemplateClass() + { + QByteArray original = + "template<class T>" + "class Foo\n" + "{\n" + " void fun@c();\n" + "};\n"; + QByteArray expected = + "template<class T>" + "class Foo\n" + "{\n" + " void fun@c();\n" + "};\n" + "\n" + "template<class T>\n" + "inline void Foo<T>::func()\n" + "{\n" + "\n" + "}\n"; + + InsertDefFromDecl factory; + factory.setOutside(); + QuickFixOperationTest(singleHeader(original, expected), &factory); + } + + void testOutsideTemplateFunction() + { + QByteArray original = + "class Foo\n" + "{\n" + " template<class U>\n" + " void fun@c();\n" + "};\n"; + QByteArray expected = + "class Foo\n" + "{\n" + " template<class U>\n" + " void fun@c();\n" + "};\n" + "\n" + "template<class U>\n" + "inline void Foo::func()\n" + "{\n" + "\n" + "}\n"; + + InsertDefFromDecl factory; + factory.setOutside(); + QuickFixOperationTest(singleHeader(original, expected), &factory); + } + + void testOutsideFunction() + { + QByteArray original = + "class Foo\n" + "{\n" + " void fun@c();\n" + "};\n"; + QByteArray expected = + "class Foo\n" + "{\n" + " void fun@c();\n" + "};\n" + "\n" + "inline void Foo::func()\n" + "{\n" + "\n" + "}\n"; + + InsertDefFromDecl factory; + factory.setOutside(); + QuickFixOperationTest(singleHeader(original, expected), &factory); + + } + +}; + +class InsertDefsFromDeclsTest : public QObject +{ + Q_OBJECT + +private slots: + + void test_data() + { + QTest::addColumn<QByteArrayList>("headers"); + QTest::addColumn<QByteArrayList>("sources"); + QTest::addColumn<int>("mode"); + + QByteArray origHeader = R"( +namespace N { +class @C +{ +public: + friend void ignoredFriend(); + void ignoredImplemented() {}; + void ignoredImplemented2(); // Below + void ignoredImplemented3(); // In cpp file + void funcNotSelected(); + void funcInline(); + void funcBelow(); + void funcCppFile(); + +signals: + void ignoredSignal(); +}; + +inline void C::ignoredImplemented2() {} + +} // namespace N)"; + QByteArray origSource = R"( +#include "file.h" + +namespace N { + +void C::ignoredImplemented3() {} + +} // namespace N)"; + + QByteArray expectedHeader = R"( +namespace N { +class C +{ +public: + friend void ignoredFriend(); + void ignoredImplemented() {}; + void ignoredImplemented2(); // Below + void ignoredImplemented3(); // In cpp file + void funcNotSelected(); + void funcInline() + { + + } + void funcBelow(); + void funcCppFile(); + +signals: + void ignoredSignal(); +}; + +inline void C::ignoredImplemented2() {} + +inline void C::funcBelow() +{ + +} + +} // namespace N)"; + QByteArray expectedSource = R"( +#include "file.h" + +namespace N { + +void C::ignoredImplemented3() {} + +void C::funcCppFile() +{ + +} + +} // namespace N)"; + QTest::addRow("normal case") + << QByteArrayList{origHeader, expectedHeader} + << QByteArrayList{origSource, expectedSource} + << int(InsertDefsFromDeclsMode::Alternating); + QTest::addRow("aborted dialog") + << QByteArrayList{origHeader, origHeader} + << QByteArrayList{origSource, origSource} + << int(InsertDefsFromDeclsMode::Off); + + origHeader = R"( + namespace N { + class @C + { + public: + friend void ignoredFriend(); + void ignoredImplemented() {}; + void ignoredImplemented2(); // Below + void ignoredImplemented3(); // In cpp file + + signals: + void ignoredSignal(); + }; + + inline void C::ignoredImplemented2() {} + + } // namespace N)"; + QTest::addRow("no candidates") + << QByteArrayList{origHeader, origHeader} + << QByteArrayList{origSource, origSource} + << int(InsertDefsFromDeclsMode::Alternating); + + origHeader = R"( + namespace N { + class @C + { + public: + friend void ignoredFriend(); + void ignoredImplemented() {}; + + signals: + void ignoredSignal(); + }; + } // namespace N)"; + QTest::addRow("no member functions") + << QByteArrayList{origHeader, ""} + << QByteArrayList{origSource, ""} + << int(InsertDefsFromDeclsMode::Alternating); + + + } + + void test() + { + QFETCH(QByteArrayList, headers); + QFETCH(QByteArrayList, sources); + QFETCH(int, mode); + + QList<TestDocumentPtr> testDocuments( + {CppTestDocument::create("file.h", headers.at(0), headers.at(1)), + CppTestDocument::create("file.cpp", sources.at(0), sources.at(1))}); + InsertDefsFromDecls factory; + factory.setMode(static_cast<InsertDefsFromDeclsMode>(mode)); + QuickFixOperationTest(testDocuments, &factory); + } + + void testInsertAndFormat() + { + if (!isClangFormatPresent()) + QSKIP("This test reqires ClangFormat"); + + const QByteArray origHeader = R"( +class @C +{ +public: + void func1 (int const &i); + void func2 (double const d); +}; +)"; + const QByteArray origSource = R"( +#include "file.h" +)"; + + const QByteArray expectedSource = R"( +#include "file.h" + +void C::func1 (int const &i) +{ + +} + +void C::func2 (double const d) +{ + +} +)"; + + const QByteArray clangFormatSettings = R"( +BreakBeforeBraces: Allman +QualifierAlignment: Right +SpaceBeforeParens: Always +)"; + + const QList<TestDocumentPtr> testDocuments({ + CppTestDocument::create("file.h", origHeader, origHeader), + CppTestDocument::create("file.cpp", origSource, expectedSource)}); + InsertDefsFromDecls factory; + factory.setMode(InsertDefsFromDeclsMode::Impl); + CppCodeStylePreferences * const prefs = CppToolsSettings::cppCodeStyle(); + const CppCodeStyleSettings settings = prefs->codeStyleSettings(); + CppCodeStyleSettings tempSettings = settings; + tempSettings.forceFormatting = true; + prefs->setCodeStyleSettings(tempSettings); + QuickFixOperationTest(testDocuments, &factory, {}, {}, {}, clangFormatSettings); + prefs->setCodeStyleSettings(settings); + } +}; + +QObject *InsertDefFromDecl::createTest() +{ + return new InsertDefFromDeclTest; +} + +QObject *InsertDefsFromDecls::createTest() +{ + return new InsertDefsFromDeclsTest; +} + +#endif // WITH_TESTS +} // namespace + +void registerInsertFunctionDefinitionQuickfixes() +{ + CppQuickFixFactory::registerFactory<InsertDefFromDecl>(); + CppQuickFixFactory::registerFactory<InsertDefsFromDecls>(); +} + +} // namespace CppEditor::Internal + +#ifdef WITH_TESTS +#include <insertfunctiondefinition.moc> +#endif diff --git a/src/plugins/cppeditor/quickfixes/insertfunctiondefinition.h b/src/plugins/cppeditor/quickfixes/insertfunctiondefinition.h new file mode 100644 index 0000000000..77c4c87b6d --- /dev/null +++ b/src/plugins/cppeditor/quickfixes/insertfunctiondefinition.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 registerInsertFunctionDefinitionQuickfixes(); +} // namespace CppEditor::Internal diff --git a/src/plugins/cppeditor/quickfixes/logicaloperationquickfixes.cpp b/src/plugins/cppeditor/quickfixes/logicaloperationquickfixes.cpp new file mode 100644 index 0000000000..36b5aa6247 --- /dev/null +++ b/src/plugins/cppeditor/quickfixes/logicaloperationquickfixes.cpp @@ -0,0 +1,335 @@ +// 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 "logicaloperationquickfixes.h" + +#include "../cppeditortr.h" +#include "../cpprefactoringchanges.h" +#include "cppquickfix.h" + +using namespace CPlusPlus; +using namespace Utils; + +namespace CppEditor::Internal { +namespace { + +class FlipLogicalOperandsOp : public CppQuickFixOperation +{ +public: + FlipLogicalOperandsOp(const CppQuickFixInterface &interface, int priority, + BinaryExpressionAST *binary, QString replacement) + : CppQuickFixOperation(interface) + , binary(binary) + , replacement(replacement) + { + setPriority(priority); + } + + QString description() const override + { + if (replacement.isEmpty()) + return Tr::tr("Swap Operands"); + else + return Tr::tr("Rewrite Using %1").arg(replacement); + } + + void perform() override + { + CppRefactoringChanges refactoring(snapshot()); + CppRefactoringFilePtr currentFile = refactoring.cppFile(filePath()); + + ChangeSet changes; + changes.flip(currentFile->range(binary->left_expression), + currentFile->range(binary->right_expression)); + if (!replacement.isEmpty()) + changes.replace(currentFile->range(binary->binary_op_token), replacement); + + currentFile->setChangeSet(changes); + currentFile->apply(); + } + +private: + BinaryExpressionAST *binary; + QString replacement; +}; + +class InverseLogicalComparisonOp : public CppQuickFixOperation +{ +public: + InverseLogicalComparisonOp(const CppQuickFixInterface &interface, + int priority, + BinaryExpressionAST *binary, + Kind invertToken) + : CppQuickFixOperation(interface, priority) + , binary(binary) + { + Token tok; + tok.f.kind = invertToken; + replacement = QLatin1String(tok.spell()); + + // check for enclosing nested expression + if (priority - 1 >= 0) + nested = interface.path()[priority - 1]->asNestedExpression(); + + // check for ! before parentheses + if (nested && priority - 2 >= 0) { + negation = interface.path()[priority - 2]->asUnaryExpression(); + if (negation && !interface.currentFile()->tokenAt(negation->unary_op_token).is(T_EXCLAIM)) + negation = nullptr; + } + } + + QString description() const override + { + return Tr::tr("Rewrite Using %1").arg(replacement); + } + + void perform() override + { + CppRefactoringChanges refactoring(snapshot()); + CppRefactoringFilePtr currentFile = refactoring.cppFile(filePath()); + + ChangeSet changes; + if (negation) { + // can't remove parentheses since that might break precedence + changes.remove(currentFile->range(negation->unary_op_token)); + } else if (nested) { + changes.insert(currentFile->startOf(nested), QLatin1String("!")); + } else { + changes.insert(currentFile->startOf(binary), QLatin1String("!(")); + changes.insert(currentFile->endOf(binary), QLatin1String(")")); + } + changes.replace(currentFile->range(binary->binary_op_token), replacement); + currentFile->setChangeSet(changes); + currentFile->apply(); + } + +private: + BinaryExpressionAST *binary = nullptr; + NestedExpressionAST *nested = nullptr; + UnaryExpressionAST *negation = nullptr; + + QString replacement; +}; + +class RewriteLogicalAndOp : public CppQuickFixOperation +{ +public: + std::shared_ptr<ASTPatternBuilder> mk; + UnaryExpressionAST *left; + UnaryExpressionAST *right; + BinaryExpressionAST *pattern; + + RewriteLogicalAndOp(const CppQuickFixInterface &interface) + : CppQuickFixOperation(interface) + , mk(new ASTPatternBuilder) + { + left = mk->UnaryExpression(); + right = mk->UnaryExpression(); + pattern = mk->BinaryExpression(left, right); + } + + void perform() override + { + CppRefactoringChanges refactoring(snapshot()); + CppRefactoringFilePtr currentFile = refactoring.cppFile(filePath()); + + ChangeSet changes; + changes.replace(currentFile->range(pattern->binary_op_token), QLatin1String("||")); + changes.remove(currentFile->range(left->unary_op_token)); + changes.remove(currentFile->range(right->unary_op_token)); + const int start = currentFile->startOf(pattern); + const int end = currentFile->endOf(pattern); + changes.insert(start, QLatin1String("!(")); + changes.insert(end, QLatin1String(")")); + + currentFile->setChangeSet(changes); + currentFile->apply(); + } +}; + +/*! + Rewrite + a op b + + As + b flipop a + + Activates on: <= < > >= == != && || +*/ +class FlipLogicalOperands : public CppQuickFixFactory +{ +#ifdef WITH_TESTS +public: + static QObject *createTest() { return new QObject; } +#endif + +private: + void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override + { + const QList<AST *> &path = interface.path(); + if (path.isEmpty()) + return; + CppRefactoringFilePtr file = interface.currentFile(); + + int index = path.size() - 1; + BinaryExpressionAST *binary = path.at(index)->asBinaryExpression(); + if (!binary) + return; + if (!interface.isCursorOn(binary->binary_op_token)) + return; + + Kind flipToken; + switch (file->tokenAt(binary->binary_op_token).kind()) { + case T_LESS_EQUAL: + flipToken = T_GREATER_EQUAL; + break; + case T_LESS: + flipToken = T_GREATER; + break; + case T_GREATER: + flipToken = T_LESS; + break; + case T_GREATER_EQUAL: + flipToken = T_LESS_EQUAL; + break; + case T_EQUAL_EQUAL: + case T_EXCLAIM_EQUAL: + case T_AMPER_AMPER: + case T_PIPE_PIPE: + flipToken = T_EOF_SYMBOL; + break; + default: + return; + } + + QString replacement; + if (flipToken != T_EOF_SYMBOL) { + Token tok; + tok.f.kind = flipToken; + replacement = QLatin1String(tok.spell()); + } + + result << new FlipLogicalOperandsOp(interface, index, binary, replacement); + } +}; + +/*! + Rewrite + a op b -> !(a invop b) + (a op b) -> !(a invop b) + !(a op b) -> (a invob b) + + Activates on: <= < > >= == != +*/ +class InverseLogicalComparison : public CppQuickFixFactory +{ +#ifdef WITH_TESTS +public: + static QObject *createTest() { return new QObject; } +#endif + +private: + void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override + { + CppRefactoringFilePtr file = interface.currentFile(); + + const QList<AST *> &path = interface.path(); + if (path.isEmpty()) + return; + int index = path.size() - 1; + BinaryExpressionAST *binary = path.at(index)->asBinaryExpression(); + if (!binary) + return; + if (!interface.isCursorOn(binary->binary_op_token)) + return; + + Kind invertToken; + switch (file->tokenAt(binary->binary_op_token).kind()) { + case T_LESS_EQUAL: + invertToken = T_GREATER; + break; + case T_LESS: + invertToken = T_GREATER_EQUAL; + break; + case T_GREATER: + invertToken = T_LESS_EQUAL; + break; + case T_GREATER_EQUAL: + invertToken = T_LESS; + break; + case T_EQUAL_EQUAL: + invertToken = T_EXCLAIM_EQUAL; + break; + case T_EXCLAIM_EQUAL: + invertToken = T_EQUAL_EQUAL; + break; + default: + return; + } + + result << new InverseLogicalComparisonOp(interface, index, binary, invertToken); + } +}; + +/*! + Rewrite + !a && !b + + As + !(a || b) + + Activates on: && +*/ +class RewriteLogicalAnd : public CppQuickFixFactory +{ +#ifdef WITH_TESTS +public: + static QObject *createTest() { return new QObject; } +#endif + +private: + void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override + { + BinaryExpressionAST *expression = nullptr; + const QList<AST *> &path = interface.path(); + CppRefactoringFilePtr file = interface.currentFile(); + + int index = path.size() - 1; + for (; index != -1; --index) { + expression = path.at(index)->asBinaryExpression(); + if (expression) + break; + } + + if (!expression) + return; + + if (!interface.isCursorOn(expression->binary_op_token)) + return; + + QSharedPointer<RewriteLogicalAndOp> op(new RewriteLogicalAndOp(interface)); + + ASTMatcher matcher; + + if (expression->match(op->pattern, &matcher) && + file->tokenAt(op->pattern->binary_op_token).is(T_AMPER_AMPER) && + file->tokenAt(op->left->unary_op_token).is(T_EXCLAIM) && + file->tokenAt(op->right->unary_op_token).is(T_EXCLAIM)) { + op->setDescription(Tr::tr("Rewrite Condition Using ||")); + op->setPriority(index); + result.append(op); + } + } +}; + +} // namespace + +void registerLogicalOperationQuickfixes() +{ + CppQuickFixFactory::registerFactory<FlipLogicalOperands>(); + CppQuickFixFactory::registerFactory<InverseLogicalComparison>(); + CppQuickFixFactory::registerFactory<RewriteLogicalAnd>(); +} + +} // namespace CppEditor::Internal diff --git a/src/plugins/cppeditor/quickfixes/logicaloperationquickfixes.h b/src/plugins/cppeditor/quickfixes/logicaloperationquickfixes.h new file mode 100644 index 0000000000..cf77bed8e0 --- /dev/null +++ b/src/plugins/cppeditor/quickfixes/logicaloperationquickfixes.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 registerLogicalOperationQuickfixes(); +} // namespace CppEditor::Internal diff --git a/src/plugins/cppeditor/quickfixes/moveclasstoownfile.cpp b/src/plugins/cppeditor/quickfixes/moveclasstoownfile.cpp new file mode 100644 index 0000000000..fa86fd96d0 --- /dev/null +++ b/src/plugins/cppeditor/quickfixes/moveclasstoownfile.cpp @@ -0,0 +1,742 @@ +// 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 "moveclasstoownfile.h" + +#include "../cppeditortr.h" +#include "../cppeditorwidget.h" +#include "../cppfilesettingspage.h" +#include "../cpprefactoringchanges.h" +#include "cppquickfix.h" +#include "cppquickfixhelpers.h" + +#include <coreplugin/messagemanager.h> +#include <cplusplus/ASTPath.h> +#include <cplusplus/Overview.h> +#include <projectexplorer/projectmanager.h> +#include <projectexplorer/projectnodes.h> +#include <utils/codegeneration.h> +#include <utils/layoutbuilder.h> +#include <utils/treemodel.h> +#include <utils/treeviewcombobox.h> + +#include <QCheckBox> +#include <QDialog> +#include <QDialogButtonBox> + +#ifdef WITH_TESTS +#include "../cpptoolstestcase.h" +#include <coreplugin/editormanager/editormanager.h> +#include <projectexplorer/kitmanager.h> +#include <texteditor/textdocument.h> +#include <QtTest> +#endif // WITH_TESTS + +using namespace CPlusPlus; +using namespace Core; +using namespace ProjectExplorer; +using namespace Utils; + +namespace CppEditor::Internal { +namespace { + +class MoveClassToOwnFileOp : public CppQuickFixOperation +{ +public: + MoveClassToOwnFileOp( + const CppQuickFixInterface &interface, + AST *fullDecl, + ClassSpecifierAST *classAst, + const QList<Namespace *> &namespacePath, + bool interactive) + : CppQuickFixOperation(interface) + , m_state(std::make_shared<State>()) + { + setDescription(Tr::tr("Move Class to a Dedicated Set of Source Files")); + m_state->originalFilePath = interface.currentFile()->filePath(); + m_state->classAst = classAst; + m_state->namespacePath = namespacePath; + m_state->interactive = interactive; + PerFileState &perFileState = m_state->perFileState[interface.currentFile()->filePath()]; + perFileState.refactoringFile = interface.currentFile(); + perFileState.declarationsToMove << fullDecl; + } + +private: + struct PerFileState { + // We want to keep the relative order of moved code. + void insertSorted(AST *decl) { + declarationsToMove.insert(std::lower_bound( + declarationsToMove.begin(), + declarationsToMove.end(), + decl, + [](const AST *elem, const AST *value) { + return elem->firstToken() < value->firstToken(); + }), decl); + } + + CppRefactoringFilePtr refactoringFile; + QList<AST *> declarationsToMove; + }; + struct State { + using Ptr = std::shared_ptr<State>; + + FilePath originalFilePath; + AST *fullDecl = nullptr; + ClassSpecifierAST *classAst = nullptr; + QList<Namespace *> namespacePath; + Links lookupResults; + QMap<FilePath, PerFileState> perFileState; // A map for deterministic order of moved code. + CppRefactoringChanges factory{CppModelManager::snapshot()}; + int remainingFollowSymbolOps = 0; + bool interactive = true; + }; + class Dialog : public QDialog { + public: + Dialog(const FilePath &defaultHeaderFilePath, const FilePath &defaultSourceFilePath, + ProjectNode *defaultProjectNode) + : m_buttonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel) + { + ProjectNode * const rootNode = defaultProjectNode + ? defaultProjectNode->getProject()->rootProjectNode() + : nullptr; + if (rootNode) { + const auto projectRootItem = new NodeItem(rootNode); + buildTree(projectRootItem); + m_projectModel.rootItem()->appendChild(projectRootItem); + } + m_projectNodeComboBox.setModel(&m_projectModel); + if (defaultProjectNode) { + const auto matcher = [defaultProjectNode](TreeItem *item) { + return static_cast<NodeItem *>(item)->node == defaultProjectNode; + }; + TreeItem * const defaultItem = m_projectModel.rootItem()->findAnyChild(matcher); + if (defaultItem ) { + QModelIndex index = m_projectModel.indexForItem(defaultItem); + m_projectNodeComboBox.setCurrentIndex(index); + while (index.isValid()) { + m_projectNodeComboBox.view()->expand(index); + index = index.parent(); + } + } + + } + connect(&m_projectNodeComboBox, &QComboBox::currentIndexChanged, + this, [this] { + if (m_filesEdited) + return; + const auto newProjectNode = projectNode(); + QTC_ASSERT(newProjectNode, return); + const FilePath baseDir = newProjectNode->directory(); + m_sourcePathChooser.setFilePath( + baseDir.pathAppended(sourceFilePath().fileName())); + m_headerPathChooser.setFilePath( + baseDir.pathAppended(headerFilePath().fileName())); + m_filesEdited = false; + }); + + m_headerOnlyCheckBox.setText(Tr::tr("Header file only")); + m_headerOnlyCheckBox.setChecked(false); + connect(&m_headerOnlyCheckBox, &QCheckBox::toggled, + this, [this](bool checked) { m_sourcePathChooser.setEnabled(!checked); }); + + m_headerPathChooser.setExpectedKind(PathChooser::SaveFile); + m_sourcePathChooser.setExpectedKind(PathChooser::SaveFile); + m_headerPathChooser.setFilePath(defaultHeaderFilePath); + m_sourcePathChooser.setFilePath(defaultSourceFilePath); + connect(&m_headerPathChooser, &PathChooser::textChanged, + this, [this] { m_filesEdited = true; }); + connect(&m_sourcePathChooser, &PathChooser::textChanged, + this, [this] { m_filesEdited = true; }); + + connect(&m_buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); + connect(&m_buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); + + using namespace Layouting; + Column { + Form { + Tr::tr("Project:"), &m_projectNodeComboBox, br, + &m_headerOnlyCheckBox, br, + Tr::tr("Header file:"), &m_headerPathChooser, br, + Tr::tr("Implementation file:"), &m_sourcePathChooser, br, + }, + &m_buttonBox + }.attachTo(this); + } + + ProjectNode *projectNode() const + { + const QVariant v = m_projectNodeComboBox.currentData(Qt::UserRole); + return v.isNull() ? nullptr : static_cast<ProjectNode *>(v.value<void *>()); + } + bool createSourceFile() const { return !m_headerOnlyCheckBox.isChecked(); } + FilePath headerFilePath() const { return m_headerPathChooser.absoluteFilePath(); } + FilePath sourceFilePath() const { return m_sourcePathChooser.absoluteFilePath(); } + + private: + struct NodeItem : public StaticTreeItem { + NodeItem(ProjectNode *node) + : StaticTreeItem({node->displayName()}, {node->directory().toUserOutput()}) + , node(node) + {} + Qt::ItemFlags flags(int) const override + { + return Qt::ItemIsEnabled | Qt::ItemIsSelectable; + } + QVariant data(int column, int role) const override + { + if (role == Qt::UserRole) + return QVariant::fromValue(static_cast<void *>(node)); + return StaticTreeItem::data(column, role); + } + + ProjectNode * const node; + }; + + void buildTree(NodeItem *parent) + { + for (Node * const node : parent->node->nodes()) { + if (const auto projNode = node->asProjectNode()) { + const auto child = new NodeItem(projNode); + buildTree(child); + parent->appendChild(child); + } + } + } + + TreeViewComboBox m_projectNodeComboBox; + QCheckBox m_headerOnlyCheckBox; + PathChooser m_headerPathChooser; + PathChooser m_sourcePathChooser; + QDialogButtonBox m_buttonBox; + TreeModel<> m_projectModel; + bool m_filesEdited = false; + }; + + void perform() override + { + collectImplementations(m_state->classAst->symbol, m_state); + if (m_state->remainingFollowSymbolOps == 0) + finish(m_state); + } + + static CppRefactoringFilePtr getRefactoringFile(const FilePath &filePath, const State::Ptr &state) + { + CppRefactoringFilePtr &refactoringFile = state->perFileState[filePath].refactoringFile; + if (refactoringFile) + return refactoringFile; + CppEditorWidget *editorWidget = nullptr; + const QList<IEditor *> editors = DocumentModel::editorsForFilePath(filePath); + for (IEditor *editor : editors) { + const auto textEditor = qobject_cast<TextEditor::BaseTextEditor *>(editor); + if (textEditor) + editorWidget = qobject_cast<CppEditorWidget *>(textEditor->editorWidget()); + if (editorWidget) + break; + } + refactoringFile = editorWidget + ? state->factory.file(editorWidget, editorWidget->semanticInfo().doc) + : state->factory.cppFile(filePath); + return refactoringFile; + } + + static void lookupSymbol(Symbol *symbol, const State::Ptr &state) + { + const CppRefactoringFilePtr refactoringFile = getRefactoringFile(symbol->filePath(), state); + const auto editorWidget = qobject_cast<CppEditorWidget *>(refactoringFile->editor()); + QTextCursor cursor(refactoringFile->document()->begin()); + TranslationUnit * const tu = refactoringFile->cppDocument()->translationUnit(); + const int symbolPos = tu->getTokenPositionInDocument(symbol->sourceLocation(), + refactoringFile->document()); + cursor.setPosition(symbolPos); + const CursorInEditor cursorInEditor( + cursor, + symbol->filePath(), + editorWidget, + editorWidget ? editorWidget->textDocument() : nullptr, + refactoringFile->cppDocument()); + const auto callback = [symbol, symbolPos, doc = cursor.document(), state](const Link &link) { + class FinishedChecker { + public: + FinishedChecker(const State::Ptr &state) : m_state(state) {} + ~FinishedChecker() { + if (--m_state->remainingFollowSymbolOps == 0) + finish(m_state); + }; + private: + const State::Ptr &m_state; + } finishedChecker(state); + if (!link.hasValidTarget()) + return; + if (symbol->filePath() == link.targetFilePath) { + const int linkPos = Text::positionInText(doc, link.targetLine, + link.targetColumn + 1); + if (linkPos == symbolPos) + return; + } + const CppRefactoringFilePtr refactoringFile + = getRefactoringFile(link.targetFilePath, state); + const QList<AST *> astPath = ASTPath( + refactoringFile->cppDocument())(link.targetLine, link.targetColumn); + const bool isTemplate = symbol->asTemplate(); + const bool isFunction = symbol->type()->asFunctionType(); + for (auto it = astPath.rbegin(); it != astPath.rend(); ++it) { + const bool match = isTemplate ? bool((*it)->asTemplateDeclaration()) + : isFunction ? bool((*it)->asFunctionDefinition()) + : bool((*it)->asSimpleDeclaration()); + if (match) { + // For member functions of class templates. + if (isFunction) { + const auto next = std::next(it); + if (next != astPath.rend() && (*next)->asTemplateDeclaration()) + it = next; + } + state->perFileState[link.targetFilePath].insertSorted(*it); + if (symbol->asForwardClassDeclaration()) { + if (const auto classSpec = (*(it - 1))->asClassSpecifier(); + classSpec && classSpec->symbol) { + collectImplementations(classSpec->symbol, state); + } + } + break; + } + } + }; + ++state->remainingFollowSymbolOps; + + // Force queued execution, as the built-in editor can run the callback synchronously. + const auto followSymbol = [cursorInEditor, callback] { + CppModelManager::followSymbol( + cursorInEditor, callback, true, false, FollowSymbolMode::Exact); + }; + QMetaObject::invokeMethod(CppModelManager::instance(), followSymbol, Qt::QueuedConnection); + } + + static void collectImplementations(Class *klass, const State::Ptr &state) + { + for (int i = 0; i < klass->memberCount(); ++i) { + Symbol * const member = klass->memberAt(i); + if (member->asForwardClassDeclaration() || member->asTemplate()) { + lookupSymbol(member, state); + continue; + } + const auto decl = member->asDeclaration(); + if (!decl) + continue; + if (decl->type().type()->asFunctionType()) { + if (!decl->asFunction()) + lookupSymbol(member, state); + } else if (decl->isStatic() && !decl->type().isInline()) { + lookupSymbol(member, state); + } + } + } + + static void finish(const State::Ptr &state) + { + Overview ov; + Project * const project = ProjectManager::projectForFile(state->originalFilePath); + const CppFileSettings fileSettings = cppFileSettingsForProject(project); + const auto constructDefaultFilePaths = [&] { + const QString className = ov.prettyName(state->classAst->symbol->name()); + const QString baseFileName = fileSettings.lowerCaseFiles ? className.toLower() : className; + const QString headerFileName = baseFileName + '.' + fileSettings.headerSuffix; + const FilePath baseDir = state->originalFilePath.parentDir(); + const FilePath headerFilePath = baseDir.pathAppended(headerFileName); + const QString sourceFileName = baseFileName + '.' + fileSettings.sourceSuffix; + const FilePath sourceFilePath = baseDir.pathAppended(sourceFileName); + return std::make_pair(headerFilePath, sourceFilePath); + }; + auto [headerFilePath, sourceFilePath] = constructDefaultFilePaths(); + bool mustCreateSourceFile = false; + bool mustNotCreateSourceFile = false; + ProjectNode *projectNode = nullptr; + if (project && project->rootProjectNode()) { + const Node * const origNode = project->nodeForFilePath(state->originalFilePath); + if (origNode) + projectNode = const_cast<Node *>(origNode)->managingProject(); + } + if (state->interactive) { + Dialog dlg(headerFilePath, sourceFilePath, projectNode); + if (dlg.exec() != QDialog::Accepted) + return; + projectNode = dlg.projectNode(); + headerFilePath = dlg.headerFilePath(); + sourceFilePath = dlg.sourceFilePath(); + mustCreateSourceFile = dlg.createSourceFile(); + mustNotCreateSourceFile = !dlg.createSourceFile(); + } + const auto fileListForDisplay = [](const FilePaths &files) { + return Utils::transform<QStringList>(files, [](const FilePath &fp) { + return '"' + fp.toUserOutput() + '"'; + }).join(", "); + }; + FilePaths existingFiles; + if (headerFilePath.exists()) + existingFiles << headerFilePath; + if (!mustNotCreateSourceFile && sourceFilePath.exists()) + existingFiles << sourceFilePath; + if (!existingFiles.isEmpty()) { + MessageManager::writeDisrupting( + Tr::tr("Refusing to overwrite the following files: %1\n") + .arg(fileListForDisplay(existingFiles))); + return; + } + const QString headerFileName = headerFilePath.fileName(); + + QString headerContent; + QString sourceContent; + QList<QString *> commonContent{&headerContent}; + if (!mustNotCreateSourceFile) + commonContent << &sourceContent; + for (QString *const content : std::as_const(commonContent)) { + content->append(fileSettings.licenseTemplate()); + if (!content->isEmpty()) + content->append('\n'); + } + sourceContent.append('\n').append("#include \"").append(headerFileName).append("\"\n"); + const QStringList namespaceNames + = Utils::transform<QStringList>(state->namespacePath, [&](const Namespace *ns) { + return ov.prettyName(ns->name()); + }); + const QString headerGuard = Utils::headerGuard(headerFileName, namespaceNames); + if (fileSettings.headerPragmaOnce) { + headerContent.append("#pragma once\n"); + } else { + headerContent.append("#ifndef " + headerGuard + "\n"); + headerContent.append("#define " + headerGuard + "\n"); + } + if (!namespaceNames.isEmpty()) { + for (QString *const content : std::as_const(commonContent)) { + content->append('\n'); + for (const QString &ns : namespaceNames) + content->append("namespace " + ns + " {\n"); + } + } + bool hasSourceContent = false; + for (auto it = state->perFileState.begin(); it != state->perFileState.end(); ++it) { + if (it->declarationsToMove.isEmpty()) + continue; + const CppRefactoringFilePtr refactoringFile = it->refactoringFile; + QTC_ASSERT(refactoringFile, continue); + const bool isDeclFile = refactoringFile->filePath() == state->originalFilePath; + ChangeSet changes; + if (isDeclFile) { + QString relInclude = headerFilePath.relativePathFrom( + refactoringFile->filePath().parentDir()).toString(); + if (!relInclude.isEmpty()) + relInclude.append('/'); + relInclude.append('"').append(headerFileName).append('"'); + insertNewIncludeDirective(relInclude, refactoringFile, + refactoringFile->cppDocument(), changes); + } + for (AST * const declToMove : std::as_const(it->declarationsToMove)) { + const ChangeSet::Range rangeToMove = refactoringFile->range(declToMove); + QString &content = isDeclFile || mustNotCreateSourceFile ? headerContent + : sourceContent; + if (&content == &sourceContent) + hasSourceContent = true; + content.append('\n') + .append(refactoringFile->textOf(rangeToMove)) + .append('\n'); + changes.remove(rangeToMove); + } + refactoringFile->setChangeSet(changes); + refactoringFile->apply(); + } + + if (!namespaceNames.isEmpty()) { + for (QString *const content : std::as_const(commonContent)) { + content->append('\n'); + for (auto it = namespaceNames.rbegin(); it != namespaceNames.rend(); ++it) + content->append("} // namespace " + *it + '\n'); + } + } + if (!fileSettings.headerPragmaOnce) + headerContent.append("\n#endif // " + headerGuard + '\n'); + + CppRefactoringFilePtr headerFile = state->factory.cppFile(headerFilePath); + headerFilePath.ensureExistingFile(); + ChangeSet headerChanges; + headerChanges.insert(0, headerContent); + headerFile->setChangeSet(headerChanges); + headerFile->apply(); + if (hasSourceContent || mustCreateSourceFile) { + sourceFilePath.ensureExistingFile(); + CppRefactoringFilePtr sourceFile = state->factory.cppFile(sourceFilePath); + ChangeSet sourceChanges; + sourceChanges.insert(0, sourceContent); + sourceFile->setChangeSet(sourceChanges); + sourceFile->apply(); + } + + if (!projectNode) + return; + FilePaths toAdd{headerFilePath}; + if (hasSourceContent) + toAdd << sourceFilePath; + FilePaths notAdded; + projectNode->addFiles(toAdd, ¬Added); + if (!notAdded.isEmpty()) { + MessageManager::writeDisrupting( + Tr::tr("Failed to add to project file \"%1\": %2") + .arg(projectNode->filePath().toUserOutput(), fileListForDisplay(notAdded))); + } + + if (state->interactive) + EditorManager::openEditor(headerFilePath); + } + + const State::Ptr m_state; +}; + +//! Move a class into a dedicates set of files. +class MoveClassToOwnFile : public CppQuickFixFactory +{ +#ifdef WITH_TESTS +public: + void setNonInteractive() { m_interactive = false; } + static QObject *createTest(); +#endif + +private: + // Applies if and only if: + // - Class is not a nested class. + // - Class name does not match file name via any of the usual transformations. + // - There are other declarations in the same file. + void doMatch(const CppQuickFixInterface &interface, + TextEditor::QuickFixOperations &result) override + { + ClassSpecifierAST * const classAst = astForClassOperations(interface); + if (!classAst || !classAst->symbol) + return; + AST *fullDecl = nullptr; + for (auto it = interface.path().rbegin(); it != interface.path().rend() && !fullDecl; ++it) { + if (*it == classAst && it != interface.path().rend() - 1) { + auto next = std::next(it); + fullDecl = (*next)->asSimpleDeclaration(); + if (next != interface.path().rend() - 1) { + next = std::next(next); + if (const auto templ = (*next)->asTemplateDeclaration()) + fullDecl = templ; + } + } + } + if (!fullDecl) + return; + + // Check file name. + const QString className = Overview().prettyName(classAst->symbol->name()); + if (className.isEmpty()) + return; + const QString lowerFileBaseName = interface.filePath().baseName().toLower(); + if (lowerFileBaseName.contains(className.toLower())) + return; + QString underscoredClassName = className; + QChar curChar = underscoredClassName.at(0); + for (int i = 1; i < underscoredClassName.size(); ++i) { + const QChar prevChar = curChar; + curChar = underscoredClassName.at(i); + if (curChar.isUpper() && prevChar.isLetterOrNumber() && !prevChar.isUpper()) { + underscoredClassName.insert(i, '_'); + ++i; + } + } + if (lowerFileBaseName.contains(underscoredClassName.toLower())) + return; + + // Is there more than one class definition in the file? + AST * const ast = interface.currentFile()->cppDocument()->translationUnit()->ast(); + if (!ast) + return; + DeclarationListAST * const topLevelDecls = ast->asTranslationUnit()->declaration_list; + if (!topLevelDecls) + return; + QList<Namespace *> namespacePath; + QList<Namespace *> currentNamespacePath; + bool foundOtherDecls = false; + bool foundSelf = false; + std::function<void(Namespace *)> collectSymbolsFromNamespace; + const auto handleSymbol = [&](Symbol *symbol) { + if (!symbol) + return; + if (const auto nsMember = symbol->asNamespace()) { + collectSymbolsFromNamespace(nsMember); + return; + } + if (symbol != classAst->symbol) { + if (!symbol->asForwardClassDeclaration()) + foundOtherDecls = true; + return; + } + QTC_ASSERT(symbol->asClass(), return); + foundSelf = true; + namespacePath = currentNamespacePath; + }; + collectSymbolsFromNamespace = [&](Namespace *ns) { + currentNamespacePath << ns; + for (int i = 0; i < ns->memberCount() && (!foundSelf || !foundOtherDecls); ++i) + handleSymbol(ns->memberAt(i)); + currentNamespacePath.removeLast(); + }; + for (DeclarationListAST *it = topLevelDecls; it && (!foundSelf || !foundOtherDecls); + it = it->next) { + DeclarationAST *decl = it->value; + if (!decl) + continue; + if (const auto templ = decl->asTemplateDeclaration()) + decl = templ->declaration; + if (!decl) + continue; + if (const auto ns = decl->asNamespace(); ns && ns->symbol) { + collectSymbolsFromNamespace(ns->symbol); + continue; + } + if (const auto simpleDecl = decl->asSimpleDeclaration()) { + if (!simpleDecl->decl_specifier_list) + continue; + for (SpecifierListAST *spec = simpleDecl->decl_specifier_list; spec; spec = spec->next) { + if (!spec->value) + continue; + if (const auto klass = spec->value->asClassSpecifier()) + handleSymbol(klass->symbol); + else if (!spec->value->asElaboratedTypeSpecifier()) // forward decl + foundOtherDecls = true; + } + } else if (decl->asDeclaration()) { + foundOtherDecls = true; + } + } + + if (foundSelf && foundOtherDecls) { + result << new MoveClassToOwnFileOp( + interface, fullDecl, classAst, namespacePath, m_interactive); + } + } + + bool m_interactive = true; +}; + +#ifdef WITH_TESTS +class MoveClassToOwnFileTest : public QObject +{ + Q_OBJECT + +private slots: + void test_data() + { + QTest::addColumn<QString>("projectName"); + QTest::addColumn<QString>("fileName"); + QTest::addColumn<QString>("className"); + QTest::addColumn<bool>("applicable"); + + QTest::newRow("nested") << "nested" << "main.cpp" << "Inner" << false; + QTest::newRow("file name match 1") << "match1" << "TheClass.h" << "TheClass" << false; + QTest::newRow("file name match 2") << "match2" << "theclass.h" << "TheClass" << false; + QTest::newRow("file name match 3") << "match3" << "the_class.h" << "TheClass" << false; + QTest::newRow("single") << "single" << "theheader.h" << "TheClass" << false; + QTest::newRow("complex") << "complex" << "theheader.h" << "TheClass" << true; + QTest::newRow("header only") << "header-only" << "theheader.h" << "TheClass" << true; + QTest::newRow("decl in source file") << "decl-in-source" << "thesource.cpp" << "TheClass" << true; + QTest::newRow("template") << "template" << "theheader.h" << "TheClass" << true; + } + + void test() + { + QFETCH(QString, projectName); + QFETCH(QString, fileName); + QFETCH(QString, className); + QFETCH(bool, applicable); + using namespace CppEditor::Tests; + using namespace TextEditor; + + // Set up project. + Kit * const kit = Utils::findOr(KitManager::kits(), nullptr, [](const Kit *k) { + return k->isValid() && !k->hasWarning() && k->value("QtSupport.QtInformation").isValid(); + }); + if (!kit) + QSKIP("The test requires at least one valid kit with a valid Qt"); + const auto projectDir = std::make_unique<TemporaryCopiedDir>( + ":/cppeditor/testcases/move-class/" + projectName); + SourceFilesRefreshGuard refreshGuard; + ProjectOpenerAndCloser projectMgr; + QVERIFY(projectMgr.open(projectDir->absolutePath(projectName + ".pro"), true, kit)); + QVERIFY(refreshGuard.wait()); + + // Open header file and locate class. + const auto headerFilePath = projectDir->absolutePath(fileName); + QVERIFY2(headerFilePath.exists(), qPrintable(headerFilePath.toUserOutput())); + const auto editor = qobject_cast<BaseTextEditor *>(EditorManager::openEditor(headerFilePath)); + QVERIFY(editor); + const auto doc = qobject_cast<TextEditor::TextDocument *>(editor->document()); + QVERIFY(doc); + QTextCursor classCursor = doc->document()->find("class " + className); + QVERIFY(!classCursor.isNull()); + editor->setCursorPosition(classCursor.position()); + const auto editorWidget = qobject_cast<CppEditorWidget *>(editor->editorWidget()); + QVERIFY(editorWidget); + QVERIFY(TestCase::waitForRehighlightedSemanticDocument(editorWidget)); + + // Query factory. + MoveClassToOwnFile factory; + factory.setNonInteractive(); + CppQuickFixInterface quickFixInterface(editorWidget, ExplicitlyInvoked); + QuickFixOperations operations; + factory.match(quickFixInterface, operations); + QCOMPARE(operations.isEmpty(), !applicable); + if (!applicable) + return; + operations.first()->perform(); + QVERIFY(waitForSignalOrTimeout(doc, &IDocument::saved, 30000)); + QTest::qWait(1000); + + // Compare all files. + const FileFilter filter({"*_expected"}, QDir::Files); + const FilePaths expectedDocuments = projectDir->filePath().dirEntries(filter); + QVERIFY(!expectedDocuments.isEmpty()); + for (const FilePath &expected : expectedDocuments) { + static const QString suffix = "_expected"; + const FilePath actual = expected.parentDir() + .pathAppended(expected.fileName().chopped(suffix.length())); + QVERIFY(actual.exists()); + const auto actualContents = actual.fileContents(); + QVERIFY(actualContents); + const auto expectedContents = expected.fileContents(); + const QByteArrayList actualLines = actualContents->split('\n'); + const QByteArrayList expectedLines = expectedContents->split('\n'); + if (actualLines.size() != expectedLines.size()) { + qDebug().noquote().nospace() << "---\n" << *expectedContents << "EOF"; + qDebug().noquote().nospace() << "+++\n" << *actualContents << "EOF"; + } + QCOMPARE(actualLines.size(), expectedLines.size()); + for (int i = 0; i < actualLines.size(); ++i) { + const QByteArray actualLine = actualLines.at(i); + const QByteArray expectedLine = expectedLines.at(i); + if (actualLine != expectedLine) + qDebug() << "Unexpected content in line" << (i + 1) << "of file" + << actual.fileName(); + QCOMPARE(actualLine, expectedLine); + } + } + } +}; + +QObject *MoveClassToOwnFile::createTest() +{ + return new MoveClassToOwnFileTest; +} + +#endif // WITH_TESTS + +} // namespace + +void registerMoveClassToOwnFileQuickfix() +{ + CppQuickFixFactory::registerFactory<MoveClassToOwnFile>(); +} + +} // namespace CppEditor::Internal + +#ifdef WITH_TESTS +#include <moveclasstoownfile.moc> +#endif diff --git a/src/plugins/cppeditor/quickfixes/moveclasstoownfile.h b/src/plugins/cppeditor/quickfixes/moveclasstoownfile.h new file mode 100644 index 0000000000..c46b0e5c3a --- /dev/null +++ b/src/plugins/cppeditor/quickfixes/moveclasstoownfile.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 registerMoveClassToOwnFileQuickfix(); +} // namespace CppEditor::Internal diff --git a/src/plugins/cppeditor/quickfixes/movefunctiondefinition.cpp b/src/plugins/cppeditor/quickfixes/movefunctiondefinition.cpp new file mode 100644 index 0000000000..ca574a8671 --- /dev/null +++ b/src/plugins/cppeditor/quickfixes/movefunctiondefinition.cpp @@ -0,0 +1,1904 @@ +// 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 "movefunctiondefinition.h" + +#include "../cppcodestylesettings.h" +#include "../cppeditortr.h" +#include "../cpprefactoringchanges.h" +#include "../insertionpointlocator.h" +#include "../symbolfinder.h" +#include "cppquickfix.h" +#include "cppquickfixhelpers.h" + +#include <cplusplus/ASTPath.h> +#include <cplusplus/CppRewriter.h> +#include <cplusplus/Overview.h> + +using namespace CPlusPlus; +using namespace TextEditor; +using namespace Utils; + +#ifdef WITH_TESTS +#include "cppquickfix_test.h" +#include <QtTest> +#endif + +namespace CppEditor::Internal { +namespace { + +static QString definitionSignature( + const CppQuickFixInterface *assist, + FunctionDefinitionAST *functionDefinitionAST, + CppRefactoringFilePtr &baseFile, + CppRefactoringFilePtr &targetFile, + Scope *scope) +{ + QTC_ASSERT(assist, return QString()); + QTC_ASSERT(functionDefinitionAST, return QString()); + QTC_ASSERT(scope, return QString()); + Function *func = functionDefinitionAST->symbol; + QTC_ASSERT(func, return QString()); + + LookupContext cppContext(targetFile->cppDocument(), assist->snapshot()); + ClassOrNamespace *cppCoN = cppContext.lookupType(scope); + if (!cppCoN) + cppCoN = cppContext.globalNamespace(); + SubstitutionEnvironment env; + env.setContext(assist->context()); + env.switchScope(func->enclosingScope()); + UseMinimalNames q(cppCoN); + env.enter(&q); + Control *control = assist->context().bindings()->control().get(); + Overview oo = CppCodeStyleSettings::currentProjectCodeStyleOverview(); + oo.showFunctionSignatures = true; + oo.showReturnTypes = true; + oo.showArgumentNames = true; + oo.showEnclosingTemplate = true; + oo.showTemplateParameters = true; + oo.trailingReturnType = functionDefinitionAST->declarator + && functionDefinitionAST->declarator->postfix_declarator_list + && functionDefinitionAST->declarator->postfix_declarator_list->value + && functionDefinitionAST->declarator->postfix_declarator_list + ->value->asFunctionDeclarator() + && functionDefinitionAST->declarator->postfix_declarator_list + ->value->asFunctionDeclarator()->trailing_return_type; + const Name *name = func->name(); + if (name && nameIncludesOperatorName(name)) { + CoreDeclaratorAST *coreDeclarator = functionDefinitionAST->declarator->core_declarator; + const QString operatorNameText = baseFile->textOf(coreDeclarator); + oo.includeWhiteSpaceInOperatorName = operatorNameText.contains(QLatin1Char(' ')); + } + const QString nameText = oo.prettyName(LookupContext::minimalName(func, cppCoN, control)); + oo.showTemplateParameters = false; + const FullySpecifiedType tn = rewriteType(func->type(), &env, control); + + return oo.prettyType(tn, nameText); +} + +class MoveFuncDefRefactoringHelper +{ +public: + enum MoveType { + MoveOutside, + MoveToCppFile, + MoveOutsideMemberToCppFile + }; + + MoveFuncDefRefactoringHelper(CppQuickFixOperation *operation, MoveType type, + const FilePath &fromFile, const FilePath &toFile) + : m_operation(operation), m_type(type), m_changes(m_operation->snapshot()) + { + m_fromFile = m_changes.cppFile(fromFile); + m_toFile = (m_type == MoveOutside) ? m_fromFile : m_changes.cppFile(toFile); + } + + void performMove(FunctionDefinitionAST *funcAST) + { + // Determine file, insert position and scope + InsertionLocation l = insertLocationForMethodDefinition( + funcAST->symbol, false, NamespaceHandling::Ignore, + m_changes, m_toFile->filePath()); + const QString prefix = l.prefix(); + const QString suffix = l.suffix(); + const int insertPos = m_toFile->position(l.line(), l.column()); + Scope *scopeAtInsertPos = m_toFile->cppDocument()->scopeAt(l.line(), l.column()); + + // construct definition + const QString funcDec = inlinePrefix(m_toFile->filePath(), [this] { return m_type == MoveOutside; }) + + definitionSignature(m_operation, funcAST, m_fromFile, m_toFile, + scopeAtInsertPos); + QString funcDef = prefix + funcDec; + const int startPosition = m_fromFile->endOf(funcAST->declarator); + const int endPosition = m_fromFile->endOf(funcAST); + funcDef += m_fromFile->textOf(startPosition, endPosition); + funcDef += suffix; + + // insert definition at new position + m_toFileChangeSet.insert(insertPos, funcDef); + m_toFile->setOpenEditor(true, insertPos); + + // remove definition from fromFile + if (m_type == MoveOutsideMemberToCppFile) { + m_fromFileChangeSet.remove(m_fromFile->range(funcAST)); + } else { + QString textFuncDecl = m_fromFile->textOf(funcAST); + textFuncDecl.truncate(startPosition - m_fromFile->startOf(funcAST)); + if (textFuncDecl.left(7) == QLatin1String("inline ")) + textFuncDecl = textFuncDecl.mid(7); + else + textFuncDecl.replace(" inline ", QLatin1String(" ")); + textFuncDecl = textFuncDecl.trimmed() + QLatin1Char(';'); + m_fromFileChangeSet.replace(m_fromFile->range(funcAST), textFuncDecl); + } + } + + void applyChanges() + { + if (!m_toFileChangeSet.isEmpty()) { + m_toFile->setChangeSet(m_toFileChangeSet); + m_toFile->apply(); + } + if (!m_fromFileChangeSet.isEmpty()) { + m_fromFile->setChangeSet(m_fromFileChangeSet); + m_fromFile->apply(); + } + } + +private: + CppQuickFixOperation *m_operation; + MoveType m_type; + CppRefactoringChanges m_changes; + CppRefactoringFilePtr m_fromFile; + CppRefactoringFilePtr m_toFile; + ChangeSet m_fromFileChangeSet; + ChangeSet m_toFileChangeSet; +}; + +class MoveFuncDefOutsideOp : public CppQuickFixOperation +{ +public: + MoveFuncDefOutsideOp(const CppQuickFixInterface &interface, + MoveFuncDefRefactoringHelper::MoveType type, + FunctionDefinitionAST *funcDef, const FilePath &cppFilePath) + : CppQuickFixOperation(interface, 0) + , m_funcDef(funcDef) + , m_type(type) + , m_cppFilePath(cppFilePath) + , m_headerFilePath(funcDef->symbol->filePath()) + { + if (m_type == MoveFuncDefRefactoringHelper::MoveOutside) { + setDescription(Tr::tr("Move Definition Outside Class")); + } else { + const FilePath resolved = m_cppFilePath.relativePathFrom(m_headerFilePath.parentDir()); + setDescription(Tr::tr("Move Definition to %1").arg(resolved.displayName())); + } + } + + void perform() override + { + MoveFuncDefRefactoringHelper helper(this, m_type, m_headerFilePath, m_cppFilePath); + helper.performMove(m_funcDef); + helper.applyChanges(); + } + +private: + FunctionDefinitionAST *m_funcDef; + MoveFuncDefRefactoringHelper::MoveType m_type; + const FilePath m_cppFilePath; + const FilePath m_headerFilePath; +}; + +class MoveAllFuncDefOutsideOp : public CppQuickFixOperation +{ +public: + MoveAllFuncDefOutsideOp(const CppQuickFixInterface &interface, + MoveFuncDefRefactoringHelper::MoveType type, + ClassSpecifierAST *classDef, const FilePath &cppFileName) + : CppQuickFixOperation(interface, 0) + , m_type(type) + , m_classDef(classDef) + , m_cppFilePath(cppFileName) + , m_headerFilePath(classDef->symbol->filePath()) + { + if (m_type == MoveFuncDefRefactoringHelper::MoveOutside) { + setDescription(Tr::tr("Definitions Outside Class")); + } else { + const FilePath resolved = m_cppFilePath.relativePathFrom(m_headerFilePath.parentDir()); + setDescription(Tr::tr("Move All Function Definitions to %1") + .arg(resolved.displayName())); + } + } + + void perform() override + { + MoveFuncDefRefactoringHelper helper(this, m_type, m_headerFilePath, m_cppFilePath); + for (DeclarationListAST *it = m_classDef->member_specifier_list; it; it = it->next) { + if (FunctionDefinitionAST *funcAST = it->value->asFunctionDefinition()) { + if (funcAST->symbol && !funcAST->symbol->isGenerated()) + helper.performMove(funcAST); + } + } + helper.applyChanges(); + } + +private: + MoveFuncDefRefactoringHelper::MoveType m_type; + ClassSpecifierAST *m_classDef; + const FilePath m_cppFilePath; + const FilePath m_headerFilePath; +}; + +class MoveFuncDefToDeclOp : public CppQuickFixOperation +{ +public: + enum Type { Push, Pull }; + MoveFuncDefToDeclOp(const CppQuickFixInterface &interface, + const FilePath &fromFilePath, const FilePath &toFilePath, + FunctionDefinitionAST *funcAst, Function *func, const QString &declText, + const ChangeSet::Range &fromRange, + const ChangeSet::Range &toRange, + Type type) + : CppQuickFixOperation(interface, 0) + , m_fromFilePath(fromFilePath) + , m_toFilePath(toFilePath) + , m_funcAST(funcAst) + , m_func(func) + , m_declarationText(declText) + , m_fromRange(fromRange) + , m_toRange(toRange) + { + if (type == Type::Pull) { + setDescription(Tr::tr("Move Definition Here")); + } else if (m_toFilePath == m_fromFilePath) { + setDescription(Tr::tr("Move Definition to Class")); + } else { + const FilePath resolved = m_toFilePath.relativePathFrom(m_fromFilePath.parentDir()); + setDescription(Tr::tr("Move Definition to %1").arg(resolved.displayName())); + } + } + +private: + void perform() override + { + CppRefactoringChanges refactoring(snapshot()); + CppRefactoringFilePtr fromFile = refactoring.cppFile(m_fromFilePath); + CppRefactoringFilePtr toFile = refactoring.cppFile(m_toFilePath); + + ensureFuncDefAstAndRange(*fromFile); + if (!m_funcAST) + return; + + const QString wholeFunctionText = m_declarationText + + fromFile->textOf(fromFile->endOf(m_funcAST->declarator), + fromFile->endOf(m_funcAST->function_body)); + + // Replace declaration with function and delete old definition + ChangeSet toTarget; + toTarget.replace(m_toRange, wholeFunctionText); + if (m_toFilePath == m_fromFilePath) + toTarget.remove(m_fromRange); + toFile->setChangeSet(toTarget); + toFile->setOpenEditor(true, m_toRange.start); + toFile->apply(); + if (m_toFilePath != m_fromFilePath) { + ChangeSet fromTarget; + fromTarget.remove(m_fromRange); + fromFile->setChangeSet(fromTarget); + fromFile->apply(); + } + } + + void ensureFuncDefAstAndRange(CppRefactoringFile &defFile) + { + if (m_funcAST) { + QTC_CHECK(m_fromRange.end > m_fromRange.start); + return; + } + QTC_ASSERT(m_func, return); + const QList<AST *> astPath = ASTPath(defFile.cppDocument())(m_func->line(), + m_func->column()); + if (astPath.isEmpty()) + return; + for (auto it = std::rbegin(astPath); it != std::rend(astPath); ++it) { + m_funcAST = (*it)->asFunctionDefinition(); + if (!m_funcAST) + continue; + AST *astForRange = m_funcAST; + const auto prev = std::next(it); + if (prev != std::rend(astPath)) { + if (const auto templAst = (*prev)->asTemplateDeclaration()) + astForRange = templAst; + } + m_fromRange = defFile.range(astForRange); + return; + } + } + + const FilePath m_fromFilePath; + const FilePath m_toFilePath; + FunctionDefinitionAST *m_funcAST; + Function *m_func; + const QString m_declarationText; + ChangeSet::Range m_fromRange; + const ChangeSet::Range m_toRange; +}; + +/*! + Moves the definition of a member function outside the class or moves the definition of a member + function or a normal function to the implementation file. + */ +class MoveFuncDefOutside : public CppQuickFixFactory +{ +public: +#ifdef WITH_TESTS + static QObject *createTest(); +#endif + +private: + void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override + { + const QList<AST *> &path = interface.path(); + SimpleDeclarationAST *classAST = nullptr; + FunctionDefinitionAST *funcAST = nullptr; + bool moveOutsideMemberDefinition = false; + + const int pathSize = path.size(); + for (int idx = 1; idx < pathSize; ++idx) { + if ((funcAST = path.at(idx)->asFunctionDefinition())) { + // check cursor position + if (idx != pathSize - 1 // Do not allow "void a() @ {..." + && funcAST->function_body + && !interface.isCursorOn(funcAST->function_body)) { + if (path.at(idx - 1)->asTranslationUnit()) { // normal function + if (idx + 3 < pathSize && path.at(idx + 3)->asQualifiedName()) // Outside member + moveOutsideMemberDefinition = true; // definition + break; + } + + if (idx > 1) { + if ((classAST = path.at(idx - 2)->asSimpleDeclaration())) // member function + break; + if (path.at(idx - 2)->asNamespace()) // normal function in namespace + break; + } + if (idx > 2 && path.at(idx - 1)->asTemplateDeclaration()) { + if ((classAST = path.at(idx - 3)->asSimpleDeclaration())) // member template + break; + } + } + funcAST = nullptr; + } + } + + if (!funcAST || !funcAST->symbol) + return; + + bool isHeaderFile = false; + const FilePath cppFileName = correspondingHeaderOrSource(interface.filePath(), &isHeaderFile); + + if (isHeaderFile && !cppFileName.isEmpty()) { + const MoveFuncDefRefactoringHelper::MoveType type = moveOutsideMemberDefinition + ? MoveFuncDefRefactoringHelper::MoveOutsideMemberToCppFile + : MoveFuncDefRefactoringHelper::MoveToCppFile; + result << new MoveFuncDefOutsideOp(interface, type, funcAST, cppFileName); + } + + if (classAST) + result << new MoveFuncDefOutsideOp(interface, MoveFuncDefRefactoringHelper::MoveOutside, + funcAST, FilePath()); + + return; + } +}; + +//! Moves all member function definitions outside the class or to the implementation file. +class MoveAllFuncDefOutside : public CppQuickFixFactory +{ +public: +#ifdef WITH_TESTS + static QObject *createTest(); +#endif + +private: + void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override + { + ClassSpecifierAST * const classAST = astForClassOperations(interface); + if (!classAST) + return; + + // Determine if the class has at least one function definition + bool classContainsFunctions = false; + for (DeclarationListAST *it = classAST->member_specifier_list; it; it = it->next) { + if (FunctionDefinitionAST *funcAST = it->value->asFunctionDefinition()) { + if (funcAST->symbol && !funcAST->symbol->isGenerated()) { + classContainsFunctions = true; + break; + } + } + } + if (!classContainsFunctions) + return; + + bool isHeaderFile = false; + const FilePath cppFileName = correspondingHeaderOrSource(interface.filePath(), &isHeaderFile); + if (isHeaderFile && !cppFileName.isEmpty()) { + result << new MoveAllFuncDefOutsideOp(interface, + MoveFuncDefRefactoringHelper::MoveToCppFile, + classAST, cppFileName); + } + result << new MoveAllFuncDefOutsideOp(interface, MoveFuncDefRefactoringHelper::MoveOutside, + classAST, FilePath()); + } +}; + +//! Moves the definition of a function to its declaration, with the cursor on the definition. +class MoveFuncDefToDeclPush : public CppQuickFixFactory +{ +public: +#ifdef WITH_TESTS + static QObject *createTest(); +#endif + +private: + void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override + { + const QList<AST *> &path = interface.path(); + AST *completeDefAST = nullptr; + FunctionDefinitionAST *funcAST = nullptr; + + const int pathSize = path.size(); + for (int idx = 1; idx < pathSize; ++idx) { + if ((funcAST = path.at(idx)->asFunctionDefinition())) { + AST *enclosingAST = path.at(idx - 1); + if (enclosingAST->asClassSpecifier()) + return; + + // check cursor position + if (idx != pathSize - 1 // Do not allow "void a() @ {..." + && funcAST->function_body + && !interface.isCursorOn(funcAST->function_body)) { + completeDefAST = enclosingAST->asTemplateDeclaration() ? enclosingAST : funcAST; + break; + } + funcAST = nullptr; + } + } + + if (!funcAST || !funcAST->symbol) + return; + + const CppRefactoringChanges refactoring(interface.snapshot()); + const CppRefactoringFilePtr defFile = refactoring.cppFile(interface.filePath()); + const ChangeSet::Range defRange = defFile->range(completeDefAST); + + // Determine declaration (file, range, text); + ChangeSet::Range declRange; + QString declText; + FilePath declFilePath; + + Function *func = funcAST->symbol; + if (Class *matchingClass = isMemberFunction(interface.context(), func)) { + // Dealing with member functions + const QualifiedNameId *qName = func->name()->asQualifiedNameId(); + for (Symbol *symbol = matchingClass->find(qName->identifier()); + symbol; symbol = symbol->next()) { + Symbol *s = symbol; + if (func->enclosingScope()->asTemplate()) { + if (const Template *templ = s->type()->asTemplateType()) { + if (Symbol *decl = templ->declaration()) { + if (decl->type()->asFunctionType()) + s = decl; + } + } + } + if (!s->name() + || !qName->identifier()->match(s->identifier()) + || !s->type()->asFunctionType() + || !s->type().match(func->type()) + || s->asFunction()) { + continue; + } + + declFilePath = matchingClass->filePath(); + const CppRefactoringFilePtr declFile = refactoring.cppFile(declFilePath); + ASTPath astPath(declFile->cppDocument()); + const QList<AST *> path = astPath(s->line(), s->column()); + for (int idx = path.size() - 1; idx > 0; --idx) { + AST *node = path.at(idx); + if (SimpleDeclarationAST *simpleDecl = node->asSimpleDeclaration()) { + if (simpleDecl->symbols && !simpleDecl->symbols->next) { + declRange = declFile->range(simpleDecl); + declText = declFile->textOf(simpleDecl); + declText.remove(-1, 1); // remove ';' from declaration text + break; + } + } + } + + if (!declText.isEmpty()) + break; + } + } else if (Namespace *matchingNamespace = isNamespaceFunction(interface.context(), func)) { + // Dealing with free functions + bool isHeaderFile = false; + declFilePath = correspondingHeaderOrSource(interface.filePath(), &isHeaderFile); + if (isHeaderFile) + return; + + const CppRefactoringFilePtr declFile = refactoring.cppFile(declFilePath); + const LookupContext lc(declFile->cppDocument(), interface.snapshot()); + const QList<LookupItem> candidates = lc.lookup(func->name(), matchingNamespace); + for (const LookupItem &candidate : candidates) { + if (Symbol *s = candidate.declaration()) { + if (s->asDeclaration()) { + ASTPath astPath(declFile->cppDocument()); + const QList<AST *> path = astPath(s->line(), s->column()); + for (AST *node : path) { + if (SimpleDeclarationAST *simpleDecl = node->asSimpleDeclaration()) { + declRange = declFile->range(simpleDecl); + declText = declFile->textOf(simpleDecl); + declText.remove(-1, 1); // remove ';' from declaration text + break; + } + } + } + } + + if (!declText.isEmpty()) { + declText.prepend(inlinePrefix(declFilePath)); + break; + } + } + } + + if (!declFilePath.isEmpty() && !declText.isEmpty()) + result << new MoveFuncDefToDeclOp(interface, + interface.filePath(), + declFilePath, + funcAST, func, declText, + defRange, declRange, MoveFuncDefToDeclOp::Push); + } +}; + +//! Moves the definition of a function to its declaration, with the cursor on the declaration. +class MoveFuncDefToDeclPull : public CppQuickFixFactory +{ +public: +#ifdef WITH_TESTS + static QObject *createTest(); +#endif + +private: + void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override + { + const QList<AST *> &path = interface.path(); + for (auto it = std::rbegin(path); it != std::rend(path); ++it) { + SimpleDeclarationAST * const simpleDecl = (*it)->asSimpleDeclaration(); + if (!simpleDecl) + continue; + const auto prev = std::next(it); + if (prev != std::rend(path) && (*prev)->asStatement()) + return; + if (!simpleDecl->symbols || !simpleDecl->symbols->value || simpleDecl->symbols->next) + return; + Declaration * const decl = simpleDecl->symbols->value->asDeclaration(); + if (!decl) + return; + Function * const funcDecl = decl->type()->asFunctionType(); + if (!funcDecl) + return; + if (funcDecl->isSignal() || funcDecl->isPureVirtual() || funcDecl->isFriend()) + return; + + // Is there a definition? + SymbolFinder symbolFinder; + Function * const funcDef = symbolFinder.findMatchingDefinition(decl, interface.snapshot(), + true); + if (!funcDef) + return; + + QString declText = interface.currentFile()->textOf(simpleDecl); + declText.chop(1); // semicolon + declText.prepend(inlinePrefix(interface.filePath(), [funcDecl] { + return !funcDecl->enclosingScope()->asClass(); + })); + result << new MoveFuncDefToDeclOp(interface, funcDef->filePath(), decl->filePath(), nullptr, + funcDef, declText, {}, + interface.currentFile()->range(simpleDecl), + MoveFuncDefToDeclOp::Pull); + return; + } + } +}; + +#ifdef WITH_TESTS +using namespace Tests; + +class MoveFuncDefOutsideTest : public QObject +{ + Q_OBJECT + +private slots: + /// Check: Move definition from header to cpp. + void testMemberFuncToCpp() + { + QList<TestDocumentPtr> testDocuments; + QByteArray original; + QByteArray expected; + + // Header File + original = + "class Foo {\n" + " inline int numbe@r() const\n" + " {\n" + " return 5;\n" + " }\n" + "\n" + " void bar();\n" + "};\n"; + expected = + "class Foo {\n" + " int number() const;\n" + "\n" + " void bar();\n" + "};\n"; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File + original = + "#include \"file.h\"\n"; + expected = + "#include \"file.h\"\n" + "\n" + "int Foo::number() const\n" + "{\n" + " return 5;\n" + "}\n" + ; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + MoveFuncDefOutside factory; + QuickFixOperationTest(testDocuments, &factory); + } + + void testMemberFuncToCppInsideNS() + { + QList<TestDocumentPtr> testDocuments; + QByteArray original; + QByteArray expected; + + // Header File + original = + "namespace SomeNamespace {\n" + "class Foo {\n" + " int ba@r()\n" + " {\n" + " return 5;\n" + " }\n" + "};\n" + "}\n"; + expected = + "namespace SomeNamespace {\n" + "class Foo {\n" + " int ba@r();\n" + "};\n" + "}\n"; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File + original = + "#include \"file.h\"\n" + "namespace SomeNamespace {\n" + "\n" + "}\n"; + expected = + "#include \"file.h\"\n" + "namespace SomeNamespace {\n" + "\n" + "int Foo::bar()\n" + "{\n" + " return 5;\n" + "}\n" + "\n" + "}\n"; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + MoveFuncDefOutside factory; + QuickFixOperationTest(testDocuments, &factory); + } + + /// Check: Move definition outside class + void testMemberFuncOutside1() + { + QByteArray original = + "class Foo {\n" + " void f1();\n" + " inline int f2@() const\n" + " {\n" + " return 1;\n" + " }\n" + " void f3();\n" + " void f4();\n" + "};\n" + "\n" + "void Foo::f4() {}\n"; + QByteArray expected = + "class Foo {\n" + " void f1();\n" + " int f2@() const;\n" + " void f3();\n" + " void f4();\n" + "};\n" + "\n" + "int Foo::f2() const\n" + "{\n" + " return 1;\n" + "}\n" + "\n" + "void Foo::f4() {}\n"; + + MoveFuncDefOutside factory; + QuickFixOperationTest(singleDocument(original, expected), &factory); + } + + /// Check: Move definition outside class + void testMemberFuncOutside2() + { + QList<TestDocumentPtr> testDocuments; + QByteArray original; + QByteArray expected; + + // Header File + original = + "class Foo {\n" + " void f1();\n" + " int f2@()\n" + " {\n" + " return 1;\n" + " }\n" + " void f3();\n" + "};\n"; + expected = + "class Foo {\n" + " void f1();\n" + " int f2();\n" + " void f3();\n" + "};\n" + "\n" + "inline int Foo::f2()\n" + "{\n" + " return 1;\n" + "}\n"; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File + original = + "#include \"file.h\"\n" + "void Foo::f1() {}\n" + "void Foo::f3() {}\n"; + expected = original; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + MoveFuncDefOutside factory; + QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), 1); + } + + /// Check: Move definition from header to cpp (with namespace). + void testMemberFuncToCppNS() + { + QList<TestDocumentPtr> testDocuments; + QByteArray original; + QByteArray expected; + + // Header File + original = + "namespace MyNs {\n" + "class Foo {\n" + " inline int numbe@r() const\n" + " {\n" + " return 5;\n" + " }\n" + "};\n" + "}\n"; + expected = + "namespace MyNs {\n" + "class Foo {\n" + " int number() const;\n" + "};\n" + "}\n"; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File + original = + "#include \"file.h\"\n"; + expected = + "#include \"file.h\"\n" + "\n" + "int MyNs::Foo::number() const\n" + "{\n" + " return 5;\n" + "}\n"; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + MoveFuncDefOutside factory; + QuickFixOperationTest(testDocuments, &factory); + } + + /// Check: Move definition from header to cpp (with namespace + using). + void testMemberFuncToCppNSUsing() + { + QList<TestDocumentPtr> testDocuments; + QByteArray original; + QByteArray expected; + + // Header File + original = + "namespace MyNs {\n" + "class Foo {\n" + " inline int numbe@r() const\n" + " {\n" + " return 5;\n" + " }\n" + "};\n" + "}\n"; + expected = + "namespace MyNs {\n" + "class Foo {\n" + " int number() const;\n" + "};\n" + "}\n"; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File + original = + "#include \"file.h\"\n" + "using namespace MyNs;\n"; + expected = + "#include \"file.h\"\n" + "using namespace MyNs;\n" + "\n" + "int Foo::number() const\n" + "{\n" + " return 5;\n" + "}\n"; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + MoveFuncDefOutside factory; + QuickFixOperationTest(testDocuments, &factory); + } + + /// Check: Move definition outside class with Namespace + void testMemberFuncOutsideWithNs() + { + QByteArray original = + "namespace MyNs {\n" + "class Foo {\n" + " inline int numbe@r() const\n" + " {\n" + " return 5;\n" + " }\n" + "};}\n"; + QByteArray expected = + "namespace MyNs {\n" + "class Foo {\n" + " int number() const;\n" + "};\n" + "\n" + "int Foo::number() const\n" + "{\n" + " return 5;\n" + "}\n" + "\n}\n"; + + MoveFuncDefOutside factory; + QuickFixOperationTest(singleDocument(original, expected), &factory); + } + + /// Check: Move free function from header to cpp. + void testFreeFuncToCpp() + { + QList<TestDocumentPtr> testDocuments; + QByteArray original; + QByteArray expected; + + // Header File + original = + "int numbe@r() const\n" + "{\n" + " return 5;\n" + "}\n"; + expected = + "int number() const;\n" + ; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File + original = + "#include \"file.h\"\n"; + expected = + "#include \"file.h\"\n" + "\n" + "int number() const\n" + "{\n" + " return 5;\n" + "}\n"; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + MoveFuncDefOutside factory; + QuickFixOperationTest(testDocuments, &factory); + } + + /// Check: Move free function from header to cpp (with namespace). + void testFreeFuncToCppNS() + { + QList<TestDocumentPtr> testDocuments; + QByteArray original; + QByteArray expected; + + // Header File + original = + "namespace MyNamespace {\n" + "int numbe@r() const\n" + "{\n" + " return 5;\n" + "}\n" + "}\n"; + expected = + "namespace MyNamespace {\n" + "int number() const;\n" + "}\n"; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File + original = + "#include \"file.h\"\n"; + expected = + "#include \"file.h\"\n" + "\n" + "int MyNamespace::number() const\n" + "{\n" + " return 5;\n" + "}\n"; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + MoveFuncDefOutside factory; + QuickFixOperationTest(testDocuments, &factory); + } + + /// Check: Move Ctor with member initialization list (QTCREATORBUG-9157). + void testCtorWithInitialization1() + { + QList<TestDocumentPtr> testDocuments; + QByteArray original; + QByteArray expected; + + // Header File + original = + "class Foo {\n" + "public:\n" + " Fo@o() : a(42), b(3.141) {}\n" + "private:\n" + " int a;\n" + " float b;\n" + "};\n"; + expected = + "class Foo {\n" + "public:\n" + " Foo();\n" + "private:\n" + " int a;\n" + " float b;\n" + "};\n"; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File + original ="#include \"file.h\"\n"; + expected = + "#include \"file.h\"\n" + "\n" + "Foo::Foo() : a(42), b(3.141) {}\n" + ; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + MoveFuncDefOutside factory; + QuickFixOperationTest(testDocuments, &factory); + } + + /// Check: Move Ctor with member initialization list (QTCREATORBUG-9462). + void testCtorWithInitialization2() + { + QList<TestDocumentPtr> testDocuments; + QByteArray original; + QByteArray expected; + + // Header File + original = + "class Foo\n" + "{\n" + "public:\n" + " Fo@o() : member(2)\n" + " {\n" + " }\n" + "\n" + " int member;\n" + "};\n"; + + expected = + "class Foo\n" + "{\n" + "public:\n" + " Foo();\n" + "\n" + " int member;\n" + "};\n"; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File + original ="#include \"file.h\"\n"; + expected = + "#include \"file.h\"\n" + "\n" + "Foo::Foo() : member(2)\n" + "{\n" + "}\n" + ; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + MoveFuncDefOutside factory; + QuickFixOperationTest(testDocuments, &factory); + } + + /// Check if definition is inserted right after class for move definition outside + void testAfterClass() + { + QList<TestDocumentPtr> testDocuments; + QByteArray original; + QByteArray expected; + + // Header File + original = + "class Foo\n" + "{\n" + " Foo();\n" + " void a@() {}\n" + "};\n" + "\n" + "class Bar {};\n"; + expected = + "class Foo\n" + "{\n" + " Foo();\n" + " void a();\n" + "};\n" + "\n" + "inline void Foo::a() {}\n" + "\n" + "class Bar {};\n"; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File + original = + "#include \"file.h\"\n" + "\n" + "Foo::Foo()\n" + "{\n\n" + "}\n"; + expected = original; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + MoveFuncDefOutside factory; + QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), 1); + } + + /// Check if whitespace is respected for operator functions + void testRespectWsInOperatorNames1() + { + QByteArray original = + "class Foo\n" + "{\n" + " Foo &opera@tor =() {}\n" + "};\n"; + QByteArray expected = + "class Foo\n" + "{\n" + " Foo &operator =();\n" + "};\n" + "\n" + "Foo &Foo::operator =() {}\n" + ; + + MoveFuncDefOutside factory; + QuickFixOperationTest(singleDocument(original, expected), &factory); + } + + /// Check if whitespace is respected for operator functions + void testRespectWsInOperatorNames2() + { + QByteArray original = + "class Foo\n" + "{\n" + " Foo &opera@tor=() {}\n" + "};\n"; + QByteArray expected = + "class Foo\n" + "{\n" + " Foo &operator=();\n" + "};\n" + "\n" + "Foo &Foo::operator=() {}\n" + ; + + MoveFuncDefOutside factory; + QuickFixOperationTest(singleDocument(original, expected), &factory); + } + + void testMacroUses() + { + QByteArray original = + "#define CONST const\n" + "#define VOLATILE volatile\n" + "class Foo\n" + "{\n" + " int fu@nc(int a, int b) CONST VOLATILE\n" + " {\n" + " return 42;\n" + " }\n" + "};\n"; + QByteArray expected = + "#define CONST const\n" + "#define VOLATILE volatile\n" + "class Foo\n" + "{\n" + " int func(int a, int b) CONST VOLATILE;\n" + "};\n" + "\n" + "\n" + // const volatile become lowercase: QTCREATORBUG-12620 + "int Foo::func(int a, int b) const volatile\n" + "{\n" + " return 42;\n" + "}\n" + ; + + MoveFuncDefOutside factory; + QuickFixOperationTest(singleDocument(original, expected), &factory, + ProjectExplorer::HeaderPaths(), 0, "QTCREATORBUG-12314"); + } + + void testTemplate() + { + QByteArray original = + "template<class T>\n" + "class Foo { void fu@nc() {} };\n"; + QByteArray expected = + "template<class T>\n" + "class Foo { void fu@nc(); };\n" + "\n" + "template<class T>\n" + "void Foo<T>::func() {}\n"; + ; + + MoveFuncDefOutside factory; + QuickFixOperationTest(singleDocument(original, expected), &factory); + } + + void testMemberFunctionTemplate() + { + const QByteArray original = R"( +struct S { + template<typename In> + void @foo(In in) { (void)in; } +}; +)"; + const QByteArray expected = R"( +struct S { + template<typename In> + void foo(In in); +}; + +template<typename In> +void S::foo(In in) { (void)in; } +)"; + + MoveFuncDefOutside factory; + QuickFixOperationTest(singleDocument(original, expected), &factory); + } + + void testTemplateSpecializedClass() + { + QByteArray original = R"( +template<typename T> class base {}; +template<> +class base<int> +{ +public: + void @bar() {} +}; +)"; + QByteArray expected = R"( +template<typename T> class base {}; +template<> +class base<int> +{ +public: + void bar(); +}; + +void base<int>::bar() {} +)"; + + MoveFuncDefOutside factory; + QuickFixOperationTest(singleDocument(original, expected), &factory); + } + + void testUnnamedTemplate() + { + QByteArray original = + "template<typename T, typename>\n" + "class Foo { void fu@nc() {} };\n"; + QByteArray expected = + "template<typename T, typename>\n" + "class Foo { void fu@nc(); };\n" + "\n" + "template<typename T, typename T2>\n" + "void Foo<T, T2>::func() {}\n"; + ; + + MoveFuncDefOutside factory; + QuickFixOperationTest(singleDocument(original, expected), &factory); + } + + void testMemberFuncToCppStatic() + { + QList<TestDocumentPtr> testDocuments; + QByteArray original; + QByteArray expected; + + // Header File + original = + "class Foo {\n" + " static inline int numbe@r() const\n" + " {\n" + " return 5;\n" + " }\n" + "\n" + " void bar();\n" + "};\n"; + expected = + "class Foo {\n" + " static int number() const;\n" + "\n" + " void bar();\n" + "};\n"; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File + original = + "#include \"file.h\"\n"; + expected = + "#include \"file.h\"\n" + "\n" + "int Foo::number() const\n" + "{\n" + " return 5;\n" + "}\n"; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + MoveFuncDefOutside factory; + QuickFixOperationTest(testDocuments, &factory); + } + + void testMemberFuncToCppWithInlinePartOfName() + { + QList<TestDocumentPtr> testDocuments; + QByteArray original; + QByteArray expected; + + // Header File + original = + "class Foo {\n" + " static inline int numbe@r_inline () const\n" + " {\n" + " return 5;\n" + " }\n" + "\n" + " void bar();\n" + "};\n"; + expected = + "class Foo {\n" + " static int number_inline () const;\n" + "\n" + " void bar();\n" + "};\n"; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File + original = + "#include \"file.h\"\n"; + expected = + "#include \"file.h\"\n" + "\n" + "int Foo::number_inline() const\n" + "{\n" + " return 5;\n" + "}\n"; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + MoveFuncDefOutside factory; + QuickFixOperationTest(testDocuments, &factory); + } + + void testMixedQualifiers() + { + QList<TestDocumentPtr> testDocuments; + QByteArray original; + QByteArray expected; + + // Header File + original = R"( +struct Base { + virtual auto func() const && noexcept -> void = 0; +}; +struct Derived : public Base { + auto @func() const && noexcept -> void override {} +};)"; + expected = R"( +struct Base { + virtual auto func() const && noexcept -> void = 0; +}; +struct Derived : public Base { + auto func() const && noexcept -> void override; +};)"; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File + original = "#include \"file.h\"\n"; + expected = R"DELIM(#include "file.h" + +auto Derived::func() const && noexcept -> void {} +)DELIM"; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + MoveFuncDefOutside factory; + QuickFixOperationTest(testDocuments, &factory); + } + +}; + +class MoveAllFuncDefOutsideTest : public QObject +{ + Q_OBJECT + +private slots: + void testMemberFuncToCpp() + { + QList<TestDocumentPtr> testDocuments; + QByteArray original; + QByteArray expected; + + // Header File + original = + "class Foo {@\n" + " int numberA() const\n" + " {\n" + " return 5;\n" + " }\n" + " int numberB() const\n" + " {\n" + " return 5;\n" + " }\n" + "};\n"; + expected = + "class Foo {\n" + " int numberA() const;\n" + " int numberB() const;\n" + "};\n"; + testDocuments << CppTestDocument::create("file.h", original, expected); + + // Source File + original = + "#include \"file.h\"\n"; + expected = + "#include \"file.h\"\n" + "\n" + "int Foo::numberA() const\n" + "{\n" + " return 5;\n" + "}\n" + "\n" + "int Foo::numberB() const\n" + "{\n" + " return 5;\n" + "}\n" + ; + testDocuments << CppTestDocument::create("file.cpp", original, expected); + + MoveAllFuncDefOutside factory; + QuickFixOperationTest(testDocuments, &factory); + } + + void testMemberFuncOutside() + { + QByteArray original = + "class F@oo {\n" + " int f1()\n" + " {\n" + " return 1;\n" + " }\n" + " int f2() const\n" + " {\n" + " return 2;\n" + " }\n" + "};\n"; + QByteArray expected = + "class Foo {\n" + " int f1();\n" + " int f2() const;\n" + "};\n" + "\n" + "int Foo::f1()\n" + "{\n" + " return 1;\n" + "}\n" + "\n" + "int Foo::f2() const\n" + "{\n" + " return 2;\n" + "}\n"; + + MoveAllFuncDefOutside factory; + QuickFixOperationTest(singleDocument(original, expected), &factory); + } + + void testDoNotTriggerOnBaseClass() + { + QByteArray original = + "class Bar;\n" + "class Foo : public Ba@r {\n" + " int f1()\n" + " {\n" + " return 1;\n" + " }\n" + "};\n"; + + MoveAllFuncDefOutside factory; + QuickFixOperationTest(singleDocument(original, ""), &factory); + } + + void testClassWithBaseClass() + { + QByteArray original = + "class Bar;\n" + "class Fo@o : public Bar {\n" + " int f1()\n" + " {\n" + " return 1;\n" + " }\n" + "};\n"; + QByteArray expected = + "class Bar;\n" + "class Foo : public Bar {\n" + " int f1();\n" + "};\n" + "\n" + "int Foo::f1()\n" + "{\n" + " return 1;\n" + "}\n"; + + MoveAllFuncDefOutside factory; + QuickFixOperationTest(singleDocument(original, expected), &factory); + } + + /// Check: Do not take macro expanded code into account (QTCREATORBUG-13900) + void testIgnoreMacroCode() + { + QByteArray original = + "#define FAKE_Q_OBJECT int bar() {return 5;}\n" + "class Fo@o {\n" + " FAKE_Q_OBJECT\n" + " int f1()\n" + " {\n" + " return 1;\n" + " }\n" + "};\n"; + QByteArray expected = + "#define FAKE_Q_OBJECT int bar() {return 5;}\n" + "class Foo {\n" + " FAKE_Q_OBJECT\n" + " int f1();\n" + "};\n" + "\n" + "int Foo::f1()\n" + "{\n" + " return 1;\n" + "}\n"; + + MoveAllFuncDefOutside factory; + QuickFixOperationTest(singleDocument(original, expected), &factory); + } + +}; + +class MoveFuncDefToDeclTest : public QObject +{ + Q_OBJECT + +private slots: + void test_data() + { + QTest::addColumn<QByteArrayList>("headers"); + QTest::addColumn<QByteArrayList>("sources"); + + QByteArray originalHeader; + QByteArray expectedHeader; + QByteArray originalSource; + QByteArray expectedSource; + + originalHeader = + "class Foo {\n" + " inline int @number() const;\n" + "};\n"; + expectedHeader = + "class Foo {\n" + " inline int number() const {return 5;}\n" + "};\n"; + originalSource = + "#include \"file.h\"\n" + "\n" + "int Foo::num@ber() const {return 5;}\n"; + expectedSource = + "#include \"file.h\"\n" + "\n\n"; + QTest::newRow("member function, two files") << QByteArrayList{originalHeader, expectedHeader} + << QByteArrayList{originalSource, expectedSource}; + + originalSource = + "class Foo {\n" + " inline int @number() const;\n" + "};\n" + "\n" + "int Foo::num@ber() const\n" + "{\n" + " return 5;\n" + "}\n"; + + expectedSource = + "class Foo {\n" + " inline int number() const\n" + " {\n" + " return 5;\n" + " }\n" + "};\n\n\n"; + QTest::newRow("member function, one file") << QByteArrayList() + << QByteArrayList{originalSource, expectedSource}; + + originalHeader = + "namespace MyNs {\n" + "class Foo {\n" + " inline int @number() const;\n" + "};\n" + "}\n"; + expectedHeader = + "namespace MyNs {\n" + "class Foo {\n" + " inline int number() const\n" + " {\n" + " return 5;\n" + " }\n" + "};\n" + "}\n"; + originalSource = + "#include \"file.h\"\n" + "\n" + "int MyNs::Foo::num@ber() const\n" + "{\n" + " return 5;\n" + "}\n"; + expectedSource = "#include \"file.h\"\n\n\n"; + QTest::newRow("member function, two files, namespace") + << QByteArrayList{originalHeader, expectedHeader} + << QByteArrayList{originalSource, expectedSource}; + + originalHeader = + "namespace MyNs {\n" + "class Foo {\n" + " inline int numbe@r() const;\n" + "};\n" + "}\n"; + expectedHeader = + "namespace MyNs {\n" + "class Foo {\n" + " inline int number() const\n" + " {\n" + " return 5;\n" + " }\n" + "};\n" + "}\n"; + originalSource = + "#include \"file.h\"\n" + "using namespace MyNs;\n" + "\n" + "int Foo::num@ber() const\n" + "{\n" + " return 5;\n" + "}\n"; + expectedSource = + "#include \"file.h\"\n" + "using namespace MyNs;\n" + "\n\n"; + QTest::newRow("member function, two files, namespace with using-directive") + << QByteArrayList{originalHeader, expectedHeader} + << QByteArrayList{originalSource, expectedSource}; + + originalSource = + "namespace MyNs {\n" + "class Foo {\n" + " inline int @number() const;\n" + "};\n" + "\n" + "int Foo::numb@er() const\n" + "{\n" + " return 5;\n" + "}" + "\n}\n"; + expectedSource = + "namespace MyNs {\n" + "class Foo {\n" + " inline int number() const\n" + " {\n" + " return 5;\n" + " }\n" + "};\n\n\n}\n"; + + QTest::newRow("member function, one file, namespace") + << QByteArrayList() << QByteArrayList{originalSource, expectedSource}; + + originalHeader = "int nu@mber() const;\n"; + expectedHeader = + "inline int number() const\n" + "{\n" + " return 5;\n" + "}\n"; + originalSource = + "#include \"file.h\"\n" + "\n" + "\n" + "int numb@er() const\n" + "{\n" + " return 5;\n" + "}\n"; + expectedSource = "#include \"file.h\"\n\n\n\n"; + QTest::newRow("free function") << QByteArrayList{originalHeader, expectedHeader} + << QByteArrayList{originalSource, expectedSource}; + + originalHeader = + "namespace MyNamespace {\n" + "int n@umber() const;\n" + "}\n"; + expectedHeader = + "namespace MyNamespace {\n" + "inline int number() const\n" + "{\n" + " return 5;\n" + "}\n" + "}\n"; + originalSource = + "#include \"file.h\"\n" + "\n" + "int MyNamespace::nu@mber() const\n" + "{\n" + " return 5;\n" + "}\n"; + expectedSource = + "#include \"file.h\"\n" + "\n\n"; + QTest::newRow("free function, namespace") << QByteArrayList{originalHeader, expectedHeader} + << QByteArrayList{originalSource, expectedSource}; + + originalHeader = + "class Foo {\n" + "public:\n" + " Fo@o();\n" + "private:\n" + " int a;\n" + " float b;\n" + "};\n"; + expectedHeader = + "class Foo {\n" + "public:\n" + " Foo() : a(42), b(3.141) {}\n" + "private:\n" + " int a;\n" + " float b;\n" + "};\n"; + originalSource = + "#include \"file.h\"\n" + "\n" + "Foo::F@oo() : a(42), b(3.141) {}" + ; + expectedSource ="#include \"file.h\"\n\n"; + QTest::newRow("constructor") << QByteArrayList{originalHeader, expectedHeader} + << QByteArrayList{originalSource, expectedSource}; + + originalSource = + "struct Foo\n" + "{\n" + " void f@oo();\n" + "} bar;\n" + "void Foo::fo@o()\n" + "{\n" + " return;\n" + "}"; + expectedSource = + "struct Foo\n" + "{\n" + " void foo()\n" + " {\n" + " return;\n" + " }\n" + "} bar;\n"; + QTest::newRow("QTCREATORBUG-10303") << QByteArrayList() + << QByteArrayList{originalSource, expectedSource}; + + originalSource = + "struct Base {\n" + " virtual int foo() = 0;\n" + "};\n" + "struct Derived : Base {\n" + " int @foo() override;\n" + "};\n" + "\n" + "int Derived::fo@o()\n" + "{\n" + " return 5;\n" + "}\n"; + expectedSource = + "struct Base {\n" + " virtual int foo() = 0;\n" + "};\n" + "struct Derived : Base {\n" + " int foo() override\n" + " {\n" + " return 5;\n" + " }\n" + "};\n\n\n"; + QTest::newRow("overridden virtual") << QByteArrayList() + << QByteArrayList{originalSource, expectedSource}; + + originalSource = + "template<class T>\n" + "class Foo { void @func(); };\n" + "\n" + "template<class T>\n" + "void Foo<T>::fu@nc() {}\n"; + expectedSource = + "template<class T>\n" + "class Foo { void fu@nc() {} };\n\n\n"; + QTest::newRow("class template") << QByteArrayList() + << QByteArrayList{originalSource, expectedSource}; + + originalSource = + "class Foo\n" + "{\n" + " template<class T>\n" + " void @func();\n" + "};\n" + "\n" + "template<class T>\n" + "void Foo::fu@nc() {}\n"; + expectedSource = + "class Foo\n" + "{\n" + " template<class T>\n" + " void func() {}\n" + "};\n\n\n"; + QTest::newRow("function template") << QByteArrayList() + << QByteArrayList{originalSource, expectedSource}; + } + + void test() + { + QFETCH(QByteArrayList, headers); + QFETCH(QByteArrayList, sources); + + QVERIFY(headers.isEmpty() || headers.size() == 2); + QVERIFY(sources.size() == 2); + + QByteArray &declDoc = !headers.empty() ? headers.first() : sources.first(); + const int declCursorPos = declDoc.indexOf('@'); + QVERIFY(declCursorPos != -1); + const int defCursorPos = sources.first().lastIndexOf('@'); + QVERIFY(defCursorPos != -1); + QVERIFY(declCursorPos != defCursorPos); + + declDoc.remove(declCursorPos, 1); + QList<TestDocumentPtr> testDocuments; + if (!headers.isEmpty()) + testDocuments << CppTestDocument::create("file.h", headers.first(), headers.last()); + testDocuments << CppTestDocument::create("file.cpp", sources.first(), sources.last()); + + MoveFuncDefToDeclPush pushFactory; + QuickFixOperationTest(testDocuments, &pushFactory); + + declDoc.insert(declCursorPos, '@'); + sources.first().remove(defCursorPos, 1); + testDocuments.clear(); + if (!headers.isEmpty()) + testDocuments << CppTestDocument::create("file.h", headers.first(), headers.last()); + testDocuments << CppTestDocument::create("file.cpp", sources.first(), sources.last()); + + MoveFuncDefToDeclPull pullFactory; + QuickFixOperationTest(testDocuments, &pullFactory); + } + + void testMacroUses() + { + QByteArray original = + "#define CONST const\n" + "#define VOLATILE volatile\n" + "class Foo\n" + "{\n" + " int func(int a, int b) CONST VOLATILE;\n" + "};\n" + "\n" + "\n" + "int Foo::fu@nc(int a, int b) CONST VOLATILE" + "{\n" + " return 42;\n" + "}\n"; + QByteArray expected = + "#define CONST const\n" + "#define VOLATILE volatile\n" + "class Foo\n" + "{\n" + " int func(int a, int b) CONST VOLATILE\n" + " {\n" + " return 42;\n" + " }\n" + "};\n\n\n\n"; + + MoveFuncDefToDeclPush factory; + QuickFixOperationTest(singleDocument(original, expected), &factory, + ProjectExplorer::HeaderPaths(), 0, "QTCREATORBUG-12314"); + } +}; + +QObject *MoveFuncDefOutside::createTest() +{ + return new MoveFuncDefOutsideTest; +} + +QObject *MoveAllFuncDefOutside::createTest() +{ + return new MoveAllFuncDefOutsideTest; +} + +QObject *MoveFuncDefToDeclPush::createTest() +{ + return new MoveFuncDefToDeclTest; +} + +QObject *MoveFuncDefToDeclPull::createTest() +{ + return new QObject; // The test for the push factory handled both cases. +} + +#endif // WITH_TESTS + +} // namespace + +void registerMoveFunctionDefinitionQuickfixes() +{ + CppQuickFixFactory::registerFactory<MoveFuncDefOutside>(); + CppQuickFixFactory::registerFactory<MoveAllFuncDefOutside>(); + CppQuickFixFactory::registerFactory<MoveFuncDefToDeclPush>(); + CppQuickFixFactory::registerFactory<MoveFuncDefToDeclPull>(); +} + +} // namespace CppEditor::Internal + +#ifdef WITH_TESTS +#include <movefunctiondefinition.moc> +#endif diff --git a/src/plugins/cppeditor/quickfixes/movefunctiondefinition.h b/src/plugins/cppeditor/quickfixes/movefunctiondefinition.h new file mode 100644 index 0000000000..4c6e481ad4 --- /dev/null +++ b/src/plugins/cppeditor/quickfixes/movefunctiondefinition.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 registerMoveFunctionDefinitionQuickfixes(); +} // namespace CppEditor::Internal diff --git a/src/plugins/cppeditor/quickfixes/removeusingnamespace.cpp b/src/plugins/cppeditor/quickfixes/removeusingnamespace.cpp new file mode 100644 index 0000000000..e016d54b4d --- /dev/null +++ b/src/plugins/cppeditor/quickfixes/removeusingnamespace.cpp @@ -0,0 +1,958 @@ +// 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 "removeusingnamespace.h" + +#include "../cppeditortr.h" +#include "../cppprojectfile.h" +#include "../cpprefactoringchanges.h" +#include "cppquickfix.h" + +#include <cplusplus/Overview.h> +#include <projectexplorer/projectmanager.h> + +#ifdef WITH_TESTS +#include "cppquickfix_test.h" +#include <QtTest> +#endif + +using namespace CPlusPlus; +using namespace ProjectExplorer; +using namespace TextEditor; +using namespace Utils; + +namespace CppEditor::Internal { +namespace { + +/** + * @brief The NameCounter class counts the parts of a name. E.g. 2 for std::vector or 1 for variant + */ +class NameCounter : private NameVisitor +{ +public: + int count(const Name *name) + { + counter = 0; + accept(name); + return counter; + } + +private: + void visit(const Identifier *) override { ++counter; } + void visit(const DestructorNameId *) override { ++counter; } + void visit(const TemplateNameId *) override { ++counter; } + void visit(const QualifiedNameId *name) override + { + if (name->base()) + accept(name->base()); + accept(name->name()); + } + int counter; +}; + +/** + * @brief getBaseName returns the base name of a qualified name or nullptr. + * E.g.: foo::bar => foo; bar => bar + * @param name The Name, maybe qualified + * @return The base name of the qualified name or nullptr + */ +const Identifier *getBaseName(const Name *name) +{ + class GetBaseName : public NameVisitor + { + void visit(const Identifier *name) override { baseName = name; } + void visit(const QualifiedNameId *name) override + { + if (name->base()) + accept(name->base()); + else + accept(name->name()); + } + + public: + const Identifier *baseName = nullptr; + }; + GetBaseName getter; + getter.accept(name); + return getter.baseName; +} + +/** + * @brief countNames counts the parts of the Name. + * E.g. if the name is std::vector, the function returns 2, if the name is variant, returns 1 + * @param name The name that should be counted + * @return the number of parts of the name + */ +int countNames(const Name *name) +{ + return NameCounter{}.count(name); +} + +/** + * @brief removeLine removes the whole line in which the ast node is located if there are otherwise only whitespaces + * @param file The file in which the AST node is located + * @param ast The ast node + * @param changeSet The ChangeSet of the file + */ +void removeLine(const CppRefactoringFile *file, AST *ast, ChangeSet &changeSet) +{ + RefactoringFile::Range range = file->range(ast); + --range.start; + while (range.start >= 0) { + QChar current = file->charAt(range.start); + if (!current.isSpace()) { + ++range.start; + break; + } + if (current == QChar::ParagraphSeparator) + break; + --range.start; + } + range.start = std::max(0, range.start); + while (range.end < file->document()->characterCount()) { + QChar current = file->charAt(range.end); + if (!current.isSpace()) + break; + if (current == QChar::ParagraphSeparator) + break; + ++range.end; + } + range.end = std::min(file->document()->characterCount(), range.end); + const bool newLineStart = file->charAt(range.start) == QChar::ParagraphSeparator; + const bool newLineEnd = file->charAt(range.end) == QChar::ParagraphSeparator; + if (!newLineEnd && newLineStart) + ++range.start; + changeSet.remove(range); +} + +/** + * @brief The RemoveNamespaceVisitor class removes a using namespace and rewrites all types that + * are in the namespace if needed + */ +class RemoveNamespaceVisitor : public ASTVisitor +{ +public: + constexpr static int SearchGlobalUsingDirectivePos = std::numeric_limits<int>::max(); + RemoveNamespaceVisitor(const CppRefactoringFile *file, + const Snapshot &snapshot, + const Name *namespace_, + int symbolPos, + bool removeAllAtGlobalScope) + : ASTVisitor(file->cppDocument()->translationUnit()) + , m_file(file) + , m_snapshot(snapshot) + , m_namespace(namespace_) + , m_missingNamespace(toString(namespace_) + "::") + , m_context(m_file->cppDocument(), m_snapshot) + , m_symbolPos(symbolPos) + , m_removeAllAtGlobalScope(removeAllAtGlobalScope) + + {} + + const ChangeSet &getChanges() { return m_changeSet; } + + /** + * @brief isGlobalUsingNamespace return true if the using namespace that should be removed + * is not scoped and other files that include this file will also use the using namespace + * @return true if using namespace statement is global and not scoped, false otherwise + */ + bool isGlobalUsingNamespace() const { return m_parentNode == nullptr; } + + /** + * @brief foundGlobalUsingNamespace return true if removeAllAtGlobalScope is false and + * another using namespace is found at the global scope, so that other files that include this + * file don't have to be processed + * @return true if there was a 'global' second using namespace in this file and + * removeAllAtGlobalScope is false + */ + bool foundGlobalUsingNamespace() const { return m_foundNamespace; } + +private: + bool preVisit(AST *ast) override + { + if (!m_start) { + if (ast->asTranslationUnit()) + return true; + if (UsingDirectiveAST *usingDirective = ast->asUsingDirective()) { + if (nameEqual(usingDirective->name->name, m_namespace)) { + if (m_symbolPos == SearchGlobalUsingDirectivePos) { + // we have found a global using directive, so lets start + m_start = true; + removeLine(m_file, ast, m_changeSet); + return false; + } + // ignore the using namespace that should be removed + if (m_file->endOf(ast) != m_symbolPos) { + if (m_removeAllAtGlobalScope) + removeLine(m_file, ast, m_changeSet); + else + m_done = true; + } + } + } + // if the end of the ast is before we should start, we are not interested in the node + if (m_file->endOf(ast) <= m_symbolPos) + return false; + + if (m_file->startOf(ast) > m_symbolPos) + m_start = true; + } + return !m_foundNamespace && !m_done; + } + + bool visit(NamespaceAST *ast) override + { + if (m_start && nameEqual(m_namespace, ast->symbol->name())) + return false; + + return m_start; + } + + // scopes for using namespace statements: + bool visit(LinkageBodyAST *ast) override { return visitNamespaceScope(ast); } + bool visit(CompoundStatementAST *ast) override { return visitNamespaceScope(ast); } + bool visitNamespaceScope(AST *ast) + { + ++m_namespaceScopeCounter; + if (!m_start) + m_parentNode = ast; + return true; + } + + void endVisit(LinkageBodyAST *ast) override { endVisitNamespaceScope(ast); } + void endVisit(CompoundStatementAST *ast) override { endVisitNamespaceScope(ast); } + void endVisitNamespaceScope(AST *ast) + { + --m_namespaceScopeCounter; + m_foundNamespace = false; + // if we exit the scope of the using namespace we are done + if (ast == m_parentNode) + m_done = true; + } + + bool visit(UsingDirectiveAST *ast) override + { + if (nameEqual(ast->name->name, m_namespace)) { + if (m_removeAllAtGlobalScope && m_namespaceScopeCounter == 0) + removeLine(m_file, ast, m_changeSet); + else + m_foundNamespace = true; + return false; + } + return handleAstWithLongestName(ast); + } + + bool visit(DeclaratorIdAST *ast) override + { + // e.g. we have the following code and get the following Lookup items: + // namespace test { + // struct foo { // 1. item with test::foo + // foo(); // 2. item with test::foo::foo + // }; + // } + // using namespace foo; + // foo::foo() { ... } // 3. item with foo::foo + // Our current name is foo::foo so we have to match with the 2. item / longest name + return handleAstWithLongestName(ast); + } + + template<typename AST> + bool handleAstWithLongestName(AST *ast) + { + if (m_start) { + Scope *scope = m_file->scopeAt(ast->firstToken()); + const QList<LookupItem> localLookup = m_context.lookup(ast->name->name, scope); + QList<const Name *> longestName; + for (const LookupItem &item : localLookup) { + QList<const Name *> names + = m_context.fullyQualifiedName(item.declaration(), + LookupContext::HideInlineNamespaces); + if (names.length() > longestName.length()) + longestName = names; + } + const int currentNameCount = countNames(ast->name->name); + const bool needNew = needMissingNamespaces(std::move(longestName), currentNameCount); + if (needNew) + insertMissingNamespace(ast); + } + return false; + } + + bool visit(NamedTypeSpecifierAST *ast) override { return handleAstWithName(ast); } + + bool visit(IdExpressionAST *ast) override { return handleAstWithName(ast); } + + template<typename AST> + bool handleAstWithName(AST *ast) + { + if (m_start) { + Scope *scope = m_file->scopeAt(ast->firstToken()); + const Name *wantToLookup = ast->name->name; + // first check if the base name is a typedef. Consider the following example: + // using namespace std; + // using vec = std::vector<int>; + // vec::iterator it; // we have to lookup 'vec' and not iterator (would result in + // std::vector<int>::iterator => std::vec::iterator, which is wrong) + const Name *baseName = getBaseName(wantToLookup); + QList<LookupItem> typedefCandidates = m_context.lookup(baseName, scope); + if (!typedefCandidates.isEmpty()) { + if (typedefCandidates.front().declaration()->isTypedef()) + wantToLookup = baseName; + } + + const QList<LookupItem> lookups = m_context.lookup(wantToLookup, scope); + if (!lookups.empty()) { + QList<const Name *> fullName + = m_context.fullyQualifiedName(lookups.first().declaration(), + LookupContext::HideInlineNamespaces); + const int currentNameCount = countNames(wantToLookup); + const bool needNamespace = needMissingNamespaces(std::move(fullName), + currentNameCount); + if (needNamespace) + insertMissingNamespace(ast); + } + } + return true; + } + + template<typename AST> + void insertMissingNamespace(AST *ast) + { + DestructorNameAST *destructorName = ast->name->asDestructorName(); + if (destructorName) + m_changeSet.insert(m_file->startOf(destructorName->unqualified_name), m_missingNamespace); + else + m_changeSet.insert(m_file->startOf(ast->name), m_missingNamespace); + m_changeSet.operationList().last().setFormat1(false); + } + + bool needMissingNamespaces(QList<const Name *> &&fullName, int currentNameCount) + { + if (currentNameCount > fullName.length()) + return false; + + // eg. fullName = std::vector, currentName = vector => result should be std + fullName.erase(fullName.end() - currentNameCount, fullName.end()); + if (fullName.empty()) + return false; + return nameEqual(m_namespace, fullName.last()); + } + + static bool nameEqual(const Name *name1, const Name *name2) + { + return Matcher::match(name1, name2); + } + + QString toString(const Name *id) + { + const Identifier *identifier = id->asNameId(); + QTC_ASSERT(identifier, return {}); + return QString::fromUtf8(identifier->chars(), identifier->size()); + } + + const CppRefactoringFile *const m_file; + const Snapshot &m_snapshot; + + const Name *m_namespace; // the name of the namespace that should be removed + const QString m_missingNamespace; // that should be added if a type was using the namespace + LookupContext m_context; + ChangeSet m_changeSet; + const int m_symbolPos; // the end position of the start symbol + bool m_done = false; + bool m_start = false; + // true if a using namespace was found at a scope and the scope should be left + bool m_foundNamespace = false; + bool m_removeAllAtGlobalScope; + // the scope where the using namespace that should be removed is valid + AST *m_parentNode = nullptr; + int m_namespaceScopeCounter = 0; +}; + +class RemoveUsingNamespaceOperation : public CppQuickFixOperation +{ + struct Node + { + Document::Ptr document; + bool hasGlobalUsingDirective = false; + int unprocessedParents; + std::vector<std::reference_wrapper<Node>> includes; + std::vector<std::reference_wrapper<Node>> includedBy; + Node() = default; + Node(const Node &) = delete; + Node(Node &&) = delete; + }; + +public: + RemoveUsingNamespaceOperation(const CppQuickFixInterface &interface, + UsingDirectiveAST *usingDirective, + bool removeAllAtGlobalScope) + : CppQuickFixOperation(interface, 1) + , m_usingDirective(usingDirective) + , m_removeAllAtGlobalScope(removeAllAtGlobalScope) + { + const QString name = Overview{}.prettyName(usingDirective->name->name); + if (m_removeAllAtGlobalScope) { + setDescription(Tr::tr( + "Remove All Occurrences of \"using namespace %1\" in Global Scope " + "and Adjust Type Names Accordingly") + .arg(name)); + } else { + setDescription(Tr::tr("Remove \"using namespace %1\" and " + "Adjust Type Names Accordingly") + .arg(name)); + } + } + +private: + std::map<Utils::FilePath, Node> buildIncludeGraph(CppRefactoringChanges &refactoring) + { + using namespace ProjectExplorer; + using namespace Utils; + + const Snapshot &s = refactoring.snapshot(); + std::map<Utils::FilePath, Node> includeGraph; + + auto handleFile = [&](const FilePath &filePath, Document::Ptr doc, auto shouldHandle) { + Node &node = includeGraph[filePath]; + node.document = doc; + for (const Document::Include &include : doc->resolvedIncludes()) { + const FilePath filePath = include.resolvedFileName(); + if (shouldHandle(filePath)) { + Node &includedNode = includeGraph[filePath]; + includedNode.includedBy.push_back(node); + node.includes.push_back(includedNode); + } + } + }; + + if (const Project *project = ProjectManager::projectForFile(filePath())) { + const FilePaths files = project->files(ProjectExplorer::Project::SourceFiles); + QSet<FilePath> projectFiles(files.begin(), files.end()); + for (const auto &file : files) { + const Document::Ptr doc = s.document(file); + if (!doc) + continue; + handleFile(file, doc, [&](const FilePath &file) { + return projectFiles.contains(file); + }); + } + } else { + for (auto i = s.begin(); i != s.end(); ++i) { + if (ProjectFile::classify(i.key().toString()) != ProjectFile::Unsupported) { + handleFile(i.key(), i.value(), [](const FilePath &file) { + return ProjectFile::classify(file.toString()) != ProjectFile::Unsupported; + }); + } + } + } + for (auto &[_, node] : includeGraph) { + Q_UNUSED(_) + node.unprocessedParents = static_cast<int>(node.includes.size()); + } + return includeGraph; + } + + void removeAllUsingsAtGlobalScope(CppRefactoringChanges &refactoring) + { + auto includeGraph = buildIncludeGraph(refactoring); + std::vector<std::reference_wrapper<Node>> nodesWithProcessedParents; + for (auto &[_, node] : includeGraph) { + Q_UNUSED(_) + if (!node.unprocessedParents) + nodesWithProcessedParents.push_back(node); + } + while (!nodesWithProcessedParents.empty()) { + Node &node = nodesWithProcessedParents.back(); + nodesWithProcessedParents.pop_back(); + CppRefactoringFilePtr file = refactoring.cppFile(node.document->filePath()); + const bool parentHasUsing = Utils::anyOf(node.includes, &Node::hasGlobalUsingDirective); + const int startPos = parentHasUsing + ? 0 + : RemoveNamespaceVisitor::SearchGlobalUsingDirectivePos; + const bool noGlobalUsing = refactorFile(file, refactoring.snapshot(), startPos); + node.hasGlobalUsingDirective = !noGlobalUsing || parentHasUsing; + + for (Node &subNode : node.includedBy) { + --subNode.unprocessedParents; + if (subNode.unprocessedParents == 0) + nodesWithProcessedParents.push_back(subNode); + } + } + } + + void perform() override + { + CppRefactoringChanges refactoring(snapshot()); + CppRefactoringFilePtr currentFile = refactoring.cppFile(filePath()); + if (m_removeAllAtGlobalScope) { + removeAllUsingsAtGlobalScope(refactoring); + } else if (refactorFile(currentFile, + refactoring.snapshot(), + currentFile->endOf(m_usingDirective), + true)) { + processIncludes(refactoring, filePath()); + } + + for (auto &file : std::as_const(m_changes)) + file->apply(); + } + + /** + * @brief refactorFile remove using namespace xyz in the given file and rewrite types + * @param file The file that should be processed + * @param snapshot The snapshot to work on + * @param startSymbol start processing after this index + * @param removeUsing if the using directive is in this file, remove it + * @return true if the using statement is global and there is no other global using namespace + */ + bool refactorFile(CppRefactoringFilePtr &file, + const Snapshot &snapshot, + int startSymbol, + bool removeUsing = false) + { + RemoveNamespaceVisitor visitor(file.get(), + snapshot, + m_usingDirective->name->name, + startSymbol, + m_removeAllAtGlobalScope); + visitor.accept(file->cppDocument()->translationUnit()->ast()); + Utils::ChangeSet changes = visitor.getChanges(); + if (removeUsing) + removeLine(file.get(), m_usingDirective, changes); + if (!changes.isEmpty()) { + file->setChangeSet(changes); + // apply changes at the end, otherwise the symbol finder will fail to resolve symbols if + // the using namespace is missing + m_changes.insert(file); + } + return visitor.isGlobalUsingNamespace() && !visitor.foundGlobalUsingNamespace(); + } + + void processIncludes(CppRefactoringChanges &refactoring, const FilePath &filePath) + { + QList<Snapshot::IncludeLocation> + includeLocationsOfDocument = refactoring.snapshot().includeLocationsOfDocument(filePath); + for (Snapshot::IncludeLocation &loc : includeLocationsOfDocument) { + if (!Utils::insert(m_processed, loc.first)) + continue; + + CppRefactoringFilePtr file = refactoring.cppFile(loc.first->filePath()); + const bool noGlobalUsing = refactorFile(file, + refactoring.snapshot(), + file->position(loc.second, 1)); + if (noGlobalUsing) + processIncludes(refactoring, loc.first->filePath()); + } + } + + QSet<Document::Ptr> m_processed; + QSet<CppRefactoringFilePtr> m_changes; + + UsingDirectiveAST *m_usingDirective; + bool m_removeAllAtGlobalScope; +}; + +//! Removes a using directive (using namespace xyz). +class RemoveUsingNamespace : public CppQuickFixFactory +{ +public: + RemoveUsingNamespace() { setClangdReplacement({10}); } +#ifdef WITH_TESTS + static QObject *createTest(); +#endif + +private: + void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override + { + const QList<AST *> &path = interface.path(); + // We expect something like + // [0] TranslationUnitAST + // ... + // [] UsingDirectiveAST : if activated at 'using namespace' + // [] NameAST (optional): if activated at the name e.g. 'std' + int n = path.size() - 1; + if (n <= 0) + return; + if (path.last()->asName()) + --n; + UsingDirectiveAST *usingDirective = path.at(n)->asUsingDirective(); + if (usingDirective && usingDirective->name->name->asNameId()) { + result << new RemoveUsingNamespaceOperation(interface, usingDirective, false); + const bool isHeader = ProjectFile::isHeader(ProjectFile::classify(interface.filePath().toString())); + if (isHeader && path.at(n - 1)->asTranslationUnit()) // using namespace at global scope + result << new RemoveUsingNamespaceOperation(interface, usingDirective, true); + } + } +}; + +#ifdef WITH_TESTS +using namespace Tests; +class RemoveUsingNamespaceTest : public QObject +{ + Q_OBJECT + +private slots: + void test_data() + { + QTest::addColumn<QByteArray>("header1"); + QTest::addColumn<QByteArray>("header2"); + QTest::addColumn<QByteArray>("header3"); + QTest::addColumn<QByteArray>("expected1"); + QTest::addColumn<QByteArray>("expected2"); + QTest::addColumn<QByteArray>("expected3"); + QTest::addColumn<int>("operation"); + + const QByteArray header1 = "namespace std{\n" + " template<typename T>\n" + " class vector{};\n" + " namespace chrono{\n" + " using seconds = int;\n" + " }\n" + "}\n" + "using namespace std;\n" + "namespace test{\n" + " class vector{\n" + " std::vector<int> ints;\n" + " };\n" + "}\n"; + const QByteArray header2 = "#include \"header1.h\"\n" + "using foo = test::vector;\n" + "using namespace std;\n" + "using namespace test;\n" + "vector<int> others;\n"; + + const QByteArray header3 = "#include \"header2.h\"\n" + "using namespace std;\n" + "using namespace chrono;\n" + "namespace test{\n" + " vector vec;\n" + " seconds t;\n" + "}\n" + "void scope(){\n" + " for (;;) {\n" + " using namespace std;\n" + " vector<int> fori;\n" + " }\n" + " vector<int> no;\n" + " using namespace std;\n" + " vector<int> _no_change;\n" + "}\n" + "foo foos;\n"; + + QByteArray h3 = "#include \"header2.h\"\n" + "using namespace s@td;\n" + "using namespace chrono;\n" + "namespace test{\n" + " vector vec;\n" + " seconds t;\n" + "}\n" + "void scope(){\n" + " for (;;) {\n" + " using namespace std;\n" + " vector<int> fori;\n" + " }\n" + " vector<int> no;\n" + " using namespace std;\n" + " vector<int> _no_change;\n" + "}\n" + "foo foos;\n"; + + // like header1 but without "using namespace std;\n" + QByteArray expected1 = "namespace std{\n" + " template<typename T>\n" + " class vector{};\n" + " namespace chrono{\n" + " using seconds = int;\n" + " }\n" + "}\n" + "namespace test{\n" + " class vector{\n" + " std::vector<int> ints;\n" + " };\n" + "}\n"; + + // like header2 but without "using namespace std;\n" and with std::vector + QByteArray expected2 = "#include \"header1.h\"\n" + "using foo = test::vector;\n" + "using namespace test;\n" + "std::vector<int> others;\n"; + + QByteArray expected3 = "#include \"header2.h\"\n" + "using namespace std::chrono;\n" + "namespace test{\n" + " vector vec;\n" + " seconds t;\n" + "}\n" + "void scope(){\n" + " for (;;) {\n" + " using namespace std;\n" + " vector<int> fori;\n" + " }\n" + " std::vector<int> no;\n" + " using namespace std;\n" + " vector<int> _no_change;\n" + "}\n" + "foo foos;\n"; + + QTest::newRow("remove only in one file local") + << header1 << header2 << h3 << header1 << header2 << expected3 << 0; + QTest::newRow("remove only in one file globally") + << header1 << header2 << h3 << expected1 << expected2 << expected3 << 1; + + QByteArray h2 = "#include \"header1.h\"\n" + "using foo = test::vector;\n" + "using namespace s@td;\n" + "using namespace test;\n" + "vector<int> others;\n"; + + QTest::newRow("remove across two files only this") + << header1 << h2 << header3 << header1 << expected2 << header3 << 0; + QTest::newRow("remove across two files globally1") + << header1 << h2 << header3 << expected1 << expected2 << expected3 << 1; + + QByteArray h1 = "namespace std{\n" + " template<typename T>\n" + " class vector{};\n" + " namespace chrono{\n" + " using seconds = int;\n" + " }\n" + "}\n" + "using namespace s@td;\n" + "namespace test{\n" + " class vector{\n" + " std::vector<int> ints;\n" + " };\n" + "}\n"; + + QTest::newRow("remove across tree files only this") + << h1 << header2 << header3 << expected1 << header2 << header3 << 0; + QTest::newRow("remove across tree files globally") + << h1 << header2 << header3 << expected1 << expected2 << expected3 << 1; + + expected3 = "#include \"header2.h\"\n" + "using namespace std::chrono;\n" + "namespace test{\n" + " vector vec;\n" + " seconds t;\n" + "}\n" + "void scope(){\n" + " for (;;) {\n" + " using namespace s@td;\n" + " vector<int> fori;\n" + " }\n" + " std::vector<int> no;\n" + " using namespace std;\n" + " vector<int> _no_change;\n" + "}\n" + "foo foos;\n"; + + QByteArray expected3_new = "#include \"header2.h\"\n" + "using namespace std::chrono;\n" + "namespace test{\n" + " vector vec;\n" + " seconds t;\n" + "}\n" + "void scope(){\n" + " for (;;) {\n" + " std::vector<int> fori;\n" + " }\n" + " std::vector<int> no;\n" + " using namespace std;\n" + " vector<int> _no_change;\n" + "}\n" + "foo foos;\n"; + + QTest::newRow("scoped remove") + << expected1 << expected2 << expected3 << expected1 << expected2 << expected3_new << 0; + + h2 = "#include \"header1.h\"\n" + "using foo = test::vector;\n" + "using namespace std;\n" + "using namespace t@est;\n" + "vector<int> others;\n"; + expected2 = "#include \"header1.h\"\n" + "using foo = test::vector;\n" + "using namespace std;\n" + "vector<int> others;\n"; + + QTest::newRow("existing namespace") + << header1 << h2 << header3 << header1 << expected2 << header3 << 1; + + // test: remove using directive at global scope in every file + h1 = "using namespace tes@t;"; + h2 = "using namespace test;"; + h3 = "using namespace test;"; + + expected1 = expected2 = expected3 = ""; + QTest::newRow("global scope remove in every file") + << h1 << h2 << h3 << expected1 << expected2 << expected3 << 1; + + // test: dont print inline namespaces + h1 = R"--( +namespace test { + inline namespace test { + class Foo{ + void foo1(); + void foo2(); + }; + inline int TEST = 42; + } +} +)--"; + h2 = R"--( +#include "header1.h" +using namespace tes@t; +)--"; + h3 = R"--( +#include "header2.h" +Foo f1; +test::Foo f2; +using T1 = Foo; +using T2 = test::Foo; +int i1 = TEST; +int i2 = test::TEST; +void Foo::foo1(){}; +void test::Foo::foo2(){}; +)--"; + + expected1 = h1; + expected2 = R"--( +#include "header1.h" +)--"; + expected3 = R"--( +#include "header2.h" +test::Foo f1; +test::Foo f2; +using T1 = test::Foo; +using T2 = test::Foo; +int i1 = test::TEST; +int i2 = test::TEST; +void test::Foo::foo1(){}; +void test::Foo::foo2(){}; +)--"; + QTest::newRow("don't insert inline namespaces") + << h1 << h2 << h3 << expected1 << expected2 << expected3 << 0; + } + + void test() + { + QFETCH(QByteArray, header1); + QFETCH(QByteArray, header2); + QFETCH(QByteArray, header3); + QFETCH(QByteArray, expected1); + QFETCH(QByteArray, expected2); + QFETCH(QByteArray, expected3); + QFETCH(int, operation); + + QList<TestDocumentPtr> testDocuments; + testDocuments << CppTestDocument::create("header1.h", header1, expected1); + testDocuments << CppTestDocument::create("header2.h", header2, expected2); + testDocuments << CppTestDocument::create("header3.h", header3, expected3); + + RemoveUsingNamespace factory; + QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), operation); + } + + void testSimple_data() + { + QTest::addColumn<QByteArray>("header"); + QTest::addColumn<QByteArray>("expected"); + + const QByteArray common = R"--( +namespace N{ + template<typename T> + struct vector{ + using iterator = T*; + }; + using int_vector = vector<int>; +} +)--"; + const QByteArray header = common + R"--( +using namespace N@; +int_vector ints; +int_vector::iterator intIter; +using vec = vector<int>; +vec::iterator it; +)--"; + const QByteArray expected = common + R"--( +N::int_vector ints; +N::int_vector::iterator intIter; +using vec = N::vector<int>; +vec::iterator it; +)--"; + + QTest::newRow("nested typedefs with Namespace") << header << expected; + } + + void testSimple() + { + QFETCH(QByteArray, header); + QFETCH(QByteArray, expected); + + QList<TestDocumentPtr> testDocuments; + testDocuments << CppTestDocument::create("header.h", header, expected); + + RemoveUsingNamespace factory; + QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths()); + } + + void testDifferentSymbols() + { + QByteArray header = "namespace test{\n" + " struct foo{\n" + " foo();\n" + " void bar();\n" + " };\n" + " void func();\n" + " enum E {E1, E2};\n" + " int bar;\n" + "}\n" + "using namespace t@est;\n" + "foo::foo(){}\n" + "void foo::bar(){}\n" + "void test(){\n" + " int i = bar * 4;\n" + " E val = E1;\n" + " auto p = &foo::bar;\n" + " func()\n" + "}\n"; + QByteArray expected = "namespace test{\n" + " struct foo{\n" + " foo();\n" + " void bar();\n" + " };\n" + " void func();\n" + " enum E {E1, E2};\n" + " int bar;\n" + "}\n" + "test::foo::foo(){}\n" + "void test::foo::bar(){}\n" + "void test(){\n" + " int i = test::bar * 4;\n" + " test::E val = test::E1;\n" + " auto p = &test::foo::bar;\n" + " test::func()\n" + "}\n"; + + QList<TestDocumentPtr> testDocuments; + testDocuments << CppTestDocument::create("file.h", header, expected); + RemoveUsingNamespace factory; + QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), 0); + } +}; + +QObject *RemoveUsingNamespace::createTest() { return new RemoveUsingNamespaceTest; } +#endif // WITH_TESTS + +} // namespace + +void registerRemoveUsingNamespaceQuickfix() +{ + CppQuickFixFactory::registerFactory<RemoveUsingNamespace>(); +} + +} // namespace CppEditor::Internal + +#ifdef WITH_TESTS +#include <removeusingnamespace.moc> +#endif diff --git a/src/plugins/cppeditor/quickfixes/removeusingnamespace.h b/src/plugins/cppeditor/quickfixes/removeusingnamespace.h new file mode 100644 index 0000000000..149cbe4d09 --- /dev/null +++ b/src/plugins/cppeditor/quickfixes/removeusingnamespace.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 registerRemoveUsingNamespaceQuickfix(); +} // namespace CppEditor::Internal diff --git a/src/plugins/cppeditor/quickfixes/rewritecomment.cpp b/src/plugins/cppeditor/quickfixes/rewritecomment.cpp new file mode 100644 index 0000000000..c10bdaf1af --- /dev/null +++ b/src/plugins/cppeditor/quickfixes/rewritecomment.cpp @@ -0,0 +1,875 @@ +// 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 "rewritecomment.h" + +#include "../cppeditortr.h" +#include "../cppeditorwidget.h" +#include "../cpprefactoringchanges.h" +#include "cppquickfix.h" + +#include <cplusplus/ASTPath.h> +#include <cplusplus/declarationcomments.h> +#include <projectexplorer/editorconfiguration.h> +#include <texteditor/tabsettings.h> +#include <texteditor/textdocument.h> + +#ifdef WITH_TESTS +#include "cppquickfix_test.h" +#include <QtTest> +#endif + +using namespace CPlusPlus; +using namespace TextEditor; +using namespace Utils; + +namespace CppEditor::Internal { +namespace { + +class ConvertCommentStyleOp : public CppQuickFixOperation +{ +public: + ConvertCommentStyleOp(const CppQuickFixInterface &interface, const QList<Token> &tokens, + Kind kind) + : CppQuickFixOperation(interface), + m_tokens(tokens), + m_kind(kind), + m_wasCxxStyle(m_kind == T_CPP_COMMENT || m_kind == T_CPP_DOXY_COMMENT), + m_isDoxygen(m_kind == T_DOXY_COMMENT || m_kind == T_CPP_DOXY_COMMENT) + { + setDescription(m_wasCxxStyle ? Tr::tr("Convert Comment to C-Style") + : Tr::tr("Convert Comment to C++-Style")); + } + +private: + // Turns every line of a C-style comment into a C++-style comment and vice versa. + // For C++ -> C, we use one /* */ comment block per line. However, doxygen + // requires a single comment, so there we just replace the prefix with whitespace and + // add the start and end comment in extra lines. + // For cosmetic reasons, we offer some convenience functionality: + // - Turn /***** ... into ////// ... and vice versa + // - With C -> C++, remove leading asterisks. + // - With C -> C++, remove the first and last line of a block if they have no content + // other than the comment start and end characters. + // - With C++ -> C, try to align the end comment characters. + // These are obviously heuristics; we do not guarantee perfect results for everybody. + // We also don't second-guess the users's selection: E.g. if there is an empty + // line between the tokens, then it's not the same doxygen comment, but we merge + // it anyway in C++ to C mode. + void perform() override + { + TranslationUnit * const tu = currentFile()->cppDocument()->translationUnit(); + const QString newCommentStart = getNewCommentStart(); + ChangeSet changeSet; + int endCommentColumn = -1; + const QChar oldFillChar = m_wasCxxStyle ? '/' : '*'; + const QChar newFillChar = m_wasCxxStyle ? '*' : '/'; + + for (const Token &token : m_tokens) { + const int startPos = tu->getTokenPositionInDocument(token, textDocument()); + const int endPos = tu->getTokenEndPositionInDocument(token, textDocument()); + + if (m_wasCxxStyle && m_isDoxygen) { + // Replace "///" characters with whitespace (to keep alignment). + // The insertion of "/*" and "*/" is done once after the loop. + changeSet.replace(startPos, startPos + 3, " "); + continue; + } + + const QTextBlock firstBlock = textDocument()->findBlock(startPos); + const QTextBlock lastBlock = textDocument()->findBlock(endPos); + for (QTextBlock block = firstBlock; block.isValid() && block.position() <= endPos; + block = block.next()) { + const QString &blockText = block.text(); + const int firstColumn = block == firstBlock ? startPos - block.position() : 0; + const int endColumn = block == lastBlock ? endPos - block.position() + : block.length(); + + // Returns true if the current line looks like "/********/" or "//////////", + // as is often the case at the start and end of comment blocks. + const auto fillChecker = [&] { + if (m_isDoxygen) + return false; + QString textToCheck = blockText; + if (block == firstBlock) + textToCheck.remove(0, 1); + if (block == lastBlock) + textToCheck.chop(block.length() - endColumn); + return Utils::allOf(textToCheck, [oldFillChar](const QChar &c) + { return c == oldFillChar || c == ' '; + }) && textToCheck.count(oldFillChar) > 2; + }; + + // Returns the index of the first character of actual comment content, + // as opposed to visual stuff like slashes, stars or whitespace. + const auto indexOfActualContent = [&] { + const int offset = block == firstBlock ? firstColumn + newCommentStart.length() + : firstColumn; + + for (int i = offset, lastFillChar = -1; i < blockText.length(); ++i) { + if (blockText.at(i) == oldFillChar) { + lastFillChar = i; + continue; + } + if (!blockText.at(i).isSpace()) + return lastFillChar + 1; + } + return -1; + }; + + if (fillChecker()) { + const QString replacement = QString(endColumn - 1 - firstColumn, newFillChar); + changeSet.replace(block.position() + firstColumn, + block.position() + endColumn - 1, + replacement); + if (m_wasCxxStyle) { + changeSet.replace(block.position() + firstColumn, + block.position() + firstColumn + 1, "/"); + changeSet.insert(block.position() + endColumn - 1, "*"); + endCommentColumn = endColumn - 1; + } + continue; + } + + // Remove leading noise or even the entire block, if applicable. + const bool blockIsRemovable = (block == firstBlock || block == lastBlock) + && firstBlock != lastBlock; + const auto removeBlock = [&] { + changeSet.remove(block.position() + firstColumn, block.position() + endColumn); + }; + const int contentIndex = indexOfActualContent(); + int removed = 0; + if (contentIndex == -1) { + if (blockIsRemovable) { + removeBlock(); + continue; + } else if (!m_wasCxxStyle) { + changeSet.replace(block.position() + firstColumn, + block.position() + endColumn - 1, newCommentStart); + continue; + } + } else if (block == lastBlock && contentIndex == endColumn - 1) { + if (blockIsRemovable) { + removeBlock(); + break; + } + } else { + changeSet.remove(block.position() + firstColumn, + block.position() + firstColumn + contentIndex); + removed = contentIndex; + } + + if (block == firstBlock) { + changeSet.replace(startPos, startPos + newCommentStart.length(), + newCommentStart); + } else { + // If the line starts with enough whitespace, replace it with the + // comment start characters, so we don't move the content to the right + // unnecessarily. Otherwise, insert the comment start characters. + if (blockText.startsWith(QString(newCommentStart.size() + removed + 1, ' '))) { + changeSet.replace(block.position(), + block.position() + newCommentStart.length(), + newCommentStart); + } else { + changeSet.insert(block.position(), newCommentStart); + } + } + + if (block == lastBlock) { + if (m_wasCxxStyle) { + // This is for proper alignment of the end comment character. + if (endCommentColumn != -1) { + const int endCommentPos = block.position() + endCommentColumn; + if (endPos < endCommentPos) + changeSet.insert(endPos, QString(endCommentPos - endPos - 1, ' ')); + } + changeSet.insert(endPos, " */"); + } else { + changeSet.remove(endPos - 2, endPos); + } + } + } + } + + if (m_wasCxxStyle && m_isDoxygen) { + const int startPos = tu->getTokenPositionInDocument(m_tokens.first(), textDocument()); + const int endPos = tu->getTokenEndPositionInDocument(m_tokens.last(), textDocument()); + changeSet.insert(startPos, "/*!\n"); + changeSet.insert(endPos, "\n*/"); + } + + changeSet.apply(textDocument()); + } + + QString getNewCommentStart() const + { + if (m_wasCxxStyle) { + if (m_isDoxygen) + return "/*!"; + return "/*"; + } + if (m_isDoxygen) + return "//!"; + return "//"; + } + + const QList<Token> m_tokens; + const Kind m_kind; + const bool m_wasCxxStyle; + const bool m_isDoxygen; +}; + +class MoveFunctionCommentsOp : public CppQuickFixOperation +{ +public: + enum class Direction { ToDecl, ToDef }; + MoveFunctionCommentsOp(const CppQuickFixInterface &interface, const Symbol *symbol, + const QList<Token> &commentTokens, Direction direction) + : CppQuickFixOperation(interface), m_symbol(symbol), m_commentTokens(commentTokens) + { + setDescription(direction == Direction::ToDecl + ? Tr::tr("Move Function Documentation to Declaration") + : Tr::tr("Move Function Documentation to Definition")); + } + +private: + void perform() override + { + const auto textDoc = const_cast<QTextDocument *>(currentFile()->document()); + const int pos = currentFile()->cppDocument()->translationUnit()->getTokenPositionInDocument( + m_symbol->sourceLocation(), textDoc); + QTextCursor cursor(textDoc); + cursor.setPosition(pos); + const CursorInEditor cursorInEditor(cursor, currentFile()->filePath(), editor(), + editor()->textDocument()); + const auto callback = [symbolLoc = m_symbol->toLink(), comments = m_commentTokens] + (const Link &link) { + moveComments(link, symbolLoc, comments); + }; + CppModelManager::followSymbol(cursorInEditor, callback, true, false, + FollowSymbolMode::Exact); + } + + static void moveComments(const Link &targetLoc, const Link &symbolLoc, + const QList<Token> &comments) + { + if (!targetLoc.hasValidTarget() || targetLoc.hasSameLocation(symbolLoc)) + return; + + CppRefactoringChanges changes(CppModelManager::snapshot()); + const CppRefactoringFilePtr sourceFile = changes.cppFile(symbolLoc.targetFilePath); + const CppRefactoringFilePtr targetFile + = targetLoc.targetFilePath == symbolLoc.targetFilePath + ? sourceFile + : changes.cppFile(targetLoc.targetFilePath); + const Document::Ptr &targetCppDoc = targetFile->cppDocument(); + const QList<AST *> targetAstPath = ASTPath(targetCppDoc)( + targetLoc.targetLine, targetLoc.targetColumn + 1); + if (targetAstPath.isEmpty()) + return; + const AST *targetDeclAst = nullptr; + for (auto it = std::next(std::rbegin(targetAstPath)); + it != std::rend(targetAstPath); ++it) { + AST * const node = *it; + if (node->asDeclaration()) { + targetDeclAst = node; + continue; + } + if (targetDeclAst) + break; + } + if (!targetDeclAst) + return; + const int insertionPos = targetCppDoc->translationUnit()->getTokenPositionInDocument( + targetDeclAst->firstToken(), targetFile->document()); + const TranslationUnit * const sourceTu = sourceFile->cppDocument()->translationUnit(); + const int sourceCommentStartPos = sourceTu->getTokenPositionInDocument( + comments.first(), sourceFile->document()); + const int sourceCommentEndPos = sourceTu->getTokenEndPositionInDocument( + comments.last(), sourceFile->document()); + + // Manually adjust indentation, as both our built-in indenter and ClangFormat + // are unreliable with regards to comment continuation lines. + auto tabSettings = [](CppRefactoringFilePtr file) { + if (auto editor = file->editor()) + return editor->textDocument()->tabSettings(); + return ProjectExplorer::actualTabSettings(file->filePath(), nullptr); + }; + const TabSettings &sts = tabSettings(sourceFile); + const TabSettings &tts = tabSettings(targetFile); + const QTextBlock insertionBlock = targetFile->document()->findBlock(insertionPos); + const int insertionColumn = tts.columnAt(insertionBlock.text(), + insertionPos - insertionBlock.position()); + const QTextBlock removalBlock = sourceFile->document()->findBlock(sourceCommentStartPos); + const QTextBlock removalBlockEnd = sourceFile->document()->findBlock(sourceCommentEndPos); + const int removalColumn = sts.columnAt(removalBlock.text(), + sourceCommentStartPos - removalBlock.position()); + const int columnOffset = insertionColumn - removalColumn; + QString functionDoc; + if (columnOffset != 0) { + for (QTextBlock block = removalBlock; + block.isValid() && block != removalBlockEnd.next(); + block = block.next()) { + QString text = block.text() + QChar::ParagraphSeparator; + if (block == removalBlockEnd) + text = text.left(sourceCommentEndPos - block.position()); + if (block == removalBlock) { + text = text.mid(sourceCommentStartPos - block.position()); + } else { + int lineIndentColumn = sts.indentationColumn(text) + columnOffset; + text.replace(0, + TabSettings::firstNonSpace(text), + tts.indentationString(0, lineIndentColumn, 0, insertionBlock)); + } + functionDoc += text; + } + } else { + functionDoc = sourceFile->textOf(sourceCommentStartPos, sourceCommentEndPos); + } + + // Remove comment plus leading and trailing whitespace, including trailing newline. + const auto removeAtSource = [&](ChangeSet &changeSet) { + int removalPos = sourceCommentStartPos; + const QChar newline(QChar::ParagraphSeparator); + while (true) { + const int prev = removalPos - 1; + if (prev < 0) + break; + const QChar prevChar = sourceFile->charAt(prev); + if (!prevChar.isSpace() || prevChar == newline) + break; + removalPos = prev; + } + int removalEndPos = sourceCommentEndPos; + while (true) { + if (removalEndPos == sourceFile->document()->characterCount()) + break; + const QChar nextChar = sourceFile->charAt(removalEndPos); + if (!nextChar.isSpace()) + break; + ++removalEndPos; + if (nextChar == newline) + break; + } + changeSet.remove(removalPos, removalEndPos); + }; + + ChangeSet targetChangeSet; + targetChangeSet.insert(insertionPos, functionDoc); + targetChangeSet.insert(insertionPos, "\n"); + targetChangeSet.insert(insertionPos, QString(insertionColumn, ' ')); + if (targetFile == sourceFile) + removeAtSource(targetChangeSet); + targetFile->setChangeSet(targetChangeSet); + const bool targetFileSuccess = targetFile->apply(); + if (targetFile == sourceFile || !targetFileSuccess) + return; + ChangeSet sourceChangeSet; + removeAtSource(sourceChangeSet); + sourceFile->setChangeSet(sourceChangeSet); + sourceFile->apply(); + } + + const Symbol * const m_symbol; + const QList<Token> m_commentTokens; +}; + +//! Converts C-style to C++-style comments and vice versa +class ConvertCommentStyle : public CppQuickFixFactory +{ +#ifdef WITH_TESTS +public: + static QObject* createTest(); +#endif + +private: + void doMatch(const CppQuickFixInterface &interface, + TextEditor::QuickFixOperations &result) override + { + // If there's a selection, then it must entirely consist of comment tokens. + // If there's no selection, the cursor must be on a comment. + const QList<Token> &cursorTokens = interface.currentFile()->tokensForCursor(); + if (cursorTokens.empty()) + return; + if (!cursorTokens.front().isComment()) + return; + + // All tokens must be the same kind of comment, but we make an exception for doxygen comments + // that start with "///", as these are often not intended to be doxygen. For our purposes, + // we treat them as normal comments. + const auto effectiveKind = [&interface](const Token &token) { + if (token.kind() != T_CPP_DOXY_COMMENT) + return token.kind(); + TranslationUnit * const tu = interface.currentFile()->cppDocument()->translationUnit(); + const int startPos = tu->getTokenPositionInDocument(token, interface.textDocument()); + const QString commentStart = interface.textAt(startPos, 3); + return commentStart == "///" ? T_CPP_COMMENT : T_CPP_DOXY_COMMENT; + }; + const Kind kind = effectiveKind(cursorTokens.first()); + for (int i = 1; i < cursorTokens.count(); ++i) { + if (effectiveKind(cursorTokens.at(i)) != kind) + return; + } + + // Ok, all tokens are of same(ish) comment type, offer quickfix. + result << new ConvertCommentStyleOp(interface, cursorTokens, kind); + } +}; + +//! Moves function documentation between declaration and implementation. +class MoveFunctionComments : public CppQuickFixFactory +{ +#ifdef WITH_TESTS +public: + static QObject* createTest(); +#endif + +private: + void doMatch(const CppQuickFixInterface &interface, + TextEditor::QuickFixOperations &result) override + { + const QList<AST *> &astPath = interface.path(); + if (astPath.isEmpty()) + return; + const Symbol *symbol = nullptr; + MoveFunctionCommentsOp::Direction direction = MoveFunctionCommentsOp::Direction::ToDecl; + for (auto it = std::next(std::rbegin(astPath)); it != std::rend(astPath); ++it) { + if (const auto func = (*it)->asFunctionDefinition()) { + symbol = func->symbol; + direction = MoveFunctionCommentsOp::Direction::ToDecl; + break; + } + const auto decl = (*it)->asSimpleDeclaration(); + if (!decl || !decl->declarator_list) + continue; + for (auto it = decl->declarator_list->begin(); + !symbol && it != decl->declarator_list->end(); ++it) { + PostfixDeclaratorListAST * const funcDecls = (*it)->postfix_declarator_list; + if (!funcDecls) + continue; + for (auto it = funcDecls->begin(); it != funcDecls->end(); ++it) { + if (const auto func = (*it)->asFunctionDeclarator()) { + symbol = func->symbol; + direction = MoveFunctionCommentsOp::Direction::ToDef; + break; + } + } + } + + } + if (!symbol) + return; + + if (const QList<Token> commentTokens = commentsForDeclaration( + symbol, *interface.textDocument(), interface.currentFile()->cppDocument()); + !commentTokens.isEmpty()) { + result << new MoveFunctionCommentsOp(interface, symbol, commentTokens, direction); + } + } +}; + +#ifdef WITH_TESTS +using namespace Tests; + +class ConvertCommentStyleTest : public QObject +{ + Q_OBJECT + +private slots: + void test_data() + { + QTest::addColumn<QString>("input"); + QTest::addColumn<QString>("expectedOutput"); + + QTest::newRow("C -> C++ / no selection / single line") << R"( +int var1; +/* Other comment, unaffected */ +/* Our @comment */ +/* Another unaffected comment */ +int var2;)" << R"( +int var1; +/* Other comment, unaffected */ +// Our comment +/* Another unaffected comment */ +int var2;)"; + + QTest::newRow("C -> C++ / no selection / multi-line / preserved header and footer") << R"( +/**************************************************** + * some info + * more @info + ***************************************************/)" << R"( +///////////////////////////////////////////////////// +// some info +// more info +/////////////////////////////////////////////////////)"; + + QTest::newRow("C -> C++ / no selection / multi-line / non-preserved header and footer") << R"( +/* + * some info + * more @info + */)" << R"( +// some info +// more info +)"; + + QTest::newRow("C -> C++ / no selection / qdoc") << R"( +/*! + \qmlproperty string Type::element.name + \qmlproperty int Type::element.id + + \brief Holds the @element name and id. +*/)" << R"( +//! \qmlproperty string Type::element.name +//! \qmlproperty @int Type::element.id +//! +//! \brief Holds the element name and id. +)"; + + QTest::newRow("C -> C++ / no selection / doxygen") << R"( +/*! \class Test + \brief A test class. + + A more detailed @class description. +*/)" << R"( +//! \class Test +//! \brief A test class. +//! +//! A more detailed class description. +)"; + + QTest::newRow("C -> C++ / selection / single line") << R"( +int var1; +/* Other comment, unaffected */ +@{start}/* Our comment */@{end} +/* Another unaffected comment */ +int var2;)" << R"( +int var1; +/* Other comment, unaffected */ +// Our comment +/* Another unaffected comment */ +int var2;)"; + + QTest::newRow("C -> C++ / selection / multi-line / preserved header and footer") << R"( +/**************************************************** + * @{start}some info + * more info@{end} + ***************************************************/)" << R"( +///////////////////////////////////////////////////// +// some info +// more info +/////////////////////////////////////////////////////)"; + + QTest::newRow("C -> C++ / selection / multi-line / non-preserved header and footer") << R"( +/*@{start} + * some in@{end}fo + * more info + */)" << R"( +// some info +// more info +)"; + + QTest::newRow("C -> C++ / selection / qdoc") << R"( +/*!@{start} + \qmlproperty string Type::element.name + \qmlproperty int Type::element.id + + \brief Holds the element name and id. +*/@{end})" << R"( +//! \qmlproperty string Type::element.name +//! \qmlproperty int Type::element.id +//! +//! \brief Holds the element name and id. +)"; + + QTest::newRow("C -> C++ / selection / doxygen") << R"( +/** Expand envi@{start}ronment variables in a string. + * + * Environment variables are accepted in the @{end}following forms: + * $SOMEVAR, ${SOMEVAR} on Unix and %SOMEVAR% on Windows. + * No escapes and quoting are supported. + * If a variable is not found, it is not substituted. + */)" << R"( +//! Expand environment variables in a string. +//! +//! Environment variables are accepted in the following forms: +//! $SOMEVAR, ${SOMEVAR} on Unix and %SOMEVAR% on Windows. +//! No escapes and quoting are supported. +//! If a variable is not found, it is not substituted. +)"; + + QTest::newRow("C -> C++ / selection / multiple comments") << R"( +@{start}/* Affected comment */ +/* Another affected comment */ +/* A third affected comment */@{end} +/* An unaffected comment */)" << R"( +// Affected comment +// Another affected comment +// A third affected comment +/* An unaffected comment */)"; + + // FIXME: Remove adjacent newline along with last block + // FIXME: Use CppRefactoringFile to auto-indent continuation lines? + QTest::newRow("C -> C++, indented") << R"( +struct S { + /* + * @This is an + * indented comment. + */ + void func(); +)" << R"( +struct S { + // This is an +// indented comment. + + void func(); +)"; + + QTest::newRow("C++ -> C / no selection / single line") << R"( +// Other comment, unaffected +// Our @comment +// Another unaffected comment)" << R"( +// Other comment, unaffected +/* Our comment */ +// Another unaffected comment)"; + + QTest::newRow("C++ -> C / selection / single line") << R"( +// Other comment, unaffected +@{start}// Our comment@{end} +// Another unaffected comment)" << R"( +// Other comment, unaffected +/* Our comment */ +// Another unaffected comment)"; + + QTest::newRow("C++ -> C / selection / multi-line / preserved header and footer") << R"( +@{start}///////////////////////////////////////////////////// +// some info +// more info +/////////////////////////////////////////////////////@{end})" << R"( +/****************************************************/ +/* some info */ +/* more info */ +/****************************************************/)"; + + QTest::newRow("C++ -> C / selection / qdoc") << R"( +@{start}//! \qmlproperty string Type::element.name +//! \qmlproperty int Type::element.id +//! +//! \brief Holds the element name and id.@{end} +)" << R"( +/*! + \qmlproperty string Type::element.name + \qmlproperty int Type::element.id + + \brief Holds the element name and id. +*/ +)"; + + QTest::newRow("C++ -> C / selection / doxygen") << R"( +@{start}//! \class Test +//! \brief A test class. +//! +//! A more detailed class description.@{end} +)" << R"( +/*! + \class Test + \brief A test class. + + A more detailed class description. +*/ +)"; + } + + void test() + { + QFETCH(QString, input); + QFETCH(QString, expectedOutput); + + ConvertCommentStyle factory; + QuickFixOperationTest( + {CppTestDocument::create("file.h", input.toUtf8(), expectedOutput.toUtf8())}, + &factory); + } +}; + +class MoveFunctionCommentsTest : public QObject +{ + Q_OBJECT + +private slots: + void test_data() + { + QTest::addColumn<QByteArrayList>("headers"); + QTest::addColumn<QByteArrayList>("sources"); + + const QByteArrayList headersFuncDecl2Def{R"( +// Function comment +void @aFunction(); +)", R"( +void aFunction(); +)"}; + const QByteArrayList sourcesFuncDecl2Def{R"( +#include "file.h" + +void aFunction() {} +)", R"( +#include "file.h" + +// Function comment +void aFunction() {} +)"}; + QTest::newRow("function: from decl to def") << headersFuncDecl2Def << sourcesFuncDecl2Def; + + const QByteArrayList headersFuncDef2Decl{R"( +void aFunction(); +)", R"( +/* function */ +/* comment */ +void aFunction(); +)"}; + const QByteArrayList sourcesFuncDef2Decl{R"( +#include "file.h" + +/* function */ +/* comment */ +void a@Function() {} +)", R"( +#include "file.h" + +void aFunction() {} +)"}; + QTest::newRow("function: from def to decl") << headersFuncDef2Decl << sourcesFuncDef2Decl; + + const QByteArrayList headersFuncNoDef{R"( +// Function comment +void @aFunction(); +)", R"( +// Function comment +void aFunction(); +)"}; + QTest::newRow("function: no def") << headersFuncNoDef << QByteArrayList(); + + const QByteArrayList headersFuncNoDecl{R"( +// Function comment +inline void @aFunction() {} +)", R"( +// Function comment +inline void aFunction() {} +)"}; + QTest::newRow("function: no decl") << headersFuncNoDecl << QByteArrayList(); + + const QByteArrayList headersFuncTemplateDecl2Def{R"( +// Function comment +template<typename T> T @aFunction(); + +template<typename T> inline T aFunction() { return T(); } +)", R"( +template<typename T> T aFunction(); + +// Function comment +template<typename T> inline T aFunction() { return T(); } +)"}; + QTest::newRow("function template: from decl to def") << headersFuncTemplateDecl2Def + << QByteArrayList(); + + const QByteArrayList headersFuncTemplateDef2Decl{R"( +template<typename T> T aFunction(); + +// Function comment +template<typename T> inline T @aFunction() { return T(); } +)", R"( +// Function comment +template<typename T> T aFunction(); + +template<typename T> inline T aFunction() { return T(); } +)"}; + QTest::newRow("function template: from def to decl") << headersFuncTemplateDef2Decl + << QByteArrayList(); + + const QByteArrayList headersMemberDecl2Def{R"( +class C { + /** + * \brief Foo::aMember + */ + void @aMember(); +)", R"( +class C { + void aMember(); +)"}; + const QByteArrayList sourcesMemberDecl2Def{R"( +#include "file.h" + +void C::aMember() {} +)", R"( +#include "file.h" + +/** + * \brief Foo::aMember + */ +void C::aMember() {} +)"}; + QTest::newRow("member function: from decl to def") << headersMemberDecl2Def + << sourcesMemberDecl2Def; + + const QByteArrayList headersMemberDef2Decl{R"( +class C { + void aMember(); +)", R"( +class C { + /** + * \brief Foo::aMember + */ + void aMember(); +)"}; + const QByteArrayList sourcesMemberDef2Decl{R"( +#include "file.h" + +/** + * \brief Foo::aMember + */ +void C::aMember() {@} +)", R"( +#include "file.h" + +void C::aMember() {} +)"}; + QTest::newRow("member function: from def to decl") << headersMemberDef2Decl + << sourcesMemberDef2Decl; + } + + void test() + { + QFETCH(QByteArrayList, headers); + QFETCH(QByteArrayList, sources); + + QList<TestDocumentPtr> documents; + QCOMPARE(headers.size(), 2); + documents << CppTestDocument::create("file.h", headers.at(0), headers.at(1)); + if (!sources.isEmpty()) { + QCOMPARE(sources.size(), 2); + documents << CppTestDocument::create("file.cpp", sources.at(0), sources.at(1)); + } + MoveFunctionComments factory; + QByteArray failMessage; + if (QByteArray(QTest::currentDataTag()) == "function template: from def to decl") + failMessage = "decl/def switch doesn't work for templates"; + QuickFixOperationTest(documents, &factory, {}, {}, failMessage); + } +}; + +QObject * ConvertCommentStyle::createTest() { return new ConvertCommentStyleTest; } +QObject * MoveFunctionComments::createTest() { return new MoveFunctionCommentsTest; } + +#endif +} // namespace + +void registerRewriteCommentQuickfixes() +{ + CppQuickFixFactory::registerFactory<ConvertCommentStyle>(); + CppQuickFixFactory::registerFactory<MoveFunctionComments>(); +} + +} // namespace CppEditor::Internal + +#ifdef WITH_TESTS +#include <rewritecomment.moc> +#endif diff --git a/src/plugins/cppeditor/quickfixes/rewritecomment.h b/src/plugins/cppeditor/quickfixes/rewritecomment.h new file mode 100644 index 0000000000..50ad0af415 --- /dev/null +++ b/src/plugins/cppeditor/quickfixes/rewritecomment.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 registerRewriteCommentQuickfixes(); +} // namespace CppEditor::Internal diff --git a/src/plugins/cppeditor/quickfixes/rewritecontrolstatements.cpp b/src/plugins/cppeditor/quickfixes/rewritecontrolstatements.cpp new file mode 100644 index 0000000000..0590adc306 --- /dev/null +++ b/src/plugins/cppeditor/quickfixes/rewritecontrolstatements.cpp @@ -0,0 +1,1342 @@ +// 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 "rewritecontrolstatements.h" + +#include "../cppcodestylesettings.h" +#include "../cppeditortr.h" +#include "../cppeditorwidget.h" +#include "../cpprefactoringchanges.h" +#include "cppquickfix.h" + +#include <cplusplus/Overview.h> +#include <cplusplus/TypeOfExpression.h> + +#ifdef WITH_TESTS +#include "cppquickfix_test.h" +#include <QTest> +#endif + +using namespace CPlusPlus; +using namespace TextEditor; +using namespace Utils; + +namespace CppEditor::Internal { +namespace { + +template<typename Statement> Statement *asControlStatement(AST *node) +{ + if constexpr (std::is_same_v<Statement, IfStatementAST>) + return node->asIfStatement(); + if constexpr (std::is_same_v<Statement, WhileStatementAST>) + return node->asWhileStatement(); + if constexpr (std::is_same_v<Statement, ForStatementAST>) + return node->asForStatement(); + if constexpr (std::is_same_v<Statement, RangeBasedForStatementAST>) + return node->asRangeBasedForStatement(); + if constexpr (std::is_same_v<Statement, DoStatementAST>) + return node->asDoStatement(); + return nullptr; +} + +template<typename Statement> +int triggerToken(const Statement *statement) +{ + if constexpr (std::is_same_v<Statement, IfStatementAST>) + return statement->if_token; + if constexpr (std::is_same_v<Statement, WhileStatementAST>) + return statement->while_token; + if constexpr (std::is_same_v<Statement, DoStatementAST>) + return statement->do_token; + if constexpr (std::is_same_v<Statement, ForStatementAST> + || std::is_same_v<Statement, RangeBasedForStatementAST>) { + return statement->for_token; + } +} + +template<typename Statement> +int tokenToInsertOpeningBraceAfter(const Statement *statement) +{ + if constexpr (std::is_same_v<Statement, DoStatementAST>) + return statement->do_token; + return statement->rparen_token; +} + +template<typename Statement> class AddBracesToControlStatementOp : public CppQuickFixOperation +{ +public: + AddBracesToControlStatementOp(const CppQuickFixInterface &interface, + const QList<Statement *> &statements, + StatementAST *elseStatement, + int elseToken) + : CppQuickFixOperation(interface, 0) + , m_statements(statements), m_elseStatement(elseStatement), m_elseToken(elseToken) + { + setDescription(Tr::tr("Add Curly Braces")); + } + + void perform() override + { + CppRefactoringChanges refactoring(snapshot()); + CppRefactoringFilePtr currentFile = refactoring.cppFile(filePath()); + + ChangeSet changes; + for (Statement * const statement : m_statements) { + const int start = currentFile->endOf(tokenToInsertOpeningBraceAfter(statement)); + changes.insert(start, QLatin1String(" {")); + if constexpr (std::is_same_v<Statement, DoStatementAST>) { + const int end = currentFile->startOf(statement->while_token); + changes.insert(end, QLatin1String("} ")); + } else if constexpr (std::is_same_v<Statement, IfStatementAST>) { + if (statement->else_statement) { + changes.insert(currentFile->startOf(statement->else_token), "} "); + } else { + changes.insert(currentFile->endOf(statement->statement->lastToken() - 1), + "\n}"); + } + + } else { + const int end = currentFile->endOf(statement->statement->lastToken() - 1); + changes.insert(end, QLatin1String("\n}")); + } + } + if (m_elseStatement) { + changes.insert(currentFile->endOf(m_elseToken), " {"); + changes.insert(currentFile->endOf(m_elseStatement->lastToken() - 1), "\n}"); + } + + currentFile->setChangeSet(changes); + currentFile->apply(); + } + +private: + const QList<Statement *> m_statements; + StatementAST * const m_elseStatement; + const int m_elseToken; +}; + +template<typename Statement> +bool checkControlStatementsHelper(const CppQuickFixInterface &interface, QuickFixOperations &result) +{ + Statement * const statement = asControlStatement<Statement>(interface.path().last()); + if (!statement) + return false; + + QList<Statement *> statements; + if (interface.isCursorOn(triggerToken(statement)) && statement->statement + && !statement->statement->asCompoundStatement()) { + statements << statement; + } + + StatementAST *elseStmt = nullptr; + int elseToken = 0; + if constexpr (std::is_same_v<Statement, IfStatementAST>) { + IfStatementAST *currentIfStmt = statement; + for (elseStmt = currentIfStmt->else_statement, elseToken = currentIfStmt->else_token; + elseStmt && (currentIfStmt = elseStmt->asIfStatement()); + elseStmt = currentIfStmt->else_statement, elseToken = currentIfStmt->else_token) { + if (currentIfStmt->statement && !currentIfStmt->statement->asCompoundStatement()) + statements << currentIfStmt; + } + if (elseStmt && (elseStmt->asIfStatement() || elseStmt->asCompoundStatement())) { + elseStmt = nullptr; + elseToken = 0; + } + } + + if (!statements.isEmpty() || elseStmt) + result << new AddBracesToControlStatementOp(interface, statements, elseStmt, elseToken); + return true; +} + +template<typename ...Statements> +void checkControlStatements(const CppQuickFixInterface &interface, QuickFixOperations &result) +{ + (... || checkControlStatementsHelper<Statements>(interface, result)); +} + +class MoveDeclarationOutOfIfOp: public CppQuickFixOperation +{ +public: + MoveDeclarationOutOfIfOp(const CppQuickFixInterface &interface) + : CppQuickFixOperation(interface) + { + setDescription(Tr::tr("Move Declaration out of Condition")); + + reset(); + } + + void reset() + { + condition = mk.Condition(); + pattern = mk.IfStatement(condition); + } + + void perform() override + { + CppRefactoringChanges refactoring(snapshot()); + CppRefactoringFilePtr currentFile = refactoring.cppFile(filePath()); + + ChangeSet changes; + + changes.copy(currentFile->range(core), currentFile->startOf(condition)); + + int insertPos = currentFile->startOf(pattern); + changes.move(currentFile->range(condition), insertPos); + changes.insert(insertPos, QLatin1String(";\n")); + + currentFile->setChangeSet(changes); + currentFile->apply(); + } + + ASTMatcher matcher; + ASTPatternBuilder mk; + ConditionAST *condition = nullptr; + IfStatementAST *pattern = nullptr; + CoreDeclaratorAST *core = nullptr; +}; + +class MoveDeclarationOutOfWhileOp: public CppQuickFixOperation +{ +public: + MoveDeclarationOutOfWhileOp(const CppQuickFixInterface &interface) + : CppQuickFixOperation(interface) + { + setDescription(Tr::tr("Move Declaration out of Condition")); + reset(); + } + + void reset() + { + condition = mk.Condition(); + pattern = mk.WhileStatement(condition); + } + + void perform() override + { + CppRefactoringChanges refactoring(snapshot()); + CppRefactoringFilePtr currentFile = refactoring.cppFile(filePath()); + + ChangeSet changes; + + changes.insert(currentFile->startOf(condition), QLatin1String("(")); + changes.insert(currentFile->endOf(condition), QLatin1String(") != 0")); + + int insertPos = currentFile->startOf(pattern); + const int conditionStart = currentFile->startOf(condition); + changes.move(conditionStart, currentFile->startOf(core), insertPos); + changes.copy(currentFile->range(core), insertPos); + changes.insert(insertPos, QLatin1String(";\n")); + + currentFile->setChangeSet(changes); + currentFile->apply(); + } + + ASTMatcher matcher; + ASTPatternBuilder mk; + ConditionAST *condition = nullptr; + WhileStatementAST *pattern = nullptr; + CoreDeclaratorAST *core = nullptr; +}; + +class SplitIfStatementOp: public CppQuickFixOperation +{ +public: + SplitIfStatementOp(const CppQuickFixInterface &interface, int priority, + IfStatementAST *pattern, BinaryExpressionAST *condition) + : CppQuickFixOperation(interface, priority) + , pattern(pattern) + , condition(condition) + { + setDescription(Tr::tr("Split if Statement")); + } + + void perform() override + { + CppRefactoringChanges refactoring(snapshot()); + CppRefactoringFilePtr currentFile = refactoring.cppFile(filePath()); + + const Token binaryToken = currentFile->tokenAt(condition->binary_op_token); + + if (binaryToken.is(T_AMPER_AMPER)) + splitAndCondition(currentFile); + else + splitOrCondition(currentFile); + } + + void splitAndCondition(CppRefactoringFilePtr currentFile) const + { + ChangeSet changes; + + int startPos = currentFile->startOf(pattern); + changes.insert(startPos, QLatin1String("if (")); + changes.move(currentFile->range(condition->left_expression), startPos); + changes.insert(startPos, QLatin1String(") {\n")); + + const int lExprEnd = currentFile->endOf(condition->left_expression); + changes.remove(lExprEnd, currentFile->startOf(condition->right_expression)); + changes.insert(currentFile->endOf(pattern), QLatin1String("\n}")); + + currentFile->setChangeSet(changes); + currentFile->apply(); + } + + void splitOrCondition(CppRefactoringFilePtr currentFile) const + { + ChangeSet changes; + + StatementAST *ifTrueStatement = pattern->statement; + CompoundStatementAST *compoundStatement = ifTrueStatement->asCompoundStatement(); + + int insertPos = currentFile->endOf(ifTrueStatement); + if (compoundStatement) + changes.insert(insertPos, QLatin1String(" ")); + else + changes.insert(insertPos, QLatin1String("\n")); + changes.insert(insertPos, QLatin1String("else if (")); + + const int rExprStart = currentFile->startOf(condition->right_expression); + changes.move(rExprStart, currentFile->startOf(pattern->rparen_token), insertPos); + changes.insert(insertPos, QLatin1String(")")); + + const int rParenEnd = currentFile->endOf(pattern->rparen_token); + changes.copy(rParenEnd, currentFile->endOf(pattern->statement), insertPos); + + const int lExprEnd = currentFile->endOf(condition->left_expression); + changes.remove(lExprEnd, currentFile->startOf(condition->right_expression)); + + currentFile->setChangeSet(changes); + currentFile->apply(); + } + +private: + IfStatementAST *pattern; + BinaryExpressionAST *condition; +}; + +class OptimizeForLoopOperation: public CppQuickFixOperation +{ +public: + OptimizeForLoopOperation(const CppQuickFixInterface &interface, const ForStatementAST *forAst, + const bool optimizePostcrement, const ExpressionAST *expression, + const FullySpecifiedType &type) + : CppQuickFixOperation(interface) + , m_forAst(forAst) + , m_optimizePostcrement(optimizePostcrement) + , m_expression(expression) + , m_type(type) + { + setDescription(Tr::tr("Optimize for-Loop")); + } + + void perform() override + { + QTC_ASSERT(m_forAst, return); + + const Utils::FilePath filePath = currentFile()->filePath(); + const CppRefactoringChanges refactoring(snapshot()); + const CppRefactoringFilePtr file = refactoring.cppFile(filePath); + ChangeSet change; + + // Optimize post (in|de)crement operator to pre (in|de)crement operator + if (m_optimizePostcrement && m_forAst->expression) { + PostIncrDecrAST *incrdecr = m_forAst->expression->asPostIncrDecr(); + if (incrdecr && incrdecr->base_expression && incrdecr->incr_decr_token) { + change.flip(file->range(incrdecr->base_expression), + file->range(incrdecr->incr_decr_token)); + } + } + + // Optimize Condition + int renamePos = -1; + if (m_expression) { + QString varName = QLatin1String("total"); + + if (file->textOf(m_forAst->initializer).length() == 1) { + Overview oo = CppCodeStyleSettings::currentProjectCodeStyleOverview(); + const QString typeAndName = oo.prettyType(m_type, varName); + renamePos = file->endOf(m_forAst->initializer) - 1 + typeAndName.length(); + change.insert(file->endOf(m_forAst->initializer) - 1, // "-1" because of ";" + typeAndName + QLatin1String(" = ") + file->textOf(m_expression)); + } else { + // Check if varName is already used + if (DeclarationStatementAST *ds = m_forAst->initializer->asDeclarationStatement()) { + if (DeclarationAST *decl = ds->declaration) { + if (SimpleDeclarationAST *sdecl = decl->asSimpleDeclaration()) { + for (;;) { + bool match = false; + for (DeclaratorListAST *it = sdecl->declarator_list; it; + it = it->next) { + if (file->textOf(it->value->core_declarator) == varName) { + varName += QLatin1Char('X'); + match = true; + break; + } + } + if (!match) + break; + } + } + } + } + + renamePos = file->endOf(m_forAst->initializer) + 1; + change.insert(file->endOf(m_forAst->initializer) - 1, // "-1" because of ";" + QLatin1String(", ") + varName + QLatin1String(" = ") + + file->textOf(m_expression)); + } + + ChangeSet::Range exprRange(file->startOf(m_expression), file->endOf(m_expression)); + change.replace(exprRange, varName); + } + + file->setChangeSet(change); + file->apply(); + + // Select variable name and trigger symbol rename + if (renamePos != -1) { + QTextCursor c = file->cursor(); + c.setPosition(renamePos); + editor()->setTextCursor(c); + editor()->renameSymbolUnderCursor(); + c.select(QTextCursor::WordUnderCursor); + editor()->setTextCursor(c); + } + } + +private: + const ForStatementAST *m_forAst; + const bool m_optimizePostcrement; + const ExpressionAST *m_expression; + const FullySpecifiedType m_type; +}; + +/*! + Replace + if (Type name = foo()) {...} + + With + Type name = foo(); + if (name) {...} + + Activates on: the name of the introduced variable +*/ +class MoveDeclarationOutOfIf: public CppQuickFixFactory +{ +#ifdef WITH_TESTS +public: + static QObject *createTest(); +#endif + +private: + void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override + { + const QList<AST *> &path = interface.path(); + using Ptr = QSharedPointer<MoveDeclarationOutOfIfOp>; + Ptr op(new MoveDeclarationOutOfIfOp(interface)); + + int index = path.size() - 1; + for (; index != -1; --index) { + if (IfStatementAST *statement = path.at(index)->asIfStatement()) { + if (statement->match(op->pattern, &op->matcher) && op->condition->declarator) { + DeclaratorAST *declarator = op->condition->declarator; + op->core = declarator->core_declarator; + if (!op->core) + return; + + if (interface.isCursorOn(op->core)) { + op->setPriority(index); + result.append(op); + return; + } + + op->reset(); + } + } + } + } +}; + +/*! + Replace + while (Type name = foo()) {...} + + With + Type name; + while ((name = foo()) != 0) {...} + + Activates on: the name of the introduced variable +*/ +class MoveDeclarationOutOfWhile: public CppQuickFixFactory +{ +#ifdef WITH_TESTS +public: + static QObject *createTest(); +#endif + +private: + void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override + { + const QList<AST *> &path = interface.path(); + QSharedPointer<MoveDeclarationOutOfWhileOp> op(new MoveDeclarationOutOfWhileOp(interface)); + + int index = path.size() - 1; + for (; index != -1; --index) { + if (WhileStatementAST *statement = path.at(index)->asWhileStatement()) { + if (statement->match(op->pattern, &op->matcher) && op->condition->declarator) { + DeclaratorAST *declarator = op->condition->declarator; + op->core = declarator->core_declarator; + + if (!op->core) + return; + + if (!declarator->equal_token) + return; + + if (!declarator->initializer) + return; + + if (interface.isCursorOn(op->core)) { + op->setPriority(index); + result.append(op); + return; + } + + op->reset(); + } + } + } + } +}; + +/*! + Replace + if (something && something_else) { + } + + with + if (something) + if (something_else) { + } + } + + and + if (something || something_else) + x; + + with + if (something) + x; + else if (something_else) + x; + + Activates on: && or || +*/ +class SplitIfStatement: public CppQuickFixFactory +{ +#ifdef WITH_TESTS +public: + static QObject *createTest() { return new QObject; } +#endif + +private: + void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override + { + IfStatementAST *pattern = nullptr; + const QList<AST *> &path = interface.path(); + + int index = path.size() - 1; + for (; index != -1; --index) { + AST *node = path.at(index); + if (IfStatementAST *stmt = node->asIfStatement()) { + pattern = stmt; + break; + } + } + + if (!pattern || !pattern->statement) + return; + + unsigned splitKind = 0; + for (++index; index < path.size(); ++index) { + AST *node = path.at(index); + BinaryExpressionAST *condition = node->asBinaryExpression(); + if (!condition) + return; + + Token binaryToken = interface.currentFile()->tokenAt(condition->binary_op_token); + + // only accept a chain of ||s or &&s - no mixing + if (!splitKind) { + splitKind = binaryToken.kind(); + if (splitKind != T_AMPER_AMPER && splitKind != T_PIPE_PIPE) + return; + // we can't reliably split &&s in ifs with an else branch + if (splitKind == T_AMPER_AMPER && pattern->else_statement) + return; + } else if (splitKind != binaryToken.kind()) { + return; + } + + if (interface.isCursorOn(condition->binary_op_token)) { + result << new SplitIfStatementOp(interface, index, pattern, condition); + return; + } + } + } +}; + +/*! + Add curly braces to a control statement that doesn't already contain a + compound statement. I.e. + + if (a) + b; + becomes + if (a) { + b; + } + + Activates on: the keyword +*/ +class AddBracesToControlStatement : public CppQuickFixFactory +{ +#ifdef WITH_TESTS +public: + static QObject *createTest(); +#endif + +private: + void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override + { + if (interface.path().isEmpty()) + return; + checkControlStatements<IfStatementAST, + WhileStatementAST, + ForStatementAST, + RangeBasedForStatementAST, + DoStatementAST>(interface, result); + } +}; + +/*! + Optimizes a for loop to avoid permanent condition check and forces to use preincrement + or predecrement operators in the expression of the for loop. + */ +class OptimizeForLoop : public CppQuickFixFactory +{ +#ifdef WITH_TESTS +public: + static QObject *createTest(); +#endif + +private: + void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override + { + const QList<AST *> path = interface.path(); + ForStatementAST *forAst = nullptr; + if (!path.isEmpty()) + forAst = path.last()->asForStatement(); + if (!forAst || !interface.isCursorOn(forAst)) + return; + + // Check for optimizing a postcrement + const CppRefactoringFilePtr file = interface.currentFile(); + bool optimizePostcrement = false; + if (forAst->expression) { + if (PostIncrDecrAST *incrdecr = forAst->expression->asPostIncrDecr()) { + const Token t = file->tokenAt(incrdecr->incr_decr_token); + if (t.is(T_PLUS_PLUS) || t.is(T_MINUS_MINUS)) + optimizePostcrement = true; + } + } + + // Check for optimizing condition + bool optimizeCondition = false; + FullySpecifiedType conditionType; + ExpressionAST *conditionExpression = nullptr; + if (forAst->initializer && forAst->condition) { + if (BinaryExpressionAST *binary = forAst->condition->asBinaryExpression()) { + // Get the expression against which we should evaluate + IdExpressionAST *conditionId = binary->left_expression->asIdExpression(); + if (conditionId) { + conditionExpression = binary->right_expression; + } else { + conditionId = binary->right_expression->asIdExpression(); + conditionExpression = binary->left_expression; + } + + if (conditionId && conditionExpression + && !(conditionExpression->asNumericLiteral() + || conditionExpression->asStringLiteral() + || conditionExpression->asIdExpression() + || conditionExpression->asUnaryExpression())) { + // Determine type of for initializer + FullySpecifiedType initializerType; + if (DeclarationStatementAST *stmt = forAst->initializer->asDeclarationStatement()) { + if (stmt->declaration) { + if (SimpleDeclarationAST *decl = stmt->declaration->asSimpleDeclaration()) { + if (decl->symbols) { + if (Symbol *symbol = decl->symbols->value) + initializerType = symbol->type(); + } + } + } + } + + // Determine type of for condition + TypeOfExpression typeOfExpression; + typeOfExpression.init(interface.semanticInfo().doc, interface.snapshot(), + interface.context().bindings()); + typeOfExpression.setExpandTemplates(true); + Scope *scope = file->scopeAt(conditionId->firstToken()); + const QList<LookupItem> conditionItems = typeOfExpression( + conditionId, interface.semanticInfo().doc, scope); + if (!conditionItems.isEmpty()) + conditionType = conditionItems.first().type(); + + if (conditionType.isValid() + && (file->textOf(forAst->initializer) == QLatin1String(";") + || initializerType == conditionType)) { + optimizeCondition = true; + } + } + } + } + + if (optimizePostcrement || optimizeCondition) { + result << new OptimizeForLoopOperation(interface, forAst, optimizePostcrement, + optimizeCondition ? conditionExpression : nullptr, + conditionType); + } + } +}; + +#ifdef WITH_TESTS +using namespace Tests; + +class MoveDeclarationOutOfIfTest : public QObject +{ + Q_OBJECT + +private slots: + void test_data() + { + QTest::addColumn<QByteArray>("original"); + QTest::addColumn<QByteArray>("expected"); + + QTest::newRow("ifOnly") + << QByteArray( + "void f()\n" + "{\n" + " if (Foo *@foo = g())\n" + " h();\n" + "}\n") + << QByteArray( + "void f()\n" + "{\n" + " Foo *foo = g();\n" + " if (foo)\n" + " h();\n" + "}\n"); + QTest::newRow("ifElse") + << QByteArray( + "void f()\n" + "{\n" + " if (Foo *@foo = g())\n" + " h();\n" + " else\n" + " i();\n" + "}\n") + << QByteArray( + "void f()\n" + "{\n" + " Foo *foo = g();\n" + " if (foo)\n" + " h();\n" + " else\n" + " i();\n" + "}\n"); + + QTest::newRow("MoveDeclarationOutOfIf_ifElseIf") + << QByteArray( + "void f()\n" + "{\n" + " if (Foo *foo = g()) {\n" + " if (Bar *@bar = x()) {\n" + " h();\n" + " j();\n" + " }\n" + " } else {\n" + " i();\n" + " }\n" + "}\n") + << QByteArray( + "void f()\n" + "{\n" + " if (Foo *foo = g()) {\n" + " Bar *bar = x();\n" + " if (bar) {\n" + " h();\n" + " j();\n" + " }\n" + " } else {\n" + " i();\n" + " }\n" + "}\n"); + } + + void test() + { + QFETCH(QByteArray, original); + QFETCH(QByteArray, expected); + MoveDeclarationOutOfIf factory; + QuickFixOperationTest(singleDocument(original, expected), &factory); + } +}; + +class MoveDeclarationOutOfWhileTest : public QObject +{ + Q_OBJECT + +private slots: + void test_data() + { + QTest::addColumn<QByteArray>("original"); + QTest::addColumn<QByteArray>("expected"); + + QTest::newRow("singleWhile") + << QByteArray( + "void f()\n" + "{\n" + " while (Foo *@foo = g())\n" + " j();\n" + "}\n") + << QByteArray( + "void f()\n" + "{\n" + " Foo *foo;\n" + " while ((foo = g()) != 0)\n" + " j();\n" + "}\n"); + QTest::newRow("whileInWhile") + << QByteArray( + "void f()\n" + "{\n" + " while (Foo *foo = g()) {\n" + " while (Bar *@bar = h()) {\n" + " i();\n" + " j();\n" + " }\n" + " }\n" + "}\n") + << QByteArray( + "void f()\n" + "{\n" + " while (Foo *foo = g()) {\n" + " Bar *bar;\n" + " while ((bar = h()) != 0) {\n" + " i();\n" + " j();\n" + " }\n" + " }\n" + "}\n" + ); + + } + + void test() + { + QFETCH(QByteArray, original); + QFETCH(QByteArray, expected); + MoveDeclarationOutOfWhile factory; + QuickFixOperationTest(singleDocument(original, expected), &factory); + } +}; + +class OptimizeForLoopTest : public QObject +{ + Q_OBJECT + +private slots: + void test_data() + { + QTest::addColumn<QByteArray>("original"); + QTest::addColumn<QByteArray>("expected"); + + // Check: optimize postcrement + QTest::newRow("OptimizeForLoop_postcrement") + << QByteArray("void foo() {f@or (int i = 0; i < 3; i++) {}}\n") + << QByteArray("void foo() {for (int i = 0; i < 3; ++i) {}}\n"); + + // Check: optimize condition + QTest::newRow("OptimizeForLoop_condition") + << QByteArray("void foo() {f@or (int i = 0; i < 3 + 5; ++i) {}}\n") + << QByteArray("void foo() {for (int i = 0, total = 3 + 5; i < total; ++i) {}}\n"); + + // Check: optimize fliped condition + QTest::newRow("OptimizeForLoop_flipedCondition") + << QByteArray("void foo() {f@or (int i = 0; 3 + 5 > i; ++i) {}}\n") + << QByteArray("void foo() {for (int i = 0, total = 3 + 5; total > i; ++i) {}}\n"); + + // Check: if "total" used, create other name. + QTest::newRow("OptimizeForLoop_alterVariableName") + << QByteArray("void foo() {f@or (int i = 0, total = 0; i < 3 + 5; ++i) {}}\n") + << QByteArray("void foo() {for (int i = 0, total = 0, totalX = 3 + 5; i < totalX; ++i) {}}\n"); + + // Check: optimize postcrement and condition + QTest::newRow("OptimizeForLoop_optimizeBoth") + << QByteArray("void foo() {f@or (int i = 0; i < 3 + 5; i++) {}}\n") + << QByteArray("void foo() {for (int i = 0, total = 3 + 5; i < total; ++i) {}}\n"); + + // Check: empty initializier + QTest::newRow("OptimizeForLoop_emptyInitializer") + << QByteArray("int i; void foo() {f@or (; i < 3 + 5; ++i) {}}\n") + << QByteArray("int i; void foo() {for (int total = 3 + 5; i < total; ++i) {}}\n"); + + // Check: wrong initializier type -> no trigger + QTest::newRow("OptimizeForLoop_wrongInitializer") + << QByteArray("int i; void foo() {f@or (double a = 0; i < 3 + 5; ++i) {}}\n") + << QByteArray(); + + // Check: No trigger when numeric + QTest::newRow("OptimizeForLoop_noTriggerNumeric1") + << QByteArray("void foo() {fo@r (int i = 0; i < 3; ++i) {}}\n") + << QByteArray(); + + // Check: No trigger when numeric + QTest::newRow("OptimizeForLoop_noTriggerNumeric2") + << QByteArray("void foo() {fo@r (int i = 0; i < -3; ++i) {}}\n") + << QByteArray(); + } + + void test() + { + QFETCH(QByteArray, original); + QFETCH(QByteArray, expected); + OptimizeForLoop factory; + QuickFixOperationTest(singleDocument(original, expected), &factory); + } +}; + +class AddBracesToControlStatementTest : public QObject +{ + Q_OBJECT + +private slots: + void test_data() + { + QTest::addColumn<QByteArray>("original"); + QTest::addColumn<QByteArray>("expected"); + + QByteArray original = R"delim( +void MyObject::f() +{ + @if (true) + emit mySig(); +})delim"; + QByteArray expected = R"delim( +void MyObject::f() +{ + if (true) { + emit mySig(); + } +})delim"; + QTest::newRow("if") << original << expected; + + original = R"delim( +void MyObject::f() +{ + @if (true) + emit mySig(); + else + emit otherSig(); +})delim"; + expected = R"delim( +void MyObject::f() +{ + @if (true) { + emit mySig(); + } else { + emit otherSig(); + } +})delim"; + QTest::newRow("if with one else, unbraced") << original << expected; + + original = R"delim( +void MyObject::f() +{ + @if (true) { + emit mySig(); + } else + emit otherSig(); +})delim"; + expected = R"delim( +void MyObject::f() +{ + @if (true) { + emit mySig(); + } else { + emit otherSig(); + } +})delim"; + QTest::newRow("if with one else, if braced") << original << expected; + + original = R"delim( +void MyObject::f() +{ + @if (true) + emit mySig(); + else { + emit otherSig(); + } +})delim"; + expected = R"delim( +void MyObject::f() +{ + @if (true) { + emit mySig(); + } else { + emit otherSig(); + } +})delim"; + QTest::newRow("if with one else, else braced") << original << expected; + + original = R"delim( +void MyObject::f() +{ + @if (true) { + emit mySig(); + } else { + emit otherSig(); + } +})delim"; + expected.clear(); + QTest::newRow("if with one else, both braced") << original << expected; + + original = R"delim( +void MyObject::f() +{ + @if (x == 1) + emit sig1(); + else if (x == 2) + emit sig2(); +})delim"; + expected = R"delim( +void MyObject::f() +{ + if (x == 1) { + emit sig1(); + } else if (x == 2) { + emit sig2(); + } +})delim"; + QTest::newRow("if-else chain without final else, unbraced") << original << expected; + + original = R"delim( +void MyObject::f() +{ + @if (x == 1) { + emit sig1(); + } else if (x == 2) + emit sig2(); +})delim"; + expected = R"delim( +void MyObject::f() +{ + if (x == 1) { + emit sig1(); + } else if (x == 2) { + emit sig2(); + } +})delim"; + QTest::newRow("if-else chain without final else, partially braced 1") << original << expected; + + original = R"delim( +void MyObject::f() +{ + @if (x == 1) + emit sig1(); + else if (x == 2) { + emit sig2(); + } +})delim"; + expected = R"delim( +void MyObject::f() +{ + if (x == 1) { + emit sig1(); + } else if (x == 2) { + emit sig2(); + } +})delim"; + QTest::newRow("if-else chain without final else, partially braced 2") << original << expected; + + original = R"delim( +void MyObject::f() +{ + @if (x == 1) { + emit sig1(); + } else if (x == 2) { + emit sig2(); + } +})delim"; + expected.clear(); + QTest::newRow("if-else chain without final else, fully braced") << original << expected; + + original = R"delim( +void MyObject::f() +{ + @if (x == 1) + emit sig1(); + else if (x == 2) + emit sig2(); + else if (x == 3) + emit sig3(); + else + emit otherSig(); +})delim"; + expected = R"delim( +void MyObject::f() +{ + if (x == 1) { + emit sig1(); + } else if (x == 2) { + emit sig2(); + } else if (x == 3) { + emit sig3(); + } else { + emit otherSig(); + } +})delim"; + QTest::newRow("if-else chain, unbraced") << original << expected; + + original = R"delim( +void MyObject::f() +{ + @if (x == 1) { + emit sig1(); + } else if (x == 2) + emit sig2(); + else if (x == 3) + emit sig3(); + else + emit otherSig(); +})delim"; + expected = R"delim( +void MyObject::f() +{ + if (x == 1) { + emit sig1(); + } else if (x == 2) { + emit sig2(); + } else if (x == 3) { + emit sig3(); + } else { + emit otherSig(); + } +})delim"; + QTest::newRow("if-else chain, partially braced 1") << original << expected; + + original = R"delim( +void MyObject::f() +{ + @if (x == 1) + emit sig1(); + else if (x == 2) { + emit sig2(); + } else if (x == 3) + emit sig3(); + else + emit otherSig(); +})delim"; + expected = R"delim( +void MyObject::f() +{ + if (x == 1) { + emit sig1(); + } else if (x == 2) { + emit sig2(); + } else if (x == 3) { + emit sig3(); + } else { + emit otherSig(); + } +})delim"; + QTest::newRow("if-else chain, partially braced 2") << original << expected; + + original = R"delim( +void MyObject::f() +{ + @if (x == 1) + emit sig1(); + else if (x == 2) + emit sig2(); + else if (x == 3) { + emit sig3(); + } else + emit otherSig(); +})delim"; + expected = R"delim( +void MyObject::f() +{ + if (x == 1) { + emit sig1(); + } else if (x == 2) { + emit sig2(); + } else if (x == 3) { + emit sig3(); + } else { + emit otherSig(); + } +})delim"; + QTest::newRow("if-else chain, partially braced 3") << original << expected; + + original = R"delim( +void MyObject::f() +{ + @if (x == 1) + emit sig1(); + else if (x == 2) + emit sig2(); + else if (x == 3) + emit sig3(); + else { + emit otherSig(); + } +})delim"; + expected = R"delim( +void MyObject::f() +{ + if (x == 1) { + emit sig1(); + } else if (x == 2) { + emit sig2(); + } else if (x == 3) { + emit sig3(); + } else { + emit otherSig(); + } +})delim"; + QTest::newRow("if-else chain, partially braced 4") << original << expected; + + original = R"delim( +void MyObject::f() +{ + @if (x == 1) { + emit sig1(); + } else if (x == 2) { + emit sig2(); + } else if (x == 3) { + emit sig3(); + } else { + emit otherSig(); + } +})delim"; + expected.clear(); + QTest::newRow("if-else chain, fully braced") << original << expected; + + original = R"delim( +void MyObject::f() +{ + @while (true) + emit mySig(); +})delim"; + expected = R"delim( +void MyObject::f() +{ + while (true) { + emit mySig(); + } +})delim"; + QTest::newRow("while") << original << expected; + + original = R"delim( +void MyObject::f() +{ + @for (int i = 0; i < 10; ++i) + emit mySig(); +})delim"; + expected = R"delim( +void MyObject::f() +{ + for (int i = 0; i < 10; ++i) { + emit mySig(); + } +})delim"; + QTest::newRow("for") << original << expected; + + original = R"delim( +void MyObject::f() +{ + @for (int i : list) + emit mySig(); +})delim"; + expected = R"delim( +void MyObject::f() +{ + for (int i : list) { + emit mySig(); + } +})delim"; + QTest::newRow("range-based for") << original << expected; + + original = R"delim( +void MyObject::f() +{ + @do + emit mySig(); + while (true); +})delim"; + expected = R"delim( +void MyObject::f() +{ + do { + emit mySig(); + } while (true); +})delim"; + QTest::newRow("do") << original << expected; + + original = R"delim( +void MyObject::f() +{ + @do { + emit mySig(); + } while (true); +})delim"; + expected.clear(); + QTest::newRow("already has braces") << original << expected; + } + + void test() + { + QFETCH(QByteArray, original); + QFETCH(QByteArray, expected); + + AddBracesToControlStatement factory; + QuickFixOperationTest({CppTestDocument::create("file.cpp", original, expected)}, &factory); + } +}; + +QObject *MoveDeclarationOutOfIf::createTest() { return new MoveDeclarationOutOfIfTest; } +QObject *MoveDeclarationOutOfWhile::createTest() { return new MoveDeclarationOutOfWhileTest; } +QObject *OptimizeForLoop::createTest() { return new OptimizeForLoopTest; } +QObject *AddBracesToControlStatement::createTest() { return new AddBracesToControlStatementTest; } + +#endif // WITH_TESTS +} // namespace + +void registerRewriteControlStatementQuickfixes() +{ + CppQuickFixFactory::registerFactory<AddBracesToControlStatement>(); + CppQuickFixFactory::registerFactory<MoveDeclarationOutOfIf>(); + CppQuickFixFactory::registerFactory<MoveDeclarationOutOfWhile>(); + CppQuickFixFactory::registerFactory<OptimizeForLoop>(); + CppQuickFixFactory::registerFactory<SplitIfStatement>(); +} + +} // namespace CppEditor::Internal + +#ifdef WITH_TESTS +#include <rewritecontrolstatements.moc> +#endif diff --git a/src/plugins/cppeditor/quickfixes/rewritecontrolstatements.h b/src/plugins/cppeditor/quickfixes/rewritecontrolstatements.h new file mode 100644 index 0000000000..d450708a4f --- /dev/null +++ b/src/plugins/cppeditor/quickfixes/rewritecontrolstatements.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 registerRewriteControlStatementQuickfixes(); +} // namespace CppEditor::Internal diff --git a/src/plugins/cppeditor/quickfixes/splitsimpledeclaration.cpp b/src/plugins/cppeditor/quickfixes/splitsimpledeclaration.cpp new file mode 100644 index 0000000000..c74bcc4867 --- /dev/null +++ b/src/plugins/cppeditor/quickfixes/splitsimpledeclaration.cpp @@ -0,0 +1,144 @@ +// 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 "splitsimpledeclaration.h" + +#include "../cppeditortr.h" +#include "../cpprefactoringchanges.h" +#include "cppquickfix.h" + +using namespace CPlusPlus; +using namespace Utils; + +namespace CppEditor::Internal { +namespace { + +static bool checkDeclarationForSplit(SimpleDeclarationAST *declaration) +{ + if (!declaration->semicolon_token) + return false; + + if (!declaration->decl_specifier_list) + return false; + + for (SpecifierListAST *it = declaration->decl_specifier_list; it; it = it->next) { + SpecifierAST *specifier = it->value; + if (specifier->asEnumSpecifier() || specifier->asClassSpecifier()) + return false; + } + + return declaration->declarator_list && declaration->declarator_list->next; +} + +class SplitSimpleDeclarationOp : public CppQuickFixOperation +{ +public: + SplitSimpleDeclarationOp(const CppQuickFixInterface &interface, int priority, + SimpleDeclarationAST *decl) + : CppQuickFixOperation(interface, priority) + , declaration(decl) + { + setDescription(Tr::tr("Split Declaration")); + } + + void perform() override + { + CppRefactoringChanges refactoring(snapshot()); + CppRefactoringFilePtr currentFile = refactoring.cppFile(filePath()); + + ChangeSet changes; + + SpecifierListAST *specifiers = declaration->decl_specifier_list; + int declSpecifiersStart = currentFile->startOf(specifiers->firstToken()); + int declSpecifiersEnd = currentFile->endOf(specifiers->lastToken() - 1); + int insertPos = currentFile->endOf(declaration->semicolon_token); + + DeclaratorAST *prevDeclarator = declaration->declarator_list->value; + + for (DeclaratorListAST *it = declaration->declarator_list->next; it; it = it->next) { + DeclaratorAST *declarator = it->value; + + changes.insert(insertPos, QLatin1String("\n")); + changes.copy(declSpecifiersStart, declSpecifiersEnd, insertPos); + changes.insert(insertPos, QLatin1String(" ")); + changes.move(currentFile->range(declarator), insertPos); + changes.insert(insertPos, QLatin1String(";")); + + const int prevDeclEnd = currentFile->endOf(prevDeclarator); + changes.remove(prevDeclEnd, currentFile->startOf(declarator)); + + prevDeclarator = declarator; + } + + currentFile->setChangeSet(changes); + currentFile->apply(); + } + +private: + SimpleDeclarationAST *declaration; +}; + +/*! + Rewrite + int *a, b; + + As + int *a; + int b; + + Activates on: the type or the variable names. +*/ +class SplitSimpleDeclaration : public CppQuickFixFactory +{ +#ifdef WITH_TESTS +public: + static QObject *createTest() { return new QObject; } +#endif + +private: + void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override + { + CoreDeclaratorAST *core_declarator = nullptr; + const QList<AST *> &path = interface.path(); + CppRefactoringFilePtr file = interface.currentFile(); + const int cursorPosition = file->cursor().selectionStart(); + + for (int index = path.size() - 1; index != -1; --index) { + AST *node = path.at(index); + + if (CoreDeclaratorAST *coreDecl = node->asCoreDeclarator()) { + core_declarator = coreDecl; + } else if (SimpleDeclarationAST *simpleDecl = node->asSimpleDeclaration()) { + if (checkDeclarationForSplit(simpleDecl)) { + SimpleDeclarationAST *declaration = simpleDecl; + + const int startOfDeclSpecifier = file->startOf(declaration->decl_specifier_list->firstToken()); + const int endOfDeclSpecifier = file->endOf(declaration->decl_specifier_list->lastToken() - 1); + + if (cursorPosition >= startOfDeclSpecifier && cursorPosition <= endOfDeclSpecifier) { + // the AST node under cursor is a specifier. + result << new SplitSimpleDeclarationOp(interface, index, declaration); + return; + } + + if (core_declarator && interface.isCursorOn(core_declarator)) { + // got a core-declarator under the text cursor. + result << new SplitSimpleDeclarationOp(interface, index, declaration); + return; + } + } + + return; + } + } + } +}; + +} // namespace + +void registerSplitSimpleDeclarationQuickfix() +{ + CppQuickFixFactory::registerFactory<SplitSimpleDeclaration>(); +} + +} // namespace CppEditor::Internal diff --git a/src/plugins/cppeditor/quickfixes/splitsimpledeclaration.h b/src/plugins/cppeditor/quickfixes/splitsimpledeclaration.h new file mode 100644 index 0000000000..8c3100a9f4 --- /dev/null +++ b/src/plugins/cppeditor/quickfixes/splitsimpledeclaration.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 registerSplitSimpleDeclarationQuickfix(); +} // namespace CppEditor::Internal diff --git a/src/plugins/cppeditor/testcases/move-class/complex/complex.pro b/src/plugins/cppeditor/testcases/move-class/complex/complex.pro new file mode 100644 index 0000000000..efdac1a69a --- /dev/null +++ b/src/plugins/cppeditor/testcases/move-class/complex/complex.pro @@ -0,0 +1,2 @@ +HEADERS = theheader.h +SOURCES = thesource.cpp main.cpp diff --git a/src/plugins/cppeditor/testcases/move-class/complex/complex.pro_expected b/src/plugins/cppeditor/testcases/move-class/complex/complex.pro_expected new file mode 100644 index 0000000000..9a7a013363 --- /dev/null +++ b/src/plugins/cppeditor/testcases/move-class/complex/complex.pro_expected @@ -0,0 +1,4 @@ +HEADERS = theheader.h \ + theclass.h +SOURCES = thesource.cpp main.cpp \ + theclass.cpp diff --git a/src/plugins/cppeditor/testcases/move-class/complex/main.cpp b/src/plugins/cppeditor/testcases/move-class/complex/main.cpp new file mode 100644 index 0000000000..2934727d74 --- /dev/null +++ b/src/plugins/cppeditor/testcases/move-class/complex/main.cpp @@ -0,0 +1,11 @@ +#include "theheader.h" + +namespace Project { +namespace Internal { +bool TheClass::needsDefinition = true; +} +} + +int main() +{ +} diff --git a/src/plugins/cppeditor/testcases/move-class/complex/main.cpp_expected b/src/plugins/cppeditor/testcases/move-class/complex/main.cpp_expected new file mode 100644 index 0000000000..10bdcd280a --- /dev/null +++ b/src/plugins/cppeditor/testcases/move-class/complex/main.cpp_expected @@ -0,0 +1,11 @@ +#include "theheader.h" + +namespace Project { +namespace Internal { + +} +} + +int main() +{ +} diff --git a/src/plugins/cppeditor/testcases/move-class/complex/theclass.cpp_expected b/src/plugins/cppeditor/testcases/move-class/complex/theclass.cpp_expected new file mode 100644 index 0000000000..5cb8bbb375 --- /dev/null +++ b/src/plugins/cppeditor/testcases/move-class/complex/theclass.cpp_expected @@ -0,0 +1,20 @@ + +#include "theclass.h" + +namespace Project { +namespace Internal { + +bool TheClass::needsDefinition = true; + +class TheClass::Private +{ + void func(); + int m_member = 0; +}; + +void TheClass::defined() {} + +void TheClass::Private::func() {} + +} // namespace Internal +} // namespace Project diff --git a/src/plugins/cppeditor/testcases/move-class/complex/theclass.h_expected b/src/plugins/cppeditor/testcases/move-class/complex/theclass.h_expected new file mode 100644 index 0000000000..c7db776d27 --- /dev/null +++ b/src/plugins/cppeditor/testcases/move-class/complex/theclass.h_expected @@ -0,0 +1,29 @@ +#ifndef PROJECT_INTERNAL_THECLASS_H +#define PROJECT_INTERNAL_THECLASS_H + +namespace Project { +namespace Internal { + +class TheClass +{ +public: + TheClass() = default; + + void defined(); + void undefined(); + + template<typename T> T defaultValue() const; +private: + class Private; + class Undefined; + static inline bool doesNotNeedDefinition = true; + static bool needsDefinition; + int m_value = 0; +}; + +template<typename T> T TheClass::defaultValue() const { return T(); } + +} // namespace Internal +} // namespace Project + +#endif // PROJECT_INTERNAL_THECLASS_H diff --git a/src/plugins/cppeditor/testcases/move-class/complex/theheader.h b/src/plugins/cppeditor/testcases/move-class/complex/theheader.h new file mode 100644 index 0000000000..93f0d47a81 --- /dev/null +++ b/src/plugins/cppeditor/testcases/move-class/complex/theheader.h @@ -0,0 +1,30 @@ +#ifndef THE_HEADER_H +#define THE_HEADER_H + +namespace Project { +namespace Internal { + +class SomeClass {}; +class TheClass +{ +public: + TheClass() = default; + + void defined(); + void undefined(); + + template<typename T> T defaultValue() const; +private: + class Private; + class Undefined; + static inline bool doesNotNeedDefinition = true; + static bool needsDefinition; + int m_value = 0; +}; + +template<typename T> T TheClass::defaultValue() const { return T(); } + +} +} + +#endif diff --git a/src/plugins/cppeditor/testcases/move-class/complex/theheader.h_expected b/src/plugins/cppeditor/testcases/move-class/complex/theheader.h_expected new file mode 100644 index 0000000000..65bf2ec7f4 --- /dev/null +++ b/src/plugins/cppeditor/testcases/move-class/complex/theheader.h_expected @@ -0,0 +1,18 @@ +#ifndef THE_HEADER_H +#define THE_HEADER_H + +#include "theclass.h" + + +namespace Project { +namespace Internal { + +class SomeClass {}; + + + + +} +} + +#endif diff --git a/src/plugins/cppeditor/testcases/move-class/complex/thesource.cpp b/src/plugins/cppeditor/testcases/move-class/complex/thesource.cpp new file mode 100644 index 0000000000..645b6a4b13 --- /dev/null +++ b/src/plugins/cppeditor/testcases/move-class/complex/thesource.cpp @@ -0,0 +1,19 @@ +#include "theheader.h" + +namespace Project { +namespace Internal { + +static void someFunction() {} + +class TheClass::Private +{ + void func(); + int m_member = 0; +}; + +void TheClass::defined() {} + +void TheClass::Private::func() {} + +} +} diff --git a/src/plugins/cppeditor/testcases/move-class/complex/thesource.cpp_expected b/src/plugins/cppeditor/testcases/move-class/complex/thesource.cpp_expected new file mode 100644 index 0000000000..d84f180808 --- /dev/null +++ b/src/plugins/cppeditor/testcases/move-class/complex/thesource.cpp_expected @@ -0,0 +1,15 @@ +#include "theheader.h" + +namespace Project { +namespace Internal { + +static void someFunction() {} + + + + + + + +} +} diff --git a/src/plugins/cppeditor/testcases/move-class/decl-in-source/decl-in-source.pro b/src/plugins/cppeditor/testcases/move-class/decl-in-source/decl-in-source.pro new file mode 100644 index 0000000000..d527c86ef3 --- /dev/null +++ b/src/plugins/cppeditor/testcases/move-class/decl-in-source/decl-in-source.pro @@ -0,0 +1,2 @@ +HEADERS = theheader.h +SOURCES = thesource.cpp diff --git a/src/plugins/cppeditor/testcases/move-class/decl-in-source/decl-in-source.pro_expected b/src/plugins/cppeditor/testcases/move-class/decl-in-source/decl-in-source.pro_expected new file mode 100644 index 0000000000..969d1fdb80 --- /dev/null +++ b/src/plugins/cppeditor/testcases/move-class/decl-in-source/decl-in-source.pro_expected @@ -0,0 +1,3 @@ +HEADERS = theheader.h \ + theclass.h +SOURCES = thesource.cpp diff --git a/src/plugins/cppeditor/testcases/move-class/decl-in-source/theclass.h_expected b/src/plugins/cppeditor/testcases/move-class/decl-in-source/theclass.h_expected new file mode 100644 index 0000000000..4af91c47e3 --- /dev/null +++ b/src/plugins/cppeditor/testcases/move-class/decl-in-source/theclass.h_expected @@ -0,0 +1,11 @@ +#ifndef THECLASS_H +#define THECLASS_H + +class TheClass +{ + void func(); +}; + +void TheClass::func() {} + +#endif // THECLASS_H diff --git a/src/plugins/cppeditor/testcases/move-class/decl-in-source/theheader.h b/src/plugins/cppeditor/testcases/move-class/decl-in-source/theheader.h new file mode 100644 index 0000000000..9c0f94351f --- /dev/null +++ b/src/plugins/cppeditor/testcases/move-class/decl-in-source/theheader.h @@ -0,0 +1,4 @@ +class SomeClass +{ + void func(); +}; diff --git a/src/plugins/cppeditor/testcases/move-class/decl-in-source/thesource.cpp b/src/plugins/cppeditor/testcases/move-class/decl-in-source/thesource.cpp new file mode 100644 index 0000000000..79a7651309 --- /dev/null +++ b/src/plugins/cppeditor/testcases/move-class/decl-in-source/thesource.cpp @@ -0,0 +1,8 @@ +#include "theheader.h" + +class TheClass +{ + void func(); +}; +void SomeClass::func() {} +void TheClass::func() {} diff --git a/src/plugins/cppeditor/testcases/move-class/decl-in-source/thesource.cpp_expected b/src/plugins/cppeditor/testcases/move-class/decl-in-source/thesource.cpp_expected new file mode 100644 index 0000000000..e26225d719 --- /dev/null +++ b/src/plugins/cppeditor/testcases/move-class/decl-in-source/thesource.cpp_expected @@ -0,0 +1,6 @@ +#include "theclass.h" +#include "theheader.h" + + +void SomeClass::func() {} + diff --git a/src/plugins/cppeditor/testcases/move-class/header-only/header-only.pro b/src/plugins/cppeditor/testcases/move-class/header-only/header-only.pro new file mode 100644 index 0000000000..d527c86ef3 --- /dev/null +++ b/src/plugins/cppeditor/testcases/move-class/header-only/header-only.pro @@ -0,0 +1,2 @@ +HEADERS = theheader.h +SOURCES = thesource.cpp diff --git a/src/plugins/cppeditor/testcases/move-class/header-only/header-only.pro_expected b/src/plugins/cppeditor/testcases/move-class/header-only/header-only.pro_expected new file mode 100644 index 0000000000..969d1fdb80 --- /dev/null +++ b/src/plugins/cppeditor/testcases/move-class/header-only/header-only.pro_expected @@ -0,0 +1,3 @@ +HEADERS = theheader.h \ + theclass.h +SOURCES = thesource.cpp diff --git a/src/plugins/cppeditor/testcases/move-class/header-only/theclass.h_expected b/src/plugins/cppeditor/testcases/move-class/header-only/theclass.h_expected new file mode 100644 index 0000000000..9b1f655f3f --- /dev/null +++ b/src/plugins/cppeditor/testcases/move-class/header-only/theclass.h_expected @@ -0,0 +1,10 @@ +#ifndef THECLASS_H +#define THECLASS_H + +class TheClass { + void func(); +}; + +void TheClass::func() {} + +#endif // THECLASS_H diff --git a/src/plugins/cppeditor/testcases/move-class/header-only/theheader.h b/src/plugins/cppeditor/testcases/move-class/header-only/theheader.h new file mode 100644 index 0000000000..131a6d42a6 --- /dev/null +++ b/src/plugins/cppeditor/testcases/move-class/header-only/theheader.h @@ -0,0 +1,7 @@ +class SomeClass { + void func(); +}; +class TheClass { + void func(); +}; +void TheClass::func() {} diff --git a/src/plugins/cppeditor/testcases/move-class/header-only/theheader.h_expected b/src/plugins/cppeditor/testcases/move-class/header-only/theheader.h_expected new file mode 100644 index 0000000000..1cf9e1d06c --- /dev/null +++ b/src/plugins/cppeditor/testcases/move-class/header-only/theheader.h_expected @@ -0,0 +1,7 @@ +#include "theclass.h" + +class SomeClass { + void func(); +}; + + diff --git a/src/plugins/cppeditor/testcases/move-class/header-only/thesource.cpp b/src/plugins/cppeditor/testcases/move-class/header-only/thesource.cpp new file mode 100644 index 0000000000..d01e0845ab --- /dev/null +++ b/src/plugins/cppeditor/testcases/move-class/header-only/thesource.cpp @@ -0,0 +1,3 @@ +#include "theheader.h" + +void SomeClass::func() {} diff --git a/src/plugins/cppeditor/testcases/move-class/header-only/thesource.cpp_expected b/src/plugins/cppeditor/testcases/move-class/header-only/thesource.cpp_expected new file mode 100644 index 0000000000..d01e0845ab --- /dev/null +++ b/src/plugins/cppeditor/testcases/move-class/header-only/thesource.cpp_expected @@ -0,0 +1,3 @@ +#include "theheader.h" + +void SomeClass::func() {} diff --git a/src/plugins/cppeditor/testcases/move-class/match1/TheClass.h b/src/plugins/cppeditor/testcases/move-class/match1/TheClass.h new file mode 100644 index 0000000000..11b0cf15d1 --- /dev/null +++ b/src/plugins/cppeditor/testcases/move-class/match1/TheClass.h @@ -0,0 +1,2 @@ +class SomeClass {}; +class TheClass {}; diff --git a/src/plugins/cppeditor/testcases/move-class/match1/match1.pro b/src/plugins/cppeditor/testcases/move-class/match1/match1.pro new file mode 100644 index 0000000000..72ccc46616 --- /dev/null +++ b/src/plugins/cppeditor/testcases/move-class/match1/match1.pro @@ -0,0 +1 @@ +HEADERS = TheClass.h diff --git a/src/plugins/cppeditor/testcases/move-class/match2/match2.pro b/src/plugins/cppeditor/testcases/move-class/match2/match2.pro new file mode 100644 index 0000000000..6f982e4f16 --- /dev/null +++ b/src/plugins/cppeditor/testcases/move-class/match2/match2.pro @@ -0,0 +1 @@ +HEADERS = theclass.h diff --git a/src/plugins/cppeditor/testcases/move-class/match2/theclass.h b/src/plugins/cppeditor/testcases/move-class/match2/theclass.h new file mode 100644 index 0000000000..11b0cf15d1 --- /dev/null +++ b/src/plugins/cppeditor/testcases/move-class/match2/theclass.h @@ -0,0 +1,2 @@ +class SomeClass {}; +class TheClass {}; diff --git a/src/plugins/cppeditor/testcases/move-class/match3/match3.pro b/src/plugins/cppeditor/testcases/move-class/match3/match3.pro new file mode 100644 index 0000000000..850a113845 --- /dev/null +++ b/src/plugins/cppeditor/testcases/move-class/match3/match3.pro @@ -0,0 +1 @@ +HEADERS = the_class.h diff --git a/src/plugins/cppeditor/testcases/move-class/match3/the_class.h b/src/plugins/cppeditor/testcases/move-class/match3/the_class.h new file mode 100644 index 0000000000..11b0cf15d1 --- /dev/null +++ b/src/plugins/cppeditor/testcases/move-class/match3/the_class.h @@ -0,0 +1,2 @@ +class SomeClass {}; +class TheClass {}; diff --git a/src/plugins/cppeditor/testcases/move-class/nested/main.cpp b/src/plugins/cppeditor/testcases/move-class/nested/main.cpp new file mode 100644 index 0000000000..73fd1620fa --- /dev/null +++ b/src/plugins/cppeditor/testcases/move-class/nested/main.cpp @@ -0,0 +1,6 @@ +class SomeClass {}; +class Outer { + class Inner {}; +}; + +int main() {} diff --git a/src/plugins/cppeditor/testcases/move-class/nested/nested.pro b/src/plugins/cppeditor/testcases/move-class/nested/nested.pro new file mode 100644 index 0000000000..bba41b9c12 --- /dev/null +++ b/src/plugins/cppeditor/testcases/move-class/nested/nested.pro @@ -0,0 +1 @@ +SOURCES = main.cpp diff --git a/src/plugins/cppeditor/testcases/move-class/single/single.pro b/src/plugins/cppeditor/testcases/move-class/single/single.pro new file mode 100644 index 0000000000..77db8b92e4 --- /dev/null +++ b/src/plugins/cppeditor/testcases/move-class/single/single.pro @@ -0,0 +1 @@ +HEADERS = theheader.h diff --git a/src/plugins/cppeditor/testcases/move-class/single/theheader.h b/src/plugins/cppeditor/testcases/move-class/single/theheader.h new file mode 100644 index 0000000000..5f7cd96730 --- /dev/null +++ b/src/plugins/cppeditor/testcases/move-class/single/theheader.h @@ -0,0 +1,2 @@ +class Private; +class TheClass {}; diff --git a/src/plugins/cppeditor/testcases/move-class/template/template.pro b/src/plugins/cppeditor/testcases/move-class/template/template.pro new file mode 100644 index 0000000000..77db8b92e4 --- /dev/null +++ b/src/plugins/cppeditor/testcases/move-class/template/template.pro @@ -0,0 +1 @@ +HEADERS = theheader.h diff --git a/src/plugins/cppeditor/testcases/move-class/template/template.pro_expected b/src/plugins/cppeditor/testcases/move-class/template/template.pro_expected new file mode 100644 index 0000000000..0ed81f0008 --- /dev/null +++ b/src/plugins/cppeditor/testcases/move-class/template/template.pro_expected @@ -0,0 +1,2 @@ +HEADERS = theheader.h \ + theclass.h diff --git a/src/plugins/cppeditor/testcases/move-class/template/theclass.h_expected b/src/plugins/cppeditor/testcases/move-class/template/theclass.h_expected new file mode 100644 index 0000000000..f9681cc796 --- /dev/null +++ b/src/plugins/cppeditor/testcases/move-class/template/theclass.h_expected @@ -0,0 +1,11 @@ +#ifndef THECLASS_H +#define THECLASS_H + +template<typename T> class TheClass +{ + T defaultValue() const; +}; + +template<typename T> inline T TheClass<T>::defaultValue() const { return T(); } + +#endif // THECLASS_H diff --git a/src/plugins/cppeditor/testcases/move-class/template/theheader.h b/src/plugins/cppeditor/testcases/move-class/template/theheader.h new file mode 100644 index 0000000000..58f7a07372 --- /dev/null +++ b/src/plugins/cppeditor/testcases/move-class/template/theheader.h @@ -0,0 +1,7 @@ +enum SomeEnum { E }; +template<typename T> class TheClass +{ + T defaultValue() const; +}; + +template<typename T> inline T TheClass<T>::defaultValue() const { return T(); } diff --git a/src/plugins/cppeditor/testcases/move-class/template/theheader.h_expected b/src/plugins/cppeditor/testcases/move-class/template/theheader.h_expected new file mode 100644 index 0000000000..20087c3199 --- /dev/null +++ b/src/plugins/cppeditor/testcases/move-class/template/theheader.h_expected @@ -0,0 +1,6 @@ +#include "theclass.h" + +enum SomeEnum { E }; + + + |