cppquickfixes.cpp 155 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);
110 111

    plugIn->addAutoReleasedObject(new AssignToLocalVariable);
Nikolai Kosjar's avatar
Nikolai Kosjar committed
112
}
113

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

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

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

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

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

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

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

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
216
    QString replacement;
217 218
};

219 220
} // anonymous namespace

Nikolai Kosjar's avatar
Nikolai Kosjar committed
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 255 256
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;
    }
257

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

262 263
namespace {

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

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

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

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

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

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

304 305
} // anonymous namespace

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

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

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

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

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

353 354
namespace {

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

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

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

392 393
} // anonymous namespace

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

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
407 408
    if (! expression)
        return;
409

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

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

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

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

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

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

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

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

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

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
450 451
    return true;
}
452

453 454
namespace {

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

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
472
        ChangeSet changes;
473

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

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

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

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

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
493
            prevDeclarator = declarator;
494
        }
Nikolai Kosjar's avatar
Nikolai Kosjar committed
495 496 497 498

        currentFile->setChangeSet(changes);
        currentFile->appendIndentRange(currentFile->range(declaration));
        currentFile->apply();
499 500 501
    }

private:
Nikolai Kosjar's avatar
Nikolai Kosjar committed
502 503
    SimpleDeclarationAST *declaration;
};
504

505 506
} // anonymous namespace

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

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

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

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

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

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

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
543
            return;
544
        }
Nikolai Kosjar's avatar
Nikolai Kosjar committed
545 546
    }
}
547

548 549
namespace {

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

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

        ChangeSet changes;
566

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

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

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

private:
    StatementAST *_statement;
};

582 583
} // anonymous namespace

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

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

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

615 616
namespace {

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

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
635
        ChangeSet changes;
636

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

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

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

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

656 657
} // anonymous namespace

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

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

684 685
namespace {

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

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

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
704
        ChangeSet changes;
705

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

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

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

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

728 729
} // anonymous namespace

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

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

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

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

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

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

762 763
namespace {

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

842 843
} // anonymous namespace

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

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

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

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

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

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

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

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

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

936 937
namespace {

Nikolai Kosjar's avatar
Nikolai Kosjar committed
938 939 940 941 942 943 944 945 946 947 948 949 950 951 952