cppquickfix.cpp 35 KB
Newer Older
1
2
3
4
/**************************************************************************
**
** This file is part of Qt Creator
**
hjk's avatar
hjk committed
5
** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
**
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** Commercial Usage
**
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
**
** GNU Lesser General Public License Usage
**
** 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.
**
** If you are unsure which license is appropriate for your use, please
** contact the sales department at http://qt.nokia.com/contact.
**
**************************************************************************/

#include "cppquickfix.h"
#include "cppeditor.h"
32

33
#include <cplusplus/ASTPath.h>
34
#include <cplusplus/CppDocument.h>
35
#include <cplusplus/ResolveExpression.h>
36

37
#include <TranslationUnit.h>
38
39
#include <ASTVisitor.h>
#include <AST.h>
40
41
#include <ASTPatternBuilder.h>
#include <ASTMatcher.h>
42
#include <Token.h>
43
44
45
46
47
48
#include <Type.h>
#include <CoreTypes.h>
#include <Symbol.h>
#include <Symbols.h>
#include <Name.h>
#include <Literals.h>
49

50
#include <cpptools/cpprefactoringchanges.h>
51
#include <cpptools/cpptoolsconstants.h>
52
#include <cpptools/cppmodelmanagerinterface.h>
53

Robert Loehning's avatar
Robert Loehning committed
54
#include <QtGui/QApplication>
55
#include <QtGui/QTextBlock>
56
57
58

using namespace CppEditor::Internal;
using namespace CPlusPlus;
59
using namespace Utils;
60

