cppquickfixes.cpp 147 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/ASTPath.h>
45
#include <cplusplus/CPlusPlusForwardDeclarations.h>
46 47 48 49
#include <cplusplus/CppRewriter.h>
#include <cplusplus/DependencyTable.h>
#include <cplusplus/TypeOfExpression.h>

50
#include <extensionsystem/pluginmanager.h>
51

52 53
#include <utils/qtcassert.h>

54
#include <QApplication>
55
#include <QDir>
56
#include <QFileInfo>
57 58
#include <QInputDialog>
#include <QMessageBox>
Nikolai Kosjar's avatar
Nikolai Kosjar committed
59
#include <QSharedPointer>
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

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);
107 108 109

    plugIn->addAutoReleasedObject(new MoveFuncDefOutside);
    plugIn->addAutoReleasedObject(new MoveFuncDefToDecl);
Nikolai Kosjar's avatar
Nikolai Kosjar committed
110
}
111

112 113 114 115 116
// 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)
117 118 119 120
{
    return id == "QLatin1String" || id == "QLatin1Literal" || id == "QStringLiteral";
}

121
inline bool isQtStringTranslation(const QByteArray &id)
122 123 124 125
{
    return id == "tr" || id == "trUtf8" || id == "translate" || id == "QT_TRANSLATE_NOOP";
}

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

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
176 177 178 179 180
        // 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;
181 182 183
        }
    }

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

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
209 210 211 212
private:
    BinaryExpressionAST *binary;
    NestedExpressionAST *nested;
    UnaryExpressionAST *negation;
213

Nikolai Kosjar's avatar
Nikolai Kosjar committed
214
    QString replacement;
215 216
};

217 218
} // anonymous namespace

Nikolai Kosjar's avatar
Nikolai Kosjar committed
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 252 253 254
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;
    }
255

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

260 261
namespace {

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

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
282 283 284 285
    void perform()
    {
        CppRefactoringChanges refactoring(snapshot());
        CppRefactoringFilePtr currentFile = refactoring.file(fileName());
286

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
293 294
        currentFile->setChangeSet(changes);
        currentFile->apply();
295 296 297
    }

private:
Nikolai Kosjar's avatar
Nikolai Kosjar committed
298 299 300
    BinaryExpressionAST *binary;
    QString replacement;
};
301

302 303
} // anonymous namespace

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

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

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

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

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

351 352
namespace {

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

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

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

390 391
} // anonymous namespace

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

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
405 406
    if (! expression)
        return;
407

Nikolai Kosjar's avatar
Nikolai Kosjar committed
408 409
    if (! interface->isCursorOn(expression->binary_op_token))
        return;
410

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

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

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
429 430
    if (! declaration->decl_specifier_list)
        return false;
431

Nikolai Kosjar's avatar
Nikolai Kosjar committed
432 433
    for (SpecifierListAST *it = declaration->decl_specifier_list; it; it = it->next) {
        SpecifierAST *specifier = it->value;
434

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
438
        else if (specifier->asClassSpecifier() != 0)
439
            return false;
Nikolai Kosjar's avatar
Nikolai Kosjar committed
440
    }
441

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
445 446
    else if (! declaration->declarator_list->next)
        return false;
447

Nikolai Kosjar's avatar
Nikolai Kosjar committed
448 449
    return true;
}
450

451 452
namespace {

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
465
    void perform()
466
    {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
467 468
        CppRefactoringChanges refactoring(snapshot());
        CppRefactoringFilePtr currentFile = refactoring.file(fileName());
469

Nikolai Kosjar's avatar
Nikolai Kosjar committed
470
        ChangeSet changes;
471

Nikolai Kosjar's avatar
Nikolai Kosjar committed
472 473 474 475
        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);
476

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
479 480
        for (DeclaratorListAST *it = declaration->declarator_list->next; it; it = it->next) {
            DeclaratorAST *declarator = it->value;
481

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

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
491
            prevDeclarator = declarator;
492
        }
Nikolai Kosjar's avatar
Nikolai Kosjar committed
493 494 495 496

        currentFile->setChangeSet(changes);
        currentFile->appendIndentRange(currentFile->range(declaration));
        currentFile->apply();
497 498 499
    }

