cppquickfixes.cpp 203 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 "cppeditordocument.h"
34
#include "cppfunctiondecldeflink.h"
Leandro Melo's avatar
Leandro Melo committed
35
#include "cppquickfixassistant.h"
36
#include "cppvirtualfunctionassistprovider.h"
37
#include "cppinsertvirtualmethods.h"
38

39 40
#include <coreplugin/icore.h>

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

50
#include <cplusplus/ASTPath.h>
51
#include <cplusplus/CPlusPlusForwardDeclarations.h>
52 53 54
#include <cplusplus/CppRewriter.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 72 73
using namespace CPlusPlus;
using namespace CppTools;
using namespace TextEditor;
Orgad Shaneh's avatar
Orgad Shaneh committed
74
using Utils::ChangeSet;
Nikolai Kosjar's avatar
Nikolai Kosjar committed
75

76 77 78 79
namespace CppEditor {
namespace Internal {

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

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

    plugIn->addAutoReleasedObject(new AssignToLocalVariable);
121 122

    plugIn->addAutoReleasedObject(new InsertVirtualMethods);
123 124

    plugIn->addAutoReleasedObject(new OptimizeForLoop);
125 126

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

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

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

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

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

155 156
    // ...failed,
    // if class member try to get position right after class
157
    CppRefactoringFilePtr file = refactoring.file(fileName);
158 159 160 161 162 163 164 165 166 167 168 169 170
    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
171 172 173 174 175 176 177 178 179 180
    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);
}

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

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

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

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

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

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

221 222 223 224 225 226 227 228 229 230 231 232 233
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)
234
        return 0;
235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258

    // 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;
}

259 260 261 262 263
// 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;
264
    LineForNewIncludeDirective finder(file->document(), file->cppDocument()->resolvedIncludes(),
265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289
                                      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();
}

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

296 297 298 299
} // anonymous namespace

namespace {

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

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

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

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

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

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

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

359 360
} // anonymous namespace

Nikolai Kosjar's avatar
Nikolai Kosjar committed
361 362 363 364 365 366 367 368
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();
369
    if (!binary)
Nikolai Kosjar's avatar
Nikolai Kosjar committed
370
        return;
371
    if (!interface->isCursorOn(binary->binary_op_token))
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 391 392 393 394 395 396
        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;
    }
397

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

402 403
namespace {

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

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

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

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

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

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

444 445
} // anonymous namespace

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

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

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

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

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

493 494
namespace {

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

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

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

532 533
} // anonymous namespace

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

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

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

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

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

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

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

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

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

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

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

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

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

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

593 594
namespace {

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

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

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

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

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

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

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

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

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

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

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

645 646
} // anonymous namespace

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

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

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

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

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

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

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

687 688
namespace {

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

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

        ChangeSet changes;
705

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

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

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

private:
    StatementAST *_statement;
};

721 722
} // anonymous namespace

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

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

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

754 755
namespace {

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

765 766 767 768 769
        reset();
    }

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

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

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

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

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

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

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

799 800
} // anonymous namespace

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

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

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

829 830
namespace {

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

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

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

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

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

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

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

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

876 877
} // anonymous namespace

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

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

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

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

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

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

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

912 913