61
62
namespace {

63
64
65
66
class CppQuickFixState: public TextEditor::QuickFixState
{
public:
    QList<CPlusPlus::AST *> path;
67
    SemanticInfo info;
68
69
};

70
71
/*
    Rewrite
72
73
74
    a op b -> !(a invop b)
    (a op b) -> !(a invop b)
    !(a op b) -> (a invob b)
75
*/
76
class UseInverseOp: public CppQuickFixOperation
77
78
{
public:
79
80
    UseInverseOp(TextEditor::BaseTextEditor *editor)
        : CppQuickFixOperation(editor), binary(0), nested(0), negation(0)
81
82
83
84
    {}

    virtual QString description() const
    {
Robert Loehning's avatar
Robert Loehning committed
85
        return QApplication::translate("CppTools::QuickFix", "Rewrite Using %1").arg(replacement);
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
    }

    virtual int match(const QList<AST *> &path)
    {
        int index = path.size() - 1;
        binary = path.at(index)->asBinaryExpression();
        if (! binary)
            return -1;
        if (! isCursorOn(binary->binary_op_token))
            return -1;

        CPlusPlus::Kind invertToken;
        switch (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 -1;
        }

        CPlusPlus::Token tok;
        tok.f.kind = invertToken;
        replacement = QLatin1String(tok.spell());
124
125
126
127
128
129
130
131
132
133
134
135

        // check for enclosing nested expression
        if (index - 1 >= 0)
            nested = path[index - 1]->asNestedExpression();

        // check for ! before parentheses
        if (nested && index - 2 >= 0) {
            negation = path[index - 2]->asUnaryExpression();
            if (negation && ! tokenAt(negation->unary_op_token).is(T_EXCLAIM))
                negation = 0;
        }

136
137
138
        return index;
    }

139
    virtual void createChanges()
140
    {
141
        ChangeSet changes;
142
143
        if (negation) {
            // can't remove parentheses since that might break precedence
144
            remove(&changes, negation->unary_op_token);
145
        } else if (nested) {
146
            changes.insert(startOf(nested), "!");
147
        } else {
148
149
            changes.insert(startOf(binary), "!(");
            changes.insert(endOf(binary), ")");
150
        }
151
152
        replace(&changes, binary->binary_op_token, replacement);
        cppRefactoringChanges()->changeFile(fileName(), changes);
153
154
155
156
    }

private:
    BinaryExpressionAST *binary;
157
158
159
    NestedExpressionAST *nested;
    UnaryExpressionAST *negation;

160
161
162
163
164
165
166
167
168
169
    QString replacement;
};

/*
    Rewrite
    a op b

    As
    b flipop a
*/
170
class FlipBinaryOp: public CppQuickFixOperation
171
172
{
public:
173
174
    FlipBinaryOp(TextEditor::BaseTextEditor *editor)
        : CppQuickFixOperation(editor), binary(0)
175
176
177
178
179
180
    {}


    virtual QString description() const
    {
        if (replacement.isEmpty())
Robert Loehning's avatar
Robert Loehning committed
181
            return QApplication::translate("CppTools::QuickFix", "Swap Operands");
182
        else
Robert Loehning's avatar
Robert Loehning committed
183
            return QApplication::translate("CppTools::QuickFix", "Rewrite Using %1").arg(replacement);
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
    }

    virtual int match(const QList<AST *> &path)
    {
        int index = path.size() - 1;
        binary = path.at(index)->asBinaryExpression();
        if (! binary)
            return -1;
        if (! isCursorOn(binary->binary_op_token))
            return -1;

        CPlusPlus::Kind flipToken;
        switch (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 -1;
        }

        if (flipToken != T_EOF_SYMBOL) {
            CPlusPlus::Token tok;
            tok.f.kind = flipToken;
            replacement = QLatin1String(tok.spell());
        }
        return index;
    }

227
    virtual void createChanges()
228
    {
229
230
231
        ChangeSet changes;

        flip(&changes, binary->left_expression, binary->right_expression);
232
        if (! replacement.isEmpty())
233
234
235
            replace(&changes, binary->binary_op_token, replacement);

        cppRefactoringChanges()->changeFile(fileName(), changes);
236
237
238
239
240
241
242
243
244
245
246
247
248
249
    }

private:
    BinaryExpressionAST *binary;
    QString replacement;
};

/*
    Rewrite
    !a && !b

    As
    !(a || b)
*/
250
class RewriteLogicalAndOp: public CppQuickFixOperation
251
252
{
public:
253
254
    RewriteLogicalAndOp(TextEditor::BaseTextEditor *editor)
        : CppQuickFixOperation(editor), left(0), right(0), pattern(0)
255
256
257
258
    {}

    virtual QString description() const
    {
Robert Loehning's avatar
Robert Loehning committed
259
        return QApplication::translate("CppTools::QuickFix", "Rewrite Condition Using ||");
260
261
    }

262
    virtual int match(const QList<AST *> &path)
263
    {
264
265
266
267
268
269
270
271
272
273
274
275
        BinaryExpressionAST *expression = 0;

        int index = path.size() - 1;
        for (; index != -1; --index) {
            expression = path.at(index)->asBinaryExpression();
            if (expression)
                break;
        }

        if (! expression)
            return -1;

276
        if (! isCursorOn(expression->binary_op_token))
277
278
            return -1;

279
280
281
282
283
284
285
286
        left = mk.UnaryExpression();
        right = mk.UnaryExpression();
        pattern = mk.BinaryExpression(left, right);

        if (expression->match(pattern, &matcher) &&
                tokenAt(pattern->binary_op_token).is(T_AMPER_AMPER) &&
                tokenAt(left->unary_op_token).is(T_EXCLAIM) &&
                tokenAt(right->unary_op_token).is(T_EXCLAIM)) {
287
            return index;
288
289
        }

290
        return -1;
291
292
    }

293
    virtual void createChanges()
294
    {
295
296
297
298
299
300
301
302
303
304
305
        ChangeSet changes;
        replace(&changes, pattern->binary_op_token, QLatin1String("||"));
        remove(&changes, left->unary_op_token);
        remove(&changes, right->unary_op_token);
        const int start = startOf(pattern);
        const int end = endOf(pattern);
        changes.insert(start, QLatin1String("!("));
        changes.insert(end, QLatin1String(")"));

        cppRefactoringChanges()->changeFile(fileName(), changes);
        cppRefactoringChanges()->reindent(fileName(), range(start, end));
306
307
308
309
310
311
312
313
314
315
    }

private:
    ASTMatcher matcher;
    ASTPatternBuilder mk;
    UnaryExpressionAST *left;
    UnaryExpressionAST *right;
    BinaryExpressionAST *pattern;
};

316
class SplitSimpleDeclarationOp: public CppQuickFixOperation
317
318
{
public:
319
320
    SplitSimpleDeclarationOp(TextEditor::BaseTextEditor *editor)
        : CppQuickFixOperation(editor), declaration(0)
321
322
323
324
    {}

    virtual QString description() const
    {
Robert Loehning's avatar
Robert Loehning committed
325
        return QApplication::translate("CppTools::QuickFix", "Split Declaration");
326
327
328
329
    }

    bool checkDeclaration(SimpleDeclarationAST *declaration) const
    {
330
331
332
        if (! declaration->semicolon_token)
            return false;

333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
        if (! declaration->decl_specifier_list)
            return false;

        for (SpecifierListAST *it = declaration->decl_specifier_list; it; it = it->next) {
            SpecifierAST *specifier = it->value;

            if (specifier->asEnumSpecifier() != 0)
                return false;

            else if (specifier->asClassSpecifier() != 0)
                return false;
        }

        if (! declaration->declarator_list)
            return false;

        else if (! declaration->declarator_list->next)
            return false;

        return true;
    }

355
    virtual int match(const QList<AST *> &path)
356
357
358
359
360
361
362
363
364
365
366
367
368
369
    {
        CoreDeclaratorAST *core_declarator = 0;

        int index = path.size() - 1;
        for (; index != -1; --index) {
            AST *node = path.at(index);

            if (CoreDeclaratorAST *coreDecl = node->asCoreDeclarator())
                core_declarator = coreDecl;

            else if (SimpleDeclarationAST *simpleDecl = node->asSimpleDeclaration()) {
                if (checkDeclaration(simpleDecl)) {
                    declaration = simpleDecl;

370
                    const int cursorPosition = selectionStart();
371
372
373
374
375
376
377

                    const int startOfDeclSpecifier = startOf(declaration->decl_specifier_list->firstToken());
                    const int endOfDeclSpecifier = endOf(declaration->decl_specifier_list->lastToken() - 1);

                    if (cursorPosition >= startOfDeclSpecifier && cursorPosition <= endOfDeclSpecifier)
                        return index; // the AST node under cursor is a specifier.

378
                    if (core_declarator && isCursorOn(core_declarator))
379
                        return index; // got a core-declarator under the text cursor.
380
381
382
383
384
385
386
387
388
                }

                break;
            }
        }

        return -1;
    }

389
    virtual void createChanges()
390
    {
391
392
        ChangeSet changes;

393
        SpecifierListAST *specifiers = declaration->decl_specifier_list;
394
395
396
        int declSpecifiersStart = startOf(specifiers->firstToken());
        int declSpecifiersEnd = endOf(specifiers->lastToken() - 1);
        int insertPos = endOf(declaration->semicolon_token);
397

398
        DeclaratorAST *prevDeclarator = declaration->declarator_list->value;
399
400
401
402

        for (DeclaratorListAST *it = declaration->declarator_list->next; it; it = it->next) {
            DeclaratorAST *declarator = it->value;

403
404
405
406
407
            changes.insert(insertPos, QLatin1String("\n"));
            changes.copy(declSpecifiersStart, declSpecifiersEnd - declSpecifiersStart, insertPos);
            changes.insert(insertPos, QLatin1String(" "));
            move(&changes, declarator, insertPos);
            changes.insert(insertPos, QLatin1String(";"));
408

409
410
            const int prevDeclEnd = endOf(prevDeclarator);
            changes.remove(prevDeclEnd, startOf(declarator) - prevDeclEnd);
411

412
413
            prevDeclarator = declarator;
        }
414
415
416
417

        cppRefactoringChanges()->changeFile(fileName(), changes);
        cppRefactoringChanges()->reindent(fileName(), range(startOf(declaration->firstToken()),
                                                            endOf(declaration->lastToken() - 1)));
418
419
420
421
422
423
    }

private:
    SimpleDeclarationAST *declaration;
};

424
425
426
427
/*
    Add curly braces to a if statement that doesn't already contain a
    compound statement.
*/
428
class AddBracesToIfOp: public CppQuickFixOperation
429
430
{
public:
431
432
    AddBracesToIfOp(TextEditor::BaseTextEditor *editor)
        : CppQuickFixOperation(editor), _statement(0)
433
434
435
436
    {}

    virtual QString description() const
    {
Robert Loehning's avatar
Robert Loehning committed
437
        return QApplication::translate("CppTools::QuickFix", "Add Curly Braces");
438
439
    }

440
    virtual int match(const QList<AST *> &path)
441
442
443
444
    {
        // show when we're on the 'if' of an if statement
        int index = path.size() - 1;
        IfStatementAST *ifStatement = path.at(index)->asIfStatement();
445
        if (ifStatement && isCursorOn(ifStatement->if_token)
446
447
448
449
450
451
452
453
454
            && ! ifStatement->statement->asCompoundStatement()) {
            _statement = ifStatement->statement;
            return index;
        }

        // 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) {
            IfStatementAST *ifStatement = path.at(index)->asIfStatement();
455
456
            if (ifStatement && ifStatement->statement
                && isCursorOn(ifStatement->statement)
457
458
459
460
461
462
463
464
465
466
467
468
                && ! ifStatement->statement->asCompoundStatement()) {
                _statement = ifStatement->statement;
                return index;
            }
        }

        // ### This could very well be extended to the else branch
        // and other nodes entirely.

        return -1;
    }

469
    virtual void createChanges()
470
    {
471
472
473
474
475
476
477
478
479
480
        ChangeSet changes;

        const int start = endOf(_statement->firstToken() - 1);
        changes.insert(start, QLatin1String(" {"));

        const int end = endOf(_statement->lastToken() - 1);
        changes.insert(end, "\n}");

        cppRefactoringChanges()->changeFile(fileName(), changes);
        cppRefactoringChanges()->reindent(fileName(), range(start, end));
481
482
483
484
485
486
487
488
489
490
491
492
493
494
    }

private:
    StatementAST *_statement;
};

