cppquickfixes.cpp 161 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
// In the following anonymous namespace all functions are collected, which could be of interest for
// different quick fixes.
namespace {

118 119 120 121 122 123
enum DefPos {
    DefPosInsideClass,
    DefPosOutsideClass,
    DefPosImplementationFile
};

124
inline bool isQtStringLiteral(const QByteArray &id)
125 126 127 128
{
    return id == "QLatin1String" || id == "QLatin1Literal" || id == "QStringLiteral";
}

129
inline bool isQtStringTranslation(const QByteArray &id)
130 131 132 133
{
    return id == "tr" || id == "trUtf8" || id == "translate" || id == "QT_TRANSLATE_NOOP";
}

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
180 181 182
        // check for enclosing nested expression
        if (priority - 1 >= 0)
            nested = interface->path()[priority - 1]->asNestedExpression();
183

Nikolai Kosjar's avatar
Nikolai Kosjar committed
184 185 186 187 188
        // 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;
189 190 191
        }
    }

Nikolai Kosjar's avatar
Nikolai Kosjar committed
192
    QString description() const
193
    {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
194 195
        return QApplication::translate("CppTools::QuickFix", "Rewrite Using %1").arg(replacement);
    }
196

Nikolai Kosjar's avatar
Nikolai Kosjar committed
197 198 199 200 201 202 203 204 205 206 207 208 209 210
    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(")"));
211
        }
Nikolai Kosjar's avatar
Nikolai Kosjar committed
212 213 214 215
        changes.replace(currentFile->range(binary->binary_op_token), replacement);
        currentFile->setChangeSet(changes);
        currentFile->apply();
    }
216

Nikolai Kosjar's avatar
Nikolai Kosjar committed
217 218 219 220
private:
    BinaryExpressionAST *binary;
    NestedExpressionAST *nested;
    UnaryExpressionAST *negation;
221

Nikolai Kosjar's avatar
Nikolai Kosjar committed
222
    QString replacement;
223 224
};

225 226
} // anonymous namespace

Nikolai Kosjar's avatar
Nikolai Kosjar committed
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 257 258 259 260 261 262
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;
    }
263

Nikolai Kosjar's avatar
Nikolai Kosjar committed
264 265 266
    result.append(CppQuickFixOperation::Ptr(
        new InverseLogicalComparisonOp(interface, index, binary, invertToken)));
}
267

268 269
namespace {

Nikolai Kosjar's avatar
Nikolai Kosjar committed
270
class FlipLogicalOperandsOp: public CppQuickFixOperation
271 272
{
public:
Nikolai Kosjar's avatar
Nikolai Kosjar committed
273 274 275 276 277
    FlipLogicalOperandsOp(const CppQuickFixInterface &interface, int priority,
                          BinaryExpressionAST *binary, QString replacement)
        : CppQuickFixOperation(interface)
        , binary(binary)
        , replacement(replacement)
278
    {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
279 280
        setPriority(priority);
    }
281

Nikolai Kosjar's avatar
Nikolai Kosjar committed
282 283 284 285 286 287 288
    QString description() const
    {
        if (replacement.isEmpty())
            return QApplication::translate("CppTools::QuickFix", "Swap Operands");
        else
            return QApplication::translate("CppTools::QuickFix", "Rewrite Using %1").arg(replacement);
    }
289

Nikolai Kosjar's avatar
Nikolai Kosjar committed
290 291 292 293
    void perform()
    {
        CppRefactoringChanges refactoring(snapshot());
        CppRefactoringFilePtr currentFile = refactoring.file(fileName());
294

Nikolai Kosjar's avatar
Nikolai Kosjar committed
295
        ChangeSet changes;
296 297
        changes.flip(currentFile->range(binary->left_expression),
                     currentFile->range(binary->right_expression));
Nikolai Kosjar's avatar
Nikolai Kosjar committed
298 299
        if (! replacement.isEmpty())
            changes.replace(currentFile->range(binary->binary_op_token), replacement);
300

Nikolai Kosjar's avatar
Nikolai Kosjar committed
301 302
        currentFile->setChangeSet(changes);
        currentFile->apply();
303 304 305
    }

private:
Nikolai Kosjar's avatar
Nikolai Kosjar committed
306 307 308
    BinaryExpressionAST *binary;
    QString replacement;
};
309

310 311
} // anonymous namespace

