cppquickfixes.cpp 130 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
#include <cpptools/cppclassesfilter.h>
37
#include <cpptools/cppcodestylesettings.h>
38 39
#include <cpptools/cpppointerdeclarationformatter.h>
#include <cpptools/cpptoolsconstants.h>
40 41
#include <cpptools/cpptoolsreuse.h>
#include <cpptools/insertionpointlocator.h>
42
#include <cpptools/symbolfinder.h>
43

44
#include <cplusplus/CPlusPlusForwardDeclarations.h>
45 46 47 48
#include <cplusplus/CppRewriter.h>
#include <cplusplus/DependencyTable.h>
#include <cplusplus/TypeOfExpression.h>

49
#include <extensionsystem/pluginmanager.h>
50

51 52
#include <utils/qtcassert.h>

53
#include <QApplication>
54
#include <QDir>
55
#include <QFileInfo>
56 57
#include <QInputDialog>
#include <QMessageBox>
Nikolai Kosjar's avatar
Nikolai Kosjar committed
58
#include <QSharedPointer>
59
#include <QStringBuilder>
60 61
#include <QTextBlock>
#include <QTextCursor>
62

Nikolai Kosjar's avatar
Nikolai Kosjar committed
63 64
#include <cctype>

65
using namespace CPlusPlus;
66 67
using namespace CppEditor;
using namespace CppEditor::Internal;
68 69 70
using namespace CppTools;
using namespace TextEditor;
using namespace Utils;
Nikolai Kosjar's avatar
Nikolai Kosjar committed
71 72 73 74 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);
    plugIn->addAutoReleasedObject(new GenerateGetterSetter);
    plugIn->addAutoReleasedObject(new InsertDeclFromDef);
    plugIn->addAutoReleasedObject(new InsertDefFromDecl);
}
108

109 110 111 112 113
// In the following anonymous namespace all functions are collected, which could be of interest for
// different quick fixes.
namespace {

inline bool isQtStringLiteral(const QByteArray &id)
114 115 116 117
{
    return id == "QLatin1String" || id == "QLatin1Literal" || id == "QStringLiteral";
}

118
inline bool isQtStringTranslation(const QByteArray &id)
119 120 121 122
{
    return id == "tr" || id == "trUtf8" || id == "translate" || id == "QT_TRANSLATE_NOOP";
}

123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156
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;
}

} // anonymous namespace

namespace {

Nikolai Kosjar's avatar
Nikolai Kosjar committed
157
class InverseLogicalComparisonOp: public CppQuickFixOperation
158 159
{
public:
Nikolai Kosjar's avatar
Nikolai Kosjar committed
160 161 162 163
    InverseLogicalComparisonOp(const CppQuickFixInterface &interface, int priority,
                               BinaryExpressionAST *binary, Kind invertToken)
        : CppQuickFixOperation(interface, priority)
        , binary(binary), nested(0), negation(0)
164
    {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
165 166 167
        Token tok;
        tok.f.kind = invertToken;
        replacement = QLatin1String(tok.spell());
168

Nikolai Kosjar's avatar
Nikolai Kosjar committed
169 170 171
        // check for enclosing nested expression
        if (priority - 1 >= 0)
            nested = interface->path()[priority - 1]->asNestedExpression();
172

Nikolai Kosjar's avatar
Nikolai Kosjar committed
173 174 175 176 177
        // 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;
178 179 180
        }
    }

Nikolai Kosjar's avatar
Nikolai Kosjar committed
181
    QString description() const
182
    {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
183 184
        return QApplication::translate("CppTools::QuickFix", "Rewrite Using %1").arg(replacement);
    }
185

Nikolai Kosjar's avatar
Nikolai Kosjar committed
186 187 188 189 190 191 192 193 194 195 196 197 198 199
    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(")"));
200
        }
Nikolai Kosjar's avatar
Nikolai Kosjar committed
201 202 203 204
        changes.replace(currentFile->range(binary->binary_op_token), replacement);
        currentFile->setChangeSet(changes);
        currentFile->apply();
    }
205

Nikolai Kosjar's avatar
Nikolai Kosjar committed
206 207 208 209
private:
    BinaryExpressionAST *binary;
    NestedExpressionAST *nested;
    UnaryExpressionAST *negation;