/*
    Replace
    if (Type name = foo()) {...}

    With
    Type name = foo;
    if (name) {...}
*/
495
class MoveDeclarationOutOfIfOp: public CppQuickFixOperation
496
497
{
public:
498
499
    MoveDeclarationOutOfIfOp(TextEditor::BaseTextEditor *editor)
        : CppQuickFixOperation(editor), condition(0), pattern(0), core(0)
500
501
502
503
    {}

    virtual QString description() const
    {
Robert Loehning's avatar
Robert Loehning committed
504
        return QApplication::translate("CppTools::QuickFix", "Move Declaration out of Condition");
505
506
    }

507
    virtual int match(const QList<AST *> &path)
508
509
510
511
512
513
514
515
516
517
518
519
520
    {
        condition = mk.Condition();
        pattern = mk.IfStatement(condition);

        int index = path.size() - 1;
        for (; index != -1; --index) {
            if (IfStatementAST *statement = path.at(index)->asIfStatement()) {
                if (statement->match(pattern, &matcher) && condition->declarator) {
                    DeclaratorAST *declarator = condition->declarator;
                    core = declarator->core_declarator;
                    if (! core)
                        return -1;

521
                    if (isCursorOn(core))
522
523
524
525
526
527
528
529
                        return index;
                }
            }
        }

        return -1;
    }

530
    virtual void createChanges()
531
    {
532
        ChangeSet changes;
533

534
        copy(&changes, core, startOf(condition));
535
536

        int insertPos = startOf(pattern);
537
538
539
540
541
542
        move(&changes, condition, insertPos);
        changes.insert(insertPos, QLatin1String(";\n"));

        cppRefactoringChanges()->changeFile(fileName(), changes);
        cppRefactoringChanges()->reindent(fileName(), range(startOf(pattern),
                                                            endOf(pattern)));
543
544
545
546
547
548
549
550
551
552
553
    }

private:
    ASTMatcher matcher;
    ASTPatternBuilder mk;
    CPPEditor *editor;
    ConditionAST *condition;
    IfStatementAST *pattern;
    CoreDeclaratorAST *core;
};