Nikolai Kosjar's avatar
Nikolai Kosjar committed
312 313 314 315
void FlipLogicalOperands::match(const CppQuickFixInterface &interface, QuickFixOperations &result)
{
    const QList<AST *> &path = interface->path();
    CppRefactoringFilePtr file = interface->currentFile();
316

Nikolai Kosjar's avatar
Nikolai Kosjar committed
317 318 319 320 321 322
    int index = path.size() - 1;
    BinaryExpressionAST *binary = path.at(index)->asBinaryExpression();
    if (! binary)
        return;
    if (! interface->isCursorOn(binary->binary_op_token))
        return;
323

Nikolai Kosjar's avatar
Nikolai Kosjar committed
324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346
    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;
    }
347

Nikolai Kosjar's avatar
Nikolai Kosjar committed
348 349 350 351 352 353
    QString replacement;
    if (flipToken != T_EOF_SYMBOL) {
        Token tok;
        tok.f.kind = flipToken;
        replacement = QLatin1String(tok.spell());
    }
354

Nikolai Kosjar's avatar
Nikolai Kosjar committed
355 356 357
    result.append(QuickFixOperation::Ptr(
        new FlipLogicalOperandsOp(interface, index, binary, replacement)));
}
358

359 360
namespace {

Nikolai Kosjar's avatar
Nikolai Kosjar committed
361
class RewriteLogicalAndOp: public CppQuickFixOperation
362 363
{
public:
Nikolai Kosjar's avatar
Nikolai Kosjar committed
364 365 366 367 368 369 370 371
    QSharedPointer<ASTPatternBuilder> mk;
    UnaryExpressionAST *left;
    UnaryExpressionAST *right;
    BinaryExpressionAST *pattern;

    RewriteLogicalAndOp(const CppQuickFixInterface &interface)
        : CppQuickFixOperation(interface)
        , mk(new ASTPatternBuilder)
372
    {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
373 374 375 376
        left = mk->UnaryExpression();
        right = mk->UnaryExpression();
        pattern = mk->BinaryExpression(left, right);
    }
377

Nikolai Kosjar's avatar
Nikolai Kosjar committed
378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396
    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();
    }
};
397

398 399
} // anonymous namespace

Nikolai Kosjar's avatar
Nikolai Kosjar committed
400 401 402 403 404
void RewriteLogicalAnd::match(const CppQuickFixInterface &interface, QuickFixOperations &result)
{
    BinaryExpressionAST *expression = 0;
    const QList<AST *> &path = interface->path();
    CppRefactoringFilePtr file = interface->currentFile();
405

Nikolai Kosjar's avatar
Nikolai Kosjar committed
406 407 408 409 410
    int index = path.size() - 1;
    for (; index != -1; --index) {
        expression = path.at(index)->asBinaryExpression();
        if (expression)
            break;
411 412
    }

Nikolai Kosjar's avatar
Nikolai Kosjar committed
413 414
    if (! expression)
        return;
415

Nikolai Kosjar's avatar
Nikolai Kosjar committed
416 417
    if (! interface->isCursorOn(expression->binary_op_token))
        return;
418

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
421 422 423 424
    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)) {
425 426
        op->setDescription(QApplication::translate("CppTools::QuickFix",
                                                   "Rewrite Condition Using ||"));
Nikolai Kosjar's avatar
Nikolai Kosjar committed
427 428 429 430
        op->setPriority(index);
        result.append(op);
    }
}
431

Nikolai Kosjar's avatar
Nikolai Kosjar committed
432 433 434 435
bool SplitSimpleDeclaration::checkDeclaration(SimpleDeclarationAST *declaration)
{
    if (! declaration->semicolon_token)
        return false;
436

Nikolai Kosjar's avatar
Nikolai Kosjar committed
437 438
    if (! declaration->decl_specifier_list)
        return false;
439

Nikolai Kosjar's avatar
Nikolai Kosjar committed
440 441
    for (SpecifierListAST *it = declaration->decl_specifier_list; it; it = it->next) {
        SpecifierAST *specifier = it->value;
442

Nikolai Kosjar's avatar
Nikolai Kosjar committed
443
        if (specifier->asEnumSpecifier() != 0)
444 445
            return false;

Nikolai Kosjar's avatar
Nikolai Kosjar committed
446
        else if (specifier->asClassSpecifier() != 0)
447
            return false;
Nikolai Kosjar's avatar
Nikolai Kosjar committed
448
    }
449

Nikolai Kosjar's avatar
Nikolai Kosjar committed
450 451
    if (! declaration->declarator_list)
        return false;
452

Nikolai Kosjar's avatar
Nikolai Kosjar committed
453 454
    else if (! declaration->declarator_list->next)
        return false;
455

Nikolai Kosjar's avatar
Nikolai Kosjar committed
456 457
    return true;
}
458

