cppquickfixes.cpp 192 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 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163
InsertionLocation insertLocationForMethodDefinition(Symbol *symbol,
                                                    CppRefactoringChanges& refactoring,
                                                    const QString& fileName)
{
    QTC_ASSERT(symbol, return InsertionLocation());

    // Try to find optimal location
    const InsertionPointLocator locator(refactoring);
    const QList<InsertionLocation> list = locator.methodDefinition(symbol, symbol->asDeclaration(),
                                                                   fileName);
    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 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229
Class *isMemberFunction(const LookupContext &context, Function *function)
{
    QTC_ASSERT(function, return 0);

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

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

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

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

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

    return 0;
}

230 231 232 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
// Given include is e.g. "afile.h" or <afile.h> (quotes/angle brackets included!).
void insertNewIncludeDirective(const QString &include, CppRefactoringFilePtr file)
{
    // Find optimal position
    using namespace IncludeUtils;
    LineForNewIncludeDirective finder(file->document(), file->cppDocument()->includes(),
                                      LineForNewIncludeDirective::IgnoreMocIncludes,
                                      LineForNewIncludeDirective::AutoDetect);
    unsigned newLinesToPrepend = 0;
    unsigned newLinesToAppend = 0;
    const int insertLine = finder(include, &newLinesToPrepend, &newLinesToAppend);
    QTC_ASSERT(insertLine >= 1, return);
    const int insertPosition = file->position(insertLine, 1);
    QTC_ASSERT(insertPosition >= 0, return);

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

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

261 262 263 264
} // anonymous namespace

namespace {

Nikolai Kosjar's avatar
Nikolai Kosjar committed
265
class InverseLogicalComparisonOp: public CppQuickFixOperation
266 267
{
public:
Nikolai Kosjar's avatar
Nikolai Kosjar committed
268 269 270 271
    InverseLogicalComparisonOp(const CppQuickFixInterface &interface, int priority,
                               BinaryExpressionAST *binary, Kind invertToken)
        : CppQuickFixOperation(interface, priority)
        , binary(binary), nested(0), negation(0)
272
    {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
273 274 275
        Token tok;
        tok.f.kind = invertToken;
        replacement = QLatin1String(tok.spell());
276

Nikolai Kosjar's avatar
Nikolai Kosjar committed
277 278 279
        // check for enclosing nested expression
        if (priority - 1 >= 0)
            nested = interface->path()[priority - 1]->asNestedExpression();
280

Nikolai Kosjar's avatar
Nikolai Kosjar committed
281 282 283 284 285
        // check for ! before parentheses
        if (nested && priority - 2 >= 0) {
            negation = interface->path()[priority - 2]->asUnaryExpression();
            if (negation && ! interface->currentFile()->tokenAt(negation->unary_op_token).is(T_EXCLAIM))
                negation = 0;
286 287 288
        }
    }

Nikolai Kosjar's avatar
Nikolai Kosjar committed
289
    QString description() const
290
    {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
291 292
        return QApplication::translate("CppTools::QuickFix", "Rewrite Using %1").arg(replacement);
    }
293

Nikolai Kosjar's avatar
Nikolai Kosjar committed
294 295 296 297 298 299 300 301 302 303 304 305 306 307
    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(")"));
308
        }
Nikolai Kosjar's avatar
Nikolai Kosjar committed
309 310 311 312
        changes.replace(currentFile->range(binary->binary_op_token), replacement);
        currentFile->setChangeSet(changes);
        currentFile->apply();
    }
313

Nikolai Kosjar's avatar
Nikolai Kosjar committed
314 315 316 317
private:
    BinaryExpressionAST *binary;
    NestedExpressionAST *nested;
    UnaryExpressionAST *negation;
318

Nikolai Kosjar's avatar
Nikolai Kosjar committed
319
    QString replacement;
320 321
};

322 323
} // anonymous namespace

Nikolai Kosjar's avatar
Nikolai Kosjar committed
324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359
void InverseLogicalComparison::match(const CppQuickFixInterface &interface,
                                     QuickFixOperations &result)
{
    CppRefactoringFilePtr file = interface->currentFile();

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

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
361 362 363
    result.append(CppQuickFixOperation::Ptr(
        new InverseLogicalComparisonOp(interface, index, binary, invertToken)));
}
364