554
555
556
557
558
559
560
561
/*
    Replace
    while (Type name = foo()) {...}

    With
    Type name;
    while ((name = foo()) != 0) {...}
*/
562
class MoveDeclarationOutOfWhileOp: public CppQuickFixOperation
563
564
{
public:
565
566
    MoveDeclarationOutOfWhileOp(TextEditor::BaseTextEditor *editor)
        : CppQuickFixOperation(editor), condition(0), pattern(0), core(0)
567
568
569
570
    {}

    virtual QString description() const
    {
Robert Loehning's avatar
Robert Loehning committed
571
        return QApplication::translate("CppTools::QuickFix", "Move Declaration out of Condition");
572
573
    }

574
    virtual int match(const QList<AST *> &path)
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
    {
        condition = mk.Condition();
        pattern = mk.WhileStatement(condition);

        int index = path.size() - 1;
        for (; index != -1; --index) {
            if (WhileStatementAST *statement = path.at(index)->asWhileStatement()) {
                if (statement->match(pattern, &matcher) && condition->declarator) {
                    DeclaratorAST *declarator = condition->declarator;
                    core = declarator->core_declarator;

                    if (! core)
                        return -1;

                    else if (! declarator->equals_token)
                        return -1;

                    else if (! declarator->initializer)
                        return -1;

595
                    if (isCursorOn(core))
596
597
598
599
600
601
602
603
                        return index;
                }
            }
        }

        return -1;
    }

604
    virtual void createChanges()
605
    {
606
        ChangeSet changes;
607

608
609
        changes.insert(startOf(condition), QLatin1String("("));
        changes.insert(endOf(condition), QLatin1String(") != 0"));
610
611

        int insertPos = startOf(pattern);
612
613
614
615
616
617
618
619
        const int conditionStart = startOf(condition);
        changes.move(conditionStart, startOf(core) - conditionStart, insertPos);
        copy(&changes, core, insertPos);
        changes.insert(insertPos, QLatin1String(";\n"));

        cppRefactoringChanges()->changeFile(fileName(), changes);
        cppRefactoringChanges()->reindent(fileName(), range(startOf(pattern),
                                                            endOf(pattern)));
620
621
622
623
624
625
626
627
628
629
630
    }

private:
    ASTMatcher matcher;
    ASTPatternBuilder mk;
    CPPEditor *editor;
    ConditionAST *condition;
    WhileStatementAST *pattern;
    CoreDeclaratorAST *core;
};

Roberto Raggi's avatar
Roberto Raggi committed
631
632
633
634
635
636
637
638
639
640
/*
  Replace
     if (something && something_else) {
     }

  with
     if (something) {
        if (something_else) {
        }
     }
Roberto Raggi's avatar
Roberto Raggi committed
641
642
643
644
645
646
647
648
649
650

  and
    if (something || something_else)
      x;

  with
    if (something)
      x;
    else if (something_else)
      x;
Roberto Raggi's avatar
Roberto Raggi committed
651
*/
652
class SplitIfStatementOp: public CppQuickFixOperation
Roberto Raggi's avatar
Roberto Raggi committed
653
654
{
public:
655
656
    SplitIfStatementOp(TextEditor::BaseTextEditor *editor)
        : CppQuickFixOperation(editor), condition(0), pattern(0)
Roberto Raggi's avatar
Roberto Raggi committed
657
658
659
660
    {}

    virtual QString description() const
    {
Robert Loehning's avatar
Robert Loehning committed
661
        return QApplication::translate("CppTools::QuickFix", "Split if Statement");
Roberto Raggi's avatar
Roberto Raggi committed
662
663
    }

664
    virtual int match(const QList<AST *> &path)
Roberto Raggi's avatar
Roberto Raggi committed
665
    {
666
        pattern = 0;
Roberto Raggi's avatar
Roberto Raggi committed
667

668
669
670
671
672
673
674
675
676
        int index = path.size() - 1;
        for (; index != -1; --index) {
            AST *node = path.at(index);
            if (IfStatementAST *stmt = node->asIfStatement()) {
                pattern = stmt;
                break;
            }
        }

677
        if (! pattern || ! pattern->statement)
678
679
            return -1;

680
        unsigned splitKind = 0;
681
682
683
684
685
686
687
        for (++index; index < path.size(); ++index) {
            AST *node = path.at(index);
            condition = node->asBinaryExpression();
            if (! condition)
                return -1;

            Token binaryToken = tokenAt(condition->binary_op_token);
688
689
690
691
692
693
694
695
696
697

            // 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 -1;
                // we can't reliably split &&s in ifs with an else branch
                if (splitKind == T_AMPER_AMPER && pattern->else_statement)
                    return -1;
            } else if (splitKind != binaryToken.kind()) {
698
699
                return -1;
            }
700
701
702

            if (isCursorOn(condition->binary_op_token))
                return index;
703
704
705
        }

        return -1;
Roberto Raggi's avatar
Roberto Raggi committed
706
707
    }

