cppquickfixes.cpp 190 KB
Newer Older
hjk's avatar
hjk committed
1
/****************************************************************************
2
**
3
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
hjk's avatar
hjk committed
4
** Contact: http://www.qt-project.org/legal
5
**
hjk's avatar
hjk committed
6
** This file is part of Qt Creator.
7
**
hjk's avatar
hjk committed
8 9 10 11 12 13 14
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia.  For licensing terms and
** conditions see http://qt.digia.com/licensing.  For further information
** use the contact form at http://qt.digia.com/contact-us.
15 16
**
** GNU Lesser General Public License Usage
hjk's avatar
hjk committed
17 18 19 20 21 22 23 24 25
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Digia gives you certain additional
** rights.  These rights are described in the Digia Qt LGPL Exception
con's avatar
con committed
26 27
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
hjk's avatar
hjk committed
28
****************************************************************************/
29

Nikolai Kosjar's avatar
Nikolai Kosjar committed
30 31
#include "cppquickfixes.h"

32
#include "cppeditor.h"
33
#include "cppfunctiondecldeflink.h"
Leandro Melo's avatar
Leandro Melo committed
34
#include "cppquickfixassistant.h"
35

36 37
#include <coreplugin/icore.h>

38
#include <cpptools/cppclassesfilter.h>
39
#include <cpptools/cppcodestylesettings.h>
40 41
#include <cpptools/cpppointerdeclarationformatter.h>
#include <cpptools/cpptoolsconstants.h>
42
#include <cpptools/cpptoolsreuse.h>
43
#include <cpptools/includeutils.h>
44
#include <cpptools/insertionpointlocator.h>
45
#include <cpptools/symbolfinder.h>
46

47
#include <cplusplus/ASTPath.h>
48
#include <cplusplus/CPlusPlusForwardDeclarations.h>
49 50 51 52
#include <cplusplus/CppRewriter.h>
#include <cplusplus/DependencyTable.h>
#include <cplusplus/TypeOfExpression.h>

53
#include <extensionsystem/pluginmanager.h>
54

55 56 57
#include <texteditor/fontsettings.h>
#include <texteditor/texteditorsettings.h>

58 59
#include <utils/qtcassert.h>

60
#include <QApplication>
61 62 63
#include <QCheckBox>
#include <QComboBox>
#include <QDialogButtonBox>
64
#include <QDir>
65
#include <QFileInfo>
66 67
#include <QGroupBox>
#include <QHBoxLayout>
68
#include <QInputDialog>
69 70
#include <QItemDelegate>
#include <QLabel>
71
#include <QMessageBox>
72 73 74
#include <QPointer>
#include <QPushButton>
#include <QQueue>
Nikolai Kosjar's avatar
Nikolai Kosjar committed
75
#include <QSharedPointer>
76 77
#include <QSortFilterProxyModel>
#include <QStandardItemModel>
78 79
#include <QTextBlock>
#include <QTextCursor>
80 81
#include <QTreeView>
#include <QVBoxLayout>
82

Nikolai Kosjar's avatar
Nikolai Kosjar committed
83 84
#include <cctype>

85
using namespace CPlusPlus;
86 87
using namespace CppEditor;
using namespace CppEditor::Internal;
88 89 90
using namespace CppTools;
using namespace TextEditor;
using namespace Utils;
Nikolai Kosjar's avatar
Nikolai Kosjar committed
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126

void CppEditor::Internal::registerQuickFixes(ExtensionSystem::IPlugin *plugIn)
{
    plugIn->addAutoReleasedObject(new AddIncludeForUndefinedIdentifier);
    plugIn->addAutoReleasedObject(new AddIncludeForForwardDeclaration);

    plugIn->addAutoReleasedObject(new FlipLogicalOperands);
    plugIn->addAutoReleasedObject(new InverseLogicalComparison);
    plugIn->addAutoReleasedObject(new RewriteLogicalAnd);

    plugIn->addAutoReleasedObject(new ConvertToCamelCase);

    plugIn->addAutoReleasedObject(new ConvertCStringToNSString);
    plugIn->addAutoReleasedObject(new ConvertNumericLiteral);
    plugIn->addAutoReleasedObject(new TranslateStringLiteral);
    plugIn->addAutoReleasedObject(new WrapStringLiteral);

    plugIn->addAutoReleasedObject(new MoveDeclarationOutOfIf);
    plugIn->addAutoReleasedObject(new MoveDeclarationOutOfWhile);

    plugIn->addAutoReleasedObject(new SplitIfStatement);
    plugIn->addAutoReleasedObject(new SplitSimpleDeclaration);

    plugIn->addAutoReleasedObject(new AddLocalDeclaration);
    plugIn->addAutoReleasedObject(new AddBracesToIf);
    plugIn->addAutoReleasedObject(new RearrangeParamDeclarationList);
    plugIn->addAutoReleasedObject(new ReformatPointerDeclaration);

    plugIn->addAutoReleasedObject(new CompleteSwitchCaseStatement);
    plugIn->addAutoReleasedObject(new InsertQtPropertyMembers);

    plugIn->addAutoReleasedObject(new ApplyDeclDefLinkChanges);
    plugIn->addAutoReleasedObject(new ExtractFunction);
    plugIn->addAutoReleasedObject(new GenerateGetterSetter);
    plugIn->addAutoReleasedObject(new InsertDeclFromDef);
    plugIn->addAutoReleasedObject(new InsertDefFromDecl);
127 128 129

    plugIn->addAutoReleasedObject(new MoveFuncDefOutside);
    plugIn->addAutoReleasedObject(new MoveFuncDefToDecl);
130 131

    plugIn->addAutoReleasedObject(new AssignToLocalVariable);
132 133

    plugIn->addAutoReleasedObject(new InsertVirtualMethods);
Nikolai Kosjar's avatar
Nikolai Kosjar committed
134
}
135

