cppquickfixes.cpp 215 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

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);
124
    plugIn->addAutoReleasedObject(new ExtractLiteralAsParameter);
Nikolai Kosjar's avatar
Nikolai Kosjar committed
125 126 127
    plugIn->addAutoReleasedObject(new GenerateGetterSetter);
    plugIn->addAutoReleasedObject(new InsertDeclFromDef);
    plugIn->addAutoReleasedObject(new InsertDefFromDecl);
128 129 130

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

    plugIn->addAutoReleasedObject(new AssignToLocalVariable);
133 134

    plugIn->addAutoReleasedObject(new InsertVirtualMethods);
135 136

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

139 140 141 142
// In the following anonymous namespace all functions are collected, which could be of interest for
// different quick fixes.
namespace {

143 144 145 146 147 148
enum DefPos {
    DefPosInsideClass,
    DefPosOutsideClass,
    DefPosImplementationFile
};

149
InsertionLocation insertLocationForMethodDefinition(Symbol *symbol, const bool useSymbolFinder,
150 151 152 153 154 155 156
                                                    CppRefactoringChanges& refactoring,
                                                    const QString& fileName)
{
    QTC_ASSERT(symbol, return InsertionLocation());

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

167 168
    // ...failed,
    // if class member try to get position right after class
169
    CppRefactoringFilePtr file = refactoring.file(fileName);
170 171 172 173 174 175 176 177 178 179 180 181 182
    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
183 184 185 186 187 188 189 190 191 192
    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);
}

193
inline bool isQtStringLiteral(const QByteArray &id)
194 195 196 197
{
    return id == "QLatin1String" || id == "QLatin1Literal" || id == "QStringLiteral";
}

198
inline bool isQtStringTranslation(const QByteArray &id)
199 200 201 202
{
    return id == "tr" || id == "trUtf8" || id == "translate" || id == "QT_TRANSLATE_NOOP";
}

203 204 205 206 207
Class *isMemberFunction(const LookupContext &context, Function *function)
{
    QTC_ASSERT(function, return 0);

    Scope *enclosingScope = function->enclosingScope();
208
    while (!(enclosingScope->isNamespace() || enclosingScope->isClass()))
209 210 211 212
        enclosingScope = enclosingScope->enclosingScope();
    QTC_ASSERT(enclosingScope != 0, return 0);

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

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

233 234 235 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 261 262 263 264 265 266 267 268 269 270
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)
        return 0; // anonymous function names are not valid c++

    // 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;
}

271 272 273 274 275
// 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;
276
    LineForNewIncludeDirective finder(file->document(), file->cppDocument()->resolvedIncludes(),
277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301
                                      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();
}

302 303 304 305 306 307
bool nameIncludesOperatorName(const Name *name)
{
    return name->isOperatorNameId()
        || (name->isQualifiedNameId() && name->asQualifiedNameId()->name()->isOperatorNameId());
}

308 309 310 311
} // anonymous namespace

namespace {

Nikolai Kosjar's avatar
Nikolai Kosjar committed
312
class InverseLogicalComparisonOp: public CppQuickFixOperation
313 314
{
public:
Nikolai Kosjar's avatar
Nikolai Kosjar committed
315 316 317 318
    InverseLogicalComparisonOp(const CppQuickFixInterface &interface, int priority,
                               BinaryExpressionAST *binary, Kind invertToken)
        : CppQuickFixOperation(interface, priority)
        , binary(binary), nested(0), negation(0)
319
    {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
320 321 322
        Token tok;
        tok.f.kind = invertToken;
        replacement = QLatin1String(tok.spell());
323

Nikolai Kosjar's avatar
Nikolai Kosjar committed
324 325 326
        // check for enclosing nested expression
        if (priority - 1 >= 0)
            nested = interface->path()[priority - 1]->asNestedExpression();
327

Nikolai Kosjar's avatar
Nikolai Kosjar committed
328 329 330
        // check for ! before parentheses
        if (nested && priority - 2 >= 0) {
            negation = interface->path()[priority - 2]->asUnaryExpression();
331 332
            if (negation
                    && !interface->currentFile()->tokenAt(negation->unary_op_token).is(T_EXCLAIM)) {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
333
                negation = 0;
334
            }
335 336 337
        }
    }

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
343 344 345 346 347 348 349 350 351 352 353 354 355 356
    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(")"));
357
        }