210

Nikolai Kosjar's avatar
Nikolai Kosjar committed
211
    QString replacement;
212 213
};

214 215
} // anonymous namespace

Nikolai Kosjar's avatar
Nikolai Kosjar committed
216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251
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;
    }
252

Nikolai Kosjar's avatar
Nikolai Kosjar committed
253 254 255
    result.append(CppQuickFixOperation::Ptr(
        new InverseLogicalComparisonOp(interface, index, binary, invertToken)));
}
256

257 258
namespace {

Nikolai Kosjar's avatar
Nikolai Kosjar committed
259
class FlipLogicalOperandsOp: public CppQuickFixOperation
260 261
{
public:
Nikolai Kosjar's avatar
Nikolai Kosjar committed
262 263 264 265 266
    FlipLogicalOperandsOp(const CppQuickFixInterface &interface, int priority,
                          BinaryExpressionAST *binary, QString replacement)
        : CppQuickFixOperation(interface)
        , binary(binary)
        , replacement(replacement)
267
    {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
268 269
        setPriority(priority);
    }
270

Nikolai Kosjar's avatar
Nikolai Kosjar committed
271 272 273 274 275 276 277
    QString description() const
    {
        if (replacement.isEmpty())
            return QApplication::translate("CppTools::QuickFix", "Swap Operands");
        else
            return QApplication::translate("CppTools::QuickFix", "Rewrite Using %1").arg(replacement);
    }
278

Nikolai Kosjar's avatar
Nikolai Kosjar committed
279 280 281 282
    void perform()
    {
        CppRefactoringChanges refactoring(snapshot());
        CppRefactoringFilePtr currentFile = refactoring.file(fileName());
283

Nikolai Kosjar's avatar
Nikolai Kosjar committed
284
        ChangeSet changes;
285 286
        changes.flip(currentFile->range(binary->left_expression),
                     currentFile->range(binary->right_expression));
Nikolai Kosjar's avatar
Nikolai Kosjar committed
287 288
        if (! replacement.isEmpty())
            changes.replace(currentFile->range(binary->binary_op_token), replacement);
289

Nikolai Kosjar's avatar
Nikolai Kosjar committed
290 291
        currentFile->setChangeSet(changes);
        currentFile->apply();
292 293 294
    }

private:
Nikolai Kosjar's avatar
Nikolai Kosjar committed
295 296 297
    BinaryExpressionAST *binary;
    QString replacement;
};
298

299 300
} // anonymous namespace

Nikolai Kosjar's avatar
Nikolai Kosjar committed
301 302 303 304
void FlipLogicalOperands::match(const CppQuickFixInterface &interface, QuickFixOperations &result)
{
    const QList<AST *> &path = interface->path();
    CppRefactoringFilePtr file = interface->currentFile();
305

Nikolai Kosjar's avatar
Nikolai Kosjar committed
306 307 308 309 310 311
    int index = path.size() - 1;
    BinaryExpressionAST *binary = path.at(index)->asBinaryExpression();
    if (! binary)
        return;
    if (! interface->isCursorOn(binary->binary_op_token))
        return;
312

Nikolai Kosjar's avatar
Nikolai Kosjar committed
313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335
    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;
    }
336

Nikolai Kosjar's avatar
Nikolai Kosjar committed
337 338 339 340 341 342
    QString replacement;
    if (flipToken != T_EOF_SYMBOL) {
        Token tok;
        tok.f.kind = flipToken;
        replacement = QLatin1String(tok.spell());
    }
343

Nikolai Kosjar's avatar
Nikolai Kosjar committed
344 345 346
    result.append(QuickFixOperation::Ptr(
        new FlipLogicalOperandsOp(interface, index, binary, replacement)));
}
347

348 349
namespace {

Nikolai Kosjar's avatar
Nikolai Kosjar committed
350
class RewriteLogicalAndOp: public CppQuickFixOperation
351 352
{
public:
Nikolai Kosjar's avatar
Nikolai Kosjar committed
353 354 355 356 357 358 359 360
    QSharedPointer<ASTPatternBuilder> mk;
    UnaryExpressionAST *left;
    UnaryExpressionAST *right;
    BinaryExpressionAST *pattern;

    RewriteLogicalAndOp(const CppQuickFixInterface &interface)
        : CppQuickFixOperation(interface)
        , mk(new ASTPatternBuilder)
361
    {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
362 363 364 365
        left = mk->UnaryExpression();
        right = mk->UnaryExpression();
        pattern = mk->BinaryExpression(left, right);
    }
366

Nikolai Kosjar's avatar
Nikolai Kosjar committed
367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385
    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();
    }
};
386