365 366
namespace {

Nikolai Kosjar's avatar
Nikolai Kosjar committed
367
class FlipLogicalOperandsOp: public CppQuickFixOperation
368 369
{
public:
Nikolai Kosjar's avatar
Nikolai Kosjar committed
370 371 372 373 374
    FlipLogicalOperandsOp(const CppQuickFixInterface &interface, int priority,
                          BinaryExpressionAST *binary, QString replacement)
        : CppQuickFixOperation(interface)
        , binary(binary)
        , replacement(replacement)
375
    {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
376 377
        setPriority(priority);
    }
378

Nikolai Kosjar's avatar
Nikolai Kosjar committed
379 380 381 382 383 384 385
    QString description() const
    {
        if (replacement.isEmpty())
            return QApplication::translate("CppTools::QuickFix", "Swap Operands");
        else
            return QApplication::translate("CppTools::QuickFix", "Rewrite Using %1").arg(replacement);
    }
386

Nikolai Kosjar's avatar
Nikolai Kosjar committed
387 388 389 390
    void perform()
    {
        CppRefactoringChanges refactoring(snapshot());
        CppRefactoringFilePtr currentFile = refactoring.file(fileName());
391

Nikolai Kosjar's avatar
Nikolai Kosjar committed
392
        ChangeSet changes;
393 394
        changes.flip(currentFile->range(binary->left_expression),
                     currentFile->range(binary->right_expression));
Nikolai Kosjar's avatar
Nikolai Kosjar committed
395 396
        if (! replacement.isEmpty())
            changes.replace(currentFile->range(binary->binary_op_token), replacement);
397

Nikolai Kosjar's avatar
Nikolai Kosjar committed
398 399
        currentFile->setChangeSet(changes);
        currentFile->apply();
400 401 402
    }

private:
Nikolai Kosjar's avatar
Nikolai Kosjar committed
403 404 405
    BinaryExpressionAST *binary;
    QString replacement;
};
406

407 408
} // anonymous namespace

Nikolai Kosjar's avatar
Nikolai Kosjar committed
409 410 411 412
void FlipLogicalOperands::match(const CppQuickFixInterface &interface, QuickFixOperations &result)
{
    const QList<AST *> &path = interface->path();
    CppRefactoringFilePtr file = interface->currentFile();
413

Nikolai Kosjar's avatar
Nikolai Kosjar committed
414 415 416 417 418 419
    int index = path.size() - 1;
    BinaryExpressionAST *binary = path.at(index)->asBinaryExpression();
    if (! binary)
        return;
    if (! interface->isCursorOn(binary->binary_op_token))
        return;
420

Nikolai Kosjar's avatar
Nikolai Kosjar committed
421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443
    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;
    }
444

Nikolai Kosjar's avatar
Nikolai Kosjar committed
445 446 447 448 449 450
    QString replacement;
    if (flipToken != T_EOF_SYMBOL) {
        Token tok;
        tok.f.kind = flipToken;
        replacement = QLatin1String(tok.spell());
    }
451

Nikolai Kosjar's avatar
Nikolai Kosjar committed
452 453 454
    result.append(QuickFixOperation::Ptr(
        new FlipLogicalOperandsOp(interface, index, binary, replacement)));
}
455

456 457
namespace {

Nikolai Kosjar's avatar
Nikolai Kosjar committed
458
class RewriteLogicalAndOp: public CppQuickFixOperation
459 460
{
public:
Nikolai Kosjar's avatar
Nikolai Kosjar committed
461 462 463 464 465 466 467 468
    QSharedPointer<ASTPatternBuilder> mk;
    UnaryExpressionAST *left;
    UnaryExpressionAST *right;
    BinaryExpressionAST *pattern;

    RewriteLogicalAndOp(const CppQuickFixInterface &interface)
        : CppQuickFixOperation(interface)
        , mk(new ASTPatternBuilder)
469
    {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
470 471 472 473
        left = mk->UnaryExpression();
        right = mk->UnaryExpression();
        pattern = mk->BinaryExpression(left, right);
    }
474

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

495 496
} // anonymous namespace

Nikolai Kosjar's avatar
Nikolai Kosjar committed
497 498 499 500 501
void RewriteLogicalAnd::match(const CppQuickFixInterface &interface, QuickFixOperations &result)
{
    BinaryExpressionAST *expression = 0;
    const QList<AST *> &path = interface->path();
    CppRefactoringFilePtr file = interface->currentFile();
502

Nikolai Kosjar's avatar
Nikolai Kosjar committed
503 504 505 506 507
    int index = path.size() - 1;
    for (; index != -1; --index) {
        expression = path.at(index)->asBinaryExpression();
        if (expression)
            break;
508 509
    }

Nikolai Kosjar's avatar
Nikolai Kosjar committed
510 511
    if (! expression)
        return;
512

Nikolai Kosjar's avatar
Nikolai Kosjar committed
513 514
    if (! interface->isCursorOn(expression->binary_op_token))
        return;
515

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
518 519 520 521
    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)) {
522 523
        op->setDescription(QApplication::translate("CppTools::QuickFix",
                                                   "Rewrite Condition Using ||"));
Nikolai Kosjar's avatar
Nikolai Kosjar committed
524 525 526 527
        op->setPriority(index);
        result.append(op);
    }
}
528