459 460
namespace {

Nikolai Kosjar's avatar
Nikolai Kosjar committed
461 462 463 464 465 466 467 468 469 470
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"));
471 472
    }

Nikolai Kosjar's avatar
Nikolai Kosjar committed
473
    void perform()
474
    {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
475 476
        CppRefactoringChanges refactoring(snapshot());
        CppRefactoringFilePtr currentFile = refactoring.file(fileName());
477

Nikolai Kosjar's avatar
Nikolai Kosjar committed
478
        ChangeSet changes;
479

Nikolai Kosjar's avatar
Nikolai Kosjar committed
480 481 482 483
        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);
484

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
487 488
        for (DeclaratorListAST *it = declaration->declarator_list->next; it; it = it->next) {
            DeclaratorAST *declarator = it->value;
489

Nikolai Kosjar's avatar
Nikolai Kosjar committed
490 491 492 493 494
            changes.insert(insertPos, QLatin1String("\n"));
            changes.copy(declSpecifiersStart, declSpecifiersEnd, insertPos);
            changes.insert(insertPos, QLatin1String(" "));
            changes.move(currentFile->range(declarator), insertPos);
            changes.insert(insertPos, QLatin1String(";"));
495

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
499
            prevDeclarator = declarator;
500
        }
Nikolai Kosjar's avatar
Nikolai Kosjar committed
501 502 503 504

        currentFile->setChangeSet(changes);
        currentFile->appendIndentRange(currentFile->range(declaration));
        currentFile->apply();
505 506 507
    }

private:
Nikolai Kosjar's avatar
Nikolai Kosjar committed
508 509
    SimpleDeclarationAST *declaration;
};
510

511 512
} // anonymous namespace

Nikolai Kosjar's avatar
Nikolai Kosjar committed
513 514 515 516 517 518 519
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();
520

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
524 525
        if (CoreDeclaratorAST *coreDecl = node->asCoreDeclarator())
            core_declarator = coreDecl;
526

Nikolai Kosjar's avatar
Nikolai Kosjar committed
527 528 529
        else if (SimpleDeclarationAST *simpleDecl = node->asSimpleDeclaration()) {
            if (checkDeclaration(simpleDecl)) {
                SimpleDeclarationAST *declaration = simpleDecl;
530

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
534 535 536 537 538 539
                if (cursorPosition >= startOfDeclSpecifier && cursorPosition <= endOfDeclSpecifier) {
                    // the AST node under cursor is a specifier.
                    result.append(QuickFixOperation::Ptr(
                        new SplitSimpleDeclarationOp(interface, index, declaration)));
                    return;
                }
540

Nikolai Kosjar's avatar
Nikolai Kosjar committed
541 542 543 544 545 546
                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;
                }
547 548
            }

Nikolai Kosjar's avatar
Nikolai Kosjar committed
549
            return;
550
        }
Nikolai Kosjar's avatar
Nikolai Kosjar committed
551 552
    }
}
553

554 555
namespace {

Nikolai Kosjar's avatar
Nikolai Kosjar committed
556 557 558 559 560 561 562
class AddBracesToIfOp: public CppQuickFixOperation
{
public:
    AddBracesToIfOp(const CppQuickFixInterface &interface, int priority, StatementAST *statement)
        : CppQuickFixOperation(interface, priority)
        , _statement(statement)
    {
563
        setDescription(QApplication::translate("CppTools::QuickFix", "Add Curly Braces"));
Nikolai Kosjar's avatar
Nikolai Kosjar committed
564 565 566 567 568 569 570 571
    }

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

        ChangeSet changes;
572

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

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
579
        currentFile->setChangeSet(changes);
580
        currentFile->appendIndentRange(ChangeSet::Range(start, end));
Nikolai Kosjar's avatar
Nikolai Kosjar committed
581 582 583 584 585 586 587
        currentFile->apply();
    }

private:
    StatementAST *_statement;
};

588 589
} // anonymous namespace

Nikolai Kosjar's avatar
Nikolai Kosjar committed
590
void AddBracesToIf::match(const CppQuickFixInterface &interface, QuickFixOperations &result)
591
{
Nikolai Kosjar's avatar
Nikolai Kosjar committed
592
    const QList<AST *> &path = interface->path();
593

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
617 618 619
    // ### This could very well be extended to the else branch
    // and other nodes entirely.
}
620

621 622
namespace {

Nikolai Kosjar's avatar
Nikolai Kosjar committed
623 624 625 626 627 628 629 630 631 632 633
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);
634 635
    }