387 388
} // anonymous namespace

Nikolai Kosjar's avatar
Nikolai Kosjar committed
389 390 391 392 393
void RewriteLogicalAnd::match(const CppQuickFixInterface &interface, QuickFixOperations &result)
{
    BinaryExpressionAST *expression = 0;
    const QList<AST *> &path = interface->path();
    CppRefactoringFilePtr file = interface->currentFile();
394

Nikolai Kosjar's avatar
Nikolai Kosjar committed
395 396 397 398 399
    int index = path.size() - 1;
    for (; index != -1; --index) {
        expression = path.at(index)->asBinaryExpression();
        if (expression)
            break;
400 401
    }

Nikolai Kosjar's avatar
Nikolai Kosjar committed
402 403
    if (! expression)
        return;
404

Nikolai Kosjar's avatar
Nikolai Kosjar committed
405 406
    if (! interface->isCursorOn(expression->binary_op_token))
        return;
407

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
410 411 412 413
    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)) {
414 415
        op->setDescription(QApplication::translate("CppTools::QuickFix",
                                                   "Rewrite Condition Using ||"));
Nikolai Kosjar's avatar
Nikolai Kosjar committed
416 417 418 419
        op->setPriority(index);
        result.append(op);
    }
}
420

Nikolai Kosjar's avatar
Nikolai Kosjar committed
421 422 423 424
bool SplitSimpleDeclaration::checkDeclaration(SimpleDeclarationAST *declaration)
{
    if (! declaration->semicolon_token)
        return false;
425

Nikolai Kosjar's avatar
Nikolai Kosjar committed
426 427
    if (! declaration->decl_specifier_list)
        return false;
428

Nikolai Kosjar's avatar
Nikolai Kosjar committed
429 430
    for (SpecifierListAST *it = declaration->decl_specifier_list; it; it = it->next) {
        SpecifierAST *specifier = it->value;
431

Nikolai Kosjar's avatar
Nikolai Kosjar committed
432
        if (specifier->asEnumSpecifier() != 0)
433 434
            return false;

Nikolai Kosjar's avatar
Nikolai Kosjar committed
435
        else if (specifier->asClassSpecifier() != 0)
436
            return false;
Nikolai Kosjar's avatar
Nikolai Kosjar committed
437
    }
438

Nikolai Kosjar's avatar
Nikolai Kosjar committed
439 440
    if (! declaration->declarator_list)
        return false;
441

Nikolai Kosjar's avatar
Nikolai Kosjar committed
442 443
    else if (! declaration->declarator_list->next)
        return false;
444

Nikolai Kosjar's avatar
Nikolai Kosjar committed
445 446
    return true;
}
447

448 449
namespace {

Nikolai Kosjar's avatar
Nikolai Kosjar committed
450 451 452 453 454 455 456 457 458 459
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"));
460 461
    }

Nikolai Kosjar's avatar
Nikolai Kosjar committed
462
    void perform()
463
    {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
464 465
        CppRefactoringChanges refactoring(snapshot());
        CppRefactoringFilePtr currentFile = refactoring.file(fileName());
466

Nikolai Kosjar's avatar
Nikolai Kosjar committed
467
        ChangeSet changes;
468

Nikolai Kosjar's avatar
Nikolai Kosjar committed
469 470 471 472
        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);
473

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
476 477
        for (DeclaratorListAST *it = declaration->declarator_list->next; it; it = it->next) {
            DeclaratorAST *declarator = it->value;
478

Nikolai Kosjar's avatar
Nikolai Kosjar committed
479 480 481 482 483
            changes.insert(insertPos, QLatin1String("\n"));
            changes.copy(declSpecifiersStart, declSpecifiersEnd, insertPos);
            changes.insert(insertPos, QLatin1String(" "));
            changes.move(currentFile->range(declarator), insertPos);
            changes.insert(insertPos, QLatin1String(";"));
484

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
488
            prevDeclarator = declarator;
489
        }
