cppquickfixes.cpp 194 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
InsertionLocation insertLocationForMethodDefinition(Symbol *symbol, const bool useSymbolFinder,
147 148 149 150 151 152 153
                                                    CppRefactoringChanges& refactoring,
                                                    const QString& fileName)
{
    QTC_ASSERT(symbol, return InsertionLocation());

    // Try to find optimal location
    const InsertionPointLocator locator(refactoring);
154 155
    const QList<InsertionLocation> list
            = locator.methodDefinition(symbol, useSymbolFinder, fileName);
156 157 158 159 160 161 162 163
    for (int i = 0; i < list.count(); ++i) {
        InsertionLocation location = list.at(i);
        if (location.isValid() && location.fileName() == fileName) {
            return location;
            break;
        }
    }

164 165
    // ...failed,
    // if class member try to get position right after class
166
    CppRefactoringFilePtr file = refactoring.file(fileName);
167 168 169 170 171 172 173 174 175 176 177 178 179
    unsigned line = 0, column = 0;
    if (Class *clazz = symbol->enclosingClass()) {
        if (symbol->fileName() == fileName.toUtf8()) {
            file->cppDocument()->translationUnit()->getPosition(clazz->endOffset(), &line, &column);
            if (line != 0) {
                ++column; // Skipping the ";"
                return InsertionLocation(fileName, QLatin1String("\n\n"), QLatin1String(""),
                                         line, column);
            }
        }
    }

    // fall through: position at end of file
180 181 182 183 184 185 186 187 188 189
    const QTextDocument *doc = file->document();
    int pos = qMax(0, doc->characterCount() - 1);

    //TODO watch for matching namespace
    //TODO watch for moc-includes

    file->lineAndColumn(pos, &line, &column);
    return InsertionLocation(fileName, QLatin1String("\n\n"), QLatin1String("\n"), line, column);
}

190
inline bool isQtStringLiteral(const QByteArray &id)
191 192 193 194
{
    return id == "QLatin1String" || id == "QLatin1Literal" || id == "QStringLiteral";
}

195
inline bool isQtStringTranslation(const QByteArray &id)
196 197 198 199
{
    return id == "tr" || id == "trUtf8" || id == "translate" || id == "QT_TRANSLATE_NOOP";
}

200 201 202 203 204
Class *isMemberFunction(const LookupContext &context, Function *function)
{
    QTC_ASSERT(function, return 0);

    Scope *enclosingScope = function->enclosingScope();
205
    while (!(enclosingScope->isNamespace() || enclosingScope->isClass()))
206 207 208 209
        enclosingScope = enclosingScope->enclosingScope();
    QTC_ASSERT(enclosingScope != 0, return 0);

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

213
    if (!functionName->isQualifiedNameId())
214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229
        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;
}

230 231 232 233 234
// 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;
235
    LineForNewIncludeDirective finder(file->document(), file->cppDocument()->resolvedIncludes(),
236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260
                                      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();
}

261 262 263 264 265 266
bool nameIncludesOperatorName(const Name *name)
{
    return name->isOperatorNameId()
        || (name->isQualifiedNameId() && name->asQualifiedNameId()->name()->isOperatorNameId());
}

267 268 269 270
} // anonymous namespace

namespace {

Nikolai Kosjar's avatar
Nikolai Kosjar committed
271
class InverseLogicalComparisonOp: public CppQuickFixOperation
272 273
{
public:
Nikolai Kosjar's avatar
Nikolai Kosjar committed
274 275 276 277
    InverseLogicalComparisonOp(const CppQuickFixInterface &interface, int priority,
                               BinaryExpressionAST *binary, Kind invertToken)
        : CppQuickFixOperation(interface, priority)
        , binary(binary), nested(0), negation(0)
278
    {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
279 280 281
        Token tok;
        tok.f.kind = invertToken;
        replacement = QLatin1String(tok.spell());
282

Nikolai Kosjar's avatar
Nikolai Kosjar committed
283 284 285
        // check for enclosing nested expression
        if (priority - 1 >= 0)
            nested = interface->path()[priority - 1]->asNestedExpression();
286

Nikolai Kosjar's avatar
Nikolai Kosjar committed
287 288 289
        // check for ! before parentheses
        if (nested && priority - 2 >= 0) {
            negation = interface->path()[priority - 2]->asUnaryExpression();
290 291
            if (negation
                    && !interface->currentFile()->tokenAt(negation->unary_op_token).is(T_EXCLAIM)) {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
292
                negation = 0;
293
            }
294 295 296
        }
    }

Nikolai Kosjar's avatar
Nikolai Kosjar committed
297
    QString description() const
298
    {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
299 300
        return QApplication::translate("CppTools::QuickFix", "Rewrite Using %1").arg(replacement);
    }
301

Nikolai Kosjar's avatar
Nikolai Kosjar committed
302 303 304 305 306 307 308 309 310 311 312 313 314 315
    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(")"));
316
        }