private:
Nikolai Kosjar's avatar
Nikolai Kosjar committed
500 501
    SimpleDeclarationAST *declaration;
};
502

503 504
} // anonymous namespace

Nikolai Kosjar's avatar
Nikolai Kosjar committed
505 506 507 508 509 510 511
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();
512

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
516 517
        if (CoreDeclaratorAST *coreDecl = node->asCoreDeclarator())
            core_declarator = coreDecl;
518

Nikolai Kosjar's avatar
Nikolai Kosjar committed
519 520 521
        else if (SimpleDeclarationAST *simpleDecl = node->asSimpleDeclaration()) {
            if (checkDeclaration(simpleDecl)) {
                SimpleDeclarationAST *declaration = simpleDecl;
522

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

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
533 534 535 536 537 538
                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;
                }
539 540
            }

Nikolai Kosjar's avatar
Nikolai Kosjar committed
541
            return;
542
        }
Nikolai Kosjar's avatar
Nikolai Kosjar committed
543 544
    }
}
545

546 547
namespace {

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

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

        ChangeSet changes;
564

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

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

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

private:
    StatementAST *_statement;
};

580 581
} // anonymous namespace

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

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

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

613 614
namespace {

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
628
    void perform()
629
    {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
630 631
        CppRefactoringChanges refactoring(snapshot());
        CppRefactoringFilePtr currentFile = refactoring.file(fileName());
632

Nikolai Kosjar's avatar
Nikolai Kosjar committed
633
        ChangeSet changes;
634

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

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
641 642 643 644
        currentFile->setChangeSet(changes);
        currentFile->appendIndentRange(currentFile->range(pattern));
        currentFile->apply();
    }
645

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

654 655
} // anonymous namespace

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

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

682 683
namespace {

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
693 694 695
        condition = mk.Condition();
        pattern = mk.WhileStatement(condition);
    }
696

Nikolai Kosjar's avatar
Nikolai Kosjar committed
697 698 699 700
    void perform()
    {
        CppRefactoringChanges refactoring(snapshot());
        CppRefactoringFilePtr currentFile = refactoring.file(fileName());
701

Nikolai Kosjar's avatar
Nikolai Kosjar committed
702
        ChangeSet changes;
703

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
707 708 709 710 711
        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"));
712

Nikolai Kosjar's avatar
Nikolai Kosjar committed
713 714 715 716
        currentFile->setChangeSet(changes);
        currentFile->appendIndentRange(currentFile->range(pattern));
        currentFile->apply();
    }
717

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

726 727
} // anonymous namespace

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
734 735 736 737 738 739
    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;
740

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

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
747 748
                if (! declarator->initializer)
                    return;
749

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

760 761
namespace {

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

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

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
782 783 784 785 786
        if (binaryToken.is(T_AMPER_AMPER))
            splitAndCondition(currentFile);
        else
            splitOrCondition(currentFile);
    }
787

Nikolai Kosjar's avatar
Nikolai Kosjar committed
788 789 790
    void splitAndCondition(CppRefactoringFilePtr currentFile) const
    {
        ChangeSet changes;
791

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

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

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
806
    void splitOrCondition(CppRefactoringFilePtr currentFile) const
807
    {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
808
        ChangeSet changes;
809

Nikolai Kosjar's avatar
Nikolai Kosjar committed
810 811
        StatementAST *ifTrueStatement = pattern->statement;
        CompoundStatementAST *compoundStatement = ifTrueStatement->asCompoundStatement();
812

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

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

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

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

private:
Nikolai Kosjar's avatar
Nikolai Kosjar committed
836 837 838
    IfStatementAST *pattern;
    BinaryExpressionAST *condition;
};
839

840 841
} // anonymous namespace

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

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
856 857
    if (! pattern || ! pattern->statement)
        return;
858

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

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
868 869 870 871 872 873 874 875 876 877
        // 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;
878 879
        }

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

888 889 890 891
/* 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,
892
                                          const CppRefactoringFilePtr &file, Type *type,
893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914
                                          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;
915
        }
916
    }
917

918 919 920 921 922 923 924 925
    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;
926 927 928 929
                    }
                }
            }
        }
930 931 932
    }
    return literal;
}
933

934 935
namespace {

Nikolai Kosjar's avatar
Nikolai Kosjar committed
936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959