136 137 138 139
// In the following anonymous namespace all functions are collected, which could be of interest for
// different quick fixes.
namespace {

140 141 142 143 144 145
enum DefPos {
    DefPosInsideClass,
    DefPosOutsideClass,
    DefPosImplementationFile
};

146
inline bool isQtStringLiteral(const QByteArray &id)
147 148 149 150
{
    return id == "QLatin1String" || id == "QLatin1Literal" || id == "QStringLiteral";
}

151
inline bool isQtStringTranslation(const QByteArray &id)
152 153 154 155
{
    return id == "tr" || id == "trUtf8" || id == "translate" || id == "QT_TRANSLATE_NOOP";
}

156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185
Class *isMemberFunction(const LookupContext &context, Function *function)
{
    QTC_ASSERT(function, return 0);

    Scope *enclosingScope = function->enclosingScope();
    while (! (enclosingScope->isNamespace() || enclosingScope->isClass()))
        enclosingScope = enclosingScope->enclosingScope();
    QTC_ASSERT(enclosingScope != 0, return 0);

    const Name *functionName = function->name();
    if (! functionName)
        return 0; // anonymous function names are not valid c++

    if (! functionName->isQualifiedNameId())
        return 0; // trying to add a declaration for a global function

    const QualifiedNameId *q = functionName->asQualifiedNameId();
    if (!q->base())
        return 0;

    if (ClassOrNamespace *binding = context.lookupType(q->base(), enclosingScope)) {
        foreach (Symbol *s, binding->symbols()) {
            if (Class *matchingClass = s->asClass())
                return matchingClass;
        }
    }

    return 0;
}

186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216
// Given include is e.g. "afile.h" or <afile.h> (quotes/angle brackets included!).
void insertNewIncludeDirective(const QString &include, CppRefactoringFilePtr file)
{
    // Find optimal position
    using namespace IncludeUtils;
    LineForNewIncludeDirective finder(file->document(), file->cppDocument()->includes(),
                                      LineForNewIncludeDirective::IgnoreMocIncludes,
                                      LineForNewIncludeDirective::AutoDetect);
    unsigned newLinesToPrepend = 0;
    unsigned newLinesToAppend = 0;
    const int insertLine = finder(include, &newLinesToPrepend, &newLinesToAppend);
    QTC_ASSERT(insertLine >= 1, return);
    const int insertPosition = file->position(insertLine, 1);
    QTC_ASSERT(insertPosition >= 0, return);

    // Construct text to insert
    const QString includeLine = QLatin1String("#include ") + include + QLatin1Char('\n');
    QString prependedNewLines, appendedNewLines;
    while (newLinesToAppend--)
        appendedNewLines += QLatin1String("\n");
    while (newLinesToPrepend--)
        prependedNewLines += QLatin1String("\n");
    const QString textToInsert = prependedNewLines + includeLine + appendedNewLines;

    // Insert
    ChangeSet changes;
    changes.insert(insertPosition, textToInsert);
    file->setChangeSet(changes);
    file->apply();
}

217 218 219 220
} // anonymous namespace

namespace {

Nikolai Kosjar's avatar
Nikolai Kosjar committed
221
class InverseLogicalComparisonOp: public CppQuickFixOperation
222 223
{
public:
Nikolai Kosjar's avatar
Nikolai Kosjar committed
224 225 226 227
    InverseLogicalComparisonOp(const CppQuickFixInterface &interface, int priority,
                               BinaryExpressionAST *binary, Kind invertToken)
        : CppQuickFixOperation(interface, priority)
        , binary(binary), nested(0), negation(0)
228
    {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
229 230 231
        Token tok;
        tok.f.kind = invertToken;
        replacement = QLatin1String(tok.spell());
232

Nikolai Kosjar's avatar
Nikolai Kosjar committed
233 234 235
        // check for enclosing nested expression
        if (priority - 1 >= 0)
            nested = interface->path()[priority - 1]->asNestedExpression();
236

Nikolai Kosjar's avatar
Nikolai Kosjar committed
237 238 239 240 241
        // check for ! before parentheses
        if (nested && priority - 2 >= 0) {
            negation = interface->path()[priority - 2]->asUnaryExpression();
            if (negation && ! interface->currentFile()->tokenAt(negation->unary_op_token).is(T_EXCLAIM))
                negation = 0;
242 243 244
        }
    }

Nikolai Kosjar's avatar
Nikolai Kosjar committed
245
    QString description() const
246
    {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
247 248
        return QApplication::translate("CppTools::QuickFix", "Rewrite Using %1").arg(replacement);
    }
249

Nikolai Kosjar's avatar
Nikolai Kosjar committed
250 251 252 253 254 255 256 257 258 259 260 261 262 263
    void perform()
    {
        CppRefactoringChanges refactoring(snapshot());
        CppRefactoringFilePtr currentFile = refactoring.file(fileName());

        ChangeSet changes;
        if (negation) {
            // can't remove parentheses since that might break precedence
            changes.remove(currentFile->range(negation->unary_op_token));
        } else if (nested) {
            changes.insert(currentFile->startOf(nested), QLatin1String("!"));
        } else {
            changes.insert(currentFile->startOf(binary), QLatin1String("!("));
            changes.insert(currentFile->endOf(binary), QLatin1String(")"));
264
        }
Nikolai Kosjar's avatar
Nikolai Kosjar committed
265 266 267 268
        changes.replace(currentFile->range(binary->binary_op_token), replacement);
        currentFile->setChangeSet(changes);
        currentFile->apply();
    }
269

Nikolai Kosjar's avatar
Nikolai Kosjar committed
270 271 272 273
private:
    BinaryExpressionAST *binary;
    NestedExpressionAST *nested;
    UnaryExpressionAST *negation;
274

Nikolai Kosjar's avatar
Nikolai Kosjar committed
275
    QString replacement;
276 277
};

278 279
} // anonymous namespace