Nikolai Kosjar's avatar
Nikolai Kosjar committed
317 318 319 320
        changes.replace(currentFile->range(binary->binary_op_token), replacement);
        currentFile->setChangeSet(changes);
        currentFile->apply();
    }
321

Nikolai Kosjar's avatar
Nikolai Kosjar committed
322 323 324 325
private:
    BinaryExpressionAST *binary;
    NestedExpressionAST *nested;
    UnaryExpressionAST *negation;
326

Nikolai Kosjar's avatar
Nikolai Kosjar committed
327
    QString replacement;
328 329
};

330 331
} // anonymous namespace

Nikolai Kosjar's avatar
Nikolai Kosjar committed
332 333 334 335 336 337 338 339
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();
340
    if (!binary)
Nikolai Kosjar's avatar
Nikolai Kosjar committed
341
        return;
342
    if (!interface->isCursorOn(binary->binary_op_token))
Nikolai Kosjar's avatar
Nikolai Kosjar committed
343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367
        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;
    }
368

Nikolai Kosjar's avatar
Nikolai Kosjar committed
369 370 371
    result.append(CppQuickFixOperation::Ptr(
        new InverseLogicalComparisonOp(interface, index, binary, invertToken)));
}
372

373 374
namespace {

Nikolai Kosjar's avatar
Nikolai Kosjar committed
375
class FlipLogicalOperandsOp: public CppQuickFixOperation
376 377
{
public:
Nikolai Kosjar's avatar
Nikolai Kosjar committed
378 379 380 381 382
    FlipLogicalOperandsOp(const CppQuickFixInterface &interface, int priority,
                          BinaryExpressionAST *binary, QString replacement)
        : CppQuickFixOperation(interface)
        , binary(binary)
        , replacement(replacement)
383
    {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
384 385
        setPriority(priority);
    }
386

Nikolai Kosjar's avatar
Nikolai Kosjar committed
387 388 389 390 391 392 393
    QString description() const
    {
        if (replacement.isEmpty())
            return QApplication::translate("CppTools::QuickFix", "Swap Operands");
        else
            return QApplication::translate("CppTools::QuickFix", "Rewrite Using %1").arg(replacement);
    }
394

Nikolai Kosjar's avatar
Nikolai Kosjar committed
395 396 397 398
    void perform()
    {
        CppRefactoringChanges refactoring(snapshot());
        CppRefactoringFilePtr currentFile = refactoring.file(fileName());
399

Nikolai Kosjar's avatar
Nikolai Kosjar committed
400
        ChangeSet changes;
401 402
        changes.flip(currentFile->range(binary->left_expression),
                     currentFile->range(binary->right_expression));
403
        if (!replacement.isEmpty())
Nikolai Kosjar's avatar
Nikolai Kosjar committed
404
            changes.replace(currentFile->range(binary->binary_op_token), replacement);
405

Nikolai Kosjar's avatar
Nikolai Kosjar committed
406 407
        currentFile->setChangeSet(changes);
        currentFile->apply();
408 409 410
    }

private:
Nikolai Kosjar's avatar
Nikolai Kosjar committed
411 412 413
    BinaryExpressionAST *binary;
    QString replacement;
};
414

415 416
} // anonymous namespace