708
    virtual void createChanges()
Roberto Raggi's avatar
Roberto Raggi committed
709
    {
710
        Token binaryToken = tokenAt(condition->binary_op_token);
Roberto Raggi's avatar
Roberto Raggi committed
711

712
        if (binaryToken.is(T_AMPER_AMPER))
Roberto Raggi's avatar
Roberto Raggi committed
713
714
715
716
717
718
719
            splitAndCondition();
        else
            splitOrCondition();
    }

    void splitAndCondition()
    {
720
        ChangeSet changes;
Roberto Raggi's avatar
Roberto Raggi committed
721

722
        int startPos = startOf(pattern);
723
724
725
726
727
728
729
730
731
732
733
734
        changes.insert(startPos, QLatin1String("if ("));
        move(&changes, condition->left_expression, startPos);
        changes.insert(startPos, QLatin1String(") {\n"));

        const int lExprEnd = endOf(condition->left_expression);
        changes.remove(lExprEnd,
                       startOf(condition->right_expression) - lExprEnd);
        changes.insert(endOf(pattern), QLatin1String("\n}"));

        cppRefactoringChanges()->changeFile(fileName(), changes);
        cppRefactoringChanges()->reindent(fileName(), range(startOf(pattern),
                                                            endOf(pattern)));
Roberto Raggi's avatar
Roberto Raggi committed
735
736
    }

Roberto Raggi's avatar
Roberto Raggi committed
737
738
    void splitOrCondition()
    {
739
740
        ChangeSet changes;

Roberto Raggi's avatar
Roberto Raggi committed
741
742
743
        StatementAST *ifTrueStatement = pattern->statement;
        CompoundStatementAST *compoundStatement = ifTrueStatement->asCompoundStatement();

744
        int insertPos = endOf(ifTrueStatement);
Roberto Raggi's avatar
Roberto Raggi committed
745
        if (compoundStatement)
746
            changes.insert(insertPos, QLatin1String(" "));
Roberto Raggi's avatar
Roberto Raggi committed
747
        else
748
749
750
751
752
753
754
            changes.insert(insertPos, QLatin1String("\n"));
        changes.insert(insertPos, QLatin1String("else if ("));

        const int rExprStart = startOf(condition->right_expression);
        changes.move(rExprStart, startOf(pattern->rparen_token) - rExprStart,
                     insertPos);
        changes.insert(insertPos, QLatin1String(")"));
755

756
757
758
759
760
761
762
763
764
        const int rParenEnd = endOf(pattern->rparen_token);
        changes.copy(rParenEnd, endOf(pattern->statement) - rParenEnd, insertPos);

        const int lExprEnd = endOf(condition->left_expression);
        changes.remove(lExprEnd, startOf(condition->right_expression) - lExprEnd);

        cppRefactoringChanges()->changeFile(fileName(), changes);
        cppRefactoringChanges()->reindent(fileName(), range(startOf(pattern),
                                                            endOf(pattern)));
Roberto Raggi's avatar
Roberto Raggi committed
765
766
    }

Roberto Raggi's avatar
Roberto Raggi committed
767
768
769
770
private:
    BinaryExpressionAST *condition;
    IfStatementAST *pattern;
};
771