Nikolai Kosjar's avatar
Nikolai Kosjar committed
280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315
void InverseLogicalComparison::match(const CppQuickFixInterface &interface,
                                     QuickFixOperations &result)
{
    CppRefactoringFilePtr file = interface->currentFile();

    const QList<AST *> &path = interface->path();
    int index = path.size() - 1;
    BinaryExpressionAST *binary = path.at(index)->asBinaryExpression();
    if (! binary)
        return;
    if (! interface->isCursorOn(binary->binary_op_token))
        return;

    Kind invertToken;
    switch (file->tokenAt(binary->binary_op_token).kind()) {
    case T_LESS_EQUAL:
        invertToken = T_GREATER;
        break;
    case T_LESS:
        invertToken = T_GREATER_EQUAL;
        break;
    case T_GREATER:
        invertToken = T_LESS_EQUAL;
        break;
    case T_GREATER_EQUAL:
        invertToken = T_LESS;
        break;
    case T_EQUAL_EQUAL:
        invertToken = T_EXCLAIM_EQUAL;
        break;
    case T_EXCLAIM_EQUAL:
        invertToken = T_EQUAL_EQUAL;
        break;
    default:
        return;
    }
316

Nikolai Kosjar's avatar
Nikolai Kosjar committed
317 318 319
    result.append(CppQuickFixOperation::Ptr(
        new InverseLogicalComparisonOp(interface, index, binary, invertToken)));
}
320

321 322
namespace {

Nikolai Kosjar's avatar
Nikolai Kosjar committed
323
class FlipLogicalOperandsOp: public CppQuickFixOperation
324 325
{
public:
Nikolai Kosjar's avatar
Nikolai Kosjar committed
326 327 328 329 330
    FlipLogicalOperandsOp(const CppQuickFixInterface &interface, int priority,
                          BinaryExpressionAST *binary, QString replacement)
        : CppQuickFixOperation(interface)
        , binary(binary)
        , replacement(replacement)
331
    {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
332 333
        setPriority(priority);
    }
334

Nikolai Kosjar's avatar
Nikolai Kosjar committed
335 336 337 338 339 340 341
    QString description() const
    {
        if (replacement.isEmpty())
            return QApplication::translate("CppTools::QuickFix", "Swap Operands");
        else
            return QApplication::translate("CppTools::QuickFix", "Rewrite Using %1").arg(replacement);
    }
342

Nikolai Kosjar's avatar
Nikolai Kosjar committed
343 344 345 346
    void perform()
    {
        CppRefactoringChanges refactoring(snapshot());
        CppRefactoringFilePtr currentFile = refactoring.file(fileName());
347

Nikolai Kosjar's avatar
Nikolai Kosjar committed
348
        ChangeSet changes;
349 350
        changes.flip(currentFile->range(binary->left_expression),
                     currentFile->range(binary->right_expression));
Nikolai Kosjar's avatar
Nikolai Kosjar committed
351 352
        if (! replacement.isEmpty())
            changes.replace(currentFile->range(binary->binary_op_token), replacement);
353

Nikolai Kosjar's avatar
Nikolai Kosjar committed
354 355
        currentFile->setChangeSet(changes);
        currentFile->apply();
356 357 358
    }

private:
Nikolai Kosjar's avatar
Nikolai Kosjar committed
359 360 361
    BinaryExpressionAST *binary;
    QString replacement;
};
362

363 364
} // anonymous namespace