Nikolai Kosjar's avatar
Nikolai Kosjar committed
529 530 531 532
bool SplitSimpleDeclaration::checkDeclaration(SimpleDeclarationAST *declaration)
{
    if (! declaration->semicolon_token)
        return false;
533

Nikolai Kosjar's avatar
Nikolai Kosjar committed
534 535
    if (! declaration->decl_specifier_list)
        return false;
536

Nikolai Kosjar's avatar
Nikolai Kosjar committed
537 538
    for (SpecifierListAST *it = declaration->decl_specifier_list; it; it = it->next) {
        SpecifierAST *specifier = it->value;
539

Nikolai Kosjar's avatar
Nikolai Kosjar committed
540
        if (specifier->asEnumSpecifier() != 0)
541 542
            return false;

Nikolai Kosjar's avatar
Nikolai Kosjar committed
543
        else if (specifier->asClassSpecifier() != 0)
544
            return false;
Nikolai Kosjar's avatar
Nikolai Kosjar committed
545
    }
546

Nikolai Kosjar's avatar
Nikolai Kosjar committed
547 548
    if (! declaration->declarator_list)
        return false;
549

Nikolai Kosjar's avatar
Nikolai Kosjar committed
550 551
    else if (! declaration->declarator_list->next)
        return false;
552

Nikolai Kosjar's avatar
Nikolai Kosjar committed
553 554
    return true;
}
555

556 557
namespace {

Nikolai Kosjar's avatar
Nikolai Kosjar committed
558 559 560 561 562 563 564 565 566 567
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"));
568 569
    }

Nikolai Kosjar's avatar
Nikolai Kosjar committed
570
    void perform()
571
    {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
572 573
        CppRefactoringChanges refactoring(snapshot());
        CppRefactoringFilePtr currentFile = refactoring.file(fileName());
574

Nikolai Kosjar's avatar
Nikolai Kosjar committed
575
        ChangeSet changes;
576

Nikolai Kosjar's avatar
Nikolai Kosjar committed
577 578 579 580
        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);
581

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
584 585
        for (DeclaratorListAST *it = declaration->declarator_list->next; it; it = it->next) {
            DeclaratorAST *declarator = it->value;
586

Nikolai Kosjar's avatar
Nikolai Kosjar committed
587 588 589 590 591
            changes.insert(insertPos, QLatin1String("\n"));
            changes.copy(declSpecifiersStart, declSpecifiersEnd, insertPos);
            changes.insert(insertPos, QLatin1String(" "));
            changes.move(currentFile->range(declarator), insertPos);
            changes.insert(insertPos, QLatin1String(";"));
592

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
596
            prevDeclarator = declarator;
597
        }
Nikolai Kosjar's avatar
Nikolai Kosjar committed
598 599 600 601

        currentFile->setChangeSet(changes);
        currentFile->appendIndentRange(currentFile->range(declaration));
        currentFile->apply();
602 603 604
    }

private:
Nikolai Kosjar's avatar
Nikolai Kosjar committed
605 606
    SimpleDeclarationAST *declaration;
};
607

608 609
} // anonymous namespace

Nikolai Kosjar's avatar
Nikolai Kosjar committed
610 611 612 613 614 615 616
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();
617

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
621 622
        if (CoreDeclaratorAST *coreDecl = node->asCoreDeclarator())
            core_declarator = coreDecl;
623