Nikolai Kosjar's avatar
Nikolai Kosjar committed
490 491 492 493

        currentFile->setChangeSet(changes);
        currentFile->appendIndentRange(currentFile->range(declaration));
        currentFile->apply();
494 495 496
    }

private:
Nikolai Kosjar's avatar
Nikolai Kosjar committed
497 498
    SimpleDeclarationAST *declaration;
};
499

500 501
} // anonymous namespace

Nikolai Kosjar's avatar
Nikolai Kosjar committed
502 503 504 505 506 507 508
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();
509

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
513 514
        if (CoreDeclaratorAST *coreDecl = node->asCoreDeclarator())
            core_declarator = coreDecl;
515

Nikolai Kosjar's avatar
Nikolai Kosjar committed
516 517 518
        else if (SimpleDeclarationAST *simpleDecl = node->asSimpleDeclaration()) {
            if (checkDeclaration(simpleDecl)) {
                SimpleDeclarationAST *declaration = simpleDecl;
519

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
523 524 525 526 527 528
                if (cursorPosition >= startOfDeclSpecifier && cursorPosition <= endOfDeclSpecifier) {
                    // the AST node under cursor is a specifier.
                    result.append(QuickFixOperation::Ptr(
                        new SplitSimpleDeclarationOp(interface, index, declaration)));
                    return;
                }
529

Nikolai Kosjar's avatar
Nikolai Kosjar committed
530 531 532 533 534 535
                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;
                }
536 537
            }

Nikolai Kosjar's avatar
Nikolai Kosjar committed
538
            return;
539
        }
Nikolai Kosjar's avatar
Nikolai Kosjar committed
540 541
    }
}
542

543 544
namespace {

Nikolai Kosjar's avatar
Nikolai Kosjar committed
545 546 547 548 549 550 551
class AddBracesToIfOp: public CppQuickFixOperation
{
public:
    AddBracesToIfOp(const CppQuickFixInterface &interface, int priority, StatementAST *statement)
        : CppQuickFixOperation(interface, priority)
        , _statement(statement)
    {
552
        setDescription(QApplication::translate("CppTools::QuickFix", "Add Curly Braces"));
Nikolai Kosjar's avatar
Nikolai Kosjar committed
553 554 555 556 557 558 559 560
    }

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

        ChangeSet changes;
561

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

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
568
        currentFile->setChangeSet(changes);
569
        currentFile->appendIndentRange(ChangeSet::Range(start, end));
Nikolai Kosjar's avatar
Nikolai Kosjar committed
570 571 572 573 574 575 576
        currentFile->apply();
    }

private:
    StatementAST *_statement;
};

577 578
} // anonymous namespace

Nikolai Kosjar's avatar
Nikolai Kosjar committed
579
void AddBracesToIf::match(const CppQuickFixInterface &interface, QuickFixOperations &result)
580
{
Nikolai Kosjar's avatar
Nikolai Kosjar committed
581
    const QList<AST *> &path = interface->path();
582

Nikolai Kosjar's avatar
Nikolai Kosjar committed
583 584 585 586 587 588 589 590 591 592 593 594 595
    // 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) {
596
        IfStatementAST *ifStatement = path.at(index)->asIfStatement();
Nikolai Kosjar's avatar
Nikolai Kosjar committed
597 598
        if (ifStatement && ifStatement->statement
            && interface->isCursorOn(ifStatement->statement)
599
            && ! ifStatement->statement->asCompoundStatement()) {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
600 601
            result.append(QuickFixOperation::Ptr(
                new AddBracesToIfOp(interface, index, ifStatement->statement)));
602
            return;
603
        }
Nikolai Kosjar's avatar
Nikolai Kosjar committed
604
    }
605

Nikolai Kosjar's avatar
Nikolai Kosjar committed
606 607 608
    // ### This could very well be extended to the else branch
    // and other nodes entirely.
}
609

610 611
namespace {

Nikolai Kosjar's avatar
Nikolai Kosjar committed
612 613 614 615 616 617 618 619 620 621 622
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);
623 624
    }