Nikolai Kosjar's avatar
Nikolai Kosjar committed
365 366 367 368
void FlipLogicalOperands::match(const CppQuickFixInterface &interface, QuickFixOperations &result)
{
    const QList<AST *> &path = interface->path();
    CppRefactoringFilePtr file = interface->currentFile();
369

Nikolai Kosjar's avatar
Nikolai Kosjar committed
370 371 372 373 374 375
    int index = path.size() - 1;
    BinaryExpressionAST *binary = path.at(index)->asBinaryExpression();
    if (! binary)
        return;
    if (! interface->isCursorOn(binary->binary_op_token))
        return;
376

Nikolai Kosjar's avatar
Nikolai Kosjar committed
377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399
    Kind flipToken;
    switch (file->tokenAt(binary->binary_op_token).kind()) {
    case T_LESS_EQUAL:
        flipToken = T_GREATER_EQUAL;
        break;
    case T_LESS:
        flipToken = T_GREATER;
        break;
    case T_GREATER:
        flipToken = T_LESS;
        break;
    case T_GREATER_EQUAL:
        flipToken = T_LESS_EQUAL;
        break;
    case T_EQUAL_EQUAL:
    case T_EXCLAIM_EQUAL:
    case T_AMPER_AMPER:
    case T_PIPE_PIPE:
        flipToken = T_EOF_SYMBOL;
        break;
    default:
        return;
    }
400

Nikolai Kosjar's avatar
Nikolai Kosjar committed
401 402 403 404 405 406
    QString replacement;
    if (flipToken != T_EOF_SYMBOL) {
        Token tok;
        tok.f.kind = flipToken;
        replacement = QLatin1String(tok.spell());
    }
407

Nikolai Kosjar's avatar
Nikolai Kosjar committed
408 409 410
    result.append(QuickFixOperation::Ptr(
        new FlipLogicalOperandsOp(interface, index, binary, replacement)));
}
411

412 413
namespace {

Nikolai Kosjar's avatar
Nikolai Kosjar committed
414
class RewriteLogicalAndOp: public CppQuickFixOperation
415 416
{
public:
Nikolai Kosjar's avatar
Nikolai Kosjar committed
417 418 419 420 421 422 423 424
    QSharedPointer<ASTPatternBuilder> mk;
    UnaryExpressionAST *left;
    UnaryExpressionAST *right;
    BinaryExpressionAST *pattern;

    RewriteLogicalAndOp(const CppQuickFixInterface &interface)
        : CppQuickFixOperation(interface)
        , mk(new ASTPatternBuilder)
425
    {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
426 427 428 429
        left = mk->UnaryExpression();
        right = mk->UnaryExpression();
        pattern = mk->BinaryExpression(left, right);
    }
430

Nikolai Kosjar's avatar
Nikolai Kosjar committed
431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449
    void perform()
    {
        CppRefactoringChanges refactoring(snapshot());
        CppRefactoringFilePtr currentFile = refactoring.file(fileName());

        ChangeSet changes;
        changes.replace(currentFile->range(pattern->binary_op_token), QLatin1String("||"));
        changes.remove(currentFile->range(left->unary_op_token));
        changes.remove(currentFile->range(right->unary_op_token));
        const int start = currentFile->startOf(pattern);
        const int end = currentFile->endOf(pattern);
        changes.insert(start, QLatin1String("!("));
        changes.insert(end, QLatin1String(")"));

        currentFile->setChangeSet(changes);
        currentFile->appendIndentRange(currentFile->range(pattern));
        currentFile->apply();
    }
};
450

451 452
} // anonymous namespace

Nikolai Kosjar's avatar
Nikolai Kosjar committed
453 454 455 456 457
void RewriteLogicalAnd::match(const CppQuickFixInterface &interface, QuickFixOperations &result)
{
    BinaryExpressionAST *expression = 0;
    const QList<AST *> &path = interface->path();
    CppRefactoringFilePtr file = interface->currentFile();
458

Nikolai Kosjar's avatar
Nikolai Kosjar committed
459 460 461 462 463
    int index = path.size() - 1;
    for (; index != -1; --index) {
        expression = path.at(index)->asBinaryExpression();
        if (expression)
            break;
464 465
    }

Nikolai Kosjar's avatar
Nikolai Kosjar committed
466 467
    if (! expression)
        return;
468

Nikolai Kosjar's avatar
Nikolai Kosjar committed
469 470
    if (! interface->isCursorOn(expression->binary_op_token))
        return;
471

Nikolai Kosjar's avatar
Nikolai Kosjar committed
472
    QSharedPointer<RewriteLogicalAndOp> op(new RewriteLogicalAndOp(interface));
473

Nikolai Kosjar's avatar
Nikolai Kosjar committed
474 475 476 477
    if (expression->match(op->pattern, &matcher) &&
            file->tokenAt(op->pattern->binary_op_token).is(T_AMPER_AMPER) &&
            file->tokenAt(op->left->unary_op_token).is(T_EXCLAIM) &&
            file->tokenAt(op->right->unary_op_token).is(T_EXCLAIM)) {
478 479
        op->setDescription(QApplication::translate("CppTools::QuickFix",
                                                   "Rewrite Condition Using ||"));
Nikolai Kosjar's avatar
Nikolai Kosjar committed
480 481 482 483
        op->setPriority(index);
        result.append(op);
    }
}
484

Nikolai Kosjar's avatar
Nikolai Kosjar committed
485 486 487 488
bool SplitSimpleDeclaration::checkDeclaration(SimpleDeclarationAST *declaration)
{
    if (! declaration->semicolon_token)
        return false;
489

Nikolai Kosjar's avatar
Nikolai Kosjar committed
490 491
    if (! declaration->decl_specifier_list)
        return false;
492

Nikolai Kosjar's avatar
Nikolai Kosjar committed
493 494
    for (SpecifierListAST *it = declaration->decl_specifier_list; it; it = it->next) {
        SpecifierAST *specifier = it->value;
495

Nikolai Kosjar's avatar
Nikolai Kosjar committed
496
        if (specifier->asEnumSpecifier() != 0)
497 498
            return false;

Nikolai Kosjar's avatar
Nikolai Kosjar committed
499
        else if (specifier->asClassSpecifier() != 0)
500
            return false;
Nikolai Kosjar's avatar
Nikolai Kosjar committed
501
    }
502

Nikolai Kosjar's avatar
Nikolai Kosjar committed
503 504
    if (! declaration->declarator_list)
        return false;
505

Nikolai Kosjar's avatar
Nikolai Kosjar committed
506 507
    else if (! declaration->declarator_list->next)
        return false;
508

Nikolai Kosjar's avatar
Nikolai Kosjar committed
509 510
    return true;
}
511

