CppEditor: Add curly braces for more control statements

Fixes: QTCREATORBUG-24542
Change-Id: I3e0893e1c736730d94e2c9ab2baa0aa580393fe4
Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
Reviewed-by: Christian Stenger <christian.stenger@qt.io>
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
This commit is contained in:
Christian Kandeler
2024-02-07 14:12:11 +01:00
parent 10e2ff5362
commit 837e951b29
4 changed files with 166 additions and 46 deletions

View File

@@ -8229,29 +8229,105 @@ void QuickfixTest::testExtractLiteralAsParameterNotTriggeringForInvalidCode()
QuickFixOperationTest(testDocuments, &factory); QuickFixOperationTest(testDocuments, &factory);
} }
void QuickfixTest::testAddCurlyBraces() void QuickfixTest::testAddCurlyBraces_data()
{ {
QList<TestDocumentPtr> testDocuments; QTest::addColumn<QByteArray>("original");
const QByteArray original = R"delim( QTest::addColumn<QByteArray>("expected");
QByteArray original = R"delim(
void MyObject::f() void MyObject::f()
{ {
@if (true) @if (true)
emit mySig(); emit mySig();
} })delim";
)delim"; QByteArray expected = R"delim(
const QByteArray expected = R"delim(
void MyObject::f() void MyObject::f()
{ {
if (true) { if (true) {
emit mySig(); emit mySig();
} }
})delim";
QTest::newRow("if") << original << expected;
original = R"delim(
void MyObject::f()
{
@while (true)
emit mySig();
})delim";
expected = R"delim(
void MyObject::f()
{
while (true) {
emit mySig();
} }
)delim"; })delim";
QTest::newRow("while") << original << expected;
testDocuments << CppTestDocument::create("file.cpp", original, expected); original = R"delim(
AddBracesToIf factory; void MyObject::f()
QuickFixOperationTest(testDocuments, &factory); {
@for (int i = 0; i < 10; ++i)
emit mySig();
})delim";
expected = R"delim(
void MyObject::f()
{
for (int i = 0; i < 10; ++i) {
emit mySig();
}
})delim";
QTest::newRow("for") << original << expected;
original = R"delim(
void MyObject::f()
{
@for (int i : list)
emit mySig();
})delim";
expected = R"delim(
void MyObject::f()
{
for (int i : list) {
emit mySig();
}
})delim";
QTest::newRow("range-based for") << original << expected;
original = R"delim(
void MyObject::f()
{
@do
emit mySig();
while (true);
})delim";
expected = R"delim(
void MyObject::f()
{
do {
emit mySig();
} while (true);
})delim";
QTest::newRow("do") << original << expected;
original = R"delim(
void MyObject::f()
{
@do {
emit mySig();
} while (true);
})delim";
expected.clear();
QTest::newRow("already has braces") << original << expected;
}
void QuickfixTest::testAddCurlyBraces()
{
QFETCH(QByteArray, original);
QFETCH(QByteArray, expected);
AddBracesToControlStatement factory;
QuickFixOperationTest({CppTestDocument::create("file.cpp", original, expected)}, &factory);
} }
void QuickfixTest::testConvertQt4ConnectConnectOutOfClass() void QuickfixTest::testConvertQt4ConnectConnectOutOfClass()

View File

@@ -209,6 +209,7 @@ private slots:
void testExtractLiteralAsParameterMemberFunctionSeparateFiles(); void testExtractLiteralAsParameterMemberFunctionSeparateFiles();
void testExtractLiteralAsParameterNotTriggeringForInvalidCode(); void testExtractLiteralAsParameterNotTriggeringForInvalidCode();
void testAddCurlyBraces_data();
void testAddCurlyBraces(); void testAddCurlyBraces();
void testRemoveUsingNamespace_data(); void testRemoveUsingNamespace_data();

View File