Nikolai Kosjar's avatar
Nikolai Kosjar committed
625
    void perform()
626
    {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
627 628
        CppRefactoringChanges refactoring(snapshot());
        CppRefactoringFilePtr currentFile = refactoring.file(fileName());
629

Nikolai Kosjar's avatar
Nikolai Kosjar committed
630
        ChangeSet changes;
631

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
634 635 636
        int insertPos = currentFile->startOf(pattern);
        changes.move(currentFile->range(condition), insertPos);
        changes.insert(insertPos, QLatin1String(";\n"));
637

Nikolai Kosjar's avatar
Nikolai Kosjar committed
638 639 640 641
        currentFile->setChangeSet(changes);
        currentFile->appendIndentRange(currentFile->range(pattern));
        currentFile->apply();
    }
642

Nikolai Kosjar's avatar
Nikolai Kosjar committed
643 644 645 646 647 648
    ASTMatcher matcher;
    ASTPatternBuilder mk;
    CPPEditorWidget *editor;
    ConditionAST *condition;
    IfStatementAST *pattern;
    CoreDeclaratorAST *core;
649 650
};

651 652
} // anonymous namespace

Nikolai Kosjar's avatar
Nikolai Kosjar committed
653 654
void MoveDeclarationOutOfIf::match(const CppQuickFixInterface &interface,
                                   QuickFixOperations &result)
655
{
Nikolai Kosjar's avatar
Nikolai Kosjar committed
656 657 658 659 660 661 662 663 664 665 666 667
    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;
668

Nikolai Kosjar's avatar
Nikolai Kosjar committed
669 670 671 672
                if (interface->isCursorOn(op->core)) {
                    op->setPriority(index);
                    result.append(op);
                    return;
673 674 675 676
                }
            }
        }
    }
Nikolai Kosjar's avatar
Nikolai Kosjar committed
677
}
678

679 680
namespace {

Nikolai Kosjar's avatar
Nikolai Kosjar committed
681 682 683 684 685
class MoveDeclarationOutOfWhileOp: public CppQuickFixOperation
{
public:
    MoveDeclarationOutOfWhileOp(const CppQuickFixInterface &interface)
        : CppQuickFixOperation(interface)
686
    {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
687 688
        setDescription(QApplication::translate("CppTools::QuickFix",
                                               "Move Declaration out of Condition"));
689

Nikolai Kosjar's avatar
Nikolai Kosjar committed
690 691 692
        condition = mk.Condition();
        pattern = mk.WhileStatement(condition);
    }
693

Nikolai Kosjar's avatar
Nikolai Kosjar committed
694 695 696 697
    void perform()
    {
        CppRefactoringChanges refactoring(snapshot());
        CppRefactoringFilePtr currentFile = refactoring.file(fileName());
698

Nikolai Kosjar's avatar
Nikolai Kosjar committed
699
        ChangeSet changes;
700

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
704 705 706 707 708
        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"));
709

Nikolai Kosjar's avatar
Nikolai Kosjar committed
710 711 712 713
        currentFile->setChangeSet(changes);
        currentFile->appendIndentRange(currentFile->range(pattern));
        currentFile->apply();
    }
714

Nikolai Kosjar's avatar
Nikolai Kosjar committed
715 716 717 718 719 720 721
    ASTMatcher matcher;
    ASTPatternBuilder mk;
    CPPEditorWidget *editor;
    ConditionAST *condition;
    WhileStatementAST *pattern;
    CoreDeclaratorAST *core;
};
722

723 724
} // anonymous namespace

Nikolai Kosjar's avatar
Nikolai Kosjar committed
725 726
void MoveDeclarationOutOfWhile::match(const CppQuickFixInterface &interface,
                                      QuickFixOperations &result)
727
{
Nikolai Kosjar's avatar
Nikolai Kosjar committed
728 729
    const QList<AST *> &path = interface->path();
    QSharedPointer<MoveDeclarationOutOfWhileOp> op(new MoveDeclarationOutOfWhileOp(interface));
730

Nikolai Kosjar's avatar
Nikolai Kosjar committed
731 732 733 734 735 736
    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;
737

Nikolai Kosjar's avatar
Nikolai Kosjar committed
738 739
                if (! op->core)
                    return;
740

Nikolai Kosjar's avatar
Nikolai Kosjar committed
741 742
                if (! declarator->equal_token)
                    return;
743

Nikolai Kosjar's avatar
Nikolai Kosjar committed
744 745
                if (! declarator->initializer)
                    return;
746

Nikolai Kosjar's avatar
Nikolai Kosjar committed
747 748 749 750
                if (interface->isCursorOn(op->core)) {
                    op->setPriority(index);
                    result.append(op);
                    return;
751 752 753 754
                }
            }
        }
    }
