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/moveclasstoownfile.cpp quickfixes/moveclasstoownfile.h
quickfixes/movefunctiondefinition.cpp quickfixes/movefunctiondefinition.h quickfixes/movefunctiondefinition.cpp quickfixes/movefunctiondefinition.h
quickfixes/removeusingnamespace.cpp quickfixes/removeusingnamespace.h quickfixes/removeusingnamespace.cpp quickfixes/removeusingnamespace.h
quickfixes/rewritecontrolstatements.cpp quickfixes/rewritecontrolstatements.h
resourcepreviewhoverhandler.cpp resourcepreviewhoverhandler.h resourcepreviewhoverhandler.cpp resourcepreviewhoverhandler.h
searchsymbols.cpp searchsymbols.h searchsymbols.cpp searchsymbols.h
semantichighlighter.cpp semantichighlighter.h semantichighlighter.cpp semantichighlighter.h

View File

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

View File

@@ -841,110 +841,6 @@ void QuickfixTest::testGeneric_data()
"};\n" "};\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 // Check: Just a basic test since the main functionality is tested in
// cpppointerdeclarationformatter_test.cpp // cpppointerdeclarationformatter_test.cpp
QTest::newRow("ReformatPointerDeclaration") QTest::newRow("ReformatPointerDeclaration")
@@ -1184,60 +1080,6 @@ void QuickfixTest::testGeneric_data()
"};\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")
<< _();
QTest::newRow("ConvertFromPointer") QTest::newRow("ConvertFromPointer")
<< CppQuickFixFactoryPtr(new ConvertFromAndToPointer) << CppQuickFixFactoryPtr(new ConvertFromAndToPointer)
<< _("void foo() {\n" << _("void foo() {\n"
@@ -1899,399 +1741,6 @@ void QuickfixTest::testExtractLiteralAsParameterNotTriggeringForInvalidCode()
QuickFixOperationTest(testDocuments, &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::testChangeCommentType_data() void QuickfixTest::testChangeCommentType_data()
{ {
QTest::addColumn<QString>("input"); QTest::addColumn<QString>("input");

View File

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

View File

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

View File

@@ -70,67 +70,6 @@ private:
const bool m_test; 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 Rewrite
int *a, b; int *a, b;
@@ -147,25 +86,6 @@ public:
void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override; 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 Switches places of the parameter declaration under cursor
with the next or the previous one in the parameter declaration list 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; 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 //! Converts C-style to C++-style comments and vice versa
class ConvertCommentStyle : public CppQuickFixFactory 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