Nikolai Kosjar's avatar
Nikolai Kosjar committed
624 625 626
        else if (SimpleDeclarationAST *simpleDecl = node->asSimpleDeclaration()) {
            if (checkDeclaration(simpleDecl)) {
                SimpleDeclarationAST *declaration = simpleDecl;
627

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
631 632 633 634 635 636
                if (cursorPosition >= startOfDeclSpecifier && cursorPosition <= endOfDeclSpecifier) {
                    // the AST node under cursor is a specifier.
                    result.append(QuickFixOperation::Ptr(
                        new SplitSimpleDeclarationOp(interface, index, declaration)));
                    return;
                }
637

Nikolai Kosjar's avatar
Nikolai Kosjar committed
638 639 640 641 642 643
                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;
                }
644 645
            }

Nikolai Kosjar's avatar
Nikolai Kosjar committed
646
            return;
647
        }
Nikolai Kosjar's avatar
Nikolai Kosjar committed
648 649
    }
}
650

651 652
namespace {

Nikolai Kosjar's avatar
Nikolai Kosjar committed
653 654 655 656 657 658 659
class AddBracesToIfOp: public CppQuickFixOperation
{
public:
    AddBracesToIfOp(const CppQuickFixInterface &interface, int priority, StatementAST *statement)
        : CppQuickFixOperation(interface, priority)
        , _statement(statement)
    {
660
        setDescription(QApplication::translate("CppTools::QuickFix", "Add Curly Braces"));
Nikolai Kosjar's avatar
Nikolai Kosjar committed
661 662 663 664 665 666 667 668
    }

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

        ChangeSet changes;
669

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

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
676
        currentFile->setChangeSet(changes);
677
        currentFile->appendIndentRange(ChangeSet::Range(start, end));
Nikolai Kosjar's avatar
Nikolai Kosjar committed
678 679 680 681 682 683 684
        currentFile->apply();
    }

private:
    StatementAST *_statement;
};

685 686
} // anonymous namespace

Nikolai Kosjar's avatar
Nikolai Kosjar committed
687
void AddBracesToIf::match(const CppQuickFixInterface &interface, QuickFixOperations &result)
688
{
Nikolai Kosjar's avatar
Nikolai Kosjar committed
689
    const QList<AST *> &path = interface->path();
690

Nikolai Kosjar's avatar
Nikolai Kosjar committed
691 692 693 694 695 696 697 698 699 700 701 702 703
    // show when we're on the 'if' of an if statement
    int index = path.size() - 1;
    IfStatementAST *ifStatement = path.at(index)->asIfStatement();
    if (ifStatement && interface->isCursorOn(ifStatement->if_token) && ifStatement->statement
        && ! ifStatement->statement->asCompoundStatement()) {
        result.append(QuickFixOperation::Ptr(
            new AddBracesToIfOp(interface, index, ifStatement->statement)));
        return;
    }

    // or if we're on the statement contained in the if
    // ### This may not be such a good idea, consider nested ifs...
    for (; index != -1; --index) {
704
        IfStatementAST *ifStatement = path.at(index)->asIfStatement();
Nikolai Kosjar's avatar
Nikolai Kosjar committed
705 706
        if (ifStatement && ifStatement->statement
            && interface->isCursorOn(ifStatement->statement)
707
            && ! ifStatement->statement->asCompoundStatement()) {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
708 709
            result.append(QuickFixOperation::Ptr(
                new AddBracesToIfOp(interface, index, ifStatement->statement)));
710
            return;
711
        }
Nikolai Kosjar's avatar
Nikolai Kosjar committed
712
    }
713

Nikolai Kosjar's avatar
Nikolai Kosjar committed
714 715 716
    // ### This could very well be extended to the else branch
    // and other nodes entirely.
}
717

718 719
namespace {

Nikolai Kosjar's avatar
Nikolai Kosjar committed
720 721 722 723 724 725 726 727 728 729 730
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);
731 732
    }

Nikolai Kosjar's avatar
Nikolai Kosjar committed
733
    void perform()
734
    {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
735 736
        CppRefactoringChanges refactoring(snapshot());
        CppRefactoringFilePtr currentFile = refactoring.file(fileName());
737

Nikolai Kosjar's avatar
Nikolai Kosjar committed
738
        ChangeSet changes;
739

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
742 743 744
        int insertPos = currentFile->startOf(pattern);
        changes.move(currentFile->range(condition), insertPos);
        changes.insert(insertPos, QLatin1String(";\n"));
745

Nikolai Kosjar's avatar
Nikolai Kosjar committed
746 747 748 749
        currentFile->setChangeSet(changes);
        currentFile->appendIndentRange(currentFile->range(pattern));
        currentFile->apply();
    }