Nikolai Kosjar's avatar
Nikolai Kosjar committed
755
}
756

757 758
namespace {

Nikolai Kosjar's avatar
Nikolai Kosjar committed
759 760 761 762 763 764 765 766
class SplitIfStatementOp: public CppQuickFixOperation
{
public:
    SplitIfStatementOp(const CppQuickFixInterface &interface, int priority,
                       IfStatementAST *pattern, BinaryExpressionAST *condition)
        : CppQuickFixOperation(interface, priority)
        , pattern(pattern)
        , condition(condition)
767
    {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
768 769 770 771 772 773 774 775
        setDescription(QApplication::translate("CppTools::QuickFix",
                                               "Split if Statement"));
    }

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

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
779 780 781 782 783
        if (binaryToken.is(T_AMPER_AMPER))
            splitAndCondition(currentFile);
        else
            splitOrCondition(currentFile);
    }
784

Nikolai Kosjar's avatar
Nikolai Kosjar committed
785 786 787
    void splitAndCondition(CppRefactoringFilePtr currentFile) const
    {
        ChangeSet changes;
788

Nikolai Kosjar's avatar
Nikolai Kosjar committed
789 790 791 792
        int startPos = currentFile->startOf(pattern);
        changes.insert(startPos, QLatin1String("if ("));
        changes.move(currentFile->range(condition->left_expression), startPos);
        changes.insert(startPos, QLatin1String(") {\n"));
793

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

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
803
    void splitOrCondition(CppRefactoringFilePtr currentFile) const
804
    {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
805
        ChangeSet changes;
806

Nikolai Kosjar's avatar
Nikolai Kosjar committed
807 808
        StatementAST *ifTrueStatement = pattern->statement;
        CompoundStatementAST *compoundStatement = ifTrueStatement->asCompoundStatement();
809

Nikolai Kosjar's avatar
Nikolai Kosjar committed
810 811 812 813 814 815
        int insertPos = currentFile->endOf(ifTrueStatement);
        if (compoundStatement)
            changes.insert(insertPos, QLatin1String(" "));
        else
            changes.insert(insertPos, QLatin1String("\n"));
        changes.insert(insertPos, QLatin1String("else if ("));
816

Nikolai Kosjar's avatar
Nikolai Kosjar committed
817 818 819
        const int rExprStart = currentFile->startOf(condition->right_expression);
        changes.move(rExprStart, currentFile->startOf(pattern->rparen_token), insertPos);
        changes.insert(insertPos, QLatin1String(")"));
820

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
824 825 826 827 828 829
        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();
830 831 832
    }

private:
Nikolai Kosjar's avatar
Nikolai Kosjar committed
833 834 835
    IfStatementAST *pattern;
    BinaryExpressionAST *condition;
};
836

837 838
} // anonymous namespace

