diff options
Diffstat (limited to 'src/plugins/cppeditor/quickfixes/rewritecontrolstatements.cpp')
-rw-r--r-- | src/plugins/cppeditor/quickfixes/rewritecontrolstatements.cpp | 1323 |
1 files changed, 1323 insertions, 0 deletions
diff --git a/src/plugins/cppeditor/quickfixes/rewritecontrolstatements.cpp b/src/plugins/cppeditor/quickfixes/rewritecontrolstatements.cpp new file mode 100644 index 0000000000..2b9c2392df --- /dev/null +++ b/src/plugins/cppeditor/quickfixes/rewritecontrolstatements.cpp @@ -0,0 +1,1323 @@ +// 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 + { + 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 + { + 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()->apply(changes); + } + + 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 + { + 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()->apply(changes); + } + + 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 + { + const Token binaryToken = currentFile()->tokenAt(condition->binary_op_token); + + if (binaryToken.is(T_AMPER_AMPER)) + splitAndCondition(); + else + splitOrCondition(); + } + + void splitAndCondition() 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()->apply(changes); + } + + void splitOrCondition() 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()->apply(changes); + } + +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 CppRefactoringFilePtr file = currentFile(); + 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->apply(change); + + // 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 |