Nikolai Kosjar's avatar
Nikolai Kosjar committed
358 359 360 361
        changes.replace(currentFile->range(binary->binary_op_token), replacement);
        currentFile->setChangeSet(changes);
        currentFile->apply();
    }
362

Nikolai Kosjar's avatar
Nikolai Kosjar committed
363 364 365 366
private:
    BinaryExpressionAST *binary;
    NestedExpressionAST *nested;
    UnaryExpressionAST *negation;
367

Nikolai Kosjar's avatar
Nikolai Kosjar committed
368
    QString replacement;
369 370
};

371 372
} // anonymous namespace

Nikolai Kosjar's avatar
Nikolai Kosjar committed
373 374 375 376 377 378 379 380
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();
381
    if (!binary)
Nikolai Kosjar's avatar
Nikolai Kosjar committed
382
        return;
383
    if (!interface->isCursorOn(binary->binary_op_token))
Nikolai Kosjar's avatar
Nikolai Kosjar committed
384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408
        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;
    }
409

Nikolai Kosjar's avatar
Nikolai Kosjar committed
410 411 412
    result.append(CppQuickFixOperation::Ptr(
        new InverseLogicalComparisonOp(interface, index, binary, invertToken)));
}
413

414 415
namespace {

Nikolai Kosjar's avatar
Nikolai Kosjar committed
416
class FlipLogicalOperandsOp: public CppQuickFixOperation
417 418
{
public:
Nikolai Kosjar's avatar
Nikolai Kosjar committed
419 420 421 422 423
    FlipLogicalOperandsOp(const CppQuickFixInterface &interface, int priority,
                          BinaryExpressionAST *binary, QString replacement)
        : CppQuickFixOperation(interface)
        , binary(binary)
        , replacement(replacement)
424
    {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
425 426
        setPriority(priority);
    }
427

Nikolai Kosjar's avatar
Nikolai Kosjar committed
428 429 430 431 432 433 434
    QString description() const
    {
        if (replacement.isEmpty())
            return QApplication::translate("CppTools::QuickFix", "Swap Operands");
        else
            return QApplication::translate("CppTools::QuickFix", "Rewrite Using %1").arg(replacement);
    }
435

Nikolai Kosjar's avatar
Nikolai Kosjar committed
436 437 438 439
    void perform()
    {
        CppRefactoringChanges refactoring(snapshot());
        CppRefactoringFilePtr currentFile = refactoring.file(fileName());
440

Nikolai Kosjar's avatar
Nikolai Kosjar committed
441
        ChangeSet changes;
442 443
        changes.flip(currentFile->range(binary->left_expression),
                     currentFile->range(binary->right_expression));
444
        if (!replacement.isEmpty())
Nikolai Kosjar's avatar
Nikolai Kosjar committed
445
            changes.replace(currentFile->range(binary->binary_op_token), replacement);
446

Nikolai Kosjar's avatar
Nikolai Kosjar committed
447 448
        currentFile->setChangeSet(changes);
        currentFile->apply();
449 450 451
    }

private:
Nikolai Kosjar's avatar
Nikolai Kosjar committed
452 453 454
    BinaryExpressionAST *binary;
    QString replacement;
};
455

456 457
} // anonymous namespace