512 513
namespace {

Nikolai Kosjar's avatar
Nikolai Kosjar committed
514 515 516 517 518 519 520 521 522 523
class SplitSimpleDeclarationOp: public CppQuickFixOperation
{
public:
    SplitSimpleDeclarationOp(const CppQuickFixInterface &interface, int priority,
                             SimpleDeclarationAST *decl)
        : CppQuickFixOperation(interface, priority)
        , declaration(decl)
    {
        setDescription(QApplication::translate("CppTools::QuickFix",
                                               "Split Declaration"));
524 525
    }

Nikolai Kosjar's avatar
Nikolai Kosjar committed
526
    void perform()
527
    {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
528 529
        CppRefactoringChanges refactoring(snapshot());
        CppRefactoringFilePtr currentFile = refactoring.file(fileName());
530

Nikolai Kosjar's avatar
Nikolai Kosjar committed
531
        ChangeSet changes;
532

Nikolai Kosjar's avatar
Nikolai Kosjar committed
533 534 535 536
        SpecifierListAST *specifiers = declaration->decl_specifier_list;
        int declSpecifiersStart = currentFile->startOf(specifiers->firstToken());
        int declSpecifiersEnd = currentFile->endOf(specifiers->lastToken() - 1);
        int insertPos = currentFile->endOf(declaration->semicolon_token);
537

Nikolai Kosjar's avatar
Nikolai Kosjar committed
538
        DeclaratorAST *prevDeclarator = declaration->declarator_list->value;
539

Nikolai Kosjar's avatar
Nikolai Kosjar committed
540 541
        for (DeclaratorListAST *it = declaration->declarator_list->next; it; it = it->next) {
            DeclaratorAST *declarator = it->value;
542

Nikolai Kosjar's avatar
Nikolai Kosjar committed
543 544 545 546 547
            changes.insert(insertPos, QLatin1String("\n"));
            changes.copy(declSpecifiersStart, declSpecifiersEnd, insertPos);
            changes.insert(insertPos, QLatin1String(" "));
            changes.move(currentFile->range(declarator), insertPos);
            changes.insert(insertPos, QLatin1String(";"));
548

Nikolai Kosjar's avatar
Nikolai Kosjar committed
549 550
            const int prevDeclEnd = currentFile->endOf(prevDeclarator);
            changes.remove(prevDeclEnd, currentFile->startOf(declarator));
551

Nikolai Kosjar's avatar
Nikolai Kosjar committed
552
            prevDeclarator = declarator;
553
        }
Nikolai Kosjar's avatar
Nikolai Kosjar committed
554 555 556 557

        currentFile->setChangeSet(changes);
        currentFile->appendIndentRange(currentFile->range(declaration));
        currentFile->apply();
558 559 560
    }

private:
Nikolai Kosjar's avatar
Nikolai Kosjar committed
561 562
    SimpleDeclarationAST *declaration;
};
563

564 565
} // anonymous namespace

Nikolai Kosjar's avatar
Nikolai Kosjar committed
566 567 568 569 570 571 572
void SplitSimpleDeclaration::match(const CppQuickFixInterface &interface,
                                   QuickFixOperations &result)
{
    CoreDeclaratorAST *core_declarator = 0;
    const QList<AST *> &path = interface->path();
    CppRefactoringFilePtr file = interface->currentFile();
    const int cursorPosition = file->cursor().selectionStart();
573

Nikolai Kosjar's avatar
Nikolai Kosjar committed
574 575
    for (int index = path.size() - 1; index != -1; --index) {
        AST *node = path.at(index);
576

Nikolai Kosjar's avatar
Nikolai Kosjar committed
577 578
        if (CoreDeclaratorAST *coreDecl = node->asCoreDeclarator())
            core_declarator = coreDecl;
579

Nikolai Kosjar's avatar
Nikolai Kosjar committed
580 581 582
        else if (SimpleDeclarationAST *simpleDecl = node->asSimpleDeclaration()) {
            if (checkDeclaration(simpleDecl)) {
                SimpleDeclarationAST *declaration = simpleDecl;
583

Nikolai Kosjar's avatar
Nikolai Kosjar committed
584 585
                const int startOfDeclSpecifier = file->startOf(declaration->decl_specifier_list->firstToken());
                const int endOfDeclSpecifier = file->endOf(declaration->decl_specifier_list->lastToken() - 1);
586

Nikolai Kosjar's avatar
Nikolai Kosjar committed
587 588 589 590 591 592
                if (cursorPosition >= startOfDeclSpecifier && cursorPosition <= endOfDeclSpecifier) {
                    // the AST node under cursor is a specifier.
                    result.append(QuickFixOperation::Ptr(
                        new SplitSimpleDeclarationOp(interface, index, declaration)));
                    return;
                }
593

Nikolai Kosjar's avatar
Nikolai Kosjar committed
594 595 596 597 598 599
                if (core_declarator && interface->isCursorOn(core_declarator)) {
                    // got a core-declarator under the text cursor.
                    result.append(QuickFixOperation::Ptr(
                        new SplitSimpleDeclarationOp(interface, index, declaration)));
                    return;
                }
600 601
            }

Nikolai Kosjar's avatar
Nikolai Kosjar committed
602
            return;
603
        }
Nikolai Kosjar's avatar
Nikolai Kosjar committed
604 605
    }
}
606

