CppEditor: Add "Remove braces" quickfix for control statements

E.g.:
  if (x == 1) {
    return x;
  }
=>
  if (x == 1)
    return x;

This is very helpful for instance after temporarily inserting braces to
add debugging statements.
The functionality is tailored towards our coding conventions in that we
do not offer the operation if the statement in the block spans more than
a single line.

Change-Id: I09b3c139422a9e2773811b78c12ada7730ffcb22
Reviewed-by: David Schulz <david.schulz@qt.io>
This commit is contained in:
Christian Kandeler
2025-06-12 16:09:40 +02:00
parent c9b8e99058
commit f7f43eaa80
38 changed files with 426 additions and 12 deletions

View File

@@ -12,6 +12,8 @@
#include <cplusplus/Overview.h>
#include <cplusplus/TypeOfExpression.h>
#include <functional>
#ifdef WITH_TESTS
#include "cppquickfix_test.h"
#endif
@@ -111,8 +113,81 @@ private:
const int m_elseToken;
};
template<typename Statement>
bool checkControlStatementsHelper(const CppQuickFixInterface &interface, QuickFixOperations &result)
template<typename Statement> class RemoveBracesFromControlStatementOp : public CppQuickFixOperation
{
public:
RemoveBracesFromControlStatementOp(const CppQuickFixInterface &interface,
const QList<Statement *> &statements,
StatementAST *elseStatement,
int elseToken)
: CppQuickFixOperation(interface, 0)
, m_statements(statements), m_elseStatement(elseStatement)
{
Q_UNUSED(elseToken)
setDescription(Tr::tr("Remove Curly Braces"));
}
void perform() override
{
ChangeSet changes;
const auto findNewline = [&](int bracePos, int diff, int &newlinePos, int *nextNonSpacePos) {
for (int i = bracePos + diff; true; i += diff) {
const QChar &c = currentFile()->charAt(i);
if (c == '\n' || c == QChar::ParagraphSeparator) {
newlinePos = i;
break;
}
if (!c.isSpace())
break;
if (nextNonSpacePos)
++*nextNonSpacePos;
}
};
const auto removeBraceAndPossiblyLine = [&](int braceToken, bool removeTrailingSpace) {
const int bracePos = currentFile()->startOf(braceToken);
int prevNewline = -1;
int nextNewline = -1;
int start = bracePos;
int end = bracePos + 1;
findNewline(bracePos, -1, prevNewline, nullptr);
findNewline(bracePos, 1, nextNewline, removeTrailingSpace ? &end : nullptr);
if (prevNewline != -1 && nextNewline != -1) {
start = prevNewline;
end = nextNewline;
}
changes.remove(start, end);
};
const auto apply = [&](const CompoundStatementAST *stmt) {
QTC_ASSERT(stmt, return);
removeBraceAndPossiblyLine(stmt->lbrace_token, false);
removeBraceAndPossiblyLine(stmt->rbrace_token,
std::is_same_v<Statement, DoStatementAST>
|| std::is_same_v<Statement, IfStatementAST>);
if (!stmt->statement_list)
changes.insert(currentFile()->endOf(stmt), "\n;");
};
for (Statement * const statement : m_statements)
apply(statement->statement->asCompoundStatement());
if (m_elseStatement)
apply(m_elseStatement->asCompoundStatement());
currentFile()->setChangeSet(changes);
currentFile()->apply();
}
private:
const QList<Statement *> m_statements;
StatementAST * const m_elseStatement;
};
using StmtConstraint = std::function<bool(AST *, bool &)>;
template<template<typename> typename Op, typename Statement>
bool checkControlStatementsHelper(
const CppQuickFixInterface &interface,
const StmtConstraint &constraint,
QuickFixOperations &result)
{
Statement * const statement = asControlStatement<Statement>(interface.path().last());
if (!statement)
@@ -122,8 +197,11 @@ bool checkControlStatementsHelper(const CppQuickFixInterface &interface, QuickFi
if (!Utils::anyOf(triggerTokens(statement), [&](int tok) { return interface.isCursorOn(tok); }))
return false;
if (statement->statement && !statement->statement->asCompoundStatement())
bool abort = false;
if (statement->statement && constraint(statement->statement, abort))
statements << statement;
if (abort)
return false;
StatementAST *elseStmt = nullptr;
int elseToken = 0;
@@ -132,24 +210,33 @@ bool checkControlStatementsHelper(const CppQuickFixInterface &interface, QuickFi
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())
if (currentIfStmt->statement && constraint(currentIfStmt->statement, abort))
statements << currentIfStmt;
if (abort)
return false;
}
if (elseStmt && (elseStmt->asIfStatement() || elseStmt->asCompoundStatement())) {
if (elseStmt && (elseStmt->asIfStatement() || !constraint(elseStmt, abort))) {
if (abort)
return false;
elseStmt = nullptr;
elseToken = 0;
}
}
if (!statements.isEmpty() || elseStmt)
result << new AddBracesToControlStatementOp(interface, statements, elseStmt, elseToken);
if (!statements.isEmpty() || elseStmt) {
result << new Op<Statement>(interface, statements, elseStmt, elseToken);
return false;
}
return true;
}
template<typename ...Statements>
void checkControlStatements(const CppQuickFixInterface &interface, QuickFixOperations &result)
template<template<typename> typename Op, typename... Statements>
void checkControlStatements(
const CppQuickFixInterface &interface,
const StmtConstraint &constraint,
QuickFixOperations &result)
{
(... || checkControlStatementsHelper<Statements>(interface, result));
(... || checkControlStatementsHelper<Op, Statements>(interface, constraint, result));
}
class MoveDeclarationOutOfIfOp: public CppQuickFixOperation
@@ -574,11 +661,53 @@ class AddBracesToControlStatement : public CppQuickFixFactory
{
if (interface.path().isEmpty())
return;
checkControlStatements<IfStatementAST,
const auto constraint = [](AST *ast, bool &) { return !ast->asCompoundStatement(); };
checkControlStatements<AddBracesToControlStatementOp,
IfStatementAST,
WhileStatementAST,
ForStatementAST,
RangeBasedForStatementAST,
DoStatementAST>(interface, result);
DoStatementAST>(interface, constraint, result);
}
};
/*!
* The reverse of AddBracesToControlStatement
*/
class RemoveBracesFromControlStatement : public CppQuickFixFactory
{
void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override
{
if (interface.path().isEmpty())
return;
const auto constraint = [&](AST *ast, bool &abort) {
if (const auto compoundStmt = ast->asCompoundStatement()) {
if (!compoundStmt->statement_list || !compoundStmt->statement_list->value)
return true; // No statements.
if (compoundStmt->statement_list->next) {
abort = true;
return false; // More than one statement.
}
// We have exactly one statement. Check whether it spans more than one line.
const CppRefactoringFilePtr file = interface.currentFile();
const ChangeSet::Range stmtRange = file->range(compoundStmt->statement_list->value);
int startLine, startColumn, endLine, endColumn;
file->lineAndColumn(stmtRange.start, &startLine, &startColumn);
file->lineAndColumn(stmtRange.end, &endLine, &endColumn);
if (startLine == endLine)
return true;
abort = true;
return false;
}
return false;
};
checkControlStatements<RemoveBracesFromControlStatementOp,
IfStatementAST,
WhileStatementAST,
ForStatementAST,
RangeBasedForStatementAST,
DoStatementAST>(interface, constraint, result);
}
};
@@ -676,6 +805,12 @@ class AddBracesToControlStatementTest : public Tests::CppQuickFixTestObject
public:
using CppQuickFixTestObject::CppQuickFixTestObject;
};
class RemoveBracesFromControlStatementTest : public Tests::CppQuickFixTestObject
{
Q_OBJECT
public:
using CppQuickFixTestObject::CppQuickFixTestObject;
};
class MoveDeclarationOutOfIfTest : public Tests::CppQuickFixTestObject
{
Q_OBJECT
@@ -701,6 +836,7 @@ public:
void registerRewriteControlStatementQuickfixes()
{
REGISTER_QUICKFIX_FACTORY_WITH_STANDARD_TEST(AddBracesToControlStatement);
REGISTER_QUICKFIX_FACTORY_WITH_STANDARD_TEST(RemoveBracesFromControlStatement);
REGISTER_QUICKFIX_FACTORY_WITH_STANDARD_TEST(MoveDeclarationOutOfIf);
REGISTER_QUICKFIX_FACTORY_WITH_STANDARD_TEST(MoveDeclarationOutOfWhile);
REGISTER_QUICKFIX_FACTORY_WITH_STANDARD_TEST(OptimizeForLoop);

View File

@@ -0,0 +1,6 @@
void foo(int v)
{
do
++v;
while (v < 100);
}

View File

@@ -0,0 +1,7 @@
void foo(int v)
{
@do
{
++v;
} while (v < 100);
}

View File

@@ -0,0 +1,6 @@
void foo(int v)
{
@do
++v;
while (v < 100);
}

View File

@@ -0,0 +1,8 @@
void foo(int v)
{
@do {
++v;
v
*= 2;
} while (v < 100);
}

View File

@@ -0,0 +1,5 @@
void foo(int v)
{
for (int i = 0; i < v; ++i)
;
}

View File

@@ -0,0 +1,5 @@
void foo(int v)
{
@for (int i = 0; i < v; ++i) {
}
}

View File

@@ -0,0 +1,5 @@
void foo(int v)
{
@for (int i = 0; i < v; ++i)
;
}

View File

@@ -0,0 +1,7 @@
void foo(int v)
{
@for (int i = 0; i < v; ++i) {
if (i < v/2)
++i;
}
}

View File

@@ -0,0 +1,7 @@
int foo()
{
@if (true) {
int i = 0;
return i;
}
}

View File

@@ -0,0 +1,9 @@
void foo(int &x)
{
if (x == 0)
x = 1;
else if (x < 0)
x = -x;
else
--x;
}

View File

@@ -0,0 +1,10 @@
void foo(int &x)
{
@if (x == 0) {
x = 1;
} else if (x < 0) {
x = -x;
} else {
--x;
}
}

View File

@@ -0,0 +1,11 @@
void foo(int &x)
{
@if (x == 0) {
x = 1;
} else if (x < 0) {
x
= -x;
} else {
--x;
}
}

View File

@@ -0,0 +1,9 @@
void foo(int &x)
{
if (x == 0)
x = 1;
else if (x < 0)
x = -x;
else
--x;
}

View File

@@ -0,0 +1,10 @@
void foo(int &x)
{
@if (x == 0)
x = 1;
else if (x < 0)
x = -x;
else {
--x;
}
}

View File

@@ -0,0 +1,9 @@
void foo(int &x)
{
if (x == 0)
x = 1;
else if (x < 0)
x = -x;
else
--x;
}

View File

@@ -0,0 +1,9 @@
void foo(int &x)
{
@if (x == 0)
x = 1;
else if (x < 0) {
x = -x;
} else
--x;
}

View File

@@ -0,0 +1,9 @@
void foo(int &x)
{
if (x == 0)
x = 1;
else if (x < 0)
x = -x;
else
--x;
}

View File

@@ -0,0 +1,10 @@
void foo(int &x)
{
@if (x == 0)
x = 1;
else if (x < 0) {
x = -x;
} else {
--x;
}
}

View File

@@ -0,0 +1,9 @@
void foo(int &x)
{
if (x == 0)
x = 1;
else if (x < 0)
x = -x;
else
--x;
}

View File

@@ -0,0 +1,9 @@
void foo(int &x)
{
@if (x == 0) {
x = 1;
} else if (x < 0) {
x = -x;
} else
--x;
}

View File

@@ -0,0 +1,9 @@
void foo(int &x)
{
if (x == 0)
x = 1;
else if (x < 0)
x = -x;
else
--x;
}

View File

@@ -0,0 +1,10 @@
void foo(int &x)
{
@if (x == 0) {
x = 1;
} else if (x < 0)
x = -x;
else {
--x;
}
}

View File

@@ -0,0 +1,11 @@
void foo(int &x)
{
@if (x == 0)
x = 1;
else if (x < 0)
x = -x;
else {
--x;
x /= 2;
}
}

View File

@@ -0,0 +1,10 @@
void foo(int &x)
{
@if (x == 0)
x = 1;
else if (x < 0) {
x
= -x;
} else
--x;
}

View File

@@ -0,0 +1,9 @@
void foo(int &x)
{
@if (x == 0)
x = 1;
else if (x < 0)
x = -x;
else
--x;
}

View File

@@ -0,0 +1,5 @@
void foo()
{
@if (true)
return;
}

View File

@@ -0,0 +1,5 @@
void foo(std::vector<int> &list)
{
for (int &i : list)
i *= 2;
}

View File

@@ -0,0 +1,6 @@
void foo(std::vector<int> &list)
{
@for (int &i : list) {
i *= 2;
}
}

View File

@@ -0,0 +1,5 @@
void foo(std::vector<int> &list)
{
@for (int &i : list)
i *= 2;
}

View File

@@ -0,0 +1,7 @@
void foo(std::vector<int> &list)
{
@for (int &i : list) {
i *= 2;
++i;
}
}

View File

@@ -0,0 +1,5 @@
void foo(int v)
{
while (v < 100)
++v;
}

View File

@@ -0,0 +1,6 @@
void foo(int v)
{
@while (v < 100) {
++v;
}
}

View File

@@ -0,0 +1,5 @@
void foo(int v)
{
@while (v < 100)
++v;
}

View File

@@ -0,0 +1,7 @@
void foo(int v)
{
@while (v < 100) {
v += 2;
v -= 1;
}
}