Nikolai Kosjar's avatar
Nikolai Kosjar committed
417 418 419 420
void FlipLogicalOperands::match(const CppQuickFixInterface &interface, QuickFixOperations &result)
{
    const QList<AST *> &path = interface->path();
    CppRefactoringFilePtr file = interface->currentFile();
421

Nikolai Kosjar's avatar
Nikolai Kosjar committed
422 423
    int index = path.size() - 1;
    BinaryExpressionAST *binary = path.at(index)->asBinaryExpression();
424
    if (!binary)
Nikolai Kosjar's avatar
Nikolai Kosjar committed
425
        return;
426
    if (!interface->isCursorOn(binary->binary_op_token))
Nikolai Kosjar's avatar
Nikolai Kosjar committed
427
        return;
428

Nikolai Kosjar's avatar
Nikolai Kosjar committed
429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451
    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;
    }
452

Nikolai Kosjar's avatar
Nikolai Kosjar committed
453 454 455 456 457 458
    QString replacement;
    if (flipToken != T_EOF_SYMBOL) {
        Token tok;
        tok.f.kind = flipToken;
        replacement = QLatin1String(tok.spell());
    }
459

Nikolai Kosjar's avatar
Nikolai Kosjar committed
460 461 462
    result.append(QuickFixOperation::Ptr(
        new FlipLogicalOperandsOp(interface, index, binary, replacement)));
}
463

464 465
namespace {

Nikolai Kosjar's avatar
Nikolai Kosjar committed
466
class RewriteLogicalAndOp: public CppQuickFixOperation
467 468
{
public:
Nikolai Kosjar's avatar
Nikolai Kosjar committed
469 470 471 472 473 474 475 476
    QSharedPointer<ASTPatternBuilder> mk;
    UnaryExpressionAST *left;
    UnaryExpressionAST *right;
    BinaryExpressionAST *pattern;

    RewriteLogicalAndOp(const CppQuickFixInterface &interface)
        : CppQuickFixOperation(interface)
        , mk(new ASTPatternBuilder)
477
    {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
478 479 480 481
        left = mk->UnaryExpression();
        right = mk->UnaryExpression();
        pattern = mk->BinaryExpression(left, right);
    }
482

Nikolai Kosjar's avatar
Nikolai Kosjar committed
483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501
    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();
    }
};
502

503 504
} // anonymous namespace

Nikolai Kosjar's avatar
Nikolai Kosjar committed
505 506 507 508 509
void RewriteLogicalAnd::match(const CppQuickFixInterface &interface, QuickFixOperations &result)
{
    BinaryExpressionAST *expression = 0;
    const QList<AST *> &path = interface->path();
    CppRefactoringFilePtr file = interface->currentFile();
510

Nikolai Kosjar's avatar
Nikolai Kosjar committed
511 512 513 514 515
    int index = path.size() - 1;
    for (; index != -1; --index) {
        expression = path.at(index)->asBinaryExpression();
        if (expression)
            break;
516 517
    }

518
    if (!expression)
Nikolai Kosjar's avatar
Nikolai Kosjar committed
519
        return;
520

521
    if (!interface->isCursorOn(expression->binary_op_token))
Nikolai Kosjar's avatar
Nikolai Kosjar committed
522
        return;
523

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
526 527 528 529
    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)) {
530 531
        op->setDescription(QApplication::translate("CppTools::QuickFix",
                                                   "Rewrite Condition Using ||"));
Nikolai Kosjar's avatar
Nikolai Kosjar committed
532 533 534 535
        op->setPriority(index);
        result.append(op);
    }
}
536

