// 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 "reformatpointerdeclaration.h" #include "../cppcodestylesettings.h" #include "../cppeditortr.h" #include "../cpppointerdeclarationformatter.h" #include "../cpprefactoringchanges.h" #include "cppquickfix.h" #include #ifdef WITH_TESTS #include "cppquickfix_test.h" #include #endif using namespace CPlusPlus; using namespace Utils; namespace CppEditor::Internal { 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 { currentFile()->apply(m_change); } 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 filter(const QList &astPathList) { QList 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; }; /*! 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 { #ifdef WITH_TESTS public: static QObject *createTest(); #endif private: void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override { const QList &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 suitableASTs = ReformatPointerDeclarationASTPathResultsFilter().filter(path); for (AST *ast : suitableASTs) { change = formatter.format(ast); if (!change.isEmpty()) { result << new ReformatPointerDeclarationOp(interface, change); return; } } } } }; #ifdef WITH_TESTS using namespace Tests; class ReformatPointerDeclarationTest : public QObject { Q_OBJECT private slots: void test() { // Check: Just a basic test since the main functionality is tested in // cpppointerdeclarationformatter_test.cpp ReformatPointerDeclaration factory; QuickFixOperationTest( singleDocument(QByteArray("char@*s;"), QByteArray("char *s;")), &factory); } }; QObject *ReformatPointerDeclaration::createTest() { return new ReformatPointerDeclarationTest; } #endif // WITH_TESTS } void registerReformatPointerDeclarationQuickfix() { CppQuickFixFactory::registerFactory(); } } // namespace CppEditor::Internal #ifdef WITH_TESTS #include #endif