aboutsummaryrefslogtreecommitdiffstats
path: root/src/plugins/cppeditor
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/cppeditor')
-rw-r--r--src/plugins/cppeditor/CMakeLists.txt46
-rw-r--r--src/plugins/cppeditor/cppbuiltinmodelmanagersupport.cpp3
-rw-r--r--src/plugins/cppeditor/cppcodemodelsettings.cpp24
-rw-r--r--src/plugins/cppeditor/cppcodemodelsettings.h1
-rw-r--r--src/plugins/cppeditor/cppcodestylesettingspage.cpp2
-rw-r--r--src/plugins/cppeditor/cppcompletion_test.cpp10
-rw-r--r--src/plugins/cppeditor/cppeditor.qbs99
-rw-r--r--src/plugins/cppeditor/cppeditor.qrc37
-rw-r--r--src/plugins/cppeditor/cppeditordocument.cpp2
-rw-r--r--src/plugins/cppeditor/cppeditorplugin.cpp10
-rw-r--r--src/plugins/cppeditor/cppeditorwidget.cpp2
-rw-r--r--src/plugins/cppeditor/cppfunctiondecldeflink.cpp2
-rw-r--r--src/plugins/cppeditor/cppindexingsupport.cpp36
-rw-r--r--src/plugins/cppeditor/cppindexingsupport.h8
-rw-r--r--src/plugins/cppeditor/cppmodelmanager.cpp47
-rw-r--r--src/plugins/cppeditor/cppmodelmanager_test.cpp183
-rw-r--r--src/plugins/cppeditor/cppmodelmanager_test.h2
-rw-r--r--src/plugins/cppeditor/cppquickfix_test.cpp9951
-rw-r--r--src/plugins/cppeditor/cppquickfix_test.h236
-rw-r--r--src/plugins/cppeditor/cppquickfixes.cpp10104
-rw-r--r--src/plugins/cppeditor/cppquickfixes.h630
-rw-r--r--src/plugins/cppeditor/cpprenaming_test.cpp2
-rw-r--r--src/plugins/cppeditor/cpptoolsreuse.cpp2
-rw-r--r--src/plugins/cppeditor/cpptoolstestcase.cpp15
-rw-r--r--src/plugins/cppeditor/cpptoolstestcase.h12
-rw-r--r--src/plugins/cppeditor/cursorineditor.h9
-rw-r--r--src/plugins/cppeditor/fileandtokenactions_test.cpp6
-rw-r--r--src/plugins/cppeditor/quickfixes/assigntolocalvariable.cpp513
-rw-r--r--src/plugins/cppeditor/quickfixes/assigntolocalvariable.h8
-rw-r--r--src/plugins/cppeditor/quickfixes/bringidentifierintoscope.cpp1503
-rw-r--r--src/plugins/cppeditor/quickfixes/bringidentifierintoscope.h8
-rw-r--r--src/plugins/cppeditor/quickfixes/completeswitchstatement.cpp799
-rw-r--r--src/plugins/cppeditor/quickfixes/completeswitchstatement.h8
-rw-r--r--src/plugins/cppeditor/quickfixes/convertfromandtopointer.cpp705
-rw-r--r--src/plugins/cppeditor/quickfixes/convertfromandtopointer.h8
-rw-r--r--src/plugins/cppeditor/quickfixes/convertnumericliteral.cpp205
-rw-r--r--src/plugins/cppeditor/quickfixes/convertnumericliteral.h8
-rw-r--r--src/plugins/cppeditor/quickfixes/convertqt4connect.cpp508
-rw-r--r--src/plugins/cppeditor/quickfixes/convertqt4connect.h8
-rw-r--r--src/plugins/cppeditor/quickfixes/convertstringliteral.cpp758
-rw-r--r--src/plugins/cppeditor/quickfixes/convertstringliteral.h8
-rw-r--r--src/plugins/cppeditor/quickfixes/converttocamelcase.cpp191
-rw-r--r--src/plugins/cppeditor/quickfixes/converttocamelcase.h8
-rw-r--r--src/plugins/cppeditor/quickfixes/converttometamethodcall.cpp274
-rw-r--r--src/plugins/cppeditor/quickfixes/converttometamethodcall.h8
-rw-r--r--src/plugins/cppeditor/quickfixes/cppcodegenerationquickfixes.cpp5078
-rw-r--r--src/plugins/cppeditor/quickfixes/cppcodegenerationquickfixes.h8
-rw-r--r--src/plugins/cppeditor/quickfixes/cppinsertvirtualmethods.cpp (renamed from src/plugins/cppeditor/cppinsertvirtualmethods.cpp)32
-rw-r--r--src/plugins/cppeditor/quickfixes/cppinsertvirtualmethods.h (renamed from src/plugins/cppeditor/cppinsertvirtualmethods.h)16
-rw-r--r--src/plugins/cppeditor/quickfixes/cppquickfix.cpp (renamed from src/plugins/cppeditor/cppquickfix.cpp)27
-rw-r--r--src/plugins/cppeditor/quickfixes/cppquickfix.h (renamed from src/plugins/cppeditor/cppquickfix.h)16
-rw-r--r--src/plugins/cppeditor/quickfixes/cppquickfix_test.cpp320
-rw-r--r--src/plugins/cppeditor/quickfixes/cppquickfix_test.h103
-rw-r--r--src/plugins/cppeditor/quickfixes/cppquickfixassistant.cpp (renamed from src/plugins/cppeditor/cppquickfixassistant.cpp)6
-rw-r--r--src/plugins/cppeditor/quickfixes/cppquickfixassistant.h (renamed from src/plugins/cppeditor/cppquickfixassistant.h)2
-rw-r--r--src/plugins/cppeditor/quickfixes/cppquickfixes.cpp425
-rw-r--r--src/plugins/cppeditor/quickfixes/cppquickfixes.h68
-rw-r--r--src/plugins/cppeditor/quickfixes/cppquickfixhelpers.cpp193
-rw-r--r--src/plugins/cppeditor/quickfixes/cppquickfixhelpers.h48
-rw-r--r--src/plugins/cppeditor/quickfixes/cppquickfixprojectsettings.cpp (renamed from src/plugins/cppeditor/cppquickfixprojectsettings.cpp)4
-rw-r--r--src/plugins/cppeditor/quickfixes/cppquickfixprojectsettings.h (renamed from src/plugins/cppeditor/cppquickfixprojectsettings.h)0
-rw-r--r--src/plugins/cppeditor/quickfixes/cppquickfixprojectsettingswidget.cpp (renamed from src/plugins/cppeditor/cppquickfixprojectsettingswidget.cpp)4
-rw-r--r--src/plugins/cppeditor/quickfixes/cppquickfixprojectsettingswidget.h (renamed from src/plugins/cppeditor/cppquickfixprojectsettingswidget.h)0
-rw-r--r--src/plugins/cppeditor/quickfixes/cppquickfixsettings.cpp (renamed from src/plugins/cppeditor/cppquickfixsettings.cpp)4
-rw-r--r--src/plugins/cppeditor/quickfixes/cppquickfixsettings.h (renamed from src/plugins/cppeditor/cppquickfixsettings.h)0
-rw-r--r--src/plugins/cppeditor/quickfixes/cppquickfixsettingspage.cpp (renamed from src/plugins/cppeditor/cppquickfixsettingspage.cpp)4
-rw-r--r--src/plugins/cppeditor/quickfixes/cppquickfixsettingspage.h (renamed from src/plugins/cppeditor/cppquickfixsettingspage.h)0
-rw-r--r--src/plugins/cppeditor/quickfixes/cppquickfixsettingswidget.cpp (renamed from src/plugins/cppeditor/cppquickfixsettingswidget.cpp)2
-rw-r--r--src/plugins/cppeditor/quickfixes/cppquickfixsettingswidget.h (renamed from src/plugins/cppeditor/cppquickfixsettingswidget.h)0
-rw-r--r--src/plugins/cppeditor/quickfixes/createdeclarationfromuse.cpp1213
-rw-r--r--src/plugins/cppeditor/quickfixes/createdeclarationfromuse.h8
-rw-r--r--src/plugins/cppeditor/quickfixes/extractfunction.cpp766
-rw-r--r--src/plugins/cppeditor/quickfixes/extractfunction.h7
-rw-r--r--src/plugins/cppeditor/quickfixes/extractliteralasparameter.cpp563
-rw-r--r--src/plugins/cppeditor/quickfixes/extractliteralasparameter.h8
-rw-r--r--src/plugins/cppeditor/quickfixes/insertfunctiondefinition.cpp2129
-rw-r--r--src/plugins/cppeditor/quickfixes/insertfunctiondefinition.h8
-rw-r--r--src/plugins/cppeditor/quickfixes/logicaloperationquickfixes.cpp335
-rw-r--r--src/plugins/cppeditor/quickfixes/logicaloperationquickfixes.h8
-rw-r--r--src/plugins/cppeditor/quickfixes/moveclasstoownfile.cpp742
-rw-r--r--src/plugins/cppeditor/quickfixes/moveclasstoownfile.h8
-rw-r--r--src/plugins/cppeditor/quickfixes/movefunctiondefinition.cpp1904
-rw-r--r--src/plugins/cppeditor/quickfixes/movefunctiondefinition.h8
-rw-r--r--src/plugins/cppeditor/quickfixes/removeusingnamespace.cpp958
-rw-r--r--src/plugins/cppeditor/quickfixes/removeusingnamespace.h8
-rw-r--r--src/plugins/cppeditor/quickfixes/rewritecomment.cpp875
-rw-r--r--src/plugins/cppeditor/quickfixes/rewritecomment.h8
-rw-r--r--src/plugins/cppeditor/quickfixes/rewritecontrolstatements.cpp1342
-rw-r--r--src/plugins/cppeditor/quickfixes/rewritecontrolstatements.h8
-rw-r--r--src/plugins/cppeditor/quickfixes/splitsimpledeclaration.cpp144
-rw-r--r--src/plugins/cppeditor/quickfixes/splitsimpledeclaration.h8
-rw-r--r--src/plugins/cppeditor/testcases/move-class/complex/complex.pro2
-rw-r--r--src/plugins/cppeditor/testcases/move-class/complex/complex.pro_expected4
-rw-r--r--src/plugins/cppeditor/testcases/move-class/complex/main.cpp11
-rw-r--r--src/plugins/cppeditor/testcases/move-class/complex/main.cpp_expected11
-rw-r--r--src/plugins/cppeditor/testcases/move-class/complex/theclass.cpp_expected20
-rw-r--r--src/plugins/cppeditor/testcases/move-class/complex/theclass.h_expected29
-rw-r--r--src/plugins/cppeditor/testcases/move-class/complex/theheader.h30
-rw-r--r--src/plugins/cppeditor/testcases/move-class/complex/theheader.h_expected18
-rw-r--r--src/plugins/cppeditor/testcases/move-class/complex/thesource.cpp19
-rw-r--r--src/plugins/cppeditor/testcases/move-class/complex/thesource.cpp_expected15
-rw-r--r--src/plugins/cppeditor/testcases/move-class/decl-in-source/decl-in-source.pro2
-rw-r--r--src/plugins/cppeditor/testcases/move-class/decl-in-source/decl-in-source.pro_expected3
-rw-r--r--src/plugins/cppeditor/testcases/move-class/decl-in-source/theclass.h_expected11
-rw-r--r--src/plugins/cppeditor/testcases/move-class/decl-in-source/theheader.h4
-rw-r--r--src/plugins/cppeditor/testcases/move-class/decl-in-source/thesource.cpp8
-rw-r--r--src/plugins/cppeditor/testcases/move-class/decl-in-source/thesource.cpp_expected6
-rw-r--r--src/plugins/cppeditor/testcases/move-class/header-only/header-only.pro2
-rw-r--r--src/plugins/cppeditor/testcases/move-class/header-only/header-only.pro_expected3
-rw-r--r--src/plugins/cppeditor/testcases/move-class/header-only/theclass.h_expected10
-rw-r--r--src/plugins/cppeditor/testcases/move-class/header-only/theheader.h7
-rw-r--r--src/plugins/cppeditor/testcases/move-class/header-only/theheader.h_expected7
-rw-r--r--src/plugins/cppeditor/testcases/move-class/header-only/thesource.cpp3
-rw-r--r--src/plugins/cppeditor/testcases/move-class/header-only/thesource.cpp_expected3
-rw-r--r--src/plugins/cppeditor/testcases/move-class/match1/TheClass.h2
-rw-r--r--src/plugins/cppeditor/testcases/move-class/match1/match1.pro1
-rw-r--r--src/plugins/cppeditor/testcases/move-class/match2/match2.pro1
-rw-r--r--src/plugins/cppeditor/testcases/move-class/match2/theclass.h2
-rw-r--r--src/plugins/cppeditor/testcases/move-class/match3/match3.pro1
-rw-r--r--src/plugins/cppeditor/testcases/move-class/match3/the_class.h2
-rw-r--r--src/plugins/cppeditor/testcases/move-class/nested/main.cpp6
-rw-r--r--src/plugins/cppeditor/testcases/move-class/nested/nested.pro1
-rw-r--r--src/plugins/cppeditor/testcases/move-class/single/single.pro1
-rw-r--r--src/plugins/cppeditor/testcases/move-class/single/theheader.h2
-rw-r--r--src/plugins/cppeditor/testcases/move-class/template/template.pro1
-rw-r--r--src/plugins/cppeditor/testcases/move-class/template/template.pro_expected2
-rw-r--r--src/plugins/cppeditor/testcases/move-class/template/theclass.h_expected11
-rw-r--r--src/plugins/cppeditor/testcases/move-class/template/theheader.h7
-rw-r--r--src/plugins/cppeditor/testcases/move-class/template/theheader.h_expected6
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 &params)
+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 &it;\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 &it;\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 &section, 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 &parameter = 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 &paramName,
- 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 &param) { model.addRow(&param); }
- void removeParameter(ParentClassConstructorParameter &param) { model.removeRow(&param); }
- void removeAllParameters()
- {
- for (auto &param : parameters)
- model.removeRow(&param);
- }
-};
-
-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 &parameter = 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 &param = 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(&parameterModel, 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 &param) {
- 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 &param : 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 &paramName,
+ 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 &param) { model.addRow(&param); }
+ void removeParameter(ParentClassConstructorParameter &param) { model.removeRow(&param); }
+ void removeAllParameters()
+ {
+ for (auto &param : parameters)
+ model.removeRow(&param);
+ }
+};
+
+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 &parameter = 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 &param = 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(&parameterModel, 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 &param) {
+ 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 &param : 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 &parameter = 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 &it;\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 &it;\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 &section, 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, &notAdded);
+ 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 };
+
+
+