cppquickfixes.cpp 186 KB
Newer Older
hjk's avatar
hjk committed
1
/****************************************************************************
2
**
3
** Copyright (C) 2014 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
#include "cppvirtualfunctionassistprovider.h"
36
#include "cppinsertvirtualmethods.h"
37

38 39
#include <coreplugin/icore.h>

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

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

55
#include <extensionsystem/pluginmanager.h>
56

57 58
#include <utils/qtcassert.h>

59
#include <QApplication>
60
#include <QDir>
61
#include <QFileInfo>
62 63
#include <QInputDialog>
#include <QMessageBox>
Nikolai Kosjar's avatar
Nikolai Kosjar committed
64
#include <QSharedPointer>
65
#include <QTextCursor>
66

Nikolai Kosjar's avatar
Nikolai Kosjar committed
67 68
#include <cctype>

69
using namespace CPlusPlus;
70 71
using namespace CppEditor;
using namespace CppEditor::Internal;
72 73
using namespace CppTools;
using namespace TextEditor;
Orgad Shaneh's avatar
Orgad Shaneh committed
74
using Utils::ChangeSet;
Nikolai Kosjar's avatar
Nikolai Kosjar committed
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107

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);
108
    plugIn->addAutoReleasedObject(new ExtractLiteralAsParameter);
Nikolai Kosjar's avatar
Nikolai Kosjar committed
109 110 111
    plugIn->addAutoReleasedObject(new GenerateGetterSetter);
    plugIn->addAutoReleasedObject(new InsertDeclFromDef);
    plugIn->addAutoReleasedObject(new InsertDefFromDecl);
112 113 114

    plugIn->addAutoReleasedObject(new MoveFuncDefOutside);
    plugIn->addAutoReleasedObject(new MoveFuncDefToDecl);
115 116

    plugIn->addAutoReleasedObject(new AssignToLocalVariable);
117 118

    plugIn->addAutoReleasedObject(new InsertVirtualMethods);
119 120

    plugIn->addAutoReleasedObject(new OptimizeForLoop);
Nikolai Kosjar's avatar
Nikolai Kosjar committed
121
}
122

123 124 125 126
// In the following anonymous namespace all functions are collected, which could be of interest for
// different quick fixes.
namespace {

127 128 129 130 131 132
enum DefPos {
    DefPosInsideClass,
    DefPosOutsideClass,
    DefPosImplementationFile
};

133
InsertionLocation insertLocationForMethodDefinition(Symbol *symbol, const bool useSymbolFinder,
134 135 136 137 138 139 140
                                                    CppRefactoringChanges& refactoring,
                                                    const QString& fileName)
{
    QTC_ASSERT(symbol, return InsertionLocation());

    // Try to find optimal location
    const InsertionPointLocator locator(refactoring);
141 142
    const QList<InsertionLocation> list
            = locator.methodDefinition(symbol, useSymbolFinder, fileName);
143 144
    for (int i = 0; i < list.count(); ++i) {
        InsertionLocation location = list.at(i);
Orgad Shaneh's avatar
Orgad Shaneh committed
145
        if (location.isValid() && location.fileName() == fileName)
146 147 148
            return location;
    }

149 150
    // ...failed,
    // if class member try to get position right after class
151
    CppRefactoringFilePtr file = refactoring.file(fileName);
152 153 154 155 156 157 158 159 160 161 162 163 164
    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
165 166 167 168 169 170 171 172 173 174
    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);
}

175
inline bool isQtStringLiteral(const QByteArray &id)
176 177 178 179
{
    return id == "QLatin1String" || id == "QLatin1Literal" || id == "QStringLiteral";
}

180
inline bool isQtStringTranslation(const QByteArray &id)
181 182 183 184
{
    return id == "tr" || id == "trUtf8" || id == "translate" || id == "QT_TRANSLATE_NOOP";
}

185 186 187 188 189
Class *isMemberFunction(const LookupContext &context, Function *function)
{
    QTC_ASSERT(function, return 0);

    Scope *enclosingScope = function->enclosingScope();
190
    while (!(enclosingScope->isNamespace() || enclosingScope->isClass()))
191 192 193 194
        enclosingScope = enclosingScope->enclosingScope();
    QTC_ASSERT(enclosingScope != 0, return 0);

    const Name *functionName = function->name();
195
    if (!functionName)
196
        return 0;
197

198
    if (!functionName->isQualifiedNameId())
199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214
        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;
}

215 216 217 218 219 220 221 222 223 224 225 226 227
Namespace *isNamespaceFunction(const LookupContext &context, Function *function)
{
    QTC_ASSERT(function, return 0);
    if (isMemberFunction(context, 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)
228
        return 0;
229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252

    // global namespace
    if (!functionName->isQualifiedNameId()) {
        foreach (Symbol *s, context.globalNamespace()->symbols()) {
            if (Namespace *matchingNamespace = s->asNamespace())
                return matchingNamespace;
        }
        return 0;
    }

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

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

    return 0;
}

253 254 255 256 257
// 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;
258
    LineForNewIncludeDirective finder(file->document(), file->cppDocument()->resolvedIncludes(),
259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283
                                      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();
}

284 285 286 287 288 289
bool nameIncludesOperatorName(const Name *name)
{
    return name->isOperatorNameId()
        || (name->isQualifiedNameId() && name->asQualifiedNameId()->name()->isOperatorNameId());
}

290 291 292 293
} // anonymous namespace

namespace {

Nikolai Kosjar's avatar
Nikolai Kosjar committed
294
class InverseLogicalComparisonOp: public CppQuickFixOperation
295 296
{
public:
Nikolai Kosjar's avatar
Nikolai Kosjar committed
297 298 299 300
    InverseLogicalComparisonOp(const CppQuickFixInterface &interface, int priority,
                               BinaryExpressionAST *binary, Kind invertToken)
        : CppQuickFixOperation(interface, priority)
        , binary(binary), nested(0), negation(0)
301
    {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
302 303 304
        Token tok;
        tok.f.kind = invertToken;
        replacement = QLatin1String(tok.spell());
305

Nikolai Kosjar's avatar
Nikolai Kosjar committed
306 307 308
        // check for enclosing nested expression
        if (priority - 1 >= 0)
            nested = interface->path()[priority - 1]->asNestedExpression();
309

Nikolai Kosjar's avatar
Nikolai Kosjar committed
310 311 312
        // check for ! before parentheses
        if (nested && priority - 2 >= 0) {
            negation = interface->path()[priority - 2]->asUnaryExpression();
313 314
            if (negation
                    && !interface->currentFile()->tokenAt(negation->unary_op_token).is(T_EXCLAIM)) {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
315
                negation = 0;
316
            }
317 318 319
        }
    }

Nikolai Kosjar's avatar
Nikolai Kosjar committed
320
    QString description() const
321
    {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
322 323
        return QApplication::translate("CppTools::QuickFix", "Rewrite Using %1").arg(replacement);
    }
324

Nikolai Kosjar's avatar
Nikolai Kosjar committed
325 326 327 328 329 330 331 332 333 334 335 336 337 338
    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(")"));
339
        }
Nikolai Kosjar's avatar
Nikolai Kosjar committed
340 341 342 343
        changes.replace(currentFile->range(binary->binary_op_token), replacement);
        currentFile->setChangeSet(changes);
        currentFile->apply();
    }
344

Nikolai Kosjar's avatar
Nikolai Kosjar committed
345 346 347 348
private:
    BinaryExpressionAST *binary;
    NestedExpressionAST *nested;
    UnaryExpressionAST *negation;
349

Nikolai Kosjar's avatar
Nikolai Kosjar committed
350
    QString replacement;
351 352
};

353 354
} // anonymous namespace

Nikolai Kosjar's avatar
Nikolai Kosjar committed
355 356 357 358 359 360 361 362
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();
363
    if (!binary)
Nikolai Kosjar's avatar
Nikolai Kosjar committed
364
        return;
365
    if (!interface->isCursorOn(binary->binary_op_token))
Nikolai Kosjar's avatar
Nikolai Kosjar committed
366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390
        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;
    }
391

Nikolai Kosjar's avatar
Nikolai Kosjar committed
392 393 394
    result.append(CppQuickFixOperation::Ptr(
        new InverseLogicalComparisonOp(interface, index, binary, invertToken)));
}
395

396 397
namespace {

Nikolai Kosjar's avatar
Nikolai Kosjar committed
398
class FlipLogicalOperandsOp: public CppQuickFixOperation
399 400
{
public:
Nikolai Kosjar's avatar
Nikolai Kosjar committed
401 402 403 404 405
    FlipLogicalOperandsOp(const CppQuickFixInterface &interface, int priority,
                          BinaryExpressionAST *binary, QString replacement)
        : CppQuickFixOperation(interface)
        , binary(binary)
        , replacement(replacement)
406
    {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
407 408
        setPriority(priority);
    }
409

Nikolai Kosjar's avatar
Nikolai Kosjar committed
410 411 412 413 414 415 416
    QString description() const
    {
        if (replacement.isEmpty())
            return QApplication::translate("CppTools::QuickFix", "Swap Operands");
        else
            return QApplication::translate("CppTools::QuickFix", "Rewrite Using %1").arg(replacement);
    }
417

Nikolai Kosjar's avatar
Nikolai Kosjar committed
418 419 420 421
    void perform()
    {
        CppRefactoringChanges refactoring(snapshot());
        CppRefactoringFilePtr currentFile = refactoring.file(fileName());
422

Nikolai Kosjar's avatar
Nikolai Kosjar committed
423
        ChangeSet changes;
424 425
        changes.flip(currentFile->range(binary->left_expression),
                     currentFile->range(binary->right_expression));
426
        if (!replacement.isEmpty())
Nikolai Kosjar's avatar
Nikolai Kosjar committed
427
            changes.replace(currentFile->range(binary->binary_op_token), replacement);
428

Nikolai Kosjar's avatar
Nikolai Kosjar committed
429 430
        currentFile->setChangeSet(changes);
        currentFile->apply();
431 432 433
    }

private:
Nikolai Kosjar's avatar
Nikolai Kosjar committed
434 435 436
    BinaryExpressionAST *binary;
    QString replacement;
};
437

438 439
} // anonymous namespace

Nikolai Kosjar's avatar
Nikolai Kosjar committed
440 441 442 443
void FlipLogicalOperands::match(const CppQuickFixInterface &interface, QuickFixOperations &result)
{
    const QList<AST *> &path = interface->path();
    CppRefactoringFilePtr file = interface->currentFile();
444

Nikolai Kosjar's avatar
Nikolai Kosjar committed
445 446
    int index = path.size() - 1;
    BinaryExpressionAST *binary = path.at(index)->asBinaryExpression();
447
    if (!binary)
Nikolai Kosjar's avatar
Nikolai Kosjar committed
448
        return;
449
    if (!interface->isCursorOn(binary->binary_op_token))
Nikolai Kosjar's avatar
Nikolai Kosjar committed
450
        return;
451

Nikolai Kosjar's avatar
Nikolai Kosjar committed
452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474
    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;
    }
475

Nikolai Kosjar's avatar
Nikolai Kosjar committed
476 477 478 479 480 481
    QString replacement;
    if (flipToken != T_EOF_SYMBOL) {
        Token tok;
        tok.f.kind = flipToken;
        replacement = QLatin1String(tok.spell());
    }
482

Nikolai Kosjar's avatar
Nikolai Kosjar committed
483 484 485
    result.append(QuickFixOperation::Ptr(
        new FlipLogicalOperandsOp(interface, index, binary, replacement)));
}
486

487 488
namespace {

Nikolai Kosjar's avatar
Nikolai Kosjar committed
489
class RewriteLogicalAndOp: public CppQuickFixOperation
490 491
{
public:
Nikolai Kosjar's avatar
Nikolai Kosjar committed
492 493 494 495 496 497 498 499
    QSharedPointer<ASTPatternBuilder> mk;
    UnaryExpressionAST *left;
    UnaryExpressionAST *right;
    BinaryExpressionAST *pattern;

    RewriteLogicalAndOp(const CppQuickFixInterface &interface)
        : CppQuickFixOperation(interface)
        , mk(new ASTPatternBuilder)
500
    {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
501 502 503 504
        left = mk->UnaryExpression();
        right = mk->UnaryExpression();
        pattern = mk->BinaryExpression(left, right);
    }
505

Nikolai Kosjar's avatar
Nikolai Kosjar committed
506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524
    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();
    }
};
525

526 527
} // anonymous namespace

Nikolai Kosjar's avatar
Nikolai Kosjar committed
528 529 530 531 532
void RewriteLogicalAnd::match(const CppQuickFixInterface &interface, QuickFixOperations &result)
{
    BinaryExpressionAST *expression = 0;
    const QList<AST *> &path = interface->path();
    CppRefactoringFilePtr file = interface->currentFile();
533

Nikolai Kosjar's avatar
Nikolai Kosjar committed
534 535 536 537 538
    int index = path.size() - 1;
    for (; index != -1; --index) {
        expression = path.at(index)->asBinaryExpression();
        if (expression)
            break;
539 540
    }

541
    if (!expression)
Nikolai Kosjar's avatar
Nikolai Kosjar committed
542
        return;
543

544
    if (!interface->isCursorOn(expression->binary_op_token))
Nikolai Kosjar's avatar
Nikolai Kosjar committed
545
        return;
546

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
549 550 551 552
    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)) {
553 554
        op->setDescription(QApplication::translate("CppTools::QuickFix",
                                                   "Rewrite Condition Using ||"));
Nikolai Kosjar's avatar
Nikolai Kosjar committed
555 556 557 558
        op->setPriority(index);
        result.append(op);
    }
}
559

Nikolai Kosjar's avatar
Nikolai Kosjar committed
560 561
bool SplitSimpleDeclaration::checkDeclaration(SimpleDeclarationAST *declaration)
{
562
    if (!declaration->semicolon_token)
Nikolai Kosjar's avatar
Nikolai Kosjar committed
563
        return false;
564

565
    if (!declaration->decl_specifier_list)
Nikolai Kosjar's avatar
Nikolai Kosjar committed
566
        return false;
567

Nikolai Kosjar's avatar
Nikolai Kosjar committed
568 569
    for (SpecifierListAST *it = declaration->decl_specifier_list; it; it = it->next) {
        SpecifierAST *specifier = it->value;
570

Nikolai Kosjar's avatar
Nikolai Kosjar committed
571
        if (specifier->asEnumSpecifier() != 0)
572 573
            return false;

Nikolai Kosjar's avatar
Nikolai Kosjar committed
574
        else if (specifier->asClassSpecifier() != 0)
575
            return false;
Nikolai Kosjar's avatar
Nikolai Kosjar committed
576
    }
577

578
    if (!declaration->declarator_list)
Nikolai Kosjar's avatar
Nikolai Kosjar committed
579
        return false;
580

581
    else if (!declaration->declarator_list->next)
Nikolai Kosjar's avatar
Nikolai Kosjar committed
582
        return false;
583

Nikolai Kosjar's avatar
Nikolai Kosjar committed
584 585
    return true;
}
586

587 588
namespace {

Nikolai Kosjar's avatar
Nikolai Kosjar committed
589 590 591 592 593 594 595 596 597 598
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"));
599 600
    }

Nikolai Kosjar's avatar
Nikolai Kosjar committed
601
    void perform()
602
    {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
603 604
        CppRefactoringChanges refactoring(snapshot());
        CppRefactoringFilePtr currentFile = refactoring.file(fileName());
605

Nikolai Kosjar's avatar
Nikolai Kosjar committed
606
        ChangeSet changes;
607

Nikolai Kosjar's avatar
Nikolai Kosjar committed
608 609 610 611
        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);
612

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
615 616
        for (DeclaratorListAST *it = declaration->declarator_list->next; it; it = it->next) {
            DeclaratorAST *declarator = it->value;
617

Nikolai Kosjar's avatar
Nikolai Kosjar committed
618 619 620 621 622
            changes.insert(insertPos, QLatin1String("\n"));
            changes.copy(declSpecifiersStart, declSpecifiersEnd, insertPos);
            changes.insert(insertPos, QLatin1String(" "));
            changes.move(currentFile->range(declarator), insertPos);
            changes.insert(insertPos, QLatin1String(";"));
623

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
627
            prevDeclarator = declarator;
628
        }
Nikolai Kosjar's avatar
Nikolai Kosjar committed
629 630 631 632

        currentFile->setChangeSet(changes);
        currentFile->appendIndentRange(currentFile->range(declaration));
        currentFile->apply();
633 634 635
    }

private:
Nikolai Kosjar's avatar
Nikolai Kosjar committed
636 637
    SimpleDeclarationAST *declaration;
};
638

639 640
} // anonymous namespace

Nikolai Kosjar's avatar
Nikolai Kosjar committed
641 642 643 644 645 646 647
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();
648

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

652
        if (CoreDeclaratorAST *coreDecl = node->asCoreDeclarator()) {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
653
            core_declarator = coreDecl;
654
        } else if (SimpleDeclarationAST *simpleDecl = node->asSimpleDeclaration()) {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
655 656
            if (checkDeclaration(simpleDecl)) {
                SimpleDeclarationAST *declaration = simpleDecl;
657

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
661 662 663 664 665 666
                if (cursorPosition >= startOfDeclSpecifier && cursorPosition <= endOfDeclSpecifier) {
                    // the AST node under cursor is a specifier.
                    result.append(QuickFixOperation::Ptr(
                        new SplitSimpleDeclarationOp(interface, index, declaration)));
                    return;
                }
667

Nikolai Kosjar's avatar
Nikolai Kosjar committed
668 669 670 671 672 673
                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;
                }
674 675
            }

Nikolai Kosjar's avatar
Nikolai Kosjar committed
676
            return;
677
        }
Nikolai Kosjar's avatar
Nikolai Kosjar committed
678 679
    }
}
680

681 682
namespace {

Nikolai Kosjar's avatar
Nikolai Kosjar committed
683 684 685 686 687 688 689
class AddBracesToIfOp: public CppQuickFixOperation
{
public:
    AddBracesToIfOp(const CppQuickFixInterface &interface, int priority, StatementAST *statement)
        : CppQuickFixOperation(interface, priority)
        , _statement(statement)
    {
690
        setDescription(QApplication::translate("CppTools::QuickFix", "Add Curly Braces"));
Nikolai Kosjar's avatar
Nikolai Kosjar committed
691 692 693 694 695 696 697 698
    }

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

        ChangeSet changes;
699

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

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
706
        currentFile->setChangeSet(changes);
707
        currentFile->appendIndentRange(ChangeSet::Range(start, end));
Nikolai Kosjar's avatar
Nikolai Kosjar committed
708 709 710 711 712 713 714
        currentFile->apply();
    }

private:
    StatementAST *_statement;
};

715 716
} // anonymous namespace

Nikolai Kosjar's avatar
Nikolai Kosjar committed
717
void AddBracesToIf::match(const CppQuickFixInterface &interface, QuickFixOperations &result)
718
{
Nikolai Kosjar's avatar
Nikolai Kosjar committed
719
    const QList<AST *> &path = interface->path();
720

Nikolai Kosjar's avatar
Nikolai Kosjar committed
721 722 723 724
    // 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
725
        && !ifStatement->statement->asCompoundStatement()) {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
726 727 728 729 730 731 732 733
        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) {
734
        IfStatementAST *ifStatement = path.at(index)->asIfStatement();
Nikolai Kosjar's avatar
Nikolai Kosjar committed
735 736
        if (ifStatement && ifStatement->statement
            && interface->isCursorOn(ifStatement->statement)
737
            && !ifStatement->statement->asCompoundStatement()) {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
738 739
            result.append(QuickFixOperation::Ptr(
                new AddBracesToIfOp(interface, index, ifStatement->statement)));
740
            return;
741
        }
Nikolai Kosjar's avatar
Nikolai Kosjar committed
742
    }
743

Nikolai Kosjar's avatar
Nikolai Kosjar committed
744 745 746
    // ### This could very well be extended to the else branch
    // and other nodes entirely.
}
747

748 749
namespace {

Nikolai Kosjar's avatar
Nikolai Kosjar committed
750 751 752 753 754 755 756 757 758
class MoveDeclarationOutOfIfOp: public CppQuickFixOperation
{
public:
    MoveDeclarationOutOfIfOp(const CppQuickFixInterface &interface)
        : CppQuickFixOperation(interface)
    {
        setDescription(QApplication::translate("CppTools::QuickFix",
                                               "Move Declaration out of Condition"));

759 760 761 762 763
        reset();
    }

    void reset()
    {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
764 765
        condition = mk.Condition();
        pattern = mk.IfStatement(condition);
766 767
    }

Nikolai Kosjar's avatar
Nikolai Kosjar committed
768
    void perform()
769
    {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
770 771
        CppRefactoringChanges refactoring(snapshot());
        CppRefactoringFilePtr currentFile = refactoring.file(fileName());
772

Nikolai Kosjar's avatar
Nikolai Kosjar committed
773
        ChangeSet changes;
774

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
777 778 779
        int insertPos = currentFile->startOf(pattern);
        changes.move(currentFile->range(condition), insertPos);
        changes.insert(insertPos, QLatin1String(";\n"));
780

Nikolai Kosjar's avatar
Nikolai Kosjar committed
781 782 783 784
        currentFile->setChangeSet(changes);
        currentFile->appendIndentRange(currentFile->range(pattern));
        currentFile->apply();
    }
785

Nikolai Kosjar's avatar
Nikolai Kosjar committed
786 787 788 789 790
    ASTMatcher matcher;
    ASTPatternBuilder mk;
    ConditionAST *condition;
    IfStatementAST *pattern;
    CoreDeclaratorAST *core;
791 792
};

793 794
} // anonymous namespace

Nikolai Kosjar's avatar
Nikolai Kosjar committed
795 796
void MoveDeclarationOutOfIf::match(const CppQuickFixInterface &interface,
                                   QuickFixOperations &result)
797
{
Nikolai Kosjar's avatar
Nikolai Kosjar committed
798 799 800 801 802 803 804 805 806 807
    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;
808
                if (!op->core)
Nikolai Kosjar's avatar
Nikolai Kosjar committed
809
                    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

                op->reset();
818 819 820
            }
        }
    }
Nikolai Kosjar's avatar
Nikolai Kosjar committed
821
}
822

823 824
namespace {

Nikolai Kosjar's avatar
Nikolai Kosjar committed
825 826 827 828 829
class MoveDeclarationOutOfWhileOp: public CppQuickFixOperation
{
public:
    MoveDeclarationOutOfWhileOp(const CppQuickFixInterface &interface)
        : CppQuickFixOperation(interface)
830
    {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
831 832
        setDescription(QApplication::translate("CppTools::QuickFix",
                                               "Move Declaration out of Condition"));
833 834
        reset();
    }
835

836 837
    void reset()
    {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
838 839 840
        condition = mk.Condition();
        pattern = mk.WhileStatement(condition);
    }
841

Nikolai Kosjar's avatar
Nikolai Kosjar committed
842 843 844 845
    void perform()
    {
        CppRefactoringChanges refactoring(snapshot());
        CppRefactoringFilePtr currentFile = refactoring.file(fileName());
846

Nikolai Kosjar's avatar
Nikolai Kosjar committed
847
        ChangeSet changes;
848

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
852 853 854 855 856
        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"));
857

Nikolai Kosjar's avatar
Nikolai Kosjar committed
858 859 860 861
        currentFile->setChangeSet(changes);
        currentFile->appendIndentRange(currentFile->range(pattern));
        currentFile->apply();
    }
862

Nikolai Kosjar's avatar
Nikolai Kosjar committed
863 864 865 866 867 868
    ASTMatcher matcher;
    ASTPatternBuilder mk;
    ConditionAST *condition;
    WhileStatementAST *pattern;
    CoreDeclaratorAST *core;
};
869

870 871
} // anonymous namespace

Nikolai Kosjar's avatar
Nikolai Kosjar committed
872 873
void MoveDeclarationOutOfWhile::match(const CppQuickFixInterface &interface,
                                      QuickFixOperations &result)
874
{
Nikolai Kosjar's avatar
Nikolai Kosjar committed
875 876
    const QList<AST *> &path = interface->path();
    QSharedPointer<MoveDeclarationOutOfWhileOp> op(new MoveDeclarationOutOfWhileOp(interface));
877

Nikolai Kosjar's avatar
Nikolai Kosjar committed
878 879 880 881 882 883
    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;
884

885
                if (!op->core)
Nikolai Kosjar's avatar
Nikolai Kosjar committed
886
                    return;
887

888
                if (!declarator->equal_token)
Nikolai Kosjar's avatar
Nikolai Kosjar committed
889
                    return;
890

891
                if (!declarator->initializer)
Nikolai Kosjar's avatar
Nikolai Kosjar committed
892
                    return;
893

Nikolai Kosjar's avatar
Nikolai Kosjar committed
894 895 896 897
                if (interface->isCursorOn(op->core)) {
                    op->setPriority(index);
                    result.append(op);
                    return;
898
                }
899 900

                op->reset();
901 902 903
            }
        }
    }
Nikolai Kosjar's avatar
Nikolai Kosjar committed
904
}
905

906 907
namespace {

Nikolai Kosjar's avatar
Nikolai Kosjar committed
908 909 910 911 912 913 914 915
class SplitIfStatementOp: public CppQuickFixOperation
{
public:
    SplitIfStatementOp(const CppQuickFixInterface &interface, int priority,
                       IfStatementAST *pattern, BinaryExpressionAST *condition)
        : CppQuickFixOperation(interface, priority)
        , pattern(pattern)
        , condition(condition)
Erik Verbruggen's avatar