Nikolai Kosjar's avatar
Nikolai Kosjar committed
458 459 460 461
void FlipLogicalOperands::match(const CppQuickFixInterface &interface, QuickFixOperations &result)
{
    const QList<AST *> &path = interface->path();
    CppRefactoringFilePtr file = interface->currentFile();
462

Nikolai Kosjar's avatar
Nikolai Kosjar committed
463 464
    int index = path.size() - 1;
    BinaryExpressionAST *binary = path.at(index)->asBinaryExpression();
465
    if (!binary)
Nikolai Kosjar's avatar
Nikolai Kosjar committed
466
        return;
467
    if (!interface->isCursorOn(binary->binary_op_token))
Nikolai Kosjar's avatar
Nikolai Kosjar committed
468
        return;
469

Nikolai Kosjar's avatar
Nikolai Kosjar committed
470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492
    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;
    }
493

Nikolai Kosjar's avatar
Nikolai Kosjar committed
494 495 496 497 498 499
    QString replacement;
    if (flipToken != T_EOF_SYMBOL) {
        Token tok;
        tok.f.kind = flipToken;
        replacement = QLatin1String(tok.spell());
    }
500

Nikolai Kosjar's avatar
Nikolai Kosjar committed
501 502 503
    result.append(QuickFixOperation::Ptr(
        new FlipLogicalOperandsOp(interface, index, binary, replacement)));
}
504

505 506
namespace {

Nikolai Kosjar's avatar
Nikolai Kosjar committed
507
class RewriteLogicalAndOp: public CppQuickFixOperation
508 509
{
public:
Nikolai Kosjar's avatar
Nikolai Kosjar committed
510 511 512 513 514 515 516 517
    QSharedPointer<ASTPatternBuilder> mk;
    UnaryExpressionAST *left;
    UnaryExpressionAST *right;
    BinaryExpressionAST *pattern;

    RewriteLogicalAndOp(const CppQuickFixInterface &interface)
        : CppQuickFixOperation(interface)
        , mk(new ASTPatternBuilder)
518
    {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
519 520 521 522
        left = mk->UnaryExpression();
        right = mk->UnaryExpression();
        pattern = mk->BinaryExpression(left, right);
    }
523

Nikolai Kosjar's avatar
Nikolai Kosjar committed
524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542
    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();
    }
};
543

544 545
} // anonymous namespace

Nikolai Kosjar's avatar
Nikolai Kosjar committed
546 547 548 549 550
void RewriteLogicalAnd::match(const CppQuickFixInterface &interface, QuickFixOperations &result)
{
    BinaryExpressionAST *expression = 0;
    const QList<AST *> &path = interface->path();
    CppRefactoringFilePtr file = interface->currentFile();
551

Nikolai Kosjar's avatar
Nikolai Kosjar committed
552 553 554 555 556
    int index = path.size() - 1;
    for (; index != -1; --index) {
        expression = path.at(index)->asBinaryExpression();
        if (expression)
            break;
557 558
    }

559
    if (!expression)
Nikolai Kosjar's avatar
Nikolai Kosjar committed
560
        return;
561

562
    if (!interface->isCursorOn(expression->binary_op_token))
Nikolai Kosjar's avatar
Nikolai Kosjar committed
563
        return;
564

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
567 568 569 570
    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)) {
571 572
        op->setDescription(QApplication::translate("CppTools::QuickFix",
                                                   "Rewrite Condition Using ||"));
Nikolai Kosjar's avatar
Nikolai Kosjar committed
573 574 575 576
        op->setPriority(index);
        result.append(op);
    }
}
577