Nikolai Kosjar's avatar
Nikolai Kosjar committed
537 538
bool SplitSimpleDeclaration::checkDeclaration(SimpleDeclarationAST *declaration)
{
539
    if (!declaration->semicolon_token)
Nikolai Kosjar's avatar
Nikolai Kosjar committed
540
        return false;
541

542
    if (!declaration->decl_specifier_list)
Nikolai Kosjar's avatar
Nikolai Kosjar committed
543
        return false;
544

Nikolai Kosjar's avatar
Nikolai Kosjar committed
545 546
    for (SpecifierListAST *it = declaration->decl_specifier_list; it; it = it->next) {
        SpecifierAST *specifier = it->value;
547

Nikolai Kosjar's avatar
Nikolai Kosjar committed
548
        if (specifier->asEnumSpecifier() != 0)
549 550
            return false;

Nikolai Kosjar's avatar
Nikolai Kosjar committed
551
        else if (specifier->asClassSpecifier() != 0)
552
            return false;
Nikolai Kosjar's avatar
Nikolai Kosjar committed
553
    }
554

555
    if (!declaration->declarator_list)
Nikolai Kosjar's avatar
Nikolai Kosjar committed
556
        return false;
557

558
    else if (!declaration->declarator_list->next)
Nikolai Kosjar's avatar
Nikolai Kosjar committed
559
        return false;
560

Nikolai Kosjar's avatar
Nikolai Kosjar committed
561 562
    return true;
}
563

564 565
namespace {

Nikolai Kosjar's avatar
Nikolai Kosjar committed
566 567 568 569 570 571 572 573 574 575
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"));
576 577
    }

Nikolai Kosjar's avatar
Nikolai Kosjar committed
578
    void perform()
579
    {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
580 581
        CppRefactoringChanges refactoring(snapshot());
        CppRefactoringFilePtr currentFile = refactoring.file(fileName());
582

Nikolai Kosjar's avatar
Nikolai Kosjar committed
583
        ChangeSet changes;
584

Nikolai Kosjar's avatar
Nikolai Kosjar committed
585 586 587 588
        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);
589

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
592 593
        for (DeclaratorListAST *it = declaration->declarator_list->next; it; it = it->next) {
            DeclaratorAST *declarator = it->value;
594

Nikolai Kosjar's avatar
Nikolai Kosjar committed
595 596 597 598 599
            changes.insert(insertPos, QLatin1String("\n"));
            changes.copy(declSpecifiersStart, declSpecifiersEnd, insertPos);
            changes.insert(insertPos, QLatin1String(" "));
            changes.move(currentFile->range(declarator), insertPos);
            changes.insert(insertPos, QLatin1String(";"));
600

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
604
            prevDeclarator = declarator;
605
        }
Nikolai Kosjar's avatar
Nikolai Kosjar committed
606 607 608 609

        currentFile->setChangeSet(changes);
        currentFile->appendIndentRange(currentFile->range(declaration));
        currentFile->apply();
610 611 612
    }

private:
Nikolai Kosjar's avatar
Nikolai Kosjar committed
613 614
    SimpleDeclarationAST *declaration;
};
615

616 617
} // anonymous namespace

Nikolai Kosjar's avatar
Nikolai Kosjar committed
618 619 620 621 622 623 624
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();
625

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

629
        if (CoreDeclaratorAST *coreDecl = node->asCoreDeclarator()) {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
630
            core_declarator = coreDecl;
631
        } else if (SimpleDeclarationAST *simpleDecl = node->asSimpleDeclaration()) {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
632 633
            if (checkDeclaration(simpleDecl)) {
                SimpleDeclarationAST *declaration = simpleDecl;
634

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
638 639 640 641 642 643
                if (cursorPosition >= startOfDeclSpecifier && cursorPosition <= endOfDeclSpecifier) {
                    // the AST node under cursor is a specifier.
                    result.append(QuickFixOperation::Ptr(
                        new SplitSimpleDeclarationOp(interface, index, declaration)));
                    return;
                }
644

Nikolai Kosjar's avatar
Nikolai Kosjar committed
645 646 647 648 649 650
                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;
                }
651 652
            }

Nikolai Kosjar's avatar
Nikolai Kosjar committed
653
            return;
654
        }
Nikolai Kosjar's avatar
Nikolai Kosjar committed
655 656
    }
}
657

658 659
namespace {

Nikolai Kosjar's avatar
Nikolai Kosjar committed
660 661 662 663 664 665 666
class AddBracesToIfOp: public CppQuickFixOperation
{
public:
    AddBracesToIfOp(const CppQuickFixInterface &interface, int priority, StatementAST *statement)
        : CppQuickFixOperation(interface, priority)
        , _statement(statement)
    {
667
        setDescription(QApplication::translate("CppTools::QuickFix", "Add Curly Braces"));
Nikolai Kosjar's avatar
Nikolai Kosjar committed
668 669 670 671 672 673 674 675
    }

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

