CppEditor: Move control statement quickfixes into dedicated files

Change-Id: Ie4fb4bb466c151cc7666aecb5307fee6f6fd56d8
Reviewed-by: Christian Stenger <christian.stenger@qt.io>
Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
This commit is contained in:
Christian Kandeler
2024-05-16 13:09:31 +02:00
parent 3c0258448e
commit 4cd59fc357
8 changed files with 1355 additions and 1256 deletions

View File

@@ -115,6 +115,7 @@ add_qtc_plugin(CppEditor
quickfixes/moveclasstoownfile.cpp quickfixes/moveclasstoownfile.h
quickfixes/movefunctiondefinition.cpp quickfixes/movefunctiondefinition.h
quickfixes/removeusingnamespace.cpp quickfixes/removeusingnamespace.h
quickfixes/rewritecontrolstatements.cpp quickfixes/rewritecontrolstatements.h
resourcepreviewhoverhandler.cpp resourcepreviewhoverhandler.h
searchsymbols.cpp searchsymbols.h
semantichighlighter.cpp semantichighlighter.h

View File

@@ -259,6 +259,8 @@ QtcPlugin {
"movefunctiondefinition.h",
"removeusingnamespace.cpp",
"removeusingnamespace.h",
"rewritecontrolstatements.cpp",
"rewritecontrolstatements.h",
]
}

View File

@@ -841,110 +841,6 @@ void QuickfixTest::testGeneric_data()
"};\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")
@@ -1184,60 +1080,6 @@ void QuickfixTest::testGeneric_data()
"};\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")
<< _();
QTest::newRow("ConvertFromPointer")
<< CppQuickFixFactoryPtr(new ConvertFromAndToPointer)
<< _("void foo() {\n"
@@ -1899,399 +1741,6 @@ void QuickfixTest::testExtractLiteralAsParameterNotTriggeringForInvalidCode()
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::testChangeCommentType_data()
{
QTest::addColumn<QString>("input");

View File

@@ -108,9 +108,6 @@ private slots:
void testExtractLiteralAsParameterMemberFunctionSeparateFiles();
void testExtractLiteralAsParameterNotTriggeringForInvalidCode();
void testAddCurlyBraces_data();
void testAddCurlyBraces();
void testChangeCommentType_data();
void testChangeCommentType();

View File

@@ -28,6 +28,7 @@
#include "moveclasstoownfile.h"
#include "movefunctiondefinition.h"
#include "removeusingnamespace.h"
#include "rewritecontrolstatements.h"
#include <coreplugin/icore.h>
#include <coreplugin/messagemanager.h>
@@ -237,430 +238,6 @@ void SplitSimpleDeclaration::doMatch(const CppQuickFixInterface &interface,
}
}
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;
}
}
}
namespace {
class ConvertNumericLiteralOp: public CppQuickFixOperation
@@ -2814,187 +2391,6 @@ void AssignToLocalVariable::doMatch(const CppQuickFixInterface &interface, Quick
}
}
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);
}
}
void ExtraRefactoringOperations::doMatch(const CppQuickFixInterface &interface,
QuickFixOperations &result)
{
@@ -3569,13 +2965,8 @@ void createCppQuickFixes()
new ConvertNumericLiteral;
new MoveDeclarationOutOfIf;
new MoveDeclarationOutOfWhile;
new SplitIfStatement;
new SplitSimpleDeclaration;
new AddBracesToControlStatement;
new RearrangeParamDeclarationList;
new ReformatPointerDeclaration;
@@ -3599,8 +2990,7 @@ void createCppQuickFixes()
registerConvertStringLiteralQuickfixes();
registerCreateDeclarationFromUseQuickfixes();
registerLogicalOperationQuickfixes();
new OptimizeForLoop;
registerRewriteControlStatementQuickfixes();
new ExtraRefactoringOperations;

View File

@@ -70,67 +70,6 @@ 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;
@@ -147,25 +86,6 @@ 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
@@ -260,16 +180,6 @@ 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;
};
//! Converts C-style to C++-style comments and vice versa
class ConvertCommentStyle : public CppQuickFixFactory
{

File diff suppressed because it is too large Load Diff

View File

@@ -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