607 608
namespace {

Nikolai Kosjar's avatar
Nikolai Kosjar committed
609 610 611 612 613 614 615
class AddBracesToIfOp: public CppQuickFixOperation
{
public:
    AddBracesToIfOp(const CppQuickFixInterface &interface, int priority, StatementAST *statement)
        : CppQuickFixOperation(interface, priority)
        , _statement(statement)
    {
616
        setDescription(QApplication::translate("CppTools::QuickFix", "Add Curly Braces"));
Nikolai Kosjar's avatar
Nikolai Kosjar committed
617 618 619 620 621 622 623 624
    }

    void perform()
    {
        CppRefactoringChanges refactoring(snapshot());
        CppRefactoringFilePtr currentFile = refactoring.file(fileName());

        ChangeSet changes;
625

Nikolai Kosjar's avatar
Nikolai Kosjar committed
626 627
        const int start = currentFile->endOf(_statement->firstToken() - 1);
        changes.insert(start, QLatin1String(" {"));
628

Nikolai Kosjar's avatar
Nikolai Kosjar committed
629 630
        const int end = currentFile->endOf(_statement->lastToken() - 1);
        changes.insert(end, QLatin1String("\n}"));
631

Nikolai Kosjar's avatar
Nikolai Kosjar committed
632
        currentFile->setChangeSet(changes);
633
        currentFile->appendIndentRange(ChangeSet::Range(start, end));
Nikolai Kosjar's avatar
Nikolai Kosjar committed
634 635 636 637 638 639 640
        currentFile->apply();
    }

private:
    StatementAST *_statement;
};

641 642
} // anonymous namespace

Nikolai Kosjar's avatar
Nikolai Kosjar committed
643
void AddBracesToIf::match(const CppQuickFixInterface &interface, QuickFixOperations &result)
644
{
Nikolai Kosjar's avatar
Nikolai Kosjar committed
645
    const QList<AST *> &path = interface->path();
646

Nikolai Kosjar's avatar
Nikolai Kosjar committed
647 648 649 650 651 652 653 654 655 656 657 658 659
    // show when we're on the 'if' of an if statement
    int index = path.size() - 1;
    IfStatementAST *ifStatement = path.at(index)->asIfStatement();
    if (ifStatement && interface->isCursorOn(ifStatement->if_token) && ifStatement->statement
        && ! ifStatement->statement->asCompoundStatement()) {
        result.append(QuickFixOperation::Ptr(
            new AddBracesToIfOp(interface, index, ifStatement->statement)));
        return;
    }

    // or if we're on the statement contained in the if
    // ### This may not be such a good idea, consider nested ifs...
    for (; index != -1; --index) {
660
        IfStatementAST *ifStatement = path.at(index)->asIfStatement();
Nikolai Kosjar's avatar
Nikolai Kosjar committed
661 662
        if (ifStatement && ifStatement->statement
            && interface->isCursorOn(ifStatement->statement)
663
            && ! ifStatement->statement->asCompoundStatement()) {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
664 665
            result.append(QuickFixOperation::Ptr(
                new AddBracesToIfOp(interface, index, ifStatement->statement)));
666
            return;
667
        }
Nikolai Kosjar's avatar
Nikolai Kosjar committed
668
    }
669

Nikolai Kosjar's avatar
Nikolai Kosjar committed
670 671 672
    // ### This could very well be extended to the else branch
    // and other nodes entirely.
}
673

674 675
namespace {

Nikolai Kosjar's avatar
Nikolai Kosjar committed
676 677 678 679 680 681 682 683 684 685 686
class MoveDeclarationOutOfIfOp: public CppQuickFixOperation
{
public:
    MoveDeclarationOutOfIfOp(const CppQuickFixInterface &interface)
        : CppQuickFixOperation(interface)
    {
        setDescription(QApplication::translate("CppTools::QuickFix",
                                               "Move Declaration out of Condition"));

        condition = mk.Condition();
        pattern = mk.IfStatement(condition);
687 688
    }

Nikolai Kosjar's avatar
Nikolai Kosjar committed
689
    void perform()
690
    {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
691 692
        CppRefactoringChanges refactoring(snapshot());
        CppRefactoringFilePtr currentFile = refactoring.file(fileName());
693

Nikolai Kosjar's avatar
Nikolai Kosjar committed
694
        ChangeSet changes;
695

Nikolai Kosjar's avatar
Nikolai Kosjar committed
696
        changes.copy(currentFile->range(core), currentFile->startOf(condition));
697

Nikolai Kosjar's avatar
Nikolai Kosjar committed
698 699 700
        int insertPos = currentFile->startOf(pattern);
        changes.move(currentFile->range(condition), insertPos);
        changes.insert(insertPos, QLatin1String(";\n"));
701

Nikolai Kosjar's avatar
Nikolai Kosjar committed
702 703 704 705
        currentFile->setChangeSet(changes);
        currentFile->appendIndentRange(currentFile->range(pattern));
        currentFile->apply();
    }
706

Nikolai Kosjar's avatar
Nikolai Kosjar committed
707 708 709 710 711 712
    ASTMatcher matcher;
    ASTPatternBuilder mk;
    CPPEditorWidget *editor;
    ConditionAST *condition;
    IfStatementAST *pattern;
    CoreDeclaratorAST *core;
713 714
};

715 716
} // anonymous namespace