772
773
774
775
776
777
/*
  Replace
    "abcd"
  With
    QLatin1String("abcd")
*/
778
class WrapStringLiteral: public CppQuickFixOperation
779
780
{
public:
781
782
    WrapStringLiteral(TextEditor::BaseTextEditor *editor)
        : CppQuickFixOperation(editor), stringLiteral(0), isObjCStringLiteral(false)
783
784
785
786
    {}

    virtual QString description() const
    {
Robert Loehning's avatar
Robert Loehning committed
787
        return QApplication::translate("CppTools::QuickFix", "Enclose in QLatin1String(...)");
788
789
790
791
792
793
794
795
796
797
798
799
800
    }

    virtual int match(const QList<AST *> &path)
    {
        if (path.isEmpty())
            return -1;

        int index = path.size() - 1;
        stringLiteral = path[index]->asStringLiteral();

        if (!stringLiteral)
            return -1;

801
802
        isObjCStringLiteral = charAt(startOf(stringLiteral)) == QLatin1Char('@');

803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
        // check if it is already wrapped in QLatin1String or -Literal
        if (index-2 < 0)
            return index;

        CallAST *call = path[index-1]->asCall();
        PostfixExpressionAST *postfixExp = path[index-2]->asPostfixExpression();
        if (call && postfixExp
            && postfixExp->base_expression
            && postfixExp->postfix_expression_list
            && postfixExp->postfix_expression_list->value == call)
        {
            NameAST *callName = postfixExp->base_expression->asName();
            if (!callName)
                return index;

            QByteArray callNameString(tokenAt(callName->firstToken()).spell());
            if (callNameString == "QLatin1String"
                || callNameString == "QLatin1Literal"
                )
                return -1;
        }

        return index;
    }

828
    virtual void createChanges()
829
    {
830
831
        ChangeSet changes;

832
833
834
835
        const int startPos = startOf(stringLiteral);
        const QLatin1String replacement("QLatin1String(");

        if (isObjCStringLiteral)
836
            changes.replace(startPos, 1, replacement);
837
        else
838
            changes.insert(startPos, replacement);
839

840
841
842
        changes.insert(endOf(stringLiteral), ")");

        cppRefactoringChanges()->changeFile(fileName(), changes);
843
844
845
846
    }

private:
    StringLiteralAST *stringLiteral;
847
    bool isObjCStringLiteral;
848
849
};

850
class CStringToNSString: public CppQuickFixOperation
851
852
{
public:
853
854
    CStringToNSString(TextEditor::BaseTextEditor *editor)
        : CppQuickFixOperation(editor), stringLiteral(0), qlatin1Call(0)
855
856
857
    {}

    virtual QString description() const
Robert Loehning's avatar
Robert Loehning committed
858
859
860
    {
        return QApplication::translate("CppTools::QuickFix", "Convert to Objective-C String Literal");
    }
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902

    virtual int match(const QList<AST *> &path)
    {
        if (path.isEmpty())
            return -1;

        int index = path.size() - 1;
        stringLiteral = path[index]->asStringLiteral();

        if (!stringLiteral)
            return -1;

        if (charAt(startOf(stringLiteral)) == QLatin1Char('@'))
            return -1;

        // check if it is already wrapped in QLatin1String or -Literal
        if (index-2 < 0)
            return index;

        CallAST *call = path[index-1]->asCall();
        PostfixExpressionAST *postfixExp = path[index-2]->asPostfixExpression();
        if (call && postfixExp
            && postfixExp->base_expression
            && postfixExp->postfix_expression_list
            && postfixExp->postfix_expression_list->value == call)
        {
            NameAST *callName = postfixExp->base_expression->asName();
            if (!callName)
                return index;

            if (!(postfixExp->postfix_expression_list->next)) {
                QByteArray callNameString(tokenAt(callName->firstToken()).spell());
                if (callNameString == "QLatin1String"
                    || callNameString == "QLatin1Literal"
                    )
                    qlatin1Call = postfixExp;
            }
        }

        return index;
    }

903
    virtual void createChanges()
904
    {
905
906
        ChangeSet changes;

907
        if (qlatin1Call) {
908
909
910
911
912
            changes.replace(startOf(qlatin1Call),
                            startOf(stringLiteral) - startOf(qlatin1Call),
                            QLatin1String("@"));
            changes.remove(endOf(stringLiteral),
                           endOf(qlatin1Call) - endOf(stringLiteral));
913
        } else {
914
            changes.insert(startOf(stringLiteral), "@");
915
        }
916
917

        cppRefactoringChanges()->changeFile(fileName(), changes);
918
919
920
921
922
923
924
    }

private:
    StringLiteralAST *stringLiteral;
    PostfixExpressionAST *qlatin1Call;
};

925
926
927
} // end of anonymous namespace


928
CppQuickFixOperation::CppQuickFixOperation(TextEditor::BaseTextEditor *editor)
929
930
931
    : TextEditor::QuickFixOperation(editor)
    , _refactoringChanges(0)
    , _topLevelNode(0)
932
933
{ }

934
935
CppQuickFixOperation::~CppQuickFixOperation()
{
936
937
    if (_refactoringChanges)
        delete _refactoringChanges;
938
}
Roberto Raggi's avatar
Roberto Raggi committed
939

940
int CppQuickFixOperation::match(TextEditor::QuickFixState *state)
941
{
942
    CppQuickFixState *s = static_cast<CppQuickFixState *>(state);
943
    _document = s->info.doc;
944
945
946
947
    if (_refactoringChanges)
        delete _refactoringChanges;
    CPPEditor *cppEditor = qobject_cast<CPPEditor*>(editor());
    _refactoringChanges = new CppTools::CppRefactoringChanges(s->info.snapshot, cppEditor->modelManager());
948
    return match(s->path);
949
}
Roberto Raggi's avatar
Roberto Raggi committed
950

951
952
953
QString CppQuickFixOperation::fileName() const
{ return document()->fileName(); }

954
955
956
957
958
959
960
961
962
963
964
void CppQuickFixOperation::apply()
{
    cppRefactoringChanges()->apply();
}

