cppquickfixes.cpp 204 KB
Newer Older
hjk's avatar
hjk committed
1
/****************************************************************************
2
**
3
** Copyright (C) 2014 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
#include "cppvirtualfunctionassistprovider.h"
36
#include "cppinsertvirtualmethods.h"
37

38 39
#include <coreplugin/icore.h>

40
#include <cpptools/cppclassesfilter.h>
41
#include <cpptools/cppcodestylesettings.h>
42 43
#include <cpptools/cpppointerdeclarationformatter.h>
#include <cpptools/cpptoolsconstants.h>
44
#include <cpptools/cpptoolsreuse.h>
45
#include <cpptools/includeutils.h>
46
#include <cpptools/insertionpointlocator.h>
47
#include <cpptools/symbolfinder.h>
48

49
#include <cplusplus/ASTPath.h>
50
#include <cplusplus/CPlusPlusForwardDeclarations.h>
51 52 53 54
#include <cplusplus/CppRewriter.h>
#include <cplusplus/DependencyTable.h>
#include <cplusplus/TypeOfExpression.h>

55
#include <extensionsystem/pluginmanager.h>
56

57 58
#include <utils/qtcassert.h>

59
#include <QApplication>
60
#include <QDir>
61
#include <QFileInfo>
62 63
#include <QInputDialog>
#include <QMessageBox>
Nikolai Kosjar's avatar
Nikolai Kosjar committed
64
#include <QSharedPointer>
65
#include <QStack>
66
#include <QTextCursor>
67
#include <QTextCodec>
68

Nikolai Kosjar's avatar
Nikolai Kosjar committed
69 70
#include <cctype>

71
using namespace CPlusPlus;
72 73
using namespace CppEditor;
using namespace CppEditor::Internal;
74 75
using namespace CppTools;
using namespace TextEditor;
Orgad Shaneh's avatar
Orgad Shaneh committed
76
using Utils::ChangeSet;
Nikolai Kosjar's avatar
Nikolai Kosjar committed
77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108

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);
109
    plugIn->addAutoReleasedObject(new ConvertFromAndToPointer);
Nikolai Kosjar's avatar
Nikolai Kosjar committed
110
    plugIn->addAutoReleasedObject(new ExtractFunction);
111
    plugIn->addAutoReleasedObject(new ExtractLiteralAsParameter);
Nikolai Kosjar's avatar
Nikolai Kosjar committed
112 113 114
    plugIn->addAutoReleasedObject(new GenerateGetterSetter);
    plugIn->addAutoReleasedObject(new InsertDeclFromDef);
    plugIn->addAutoReleasedObject(new InsertDefFromDecl);
115 116 117

    plugIn->addAutoReleasedObject(new MoveFuncDefOutside);
    plugIn->addAutoReleasedObject(new MoveFuncDefToDecl);
118 119

    plugIn->addAutoReleasedObject(new AssignToLocalVariable);
120 121

    plugIn->addAutoReleasedObject(new InsertVirtualMethods);
122 123

    plugIn->addAutoReleasedObject(new OptimizeForLoop);
124 125

    plugIn->addAutoReleasedObject(new EscapeStringLiteral);
Nikolai Kosjar's avatar
Nikolai Kosjar committed
126
}
127

128 129 130 131
// In the following anonymous namespace all functions are collected, which could be of interest for
// different quick fixes.
namespace {

132 133 134 135 136 137
enum DefPos {
    DefPosInsideClass,
    DefPosOutsideClass,
    DefPosImplementationFile
};

138
InsertionLocation insertLocationForMethodDefinition(Symbol *symbol, const bool useSymbolFinder,
139 140 141 142 143 144 145
                                                    CppRefactoringChanges& refactoring,
                                                    const QString& fileName)
{
    QTC_ASSERT(symbol, return InsertionLocation());

    // Try to find optimal location
    const InsertionPointLocator locator(refactoring);
146 147
    const QList<InsertionLocation> list
            = locator.methodDefinition(symbol, useSymbolFinder, fileName);
148 149
    for (int i = 0; i < list.count(); ++i) {
        InsertionLocation location = list.at(i);
Orgad Shaneh's avatar
Orgad Shaneh committed
150
        if (location.isValid() && location.fileName() == fileName)
151 152 153
            return location;
    }

154 155
    // ...failed,
    // if class member try to get position right after class
156
    CppRefactoringFilePtr file = refactoring.file(fileName);
157 158 159 160 161 162 163 164 165 166 167 168 169
    unsigned line = 0, column = 0;
    if (Class *clazz = symbol->enclosingClass()) {
        if (symbol->fileName() == fileName.toUtf8()) {
            file->cppDocument()->translationUnit()->getPosition(clazz->endOffset(), &line, &column);
            if (line != 0) {
                ++column; // Skipping the ";"
                return InsertionLocation(fileName, QLatin1String("\n\n"), QLatin1String(""),
                                         line, column);
            }
        }
    }

    // fall through: position at end of file
170 171 172 173 174 175 176 177 178 179
    const QTextDocument *doc = file->document();
    int pos = qMax(0, doc->characterCount() - 1);

    //TODO watch for matching namespace
    //TODO watch for moc-includes

    file->lineAndColumn(pos, &line, &column);
    return InsertionLocation(fileName, QLatin1String("\n\n"), QLatin1String("\n"), line, column);
}

180
inline bool isQtStringLiteral(const QByteArray &id)
181 182 183 184
{
    return id == "QLatin1String" || id == "QLatin1Literal" || id == "QStringLiteral";
}

185
inline bool isQtStringTranslation(const QByteArray &id)
186 187 188 189
{
    return id == "tr" || id == "trUtf8" || id == "translate" || id == "QT_TRANSLATE_NOOP";
}

190 191 192 193 194
Class *isMemberFunction(const LookupContext &context, Function *function)
{
    QTC_ASSERT(function, return 0);

    Scope *enclosingScope = function->enclosingScope();
195
    while (!(enclosingScope->isNamespace() || enclosingScope->isClass()))
196 197 198 199
        enclosingScope = enclosingScope->enclosingScope();
    QTC_ASSERT(enclosingScope != 0, return 0);

    const Name *functionName = function->name();
200
    if (!functionName)
201
        return 0;
202

203
    if (!functionName->isQualifiedNameId())
204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219
        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;
}

220 221 222 223 224 225 226 227 228 229 230 231 232
Namespace *isNamespaceFunction(const LookupContext &context, Function *function)
{
    QTC_ASSERT(function, return 0);
    if (isMemberFunction(context, 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)
233
        return 0;
234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257

    // global namespace
    if (!functionName->isQualifiedNameId()) {
        foreach (Symbol *s, context.globalNamespace()->symbols()) {
            if (Namespace *matchingNamespace = s->asNamespace())
                return matchingNamespace;
        }
        return 0;
    }

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

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

    return 0;
}

258 259 260 261 262
// Given include is e.g. "afile.h" or <afile.h> (quotes/angle brackets included!).
void insertNewIncludeDirective(const QString &include, CppRefactoringFilePtr file)
{
    // Find optimal position
    using namespace IncludeUtils;
263
    LineForNewIncludeDirective finder(file->document(), file->cppDocument()->resolvedIncludes(),
264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288
                                      LineForNewIncludeDirective::IgnoreMocIncludes,
                                      LineForNewIncludeDirective::AutoDetect);
    unsigned newLinesToPrepend = 0;
    unsigned newLinesToAppend = 0;
    const int insertLine = finder(include, &newLinesToPrepend, &newLinesToAppend);
    QTC_ASSERT(insertLine >= 1, return);
    const int insertPosition = file->position(insertLine, 1);
    QTC_ASSERT(insertPosition >= 0, return);

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

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

289 290 291 292 293 294
bool nameIncludesOperatorName(const Name *name)
{
    return name->isOperatorNameId()
        || (name->isQualifiedNameId() && name->asQualifiedNameId()->name()->isOperatorNameId());
}

295 296 297 298
} // anonymous namespace

namespace {

Nikolai Kosjar's avatar
Nikolai Kosjar committed
299
class InverseLogicalComparisonOp: public CppQuickFixOperation
300 301
{
public:
Nikolai Kosjar's avatar
Nikolai Kosjar committed
302 303 304 305
    InverseLogicalComparisonOp(const CppQuickFixInterface &interface, int priority,
                               BinaryExpressionAST *binary, Kind invertToken)
        : CppQuickFixOperation(interface, priority)
        , binary(binary), nested(0), negation(0)
306
    {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
307 308 309
        Token tok;
        tok.f.kind = invertToken;
        replacement = QLatin1String(tok.spell());
310

Nikolai Kosjar's avatar
Nikolai Kosjar committed
311 312 313
        // check for enclosing nested expression
        if (priority - 1 >= 0)
            nested = interface->path()[priority - 1]->asNestedExpression();
314

Nikolai Kosjar's avatar
Nikolai Kosjar committed
315 316 317
        // check for ! before parentheses
        if (nested && priority - 2 >= 0) {
            negation = interface->path()[priority - 2]->asUnaryExpression();
318 319
            if (negation
                    && !interface->currentFile()->tokenAt(negation->unary_op_token).is(T_EXCLAIM)) {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
320
                negation = 0;
321
            }
322 323 324
        }
    }

Nikolai Kosjar's avatar
Nikolai Kosjar committed
325
    QString description() const
326
    {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
327 328
        return QApplication::translate("CppTools::QuickFix", "Rewrite Using %1").arg(replacement);
    }
329

Nikolai Kosjar's avatar
Nikolai Kosjar committed
330 331 332 333 334 335 336 337 338 339 340 341 342 343
    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(")"));
344
        }
Nikolai Kosjar's avatar
Nikolai Kosjar committed
345 346 347 348
        changes.replace(currentFile->range(binary->binary_op_token), replacement);
        currentFile->setChangeSet(changes);
        currentFile->apply();
    }
349

Nikolai Kosjar's avatar
Nikolai Kosjar committed
350 351 352 353
private:
    BinaryExpressionAST *binary;
    NestedExpressionAST *nested;
    UnaryExpressionAST *negation;
354

Nikolai Kosjar's avatar
Nikolai Kosjar committed
355
    QString replacement;
356 357
};

358 359
} // anonymous namespace

Nikolai Kosjar's avatar
Nikolai Kosjar committed
360 361 362 363 364 365 366 367
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();
368
    if (!binary)
Nikolai Kosjar's avatar
Nikolai Kosjar committed
369
        return;
370
    if (!interface->isCursorOn(binary->binary_op_token))
Nikolai Kosjar's avatar
Nikolai Kosjar committed
371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395
        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;
    }
396

Nikolai Kosjar's avatar
Nikolai Kosjar committed
397 398 399
    result.append(CppQuickFixOperation::Ptr(
        new InverseLogicalComparisonOp(interface, index, binary, invertToken)));
}
400

401 402
namespace {

Nikolai Kosjar's avatar
Nikolai Kosjar committed
403
class FlipLogicalOperandsOp: public CppQuickFixOperation
404 405
{
public:
Nikolai Kosjar's avatar
Nikolai Kosjar committed
406 407 408 409 410
    FlipLogicalOperandsOp(const CppQuickFixInterface &interface, int priority,
                          BinaryExpressionAST *binary, QString replacement)
        : CppQuickFixOperation(interface)
        , binary(binary)
        , replacement(replacement)
411
    {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
412 413
        setPriority(priority);
    }
414

Nikolai Kosjar's avatar
Nikolai Kosjar committed
415 416 417 418 419 420 421
    QString description() const
    {
        if (replacement.isEmpty())
            return QApplication::translate("CppTools::QuickFix", "Swap Operands");
        else
            return QApplication::translate("CppTools::QuickFix", "Rewrite Using %1").arg(replacement);
    }
422

Nikolai Kosjar's avatar
Nikolai Kosjar committed
423 424 425 426
    void perform()
    {
        CppRefactoringChanges refactoring(snapshot());
        CppRefactoringFilePtr currentFile = refactoring.file(fileName());
427

Nikolai Kosjar's avatar
Nikolai Kosjar committed
428
        ChangeSet changes;
429 430
        changes.flip(currentFile->range(binary->left_expression),
                     currentFile->range(binary->right_expression));
431
        if (!replacement.isEmpty())
Nikolai Kosjar's avatar
Nikolai Kosjar committed
432
            changes.replace(currentFile->range(binary->binary_op_token), replacement);
433

Nikolai Kosjar's avatar
Nikolai Kosjar committed
434 435
        currentFile->setChangeSet(changes);
        currentFile->apply();
436 437 438
    }

private:
Nikolai Kosjar's avatar
Nikolai Kosjar committed
439 440 441
    BinaryExpressionAST *binary;
    QString replacement;
};
442

443 444
} // anonymous namespace

Nikolai Kosjar's avatar
Nikolai Kosjar committed
445 446 447 448
void FlipLogicalOperands::match(const CppQuickFixInterface &interface, QuickFixOperations &result)
{
    const QList<AST *> &path = interface->path();
    CppRefactoringFilePtr file = interface->currentFile();
449

Nikolai Kosjar's avatar
Nikolai Kosjar committed
450 451
    int index = path.size() - 1;
    BinaryExpressionAST *binary = path.at(index)->asBinaryExpression();
452
    if (!binary)
Nikolai Kosjar's avatar
Nikolai Kosjar committed
453
        return;
454
    if (!interface->isCursorOn(binary->binary_op_token))
Nikolai Kosjar's avatar
Nikolai Kosjar committed
455
        return;
456

Nikolai Kosjar's avatar
Nikolai Kosjar committed
457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479
    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;
    }
480

Nikolai Kosjar's avatar
Nikolai Kosjar committed
481 482 483 484 485 486
    QString replacement;
    if (flipToken != T_EOF_SYMBOL) {
        Token tok;
        tok.f.kind = flipToken;
        replacement = QLatin1String(tok.spell());
    }
487

Nikolai Kosjar's avatar
Nikolai Kosjar committed
488 489 490
    result.append(QuickFixOperation::Ptr(
        new FlipLogicalOperandsOp(interface, index, binary, replacement)));
}
491

492 493
namespace {

Nikolai Kosjar's avatar
Nikolai Kosjar committed
494
class RewriteLogicalAndOp: public CppQuickFixOperation
495 496
{
public:
Nikolai Kosjar's avatar
Nikolai Kosjar committed
497 498 499 500 501 502 503 504
    QSharedPointer<ASTPatternBuilder> mk;
    UnaryExpressionAST *left;
    UnaryExpressionAST *right;
    BinaryExpressionAST *pattern;

    RewriteLogicalAndOp(const CppQuickFixInterface &interface)
        : CppQuickFixOperation(interface)
        , mk(new ASTPatternBuilder)
505
    {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
506 507 508 509
        left = mk->UnaryExpression();
        right = mk->UnaryExpression();
        pattern = mk->BinaryExpression(left, right);
    }
510

Nikolai Kosjar's avatar
Nikolai Kosjar committed
511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529
    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();
    }
};
530

531 532
} // anonymous namespace

Nikolai Kosjar's avatar
Nikolai Kosjar committed
533 534 535 536 537
void RewriteLogicalAnd::match(const CppQuickFixInterface &interface, QuickFixOperations &result)
{
    BinaryExpressionAST *expression = 0;
    const QList<AST *> &path = interface->path();
    CppRefactoringFilePtr file = interface->currentFile();
538

Nikolai Kosjar's avatar
Nikolai Kosjar committed
539 540 541 542 543
    int index = path.size() - 1;
    for (; index != -1; --index) {
        expression = path.at(index)->asBinaryExpression();
        if (expression)
            break;
544 545
    }

546
    if (!expression)
Nikolai Kosjar's avatar
Nikolai Kosjar committed
547
        return;
548

549
    if (!interface->isCursorOn(expression->binary_op_token))
Nikolai Kosjar's avatar
Nikolai Kosjar committed
550
        return;
551

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
554 555 556 557
    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)) {
558 559
        op->setDescription(QApplication::translate("CppTools::QuickFix",
                                                   "Rewrite Condition Using ||"));
Nikolai Kosjar's avatar
Nikolai Kosjar committed
560 561 562 563
        op->setPriority(index);
        result.append(op);
    }
}
564

Nikolai Kosjar's avatar
Nikolai Kosjar committed
565 566
bool SplitSimpleDeclaration::checkDeclaration(SimpleDeclarationAST *declaration)
{
567
    if (!declaration->semicolon_token)
Nikolai Kosjar's avatar
Nikolai Kosjar committed
568
        return false;
569

570
    if (!declaration->decl_specifier_list)
Nikolai Kosjar's avatar
Nikolai Kosjar committed
571
        return false;
572

Nikolai Kosjar's avatar
Nikolai Kosjar committed
573 574
    for (SpecifierListAST *it = declaration->decl_specifier_list; it; it = it->next) {
        SpecifierAST *specifier = it->value;
575

Nikolai Kosjar's avatar
Nikolai Kosjar committed
576
        if (specifier->asEnumSpecifier() != 0)
577 578
            return false;

Nikolai Kosjar's avatar
Nikolai Kosjar committed
579
        else if (specifier->asClassSpecifier() != 0)
580
            return false;
Nikolai Kosjar's avatar
Nikolai Kosjar committed
581
    }
582

583
    if (!declaration->declarator_list)
Nikolai Kosjar's avatar
Nikolai Kosjar committed
584
        return false;
585

586
    else if (!declaration->declarator_list->next)
Nikolai Kosjar's avatar
Nikolai Kosjar committed
587
        return false;
588

Nikolai Kosjar's avatar
Nikolai Kosjar committed
589 590
    return true;
}
591

592 593
namespace {

Nikolai Kosjar's avatar
Nikolai Kosjar committed
594 595 596 597 598 599 600 601 602 603
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"));
604 605
    }

Nikolai Kosjar's avatar
Nikolai Kosjar committed
606
    void perform()
607
    {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
608 609
        CppRefactoringChanges refactoring(snapshot());
        CppRefactoringFilePtr currentFile = refactoring.file(fileName());
610

Nikolai Kosjar's avatar
Nikolai Kosjar committed
611
        ChangeSet changes;
612

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

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
620 621
        for (DeclaratorListAST *it = declaration->declarator_list->next; it; it = it->next) {
            DeclaratorAST *declarator = it->value;
622

Nikolai Kosjar's avatar
Nikolai Kosjar committed
623 624 625 626 627
            changes.insert(insertPos, QLatin1String("\n"));
            changes.copy(declSpecifiersStart, declSpecifiersEnd, insertPos);
            changes.insert(insertPos, QLatin1String(" "));
            changes.move(currentFile->range(declarator), insertPos);
            changes.insert(insertPos, QLatin1String(";"));
628

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
632
            prevDeclarator = declarator;
633
        }
Nikolai Kosjar's avatar
Nikolai Kosjar committed
634 635 636 637

        currentFile->setChangeSet(changes);
        currentFile->appendIndentRange(currentFile->range(declaration));
        currentFile->apply();
638 639 640
    }

private:
Nikolai Kosjar's avatar
Nikolai Kosjar committed
641 642
    SimpleDeclarationAST *declaration;
};
643

644 645
} // anonymous namespace

Nikolai Kosjar's avatar
Nikolai Kosjar committed
646 647 648 649 650 651 652
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();
653

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

657
        if (CoreDeclaratorAST *coreDecl = node->asCoreDeclarator()) {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
658
            core_declarator = coreDecl;
659
        } else if (SimpleDeclarationAST *simpleDecl = node->asSimpleDeclaration()) {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
660 661
            if (checkDeclaration(simpleDecl)) {
                SimpleDeclarationAST *declaration = simpleDecl;
662

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
666 667 668 669 670 671
                if (cursorPosition >= startOfDeclSpecifier && cursorPosition <= endOfDeclSpecifier) {
                    // the AST node under cursor is a specifier.
                    result.append(QuickFixOperation::Ptr(
                        new SplitSimpleDeclarationOp(interface, index, declaration)));
                    return;
                }
672

Nikolai Kosjar's avatar
Nikolai Kosjar committed
673 674 675 676 677 678
                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;
                }
679 680
            }

Nikolai Kosjar's avatar
Nikolai Kosjar committed
681
            return;
682
        }
Nikolai Kosjar's avatar
Nikolai Kosjar committed
683 684
    }
}
685

686 687
namespace {

Nikolai Kosjar's avatar
Nikolai Kosjar committed
688 689 690 691 692 693 694
class AddBracesToIfOp: public CppQuickFixOperation
{
public:
    AddBracesToIfOp(const CppQuickFixInterface &interface, int priority, StatementAST *statement)
        : CppQuickFixOperation(interface, priority)
        , _statement(statement)
    {
695
        setDescription(QApplication::translate("CppTools::QuickFix", "Add Curly Braces"));
Nikolai Kosjar's avatar
Nikolai Kosjar committed
696 697 698 699 700 701 702 703
    }

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

        ChangeSet changes;
704

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

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
711
        currentFile->setChangeSet(changes);
712
        currentFile->appendIndentRange(ChangeSet::Range(start, end));
Nikolai Kosjar's avatar
Nikolai Kosjar committed
713 714 715 716 717 718 719
        currentFile->apply();
    }

private:
    StatementAST *_statement;
};

720 721
} // anonymous namespace

Nikolai Kosjar's avatar
Nikolai Kosjar committed
722
void AddBracesToIf::match(const CppQuickFixInterface &interface, QuickFixOperations &result)
723
{
Nikolai Kosjar's avatar
Nikolai Kosjar committed
724
    const QList<AST *> &path = interface->path();
725

Nikolai Kosjar's avatar
Nikolai Kosjar committed
726 727 728 729
    // 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
730
        && !ifStatement->statement->asCompoundStatement()) {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
731 732 733 734 735 736 737 738
        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) {
739
        IfStatementAST *ifStatement = path.at(index)->asIfStatement();
Nikolai Kosjar's avatar
Nikolai Kosjar committed
740 741
        if (ifStatement && ifStatement->statement
            && interface->isCursorOn(ifStatement->statement)
742
            && !ifStatement->statement->asCompoundStatement()) {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
743 744
            result.append(QuickFixOperation::Ptr(
                new AddBracesToIfOp(interface, index, ifStatement->statement)));
745
            return;
746
        }
Nikolai Kosjar's avatar
Nikolai Kosjar committed
747
    }
748

Nikolai Kosjar's avatar
Nikolai Kosjar committed
749 750 751
    // ### This could very well be extended to the else branch
    // and other nodes entirely.
}
752

753 754
namespace {

Nikolai Kosjar's avatar
Nikolai Kosjar committed
755 756 757 758 759 760 761 762 763
class MoveDeclarationOutOfIfOp: public CppQuickFixOperation
{
public:
    MoveDeclarationOutOfIfOp(const CppQuickFixInterface &interface)
        : CppQuickFixOperation(interface)
    {
        setDescription(QApplication::translate("CppTools::QuickFix",
                                               "Move Declaration out of Condition"));

764 765 766 767 768
        reset();
    }

    void reset()
    {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
769 770
        condition = mk.Condition();
        pattern = mk.IfStatement(condition);
771 772
    }

Nikolai Kosjar's avatar
Nikolai Kosjar committed
773
    void perform()
774
    {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
775 776
        CppRefactoringChanges refactoring(snapshot());
        CppRefactoringFilePtr currentFile = refactoring.file(fileName());
777

Nikolai Kosjar's avatar
Nikolai Kosjar committed
778
        ChangeSet changes;
779

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
782 783 784
        int insertPos = currentFile->startOf(pattern);
        changes.move(currentFile->range(condition), insertPos);
        changes.insert(insertPos, QLatin1String(";\n"));
785

Nikolai Kosjar's avatar
Nikolai Kosjar committed
786 787 788 789
        currentFile->setChangeSet(changes);
        currentFile->appendIndentRange(currentFile->range(pattern));
        currentFile->apply();
    }
790

Nikolai Kosjar's avatar
Nikolai Kosjar committed
791 792 793 794 795
    ASTMatcher matcher;
    ASTPatternBuilder mk;
    ConditionAST *condition;
    IfStatementAST *pattern;
    CoreDeclaratorAST *core;
796 797
};

798 799
} // anonymous namespace

Nikolai Kosjar's avatar
Nikolai Kosjar committed
800 801
void MoveDeclarationOutOfIf::match(const CppQuickFixInterface &interface,
                                   QuickFixOperations &result)
802
{
Nikolai Kosjar's avatar
Nikolai Kosjar committed
803 804 805 806 807 808 809 810 811 812
    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;
813
                if (!op->core)
Nikolai Kosjar's avatar
Nikolai Kosjar committed
814
                    return;
815

Nikolai Kosjar's avatar
Nikolai Kosjar committed
816 817 818 819
                if (interface->isCursorOn(op->core)) {
                    op->setPriority(index);
                    result.append(op);
                    return;
820
                }
821 822

                op->reset();
823 824 825
            }
        }
    }
Nikolai Kosjar's avatar
Nikolai Kosjar committed
826
}
827

828 829
namespace {

Nikolai Kosjar's avatar
Nikolai Kosjar committed
830 831 832 833 834
class MoveDeclarationOutOfWhileOp: public CppQuickFixOperation
{
public:
    MoveDeclarationOutOfWhileOp(const CppQuickFixInterface &interface)
        : CppQuickFixOperation(interface)
835
    {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
836 837
        setDescription(QApplication::translate("CppTools::QuickFix",
                                               "Move Declaration out of Condition"));
838 839
        reset();
    }
840

841 842
    void reset()
    {
Nikolai Kosjar's avatar
Nikolai Kosjar committed
843 844 845
        condition = mk.Condition();
        pattern = mk.WhileStatement(condition);
    }
846

Nikolai Kosjar's avatar
Nikolai Kosjar committed
847 848 849 850
    void perform()
    {
        CppRefactoringChanges refactoring(snapshot());
        CppRefactoringFilePtr currentFile = refactoring.file(fileName());
851

Nikolai Kosjar's avatar
Nikolai Kosjar committed
852
        ChangeSet changes;
853

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

Nikolai Kosjar's avatar
Nikolai Kosjar committed
857 858 859 860 861
        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"));
862

Nikolai Kosjar's avatar
Nikolai Kosjar committed
863 864 865 866
        currentFile->setChangeSet(changes);
        currentFile->appendIndentRange(currentFile->range(pattern));
        currentFile->apply();
    }
867

Nikolai Kosjar's avatar
Nikolai Kosjar committed
868 869 870 871 872 873
    ASTMatcher matcher;
    ASTPatternBuilder mk;
    ConditionAST *condition;
    WhileStatementAST *pattern;
    CoreDeclaratorAST *core;
};
874

875 876
} // anonymous namespace

Nikolai Kosjar's avatar
Nikolai Kosjar committed
877 878
void MoveDeclarationOutOfWhile::match(const CppQuickFixInterface &interface,
                                      QuickFixOperations &result)
879
{
Nikolai Kosjar's avatar
Nikolai Kosjar committed
880 881
    const QList<AST *> &path = interface->path();
    QSharedPointer<MoveDeclarationOutOfWhileOp> op(new MoveDeclarationOutOfWhileOp(interface));
882

Nikolai Kosjar's avatar
Nikolai Kosjar committed
883 884 885 886 887 888
    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;
889

890
                if (!op->core)
Nikolai Kosjar's avatar
Nikolai Kosjar committed
891
                    return;
892

893
                if (!declarator->equal_token)
Nikolai Kosjar's avatar
Nikolai Kosjar committed
894
                    return;
895

896
                if (!declarator->initializer)
Nikolai Kosjar's avatar
Nikolai Kosjar committed
897
                    return;
898

Nikolai Kosjar's avatar
Nikolai Kosjar committed
899 900 901 902
                if (interface->isCursorOn(op->core)) {
                    op->setPriority(index);
                    result.append(op);
                    return;
903
                }
904 905

                op->reset();
906 907 908
            }
        }
    }
Nikolai Kosjar's avatar
Nikolai Kosjar committed
909
}
910

911 912
namespace {

Nikolai Kosjar's avatar
Nikolai Kosjar committed