750

Nikolai Kosjar's avatar
Nikolai Kosjar committed
751 752 753 754 755
    ASTMatcher matcher;
    ASTPatternBuilder mk;
    ConditionAST *condition;
    IfStatementAST *pattern;
    CoreDeclaratorAST *core;
756 757
};

758 759
} // anonymous namespace

Nikolai Kosjar's avatar
Nikolai Kosjar committed
760 761
void MoveDeclarationOutOfIf::match(const CppQuickFixInterface &interface,
                                   QuickFixOperations &result)
762
{
Nikolai Kosjar's avatar
Nikolai Kosjar committed
763 764 765 766 767 768 769 770 771 772 773 774
    const QList<AST *> &path = interface->path();
    typedef QSharedPointer<MoveDeclarationOutOfIfOp> Ptr;
    Ptr op(new MoveDeclarationOutOfIfOp(interface));

    int index = path.size() - 1;
    for (; index != -1; --index) {
        if (IfStatementAST *statement = path.at(index)->asIfStatement()) {
            if (statement->match(op->pattern, &op->matcher) && op->condition->declarator) {
                DeclaratorAST *declarator = op->condition->declarator;
                op->core = declarator->core_declarator;
                if (! op->core)
                    return;
775

Nikolai Kosjar's avatar
Nikolai Kosjar committed
776 777 778 779
                if (interface->isCursorOn(op->core)) {
                    op->setPriority(index);
                    result.append(op);
                    return;
780 781 782 783
                }
            }
        }
    }
Nikolai Kosjar's avatar
Nikolai Kosjar committed
784
}
785

786 787
namespace {

Nikolai Kosjar's avatar
Nikolai Kosjar committed
788 789 790 791 792
class MoveDeclarationOutOfWhileOp: public CppQuickFixOperation
{
public:
    MoveDeclarationOutOfWhileOp(const CppQuickFixInterface &interface)
        : CppQuickFixOperation(interface)
793
    {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
794 795
        setDescription(QApplication::translate("CppTools::QuickFix",
                                               "Move Declaration out of Condition"));
796

Nikolai Kosjar's avatar
Nikolai Kosjar committed
797 798 799
        condition = mk.Condition();
        pattern = mk.WhileStatement(condition);
    }
800

Nikolai Kosjar's avatar
Nikolai Kosjar committed
801 802 803 804
    void perform()
    {
        CppRefactoringChanges refactoring(snapshot());
        CppRefactoringFilePtr currentFile = refactoring.file(fileName());
805

Nikolai Kosjar's avatar
Nikolai Kosjar committed
806
        ChangeSet changes;
807

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
811 812 813 814 815
        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"));
816

Nikolai Kosjar's avatar
Nikolai Kosjar committed
817 818 819 820
        currentFile->setChangeSet(changes);
        currentFile->appendIndentRange(currentFile->range(pattern));
        currentFile->apply();
    }
821

Nikolai Kosjar's avatar
Nikolai Kosjar committed
822 823 824 825 826 827
    ASTMatcher matcher;
    ASTPatternBuilder mk;
    ConditionAST *condition;
    WhileStatementAST *pattern;
    CoreDeclaratorAST *core;
};
828

829 830
} // anonymous namespace

Nikolai Kosjar's avatar
Nikolai Kosjar committed
831 832
void MoveDeclarationOutOfWhile::match(const CppQuickFixInterface &interface,
                                      QuickFixOperations &result)
833
{
Nikolai Kosjar's avatar
Nikolai Kosjar committed
834 835
    const QList<AST *> &path = interface->path();
    QSharedPointer<MoveDeclarationOutOfWhileOp> op(new MoveDeclarationOutOfWhileOp(interface));
836

Nikolai Kosjar's avatar
Nikolai Kosjar committed
837 838 839 840 841 842
    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;
843

Nikolai Kosjar's avatar
Nikolai Kosjar committed
844 845
                if (! op->core)
                    return;
846

Nikolai Kosjar's avatar
Nikolai Kosjar committed
847 848
                if (! declarator->equal_token)
                    return;
849

Nikolai Kosjar's avatar
Nikolai Kosjar committed
850 851
                if (! declarator->initializer)
                    return;
852

Nikolai Kosjar's avatar
Nikolai Kosjar committed
853 854 855 856
                if (interface->isCursorOn(op->core)) {
                    op->setPriority(index);
                    result.append(op);
                    return;
857 858 859 860
                }
            }
        }
    }