Nikolai Kosjar's avatar
Nikolai Kosjar committed
636
    void perform()
637
    {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
638 639
        CppRefactoringChanges refactoring(snapshot());
        CppRefactoringFilePtr currentFile = refactoring.file(fileName());
640

Nikolai Kosjar's avatar
Nikolai Kosjar committed
641
        ChangeSet changes;
642

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
645 646 647
        int insertPos = currentFile->startOf(pattern);
        changes.move(currentFile->range(condition), insertPos);
        changes.insert(insertPos, QLatin1String(";\n"));
648

Nikolai Kosjar's avatar
Nikolai Kosjar committed
649 650 651 652
        currentFile->setChangeSet(changes);
        currentFile->appendIndentRange(currentFile->range(pattern));
        currentFile->apply();
    }
653

Nikolai Kosjar's avatar
Nikolai Kosjar committed
654 655 656 657 658 659
    ASTMatcher matcher;
    ASTPatternBuilder mk;
    CPPEditorWidget *editor;
    ConditionAST *condition;
    IfStatementAST *pattern;
    CoreDeclaratorAST *core;
660 661
};

662 663
} // anonymous namespace

Nikolai Kosjar's avatar
Nikolai Kosjar committed
664 665
void MoveDeclarationOutOfIf::match(const CppQuickFixInterface &interface,
                                   QuickFixOperations &result)
666
{
Nikolai Kosjar's avatar
Nikolai Kosjar committed
667 668 669 670 671 672 673 674 675 676 677 678
    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;
679

Nikolai Kosjar's avatar
Nikolai Kosjar committed
680 681 682 683
                if (interface->isCursorOn(op->core)) {
                    op->setPriority(index);
                    result.append(op);
                    return;
684 685 686 687
                }
            }
        }
    }
Nikolai Kosjar's avatar
Nikolai Kosjar committed
688
}
689

690 691
namespace {

Nikolai Kosjar's avatar
Nikolai Kosjar committed
692 693 694 695 696
class MoveDeclarationOutOfWhileOp: public CppQuickFixOperation
{
public:
    MoveDeclarationOutOfWhileOp(const CppQuickFixInterface &interface)
        : CppQuickFixOperation(interface)
697
    {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
698 699
        setDescription(QApplication::translate("CppTools::QuickFix",
                                               "Move Declaration out of Condition"));
700

Nikolai Kosjar's avatar
Nikolai Kosjar committed
701 702 703
        condition = mk.Condition();
        pattern = mk.WhileStatement(condition);
    }
704

Nikolai Kosjar's avatar
Nikolai Kosjar committed
705 706 707 708
    void perform()
    {
        CppRefactoringChanges refactoring(snapshot());
        CppRefactoringFilePtr currentFile = refactoring.file(fileName());
709

Nikolai Kosjar's avatar
Nikolai Kosjar committed
710
        ChangeSet changes;
711

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
715 716 717 718 719
        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"));
720

Nikolai Kosjar's avatar
Nikolai Kosjar committed
721 722 723 724
        currentFile->setChangeSet(changes);
        currentFile->appendIndentRange(currentFile->range(pattern));
        currentFile->apply();
    }
725

Nikolai Kosjar's avatar
Nikolai Kosjar committed
726 727 728 729 730 731 732
    ASTMatcher matcher;
    ASTPatternBuilder mk;
    CPPEditorWidget *editor;
    ConditionAST *condition;
    WhileStatementAST *pattern;
    CoreDeclaratorAST *core;
};
733

734 735
} // anonymous namespace

Nikolai Kosjar's avatar
Nikolai Kosjar committed
736 737
void MoveDeclarationOutOfWhile::match(const CppQuickFixInterface &interface,
                                      QuickFixOperations &result)
738
{
Nikolai Kosjar's avatar
Nikolai Kosjar committed
739 740
    const QList<AST *> &path = interface->path();
    QSharedPointer<MoveDeclarationOutOfWhileOp> op(new MoveDeclarationOutOfWhileOp(interface));
741

Nikolai Kosjar's avatar
Nikolai Kosjar committed
742 743 744 745 746 747
    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;
748

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
752 753
                if (! declarator->equal_token)
                    return;
754

Nikolai Kosjar's avatar
Nikolai Kosjar committed
755 756
                if (! declarator->initializer)
                    return;
757

Nikolai Kosjar's avatar
Nikolai Kosjar committed
758 759 760 761
                if (interface->isCursorOn(op->core)) {
                    op->setPriority(index);
                    result.append(op);
                    return;
762 763 764 765
                }
            }
        }
    }