CppTools::CppRefactoringChanges *CppQuickFixOperation::cppRefactoringChanges() const
{ return _refactoringChanges; }

TextEditor::RefactoringChanges *CppQuickFixOperation::refactoringChanges() const
{ return cppRefactoringChanges(); }

965
966
Document::Ptr CppQuickFixOperation::document() const
{ return _document; }
967

968
const Snapshot &CppQuickFixOperation::snapshot() const
969
970
971
{
    return _refactoringChanges->snapshot();
}
972

973
const CPlusPlus::Token &CppQuickFixOperation::tokenAt(unsigned index) const
Roberto Raggi's avatar
Roberto Raggi committed
974
{ return _document->translationUnit()->tokenAt(index); }
975

976
int CppQuickFixOperation::startOf(unsigned index) const
977
{
978
    unsigned line, column;
Roberto Raggi's avatar
Roberto Raggi committed
979
    _document->translationUnit()->getPosition(tokenAt(index).begin(), &line, &column);
980
    return editor()->document()->findBlockByNumber(line - 1).position() + column - 1;
981
}
982

983
int CppQuickFixOperation::startOf(const CPlusPlus::AST *ast) const
984
985
986
987
{
    return startOf(ast->firstToken());
}

988
int CppQuickFixOperation::endOf(unsigned index) const
989
990
{
    unsigned line, column;
Roberto Raggi's avatar
Roberto Raggi committed
991
    _document->translationUnit()->getPosition(tokenAt(index).end(), &line, &column);
992
    return editor()->document()->findBlockByNumber(line - 1).position() + column - 1;
993
994
}

995
int CppQuickFixOperation::endOf(const CPlusPlus::AST *ast) const
996
{
997
998
999
1000
    if (unsigned end = ast->lastToken())
        return endOf(end - 1);
    else
        return 0;
1001
1002
}

1003
void CppQuickFixOperation::startAndEndOf(unsigned index, int *start, int *end) const
1004
1005
1006
1007
{
    unsigned line, column;
    CPlusPlus::Token token(tokenAt(index));
    _document->translationUnit()->getPosition(token.begin(), &line, &column);
1008
    *start = editor()->document()->findBlockByNumber(line - 1).position() + column - 1;
1009
1010
1011
    *end = *start + token.length();
}

1012
bool CppQuickFixOperation::isCursorOn(unsigned tokenIndex) const
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
{
    QTextCursor tc = textCursor();
    int cursorBegin = tc.selectionStart();

    int start = startOf(tokenIndex);
    int end = endOf(tokenIndex);

    if (cursorBegin >= start && cursorBegin <= end)
        return true;

    return false;
}

1026
bool CppQuickFixOperation::isCursorOn(const CPlusPlus::AST *ast) const
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
{
    QTextCursor tc = textCursor();
    int cursorBegin = tc.selectionStart();

    int start = startOf(ast);
    int end = endOf(ast);

    if (cursorBegin >= start && cursorBegin <= end)
        return true;

    return false;
}

1040
1041
void CppQuickFixOperation::move(ChangeSet *changeSet, unsigned tokenIndex,
                                int to)
1042
{
1043
1044
    Q_ASSERT(changeSet);

1045
1046
    int start, end;
    startAndEndOf(tokenIndex, &start, &end);
1047
    changeSet->move(start, end - start, to);
1048
1049
}

1050
1051
void CppQuickFixOperation::move(ChangeSet *changeSet, const CPlusPlus::AST *ast,
                                int to)
1052
{
1053
1054
1055
1056
    Q_ASSERT(changeSet);

    const int start = startOf(ast);
    changeSet->move(start, endOf(ast) - start, to);
1057
1058
}

1059
1060
void CppQuickFixOperation::replace(ChangeSet *changeSet, unsigned tokenIndex,
                                   const QString &replacement)
1061
{
1062
1063
    Q_ASSERT(changeSet);

1064
1065
    int start, end;
    startAndEndOf(tokenIndex, &start, &end);
1066
    changeSet->replace(start, end - start, replacement);
1067
1068
}

1069
1070
1071
void CppQuickFixOperation::replace(ChangeSet *changeSet,
                                   const CPlusPlus::AST *ast,
                                   const QString &replacement)
1072
{
1073
1074
1075
1076
    Q_ASSERT(changeSet);

    const int start = startOf(ast);
    changeSet->replace(start, endOf(ast) - start, replacement);
1077
1078
}

1079
void CppQuickFixOperation::remove(ChangeSet *changeSet, unsigned tokenIndex)
1080
{
1081
1082
    Q_ASSERT(changeSet);

1083
1084
    int start, end;
    startAndEndOf(tokenIndex, &start, &end);
1085
    changeSet->remove(start, end - start);
1086
1087
}

1088
void CppQuickFixOperation::remove(ChangeSet *changeSet, const CPlusPlus::AST *ast)
1089
{
1090
1091
1092
1093
    Q_ASSERT(changeSet);

    const int start = startOf(ast);
    changeSet->remove(start, endOf(ast) - start);
1094
1095
}