Nikolai Kosjar's avatar
Nikolai Kosjar committed
861
}
862

863 864
namespace {

Nikolai Kosjar's avatar
Nikolai Kosjar committed
865 866 867 868 869 870 871 872
class SplitIfStatementOp: public CppQuickFixOperation
{
public:
    SplitIfStatementOp(const CppQuickFixInterface &interface, int priority,
                       IfStatementAST *pattern, BinaryExpressionAST *condition)
        : CppQuickFixOperation(interface, priority)
        , pattern(pattern)
        , condition(condition)
873
    {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
874 875 876 877 878 879 880 881
        setDescription(QApplication::translate("CppTools::QuickFix",
                                               "Split if Statement"));
    }

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

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
885 886 887 888 889
        if (binaryToken.is(T_AMPER_AMPER))
            splitAndCondition(currentFile);
        else
            splitOrCondition(currentFile);
    }
890

Nikolai Kosjar's avatar
Nikolai Kosjar committed
891 892 893
    void splitAndCondition(CppRefactoringFilePtr currentFile) const
    {
        ChangeSet changes;
894

Nikolai Kosjar's avatar
Nikolai Kosjar committed
895 896 897 898
        int startPos = currentFile->startOf(pattern);
        changes.insert(startPos, QLatin1String("if ("));
        changes.move(currentFile->range(condition->left_expression), startPos);
        changes.insert(startPos, QLatin1String(") {\n"));
899

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
904 905 906 907
        currentFile->setChangeSet(changes);
        currentFile->appendIndentRange(currentFile->range(pattern));
        currentFile->apply();
    }
908

Nikolai Kosjar's avatar
Nikolai Kosjar committed
909
    void splitOrCondition(CppRefactoringFilePtr currentFile) const
910
    {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
911
        ChangeSet changes;
912

Nikolai Kosjar's avatar
Nikolai Kosjar committed
913 914
        StatementAST *ifTrueStatement = pattern->statement;
        CompoundStatementAST *compoundStatement = ifTrueStatement->asCompoundStatement();
915

Nikolai Kosjar's avatar
Nikolai Kosjar committed
916 917 918 919 920 921
        int insertPos = currentFile->endOf(ifTrueStatement);
        if (compoundStatement)
            changes.insert(insertPos, QLatin1String(" "));
        else
            changes.insert(insertPos, QLatin1String("\n"));
        changes.insert(insertPos, QLatin1String("else if ("));
922

Nikolai Kosjar's avatar
Nikolai Kosjar committed
923 924 925
        const int rExprStart = currentFile->startOf(condition->right_expression);
        changes.move(rExprStart, currentFile->startOf(pattern->rparen_token), insertPos);
        changes.insert(insertPos, QLatin1String(")"));
926

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
930 931 932 933 934 935
        const int lExprEnd = currentFile->endOf(condition->left_expression);
        changes.remove(lExprEnd, currentFile->startOf(condition->right_expression));

        currentFile->setChangeSet(changes);
        currentFile->appendIndentRange(currentFile->range(pattern));
        currentFile->apply();
936 937 938
    }

private:
Nikolai Kosjar's avatar
Nikolai Kosjar committed
939 940 941
    IfStatementAST *pattern;
    BinaryExpressionAST *condition;
};
942

943 944
} // anonymous namespace

Nikolai Kosjar's avatar
Nikolai Kosjar committed
945 946 947 948
void SplitIfStatement::match(const CppQuickFixInterface &interface, QuickFixOperations &result)
{
    IfStatementAST *pattern = 0;
    const QList<AST *> &path = interface->path();
949

Nikolai Kosjar's avatar
Nikolai Kosjar committed
950 951 952 953 954 955
    int index = path.size() - 1;
    for (; index != -1; --index) {
        AST *node = path.at(index);
        if (IfStatementAST *stmt = node->asIfStatement()) {
            pattern = stmt;
            break;
956
        }
Nikolai Kosjar's avatar
Nikolai Kosjar committed
957
    }
958