Nikolai Kosjar's avatar
Nikolai Kosjar committed
578 579
bool SplitSimpleDeclaration::checkDeclaration(SimpleDeclarationAST *declaration)
{
580
    if (!declaration->semicolon_token)
Nikolai Kosjar's avatar
Nikolai Kosjar committed
581
        return false;
582

583
    if (!declaration->decl_specifier_list)
Nikolai Kosjar's avatar
Nikolai Kosjar committed
584
        return false;
585

Nikolai Kosjar's avatar
Nikolai Kosjar committed
586 587
    for (SpecifierListAST *it = declaration->decl_specifier_list; it; it = it->next) {
        SpecifierAST *specifier = it->value;
588

Nikolai Kosjar's avatar
Nikolai Kosjar committed
589
        if (specifier->asEnumSpecifier() != 0)
590 591
            return false;

Nikolai Kosjar's avatar
Nikolai Kosjar committed
592
        else if (specifier->asClassSpecifier() != 0)
593
            return false;
Nikolai Kosjar's avatar
Nikolai Kosjar committed
594
    }
595

596
    if (!declaration->declarator_list)
Nikolai Kosjar's avatar
Nikolai Kosjar committed
597
        return false;
598

599
    else if (!declaration->declarator_list->next)
Nikolai Kosjar's avatar
Nikolai Kosjar committed
600
        return false;
601

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

605 606
namespace {

Nikolai Kosjar's avatar
Nikolai Kosjar committed
607 608 609 610 611 612 613 614 615 616
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"));
617 618
    }

Nikolai Kosjar's avatar
Nikolai Kosjar committed
619
    void perform()
620
    {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
621 622
        CppRefactoringChanges refactoring(snapshot());
        CppRefactoringFilePtr currentFile = refactoring.file(fileName());
623

Nikolai Kosjar's avatar
Nikolai Kosjar committed
624
        ChangeSet changes;
625

Nikolai Kosjar's avatar
Nikolai Kosjar committed
626 627 628 629
        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);
630

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
633 634
        for (DeclaratorListAST *it = declaration->declarator_list->next; it; it = it->next) {
            DeclaratorAST *declarator = it->value;
635

Nikolai Kosjar's avatar
Nikolai Kosjar committed
636 637 638 639 640
            changes.insert(insertPos, QLatin1String("\n"));
            changes.copy(declSpecifiersStart, declSpecifiersEnd, insertPos);
            changes.insert(insertPos, QLatin1String(" "));
            changes.move(currentFile->range(declarator), insertPos);
            changes.insert(insertPos, QLatin1String(";"));
641

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
645
            prevDeclarator = declarator;
646
        }
Nikolai Kosjar's avatar
Nikolai Kosjar committed
647 648 649 650

        currentFile->setChangeSet(changes);
        currentFile->appendIndentRange(currentFile->range(declaration));
        currentFile->apply();
651 652 653
    }

private:
Nikolai Kosjar's avatar
Nikolai Kosjar committed
654 655
    SimpleDeclarationAST *declaration;
};
656

657 658
} // anonymous namespace

Nikolai Kosjar's avatar
Nikolai Kosjar committed
659 660 661 662 663 664 665
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();
666

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

670
        if (CoreDeclaratorAST *coreDecl = node->asCoreDeclarator()) {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
671
            core_declarator = coreDecl;
672
        } else if (SimpleDeclarationAST *simpleDecl = node->asSimpleDeclaration()) {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
673 674
            if (checkDeclaration(simpleDecl)) {
                SimpleDeclarationAST *declaration = simpleDecl;
675

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
679 680 681 682 683 684
                if (cursorPosition >= startOfDeclSpecifier && cursorPosition <= endOfDeclSpecifier) {
                    // the AST node under cursor is a specifier.
                    result.append(QuickFixOperation::Ptr(
                        new SplitSimpleDeclarationOp(interface, index, declaration)));
                    return;
                }
685

Nikolai Kosjar's avatar
Nikolai Kosjar committed
686 687 688 689 690 691
                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;
                }
692 693
            }

Nikolai Kosjar's avatar
Nikolai Kosjar committed
694
            return;
695
        }
Nikolai Kosjar's avatar
Nikolai Kosjar committed
696 697
    }
}
698