Nikolai Kosjar's avatar
Nikolai Kosjar committed
717 718
void MoveDeclarationOutOfIf::match(const CppQuickFixInterface &interface,
                                   QuickFixOperations &result)
719
{
Nikolai Kosjar's avatar
Nikolai Kosjar committed
720 721 722 723 724 725 726 727 728 729 730 731
    const QList<AST *> &path = interface->path();
    typedef QSharedPointer<MoveDeclarationOutOfIfOp> Ptr;
    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;
732

Nikolai Kosjar's avatar
Nikolai Kosjar committed
733 734 735 736
                if (interface->isCursorOn(op->core)) {
                    op->setPriority(index);
                    result.append(op);
                    return;
737 738 739 740
                }
            }
        }
    }
Nikolai Kosjar's avatar
Nikolai Kosjar committed
741
}
742

743 744
namespace {

Nikolai Kosjar's avatar
Nikolai Kosjar committed
745 746 747 748 749
class MoveDeclarationOutOfWhileOp: public CppQuickFixOperation
{
public:
    MoveDeclarationOutOfWhileOp(const CppQuickFixInterface &interface)
        : CppQuickFixOperation(interface)
750
    {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
751 752
        setDescription(QApplication::translate("CppTools::QuickFix",
                                               "Move Declaration out of Condition"));
753

Nikolai Kosjar's avatar
Nikolai Kosjar committed
754 755 756
        condition = mk.Condition();
        pattern = mk.WhileStatement(condition);
    }
757

Nikolai Kosjar's avatar
Nikolai Kosjar committed
758 759 760 761
    void perform()
    {
        CppRefactoringChanges refactoring(snapshot());
        CppRefactoringFilePtr currentFile = refactoring.file(fileName());
762

Nikolai Kosjar's avatar
Nikolai Kosjar committed
763
        ChangeSet changes;
764

Nikolai Kosjar's avatar
Nikolai Kosjar committed
765 766
        changes.insert(currentFile->startOf(condition), QLatin1String("("));
        changes.insert(currentFile->endOf(condition), QLatin1String(") != 0"));
767

Nikolai Kosjar's avatar
Nikolai Kosjar committed
768 769 770 771 772
        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"));
773

Nikolai Kosjar's avatar
Nikolai Kosjar committed
774 775 776 777
        currentFile->setChangeSet(changes);
        currentFile->appendIndentRange(currentFile->range(pattern));
        currentFile->apply();
    }
778

Nikolai Kosjar's avatar
Nikolai Kosjar committed
779 780 781 782 783 784 785
    ASTMatcher matcher;
    ASTPatternBuilder mk;
    CPPEditorWidget *editor;
    ConditionAST *condition;
    WhileStatementAST *pattern;
    CoreDeclaratorAST *core;
};
786

787 788
} // anonymous namespace

Nikolai Kosjar's avatar
Nikolai Kosjar committed
789 790
void MoveDeclarationOutOfWhile::match(const CppQuickFixInterface &interface,
                                      QuickFixOperations &result)
791
{
Nikolai Kosjar's avatar
Nikolai Kosjar committed
792 793
    const QList<AST *> &path = interface->path();
    QSharedPointer<MoveDeclarationOutOfWhileOp> op(new MoveDeclarationOutOfWhileOp(interface));
794

Nikolai Kosjar's avatar
Nikolai Kosjar committed
795 796 797 798 799 800
    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;
801

Nikolai Kosjar's avatar
Nikolai Kosjar committed
802 803
                if (! op->core)
                    return;
804

Nikolai Kosjar's avatar
Nikolai Kosjar committed
805 806
                if (! declarator->equal_token)
                    return;
807

Nikolai Kosjar's avatar
Nikolai Kosjar committed
808 809
                if (! declarator->initializer)
                    return;
810

Nikolai Kosjar's avatar
Nikolai Kosjar committed
811 812 813 814
                if (interface->isCursorOn(op->core)) {
                    op->setPriority(index);
                    result.append(op);
                    return;
815 816 817 818
                }
            }
        }
    }
Nikolai Kosjar's avatar
Nikolai Kosjar committed
819
}
820

821 822
namespace {

Nikolai Kosjar's avatar
Nikolai Kosjar committed
823 824 825 826 827 828 829 830
class SplitIfStatementOp: public CppQuickFixOperation
{
public:
    SplitIfStatementOp(const CppQuickFixInterface &interface, int priority,
                       IfStatementAST *pattern, BinaryExpressionAST *condition)
        : CppQuickFixOperation(interface, priority)
        , pattern(pattern)
        , condition(condition)
831
    {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
832 833 834 835 836 837 838 839
        setDescription(QApplication::translate("CppTools::QuickFix",
                                               "Split if Statement"));
    }