Nikolai Kosjar's avatar
Nikolai Kosjar committed
839 840 841 842
void SplitIfStatement::match(const CppQuickFixInterface &interface, QuickFixOperations &result)
{
    IfStatementAST *pattern = 0;
    const QList<AST *> &path = interface->path();
843

Nikolai Kosjar's avatar
Nikolai Kosjar committed
844 845 846 847 848 849
    int index = path.size() - 1;
    for (; index != -1; --index) {
        AST *node = path.at(index);
        if (IfStatementAST *stmt = node->asIfStatement()) {
            pattern = stmt;
            break;
850
        }
Nikolai Kosjar's avatar
Nikolai Kosjar committed
851
    }
852

Nikolai Kosjar's avatar
Nikolai Kosjar committed
853 854
    if (! pattern || ! pattern->statement)
        return;
855

Nikolai Kosjar's avatar
Nikolai Kosjar committed
856 857 858 859 860 861
    unsigned splitKind = 0;
    for (++index; index < path.size(); ++index) {
        AST *node = path.at(index);
        BinaryExpressionAST *condition = node->asBinaryExpression();
        if (! condition)
            return;
862

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
865 866 867 868 869 870 871 872 873 874
        // only accept a chain of ||s or &&s - no mixing
        if (! splitKind) {
            splitKind = binaryToken.kind();
            if (splitKind != T_AMPER_AMPER && splitKind != T_PIPE_PIPE)
                return;
            // we can't reliably split &&s in ifs with an else branch
            if (splitKind == T_AMPER_AMPER && pattern->else_statement)
                return;
        } else if (splitKind != binaryToken.kind()) {
            return;
875 876
        }

Nikolai Kosjar's avatar
Nikolai Kosjar committed
877 878 879 880
        if (interface->isCursorOn(condition->binary_op_token)) {
            result.append(QuickFixOperation::Ptr(
                new SplitIfStatementOp(interface, index, pattern, condition)));
            return;
881
        }
Nikolai Kosjar's avatar
Nikolai Kosjar committed
882 883
    }
}
884

885 886 887 888
/* Analze a string/character literal like "x", QLatin1String("x") and return the literal
 * (StringLiteral or NumericLiteral for characters) and its type
 * and the enclosing function (QLatin1String, tr...) */
ExpressionAST *WrapStringLiteral::analyze(const QList<AST *> &path,
889
                                          const CppRefactoringFilePtr &file, Type *type,
890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911
                                          QByteArray *enclosingFunction /* = 0 */,
                                          CallAST **enclosingFunctionCall /* = 0 */)
{
    *type = TypeNone;
    if (enclosingFunction)
        enclosingFunction->clear();
    if (enclosingFunctionCall)
        *enclosingFunctionCall = 0;

    if (path.isEmpty())
        return 0;

    ExpressionAST *literal = path.last()->asExpression();
    if (literal) {
        if (literal->asStringLiteral()) {
            // Check for Objective C string (@"bla")
            const QChar firstChar = file->charAt(file->startOf(literal));
            *type = firstChar == QLatin1Char('@') ? TypeObjCString : TypeString;
        } else if (NumericLiteralAST *numericLiteral = literal->asNumericLiteral()) {
            // character ('c') constants are numeric.
            if (file->tokenAt(numericLiteral->literal_token).is(T_CHAR_LITERAL))
                *type = TypeChar;
912
        }
913
    }
914

915 916 917 918 919 920 921 922
    if (*type != TypeNone && enclosingFunction && path.size() > 1) {
        if (CallAST *call = path.at(path.size() - 2)->asCall()) {
            if (call->base_expression) {
                if (IdExpressionAST *idExpr = call->base_expression->asIdExpression()) {
                    if (SimpleNameAST *functionName = idExpr->name->asSimpleName()) {
                        *enclosingFunction = file->tokenAt(functionName->identifier_token).identifier->chars();
                        if (enclosingFunctionCall)
                            *enclosingFunctionCall = call;
923 924 925 926
                    }
                }
            }
        }
927 928 929
    }
    return literal;
}
930

931 932
namespace {

Nikolai Kosjar's avatar
Nikolai Kosjar committed
933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963
/// Operation performs the operations of type ActionFlags passed in as actions.
class WrapStringLiteralOp : public CppQuickFixOperation
{
public:
    typedef WrapStringLiteral Factory;

    WrapStringLiteralOp(const CppQuickFixInterface &interface, int priority,
                        unsigned actions, const QString &description, ExpressionAST *literal,
                        const QString &translationContext = QString())
        : CppQuickFixOperation(interface, priority), m_actions(actions), m_literal(literal),
          m_translationContext(translationContext)
    {
        setDescription(description);
    }

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

        ChangeSet changes;

        const int startPos = currentFile->startOf(m_literal);
        const int endPos = currentFile->endOf(m_literal);

        // kill leading '@'. No need to adapt endPos, that is done by ChangeSet
        if (m_actions & Factory::RemoveObjectiveCAction)
            changes.remove(startPos, startPos + 1);

        // Fix quotes
        if (m_actions & (Factory::SingleQuoteAction | Factory::DoubleQuoteAction)) {
964 965