1096
1097
1098
void CppQuickFixOperation::flip(ChangeSet *changeSet,
                                const CPlusPlus::AST *ast1,
                                const CPlusPlus::AST *ast2)
Christian Kamm's avatar
Christian Kamm committed
1099
{
1100
1101
1102
1103
1104
1105
    Q_ASSERT(changeSet);

    const int start1 = startOf(ast1);
    const int start2 = startOf(ast2);
    changeSet->flip(start1, endOf(ast1) - start1,
                    start2, endOf(ast2) - start2);
Christian Kamm's avatar
Christian Kamm committed
1106
1107
}

1108
1109
void CppQuickFixOperation::copy(ChangeSet *changeSet, unsigned tokenIndex,
                                int to)
1110
{
1111
1112
    Q_ASSERT(changeSet);

1113
1114
    int start, end;
    startAndEndOf(tokenIndex, &start, &end);
1115
    changeSet->copy(start, end - start, to);
1116
1117
}

1118
1119
void CppQuickFixOperation::copy(ChangeSet *changeSet, const CPlusPlus::AST *ast,
                                int to)
1120
{
1121
1122
1123
1124
    Q_ASSERT(changeSet);

    const int start = startOf(ast);
    changeSet->copy(start, endOf(ast) - start, to);
1125
1126
}

1127
QString CppQuickFixOperation::textOf(const AST *ast) const
1128
{
1129
    return textOf(startOf(ast), endOf(ast));
1130
1131
}

1132
CppQuickFixCollector::CppQuickFixCollector()
1133
1134
{
}
1135

1136
CppQuickFixCollector::~CppQuickFixCollector()
1137
{
1138
}
1139

1140
1141
1142
1143
1144
bool CppQuickFixCollector::supportsEditor(TextEditor::ITextEditable *editor)
{
    return CppTools::CppModelManagerInterface::instance()->isCppEditor(editor);
}

1145
1146
1147
1148
TextEditor::QuickFixState *CppQuickFixCollector::initializeCompletion(TextEditor::ITextEditable *editable)
{
    if (CPPEditor *editor = qobject_cast<CPPEditor *>(editable->widget())) {
        const SemanticInfo info = editor->semanticInfo();
1149

1150
1151
1152
1153
        if (info.revision != editor->editorRevision()) {
            // outdated
            qWarning() << "TODO: outdated semantic info, force a reparse.";
            return 0;
1154
        }
Roberto Raggi's avatar
Roberto Raggi committed
1155

1156
1157
        if (info.doc) {
            ASTPath astPath(info.doc);
1158

1159
1160
1161
1162
1163
1164
1165
            const QList<AST *> path = astPath(editor->textCursor());
            if (! path.isEmpty()) {
                CppQuickFixState *state = new CppQuickFixState;
                state->path = path;
                state->info = info;
                return state;
            }
1166
        }
1167
1168
    }

1169
    return 0;
1170
1171
}

1172
QList<TextEditor::QuickFixOperation::Ptr> CppQuickFixCollector::quickFixOperations(TextEditor::BaseTextEditor *editor) const
1173
{
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
    QSharedPointer<RewriteLogicalAndOp> rewriteLogicalAndOp(new RewriteLogicalAndOp(editor));
    QSharedPointer<SplitIfStatementOp> splitIfStatementOp(new SplitIfStatementOp(editor));
    QSharedPointer<MoveDeclarationOutOfIfOp> moveDeclarationOutOfIfOp(new MoveDeclarationOutOfIfOp(editor));
    QSharedPointer<MoveDeclarationOutOfWhileOp> moveDeclarationOutOfWhileOp(new MoveDeclarationOutOfWhileOp(editor));
    QSharedPointer<SplitSimpleDeclarationOp> splitSimpleDeclarationOp(new SplitSimpleDeclarationOp(editor));
    QSharedPointer<AddBracesToIfOp> addBracesToIfOp(new AddBracesToIfOp(editor));
    QSharedPointer<UseInverseOp> useInverseOp(new UseInverseOp(editor));
    QSharedPointer<FlipBinaryOp> flipBinaryOp(new FlipBinaryOp(editor));
    QSharedPointer<WrapStringLiteral> wrapStringLiteral(new WrapStringLiteral(editor));
    QSharedPointer<CStringToNSString> wrapCString(new CStringToNSString(editor));

    QList<TextEditor::QuickFixOperation::Ptr> quickFixOperations;
    quickFixOperations.append(rewriteLogicalAndOp);
    quickFixOperations.append(splitIfStatementOp);
    quickFixOperations.append(moveDeclarationOutOfIfOp);
    quickFixOperations.append(moveDeclarationOutOfWhileOp);
    quickFixOperations.append(splitSimpleDeclarationOp);
    quickFixOperations.append(addBracesToIfOp);
    quickFixOperations.append(useInverseOp);
    quickFixOperations.append(flipBinaryOp);
    quickFixOperations.append(wrapStringLiteral);

    if (editor->mimeType() == CppTools::Constants::OBJECTIVE_CPP_SOURCE_MIMETYPE)
        quickFixOperations.append(wrapCString);

    return quickFixOperations;
1200
}