    void perform()
    {
        CppRefactoringChanges refactoring(snapshot());
        CppRefactoringFilePtr currentFile = refactoring.file(fileName());
840

Nikolai Kosjar's avatar
Nikolai Kosjar committed
841
        const Token binaryToken = currentFile->tokenAt(condition->binary_op_token);
842

Nikolai Kosjar's avatar
Nikolai Kosjar committed
843 844 845 846 847
        if (binaryToken.is(T_AMPER_AMPER))
            splitAndCondition(currentFile);
        else
            splitOrCondition(currentFile);
    }
848

Nikolai Kosjar's avatar
Nikolai Kosjar committed
849 850 851
    void splitAndCondition(CppRefactoringFilePtr currentFile) const
    {
        ChangeSet changes;
852

Nikolai Kosjar's avatar
Nikolai Kosjar committed
853 854 855 856
        int startPos = currentFile->startOf(pattern);
        changes.insert(startPos, QLatin1String("if ("));
        changes.move(currentFile->range(condition->left_expression), startPos);
        changes.insert(startPos, QLatin1String(") {\n"));
857

Nikolai Kosjar's avatar
Nikolai Kosjar committed
858 859 860
        const int lExprEnd = currentFile->endOf(condition->left_expression);
        changes.remove(lExprEnd, currentFile->startOf(condition->right_expression));
        changes.insert(currentFile->endOf(pattern), QLatin1String("\n}"));
861

Nikolai Kosjar's avatar
Nikolai Kosjar committed
862 863 864 865
        currentFile->setChangeSet(changes);
        currentFile->appendIndentRange(currentFile->range(pattern));
        currentFile->apply();
    }
866

Nikolai Kosjar's avatar
Nikolai Kosjar committed
867
    void splitOrCondition(CppRefactoringFilePtr currentFile) const
868
    {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
869
        ChangeSet changes;
870

Nikolai Kosjar's avatar
Nikolai Kosjar committed
871 872
        StatementAST *ifTrueStatement = pattern->statement;
        CompoundStatementAST *compoundStatement = ifTrueStatement->asCompoundStatement();
873

Nikolai Kosjar's avatar
Nikolai Kosjar committed
874 875 876 877 878 879
        int insertPos = currentFile->endOf(ifTrueStatement);
        if (compoundStatement)
            changes.insert(insertPos, QLatin1String(" "));
        else
            changes.insert(insertPos, QLatin1String("\n"));
        changes.insert(insertPos, QLatin1String("else if ("));
880

Nikolai Kosjar's avatar
Nikolai Kosjar committed
881 882 883
        const int rExprStart = currentFile->startOf(condition->right_expression);
        changes.move(rExprStart, currentFile->startOf(pattern->rparen_token), insertPos);
        changes.insert(insertPos, QLatin1String(")"));
884

Nikolai Kosjar's avatar
Nikolai Kosjar committed
885 886
        const int rParenEnd = currentFile->endOf(pattern->rparen_token);
        changes.copy(rParenEnd, currentFile->endOf(pattern->statement), insertPos);
887

Nikolai Kosjar's avatar
Nikolai Kosjar committed
888 889 890 891 892 893
        const int lExprEnd = currentFile->endOf(condition->left_expression);
        changes.remove(lExprEnd, currentFile->startOf(condition->right_expression));

        currentFile->setChangeSet(changes);
        currentFile->appendIndentRange(currentFile->range(pattern));
        currentFile->apply();
894 895 896
    }

private:
Nikolai Kosjar's avatar
Nikolai Kosjar committed
897 898 899
    IfStatementAST *pattern;
    BinaryExpressionAST *condition;
};
900

901 902
} // anonymous namespace

Nikolai Kosjar's avatar
Nikolai Kosjar committed
903 904 905 906
void SplitIfStatement::match(const CppQuickFixInterface &interface, QuickFixOperations &result)
{
    IfStatementAST *pattern = 0;
    const QList<AST *> &path = interface->path();
907

Nikolai Kosjar's avatar
Nikolai Kosjar committed
908 909 910 911 912 913
    int index = path.size() - 1;
    for (; index != -1; --index) {
        AST *node = path.at(index);
        if (IfStatementAST *stmt = node->asIfStatement()) {
            pattern = stmt;
            break;
914
        }
Nikolai Kosjar's avatar
Nikolai Kosjar committed
915
    }
916

Nikolai Kosjar's avatar
Nikolai Kosjar committed
917 918
    if (! pattern || ! pattern->statement)
        return;
919

Nikolai Kosjar's avatar
Nikolai Kosjar committed
920 921 922 923 924 925
    unsigned splitKind = 0;
    for (++index; index < path.size(); ++index) {
        AST *node = path.at(index);
        BinaryExpressionAST *condition = node->asBinaryExpression();
        if (! condition)
            return;
926

Nikolai Kosjar's avatar
Nikolai Kosjar committed
927
        Token binaryToken = interface->currentFile()->tokenAt(condition->binary_op_token);
928

Nikolai Kosjar's avatar
Nikolai Kosjar committed
929 930 931 932 933 934 935 936 937 938
        // 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;
939 940
        }

Nikolai Kosjar's avatar
Nikolai Kosjar committed
941 942 943 944
        if (interface->isCursorOn(condition->binary_op_token)) {
            result.append(QuickFixOperation::Ptr(
                new SplitIfStatementOp(interface, index, pattern, condition)));
            return;
945
        }
Nikolai Kosjar's avatar
Nikolai Kosjar committed
946 947
    }
}
948