699 700
namespace {

Nikolai Kosjar's avatar
Nikolai Kosjar committed
701 702 703 704 705 706 707
class AddBracesToIfOp: public CppQuickFixOperation
{
public:
    AddBracesToIfOp(const CppQuickFixInterface &interface, int priority, StatementAST *statement)
        : CppQuickFixOperation(interface, priority)
        , _statement(statement)
    {
708
        setDescription(QApplication::translate("CppTools::QuickFix", "Add Curly Braces"));
Nikolai Kosjar's avatar
Nikolai Kosjar committed
709 710 711 712 713 714 715 716
    }

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

        ChangeSet changes;
717

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

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
724
        currentFile->setChangeSet(changes);
725
        currentFile->appendIndentRange(ChangeSet::Range(start, end));
Nikolai Kosjar's avatar
Nikolai Kosjar committed
726 727 728 729 730 731 732
        currentFile->apply();
    }

private:
    StatementAST *_statement;
};

733 734
} // anonymous namespace

Nikolai Kosjar's avatar
Nikolai Kosjar committed
735
void AddBracesToIf::match(const CppQuickFixInterface &interface, QuickFixOperations &result)
736
{
Nikolai Kosjar's avatar
Nikolai Kosjar committed
737
    const QList<AST *> &path = interface->path();
738

Nikolai Kosjar's avatar
Nikolai Kosjar committed
739 740 741 742
    // 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
743
        && !ifStatement->statement->asCompoundStatement()) {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
744 745 746 747 748 749 750 751
        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) {
752
        IfStatementAST *ifStatement = path.at(index)->asIfStatement();
Nikolai Kosjar's avatar
Nikolai Kosjar committed
753 754
        if (ifStatement && ifStatement->statement
            && interface->isCursorOn(ifStatement->statement)
755
            && !ifStatement->statement->asCompoundStatement()) {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
756 757
            result.append(QuickFixOperation::Ptr(
                new AddBracesToIfOp(interface, index, ifStatement->statement)));
758
            return;
759
        }
Nikolai Kosjar's avatar
Nikolai Kosjar committed
760
    }
761

Nikolai Kosjar's avatar
Nikolai Kosjar committed
762 763 764
    // ### This could very well be extended to the else branch
    // and other nodes entirely.
}
765

766 767
namespace {

Nikolai Kosjar's avatar
Nikolai Kosjar committed
768 769 770 771 772 773 774 775 776
class MoveDeclarationOutOfIfOp: public CppQuickFixOperation
{
public:
    MoveDeclarationOutOfIfOp(const CppQuickFixInterface &interface)
        : CppQuickFixOperation(interface)
    {
        setDescription(QApplication::translate("CppTools::QuickFix",
                                               "Move Declaration out of Condition"));

777 778 779 780 781
        reset();
    }

    void reset()
    {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
782 783
        condition = mk.Condition();
        pattern = mk.IfStatement(condition);
784 785
    }

Nikolai Kosjar's avatar
Nikolai Kosjar committed
786
    void perform()
787
    {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
788 789
        CppRefactoringChanges refactoring(snapshot());
        CppRefactoringFilePtr currentFile = refactoring.file(fileName());
790

Nikolai Kosjar's avatar
Nikolai Kosjar committed
791
        ChangeSet changes;
792

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
795 796 797
        int insertPos = currentFile->startOf(pattern);
        changes.move(currentFile->range(condition), insertPos);
        changes.insert(insertPos, QLatin1String(";\n"));
798

Nikolai Kosjar's avatar
Nikolai Kosjar committed
799 800 801 802
        currentFile->setChangeSet(changes);
        currentFile->appendIndentRange(currentFile->range(pattern));
        currentFile->apply();
    }
803

Nikolai Kosjar's avatar
Nikolai Kosjar committed
804 805 806 807 808
    ASTMatcher matcher;
    ASTPatternBuilder mk;
    ConditionAST *condition;
    IfStatementAST *pattern;
    CoreDeclaratorAST *core;
809 810
};

811 812
} // anonymous namespace