        ChangeSet changes;
676

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

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
683
        currentFile->setChangeSet(changes);
684
        currentFile->appendIndentRange(ChangeSet::Range(start, end));
Nikolai Kosjar's avatar
Nikolai Kosjar committed
685 686 687 688 689 690 691
        currentFile->apply();
    }

private:
    StatementAST *_statement;
};

692 693
} // anonymous namespace

Nikolai Kosjar's avatar
Nikolai Kosjar committed
694
void AddBracesToIf::match(const CppQuickFixInterface &interface, QuickFixOperations &result)
695
{
Nikolai Kosjar's avatar
Nikolai Kosjar committed
696
    const QList<AST *> &path = interface->path();
697

Nikolai Kosjar's avatar
Nikolai Kosjar committed
698 699 700 701
    // 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
702
        && !ifStatement->statement->asCompoundStatement()) {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
703 704 705 706 707 708 709 710
        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) {
711
        IfStatementAST *ifStatement = path.at(index)->asIfStatement();
Nikolai Kosjar's avatar
Nikolai Kosjar committed
712 713
        if (ifStatement && ifStatement->statement
            && interface->isCursorOn(ifStatement->statement)
714
            && !ifStatement->statement->asCompoundStatement()) {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
715 716
            result.append(QuickFixOperation::Ptr(
                new AddBracesToIfOp(interface, index, ifStatement->statement)));
717
            return;
718
        }
Nikolai Kosjar's avatar
Nikolai Kosjar committed
719
    }
720

Nikolai Kosjar's avatar
Nikolai Kosjar committed
721 722 723
    // ### This could very well be extended to the else branch
    // and other nodes entirely.
}
724

725 726
namespace {

Nikolai Kosjar's avatar
Nikolai Kosjar committed
727 728 729 730 731 732 733 734 735 736 737
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);
738 739
    }

Nikolai Kosjar's avatar
Nikolai Kosjar committed
740
    void perform()
741
    {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
742 743
        CppRefactoringChanges refactoring(snapshot());
        CppRefactoringFilePtr currentFile = refactoring.file(fileName());
744

Nikolai Kosjar's avatar
Nikolai Kosjar committed
745
        ChangeSet changes;
746

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
749 750 751
        int insertPos = currentFile->startOf(pattern);
        changes.move(currentFile->range(condition), insertPos);
        changes.insert(insertPos, QLatin1String(";\n"));
752

Nikolai Kosjar's avatar
Nikolai Kosjar committed
753 754 755 756
        currentFile->setChangeSet(changes);
        currentFile->appendIndentRange(currentFile->range(pattern));
        currentFile->apply();
    }
757

Nikolai Kosjar's avatar
Nikolai Kosjar committed
758 759 760 761 762
    ASTMatcher matcher;
    ASTPatternBuilder mk;
    ConditionAST *condition;
    IfStatementAST *pattern;
    CoreDeclaratorAST *core;
763 764
};

765 766
} // anonymous namespace

Nikolai Kosjar's avatar
Nikolai Kosjar committed
767 768
void MoveDeclarationOutOfIf::match(const CppQuickFixInterface &interface,
                                   QuickFixOperations &result)
769
{
Nikolai Kosjar's avatar
Nikolai Kosjar committed
770 771 772 773 774 775 776 777 778 779
    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;
780
                if (!op->core)
Nikolai Kosjar's avatar
Nikolai Kosjar committed
781
                    return;
782

Nikolai Kosjar's avatar
Nikolai Kosjar committed
783 784 785 786
                if (interface->isCursorOn(op->core)) {
                    op->setPriority(index);
                    result.append(op);
                    return;
787 788 789 790
                }
            }
        }
    }
Nikolai Kosjar's avatar
Nikolai Kosjar committed
791
}
792