@@ -764,13 +764,49 @@ void SplitSimpleDeclaration::doMatch(const CppQuickFixInterface &interface,
} }
namespace { 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;
}
class AddBracesToIfOp: public CppQuickFixOperation 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: public:
AddBracesToIfOp(const CppQuickFixInterface &interface, int priority, AddBracesToControlStatementOp(const CppQuickFixInterface &interface, const Statement *statement)
const IfStatementAST *statement) : CppQuickFixOperation(interface, 0)
: CppQuickFixOperation(interface, priority)
, _statement(statement) , _statement(statement)
{ {
setDescription(Tr::tr("Add Curly Braces")); setDescription(Tr::tr("Add Curly Braces"));
@@ -783,51 +819,58 @@ public:
ChangeSet changes; ChangeSet changes;
const int start = currentFile->endOf(_statement->rparen_token); const int start = currentFile->endOf(tokenToInsertOpeningBraceAfter(_statement));
changes.insert(start, QLatin1String(" {")); 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 {
const int end = currentFile->endOf(_statement->statement->lastToken() - 1); const int end = currentFile->endOf(_statement->statement->lastToken() - 1);
changes.insert(end, QLatin1String("\n}")); changes.insert(end, QLatin1String("\n}"));
}
// TODO: For if statements, also bracify all else cases.
// Also check all else cases in the factory.
currentFile->setChangeSet(changes); currentFile->setChangeSet(changes);
currentFile->apply(); currentFile->apply();
} }
private: private:
const IfStatementAST * const _statement; const Statement * const _statement;
}; };
} // anonymous namespace } // anonymous namespace
void AddBracesToIf::doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) template<typename Statement>
bool checkControlStatementsHelper(const CppQuickFixInterface &interface, QuickFixOperations &result)
{ {
const QList<AST *> &path = interface.path(); Statement * const statement = asControlStatement<Statement>(interface.path().last());
if (path.isEmpty()) if (!statement)
return; return false;
// show when we're on the 'if' of an if statement if (interface.isCursorOn(triggerToken(statement)) && statement->statement
int index = path.size() - 1; && !statement->statement->asCompoundStatement()) {
IfStatementAST *ifStatement = path.at(index)->asIfStatement(); result << new AddBracesToControlStatementOp(interface, statement);
if (ifStatement && interface.isCursorOn(ifStatement->if_token) && ifStatement->statement }
&& !ifStatement->statement->asCompoundStatement()) { return true;
result << new AddBracesToIfOp(interface, index, ifStatement);
return;
} }
// or if we're on the statement contained in the if template<typename ...Statements>
// ### This may not be such a good idea, consider nested ifs... void checkControlStatements(const CppQuickFixInterface &interface, QuickFixOperations &result)
for (; index != -1; --index) { {
IfStatementAST *ifStatement = path.at(index)->asIfStatement(); (... || checkControlStatementsHelper<Statements>(interface, result));
if (ifStatement && ifStatement->statement
&& interface.isCursorOn(ifStatement->statement)
&& !ifStatement->statement->asCompoundStatement()) {
result << new AddBracesToIfOp(interface, index, ifStatement);
return;
}
} }
// ### This could very well be extended to the else branch void AddBracesToControlStatement::doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result)
// and other nodes entirely. {
if (interface.path().isEmpty())
return;
checkControlStatements<IfStatementAST,
WhileStatementAST,
ForStatementAST,
RangeBasedForStatementAST,
DoStatementAST>(interface, result);
} }
namespace { namespace {
@@ -9969,7 +10012,7 @@ void createCppQuickFixes()
new SplitIfStatement; new SplitIfStatement;
new SplitSimpleDeclaration; new SplitSimpleDeclaration;
new AddBracesToIf; new AddBracesToControlStatement;
new RearrangeParamDeclarationList; new RearrangeParamDeclarationList;
new ReformatPointerDeclaration; new ReformatPointerDeclaration;

View File

@@ -287,7 +287,7 @@ public:
}; };
/*! /*!
Add curly braces to a if statement that doesn't already contain a Add curly braces to a control statement that doesn't already contain a
compound statement. I.e. compound statement. I.e.
if (a) if (a)
@@ -297,9 +297,9 @@ public:
b; b;
} }
Activates on: the if Activates on: the keyword
*/ */
class AddBracesToIf: public CppQuickFixFactory class AddBracesToControlStatement : public CppQuickFixFactory
{ {
public: public:
void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override; void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override;