Nikolai Kosjar's avatar
Nikolai Kosjar committed
813 814
void MoveDeclarationOutOfIf::match(const CppQuickFixInterface &interface,
                                   QuickFixOperations &result)
815
{
Nikolai Kosjar's avatar
Nikolai Kosjar committed
816 817 818 819 820 821 822 823 824 825
    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;
826
                if (!op->core)
Nikolai Kosjar's avatar
Nikolai Kosjar committed
827
                    return;
828

Nikolai Kosjar's avatar
Nikolai Kosjar committed
829 830 831 832
                if (interface->isCursorOn(op->core)) {
                    op->setPriority(index);
                    result.append(op);
                    return;
833
                }
834 835

                op->reset();
836 837 838
            }
        }
    }
Nikolai Kosjar's avatar
Nikolai Kosjar committed
839
}
840

841 842
namespace {

Nikolai Kosjar's avatar
Nikolai Kosjar committed
843 844 845 846 847
class MoveDeclarationOutOfWhileOp: public CppQuickFixOperation
{
public:
    MoveDeclarationOutOfWhileOp(const CppQuickFixInterface &interface)
        : CppQuickFixOperation(interface)
848
    {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
849 850
        setDescription(QApplication::translate("CppTools::QuickFix",
                                               "Move Declaration out of Condition"));
851 852
        reset();
    }
853

854 855
    void reset()
    {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
856 857 858
        condition = mk.Condition();
        pattern = mk.WhileStatement(condition);
    }
859

Nikolai Kosjar's avatar
Nikolai Kosjar committed
860 861 862 863
    void perform()
    {
        CppRefactoringChanges refactoring(snapshot());
        CppRefactoringFilePtr currentFile = refactoring.file(fileName());
864

Nikolai Kosjar's avatar
Nikolai Kosjar committed
865
        ChangeSet changes;
866

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
870 871 872 873 874
        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"));
875

Nikolai Kosjar's avatar
Nikolai Kosjar committed
876 877 878 879
        currentFile->setChangeSet(changes);
        currentFile->appendIndentRange(currentFile->range(pattern));
        currentFile->apply();
    }
880

Nikolai Kosjar's avatar
Nikolai Kosjar committed
881 882 883 884 885 886
    ASTMatcher matcher;
    ASTPatternBuilder mk;
    ConditionAST *condition;
    WhileStatementAST *pattern;
    CoreDeclaratorAST *core;
};
887

888 889
} // anonymous namespace

Nikolai Kosjar's avatar
Nikolai Kosjar committed
890 891
void MoveDeclarationOutOfWhile::match(const CppQuickFixInterface &interface,
                                      QuickFixOperations &result)
892
{
Nikolai Kosjar's avatar
Nikolai Kosjar committed
893 894
    const QList<AST *> &path = interface->path();
    QSharedPointer<MoveDeclarationOutOfWhileOp> op(new MoveDeclarationOutOfWhileOp(interface));
895

Nikolai Kosjar's avatar
Nikolai Kosjar committed
896 897 898 899 900 901
    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;
902

903
                if (!op->core)
Nikolai Kosjar's avatar
Nikolai Kosjar committed
904
                    return;
905

906
                if (!declarator->equal_token)
Nikolai Kosjar's avatar
Nikolai Kosjar committed
907
                    return;
908

909
                if (!declarator->initializer)
Nikolai Kosjar's avatar
Nikolai Kosjar committed
910
                    return;
911

Nikolai Kosjar's avatar
Nikolai Kosjar committed
912 913 914 915
                if (interface->isCursorOn(op->core)) {
                    op->setPriority(index);
                    result.append(op);
                    return;
916
                }
917 918

                op->reset();
919 920 921
            }
        }
    }
Nikolai Kosjar's avatar
Nikolai Kosjar committed
922
}
923