Nikolai Kosjar's avatar
Nikolai Kosjar committed
766
}
767

768 769
namespace {

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

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

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
790 791 792 793 794
        if (binaryToken.is(T_AMPER_AMPER))
            splitAndCondition(currentFile);
        else
            splitOrCondition(currentFile);
    }
795

Nikolai Kosjar's avatar
Nikolai Kosjar committed
796 797 798
    void splitAndCondition(CppRefactoringFilePtr currentFile) const
    {
        ChangeSet changes;
799

Nikolai Kosjar's avatar
Nikolai Kosjar committed
800 801 802 803
        int startPos = currentFile->startOf(pattern);
        changes.insert(startPos, QLatin1String("if ("));
        changes.move(currentFile->range(condition->left_expression), startPos);
        changes.insert(startPos, QLatin1String(") {\n"));
804

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
809 810 811 812
        currentFile->setChangeSet(changes);
        currentFile->appendIndentRange(currentFile->range(pattern));
        currentFile->apply();
    }
813

Nikolai Kosjar's avatar
Nikolai Kosjar committed
814
    void splitOrCondition(CppRefactoringFilePtr currentFile) const
815
    {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
816
        ChangeSet changes;
817

Nikolai Kosjar's avatar
Nikolai Kosjar committed
818 819
        StatementAST *ifTrueStatement = pattern->statement;
        CompoundStatementAST *compoundStatement = ifTrueStatement->asCompoundStatement();
820

Nikolai Kosjar's avatar
Nikolai Kosjar committed
821 822 823 824 825 826
        int insertPos = currentFile->endOf(ifTrueStatement);
        if (compoundStatement)
            changes.insert(insertPos, QLatin1String(" "));
        else
            changes.insert(insertPos, QLatin1String("\n"));
        changes.insert(insertPos, QLatin1String("else if ("));
827

Nikolai Kosjar's avatar
Nikolai Kosjar committed
828 829 830
        const int rExprStart = currentFile->startOf(condition->right_expression);
        changes.move(rExprStart, currentFile->startOf(pattern->rparen_token), insertPos);
        changes.insert(insertPos, QLatin1String(")"));
831

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

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

private:
Nikolai Kosjar's avatar
Nikolai Kosjar committed
844 845 846
    IfStatementAST *pattern;
    BinaryExpressionAST *condition;
};
847

848 849
} // anonymous namespace

Nikolai Kosjar's avatar
Nikolai Kosjar committed
850 851 852 853
void SplitIfStatement::match(const CppQuickFixInterface &interface, QuickFixOperations &result)
{
    IfStatementAST *pattern = 0;
    const QList<AST *> &path = interface->path();
854

Nikolai Kosjar's avatar
Nikolai Kosjar committed
855 856 857 858 859 860
    int index = path.size() - 1;
    for (; index != -1; --index) {
        AST *node = path.at(index);
        if (IfStatementAST *stmt = node->asIfStatement()) {
            pattern = stmt;
            break;
861
        }
Nikolai Kosjar's avatar
Nikolai Kosjar committed
862
    }
863

Nikolai Kosjar's avatar
Nikolai Kosjar committed
864 865
    if (! pattern || ! pattern->statement)
        return;
866

Nikolai Kosjar's avatar
Nikolai Kosjar committed
867 868 869 870 871 872
    unsigned splitKind = 0;
    for (++index; index < path.size(); ++index) {
        AST *node = path.at(index);
        BinaryExpressionAST *condition = node->asBinaryExpression();
        if (! condition)
            return;
873

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
876 877 878 879 880 881 882 883 884 885
        // 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;
886 887
        }

Nikolai Kosjar's avatar
Nikolai Kosjar committed
888 889 890 891
        if (interface->isCursorOn(condition->binary_op_token)) {
            result.append(QuickFixOperation::Ptr(
                new SplitIfStatementOp(interface, index, pattern, condition)));
            return;
892
        }
Nikolai Kosjar's avatar
Nikolai Kosjar committed
893 894
    }
}
895

896 897 898 899
/* 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,
900
                                          const CppRefactoringFilePtr &file, Type *type,
901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922
                                          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;
923
        }
924
    }
925

926 927 928 929 930 931 932 933
    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;
934 935 936 937
                    }
                }
            }
        }
938 939 940
    }
    return literal;
}
941

942 943
namespace {

Nikolai Kosjar's avatar
Nikolai Kosjar committed
944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959