793 794
namespace {

Nikolai Kosjar's avatar
Nikolai Kosjar committed
795 796 797 798 799
class MoveDeclarationOutOfWhileOp: public CppQuickFixOperation
{
public:
    MoveDeclarationOutOfWhileOp(const CppQuickFixInterface &interface)
        : CppQuickFixOperation(interface)
800
    {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
801 802
        setDescription(QApplication::translate("CppTools::QuickFix",
                                               "Move Declaration out of Condition"));
803

Nikolai Kosjar's avatar
Nikolai Kosjar committed
804 805 806
        condition = mk.Condition();
        pattern = mk.WhileStatement(condition);
    }
807

Nikolai Kosjar's avatar
Nikolai Kosjar committed
808 809 810 811
    void perform()
    {
        CppRefactoringChanges refactoring(snapshot());
        CppRefactoringFilePtr currentFile = refactoring.file(fileName());
812

Nikolai Kosjar's avatar
Nikolai Kosjar committed
813
        ChangeSet changes;
814

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
818 819 820 821 822
        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"));
823

Nikolai Kosjar's avatar
Nikolai Kosjar committed
824 825 826 827
        currentFile->setChangeSet(changes);
        currentFile->appendIndentRange(currentFile->range(pattern));
        currentFile->apply();
    }
828

Nikolai Kosjar's avatar
Nikolai Kosjar committed
829 830 831 832 833 834
    ASTMatcher matcher;
    ASTPatternBuilder mk;
    ConditionAST *condition;
    WhileStatementAST *pattern;
    CoreDeclaratorAST *core;
};
835

836 837
} // anonymous namespace

Nikolai Kosjar's avatar
Nikolai Kosjar committed
838 839
void MoveDeclarationOutOfWhile::match(const CppQuickFixInterface &interface,
                                      QuickFixOperations &result)
840
{
Nikolai Kosjar's avatar
Nikolai Kosjar committed
841 842
    const QList<AST *> &path = interface->path();
    QSharedPointer<MoveDeclarationOutOfWhileOp> op(new MoveDeclarationOutOfWhileOp(interface));
843

Nikolai Kosjar's avatar
Nikolai Kosjar committed
844 845 846 847 848 849
    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;
850

851
                if (!op->core)
Nikolai Kosjar's avatar
Nikolai Kosjar committed
852
                    return;
853

854
                if (!declarator->equal_token)
Nikolai Kosjar's avatar
Nikolai Kosjar committed
855
                    return;
856

857
                if (!declarator->initializer)
Nikolai Kosjar's avatar
Nikolai Kosjar committed
858
                    return;
859

Nikolai Kosjar's avatar
Nikolai Kosjar committed
860 861 862 863
                if (interface->isCursorOn(op->core)) {
                    op->setPriority(index);
                    result.append(op);
                    return;
864 865 866 867
                }
            }
        }
    }
Nikolai Kosjar's avatar
Nikolai Kosjar committed
868
}
869

870 871
namespace {

Nikolai Kosjar's avatar
Nikolai Kosjar committed
872 873 874 875 876 877 878 879
class SplitIfStatementOp: public CppQuickFixOperation
{
public:
    SplitIfStatementOp(const CppQuickFixInterface &interface, int priority,
                       IfStatementAST *pattern, BinaryExpressionAST *condition)
        : CppQuickFixOperation(interface, priority)
        , pattern(pattern)
        , condition(condition)
880
    {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
881 882 883 884 885 886 887 888
        setDescription(QApplication::translate("CppTools::QuickFix",
                                               "Split if Statement"));
    }

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

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
892 893 894 895 896
        if (binaryToken.is(T_AMPER_AMPER))
            splitAndCondition(currentFile);
        else
            splitOrCondition(currentFile);
    }
897

Nikolai Kosjar's avatar
Nikolai Kosjar committed
898 899 900
    void splitAndCondition(CppRefactoringFilePtr currentFile) const
    {
        ChangeSet changes;
901

Nikolai Kosjar's avatar
Nikolai Kosjar committed
902 903 904 905
        int startPos = currentFile->startOf(pattern);
        changes.insert(startPos, QLatin1String("if ("));
        changes.move(currentFile->range(condition->left_expression), startPos);
        changes.insert(startPos, QLatin1String(") {\n"));
906

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
911