cppquickfix.cpp 31.2 KB
Newer Older
1
2
3
4
5
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
/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
**
** 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
34

#include <cplusplus/CppDocument.h>

35
#include <TranslationUnit.h>
36
37
#include <ASTVisitor.h>
#include <AST.h>
38
39
#include <ASTPatternBuilder.h>
#include <ASTMatcher.h>
40
41
42
43
44
45
46
47
#include <Token.h>

#include <cpptools/cppmodelmanagerinterface.h>
#include <QtDebug>

using namespace CppEditor::Internal;
using namespace CPlusPlus;

48
49
namespace {

50
51
52
53
54
55
56
57
58
class ASTPath: public ASTVisitor
{
    Document::Ptr _doc;
    unsigned _line;
    unsigned _column;
    QList<AST *> _nodes;

public:
    ASTPath(Document::Ptr doc)
Roberto Raggi's avatar
Roberto Raggi committed
59
60
61
        : ASTVisitor(doc->translationUnit()),
          _doc(doc), _line(0), _column(0)
    {}
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97

    QList<AST *> operator()(const QTextCursor &cursor)
    {
        _nodes.clear();
        _line = cursor.blockNumber() + 1;
        _column = cursor.columnNumber() + 1;
        accept(_doc->translationUnit()->ast());
        return _nodes;
    }

protected:
    virtual bool preVisit(AST *ast)
    {
        unsigned firstToken = ast->firstToken();
        unsigned lastToken = ast->lastToken();

        if (firstToken > 0 && lastToken > firstToken) {
            unsigned startLine, startColumn;
            getTokenStartPosition(firstToken, &startLine, &startColumn);

            if (_line > startLine || (_line == startLine && _column >= startColumn)) {

                unsigned endLine, endColumn;
                getTokenEndPosition(lastToken - 1, &endLine, &endColumn);

                if (_line < endLine || (_line == endLine && _column < endColumn)) {
                    _nodes.append(ast);
                    return true;
                }
            }
        }

        return false;
    }
};

98
99
/*
    Rewrite
100
101
102
    a op b -> !(a invop b)
    (a op b) -> !(a invop b)
    !(a op b) -> (a invob b)
103
104
105
106
107
*/
class UseInverseOp: public QuickFixOperation
{
public:
    UseInverseOp()
108
        : binary(0), nested(0), negation(0)
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
    {}

    virtual QString description() const
    {
        return QLatin1String("Rewrite using ") + replacement; // ### tr?
    }

    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());
152
153
154
155
156
157
158
159
160
161
162
163

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

164
165
166
167
168
        return index;
    }

    virtual void createChangeSet()
    {
169
170
171
172
173
174
175
176
177
        if (negation) {
            // can't remove parentheses since that might break precedence
            remove(negation->unary_op_token);
        } else if (nested) {
            insert(startOf(nested), "!");
        } else {
            insert(startOf(binary), "!(");
            insert(endOf(binary), ")");
        }
178
179
180
181
182
        replace(binary->binary_op_token, replacement);
    }

private:
    BinaryExpressionAST *binary;
183
184
185
    NestedExpressionAST *nested;
    UnaryExpressionAST *negation;

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
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
    QString replacement;
};

/*
    Rewrite
    a op b

    As
    b flipop a
*/
class FlipBinaryOp: public QuickFixOperation
{
public:
    FlipBinaryOp()
        : binary(0)
    {}


    virtual QString description() const
    {
        if (replacement.isEmpty())
            return QLatin1String("Flip");
        else
            return QLatin1String("Flip to use ") + replacement; // ### tr?
    }

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

    virtual void createChangeSet()
    {
        flip(binary->left_expression, binary->right_expression);
        if (! replacement.isEmpty())
            replace(binary->binary_op_token, replacement);
    }

private:
    BinaryExpressionAST *binary;
    QString replacement;
};

/*
    Rewrite
    !a && !b

    As
    !(a || b)
*/
272
273
274
class RewriteLogicalAndOp: public QuickFixOperation
{
public:
Roberto Raggi's avatar
Roberto Raggi committed
275
276
    RewriteLogicalAndOp()
        : left(0), right(0), pattern(0)
277
278
279
280
281
282
283
    {}

    virtual QString description() const
    {
        return QLatin1String("Rewrite condition using ||"); // ### tr?
    }

284
    virtual int match(const QList<AST *> &path)
285
    {
286
287
288
289
290
291
292
293
294
295
296
297
        BinaryExpressionAST *expression = 0;

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

        if (! expression)
            return -1;

298
        if (! isCursorOn(expression->binary_op_token))
299
300
            return -1;

301
302
303
304
305
306
307
308
        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)) {
309
            return index;
310
311
        }

312
        return -1;
313
314
    }

Roberto Raggi's avatar
Roberto Raggi committed
315
    virtual void createChangeSet()
316
    {
Roberto Raggi's avatar
Roberto Raggi committed
317
        setTopLevelNode(pattern);
318
        replace(pattern->binary_op_token, QLatin1String("||"));
319
320
321
        remove(left->unary_op_token);
        remove(right->unary_op_token);
        insert(startOf(pattern), QLatin1String("!("));
322
        insert(endOf(pattern), QLatin1String(")"));
323
324
325
326
327
328
329
330
331
332
    }

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

333
334
335
class SplitSimpleDeclarationOp: public QuickFixOperation
{
public:
Roberto Raggi's avatar
Roberto Raggi committed
336
337
    SplitSimpleDeclarationOp()
        : declaration(0)
338
339
340
341
342
343
344
345
346
    {}

    virtual QString description() const
    {
        return QLatin1String("Split declaration"); // ### tr?
    }

    bool checkDeclaration(SimpleDeclarationAST *declaration) const
    {
347
348
349
        if (! declaration->semicolon_token)
            return false;

350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
        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;
    }

372
    virtual int match(const QList<AST *> &path)
373
374
375
376
377
378
379
380
381
382
383
384
385
386
    {
        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;

387
                    const int cursorPosition = selectionStart();
388
389
390
391
392
393
394

                    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.

395
                    if (core_declarator && isCursorOn(core_declarator))
396
                        return index; // got a core-declarator under the text cursor.
397
398
399
400
401
402
403
404
405
                }

                break;
            }
        }

        return -1;
    }

Roberto Raggi's avatar
Roberto Raggi committed
406
    virtual void createChangeSet()
407
    {
Roberto Raggi's avatar
Roberto Raggi committed
408
        setTopLevelNode(declaration);
409
        SpecifierListAST *specifiers = declaration->decl_specifier_list;
410
411
412
        int declSpecifiersStart = startOf(specifiers->firstToken());
        int declSpecifiersEnd = endOf(specifiers->lastToken() - 1);
        int insertPos = endOf(declaration->semicolon_token);
413

414
        DeclaratorAST *prevDeclarator = declaration->declarator_list->value;
415
416
417
418

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

419
420
421
422
423
424
425
            insert(insertPos, QLatin1String("\n"));
            copy(declSpecifiersStart, declSpecifiersEnd, insertPos);
            insert(insertPos, QLatin1String(" "));
            move(declarator, insertPos);
            insert(insertPos, QLatin1String(";"));

            remove(endOf(prevDeclarator), startOf(declarator));
426

427
428
            prevDeclarator = declarator;
        }
429
430
431
432
433
434
    }

private:
    SimpleDeclarationAST *declaration;
};

435
436
437
438
439
440
441
/*
    Add curly braces to a if statement that doesn't already contain a
    compound statement.
*/
class AddBracesToIfOp: public QuickFixOperation
{
public:
Roberto Raggi's avatar
Roberto Raggi committed
442
443
    AddBracesToIfOp()
        : _statement(0)
444
445
446
447
448
449
450
    {}

    virtual QString description() const
    {
        return QLatin1String("Add curly braces"); // ### tr?
    }

451
    virtual int match(const QList<AST *> &path)
452
453
454
455
    {
        // show when we're on the 'if' of an if statement
        int index = path.size() - 1;
        IfStatementAST *ifStatement = path.at(index)->asIfStatement();
456
        if (ifStatement && isCursorOn(ifStatement->if_token)
457
458
459
460
461
462
463
464
465
            && ! 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();
466
467
            if (ifStatement && ifStatement->statement
                && isCursorOn(ifStatement->statement)
468
469
470
471
472
473
474
475
476
477
478
479
                && ! ifStatement->statement->asCompoundStatement()) {
                _statement = ifStatement->statement;
                return index;
            }
        }

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

        return -1;
    }

Roberto Raggi's avatar
Roberto Raggi committed
480
    virtual void createChangeSet()
481
    {
Roberto Raggi's avatar
Roberto Raggi committed
482
        setTopLevelNode(_statement);
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
        insert(endOf(_statement->firstToken() - 1), QLatin1String(" {"));
        insert(endOf(_statement->lastToken() - 1), "\n}");
    }

private:
    StatementAST *_statement;
};

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

    With
    Type name = foo;
    if (name) {...}
*/
499
class MoveDeclarationOutOfIfOp: public QuickFixOperation
500
501
{
public:
Roberto Raggi's avatar
Roberto Raggi committed
502
503
    MoveDeclarationOutOfIfOp()
        : condition(0), pattern(0), core(0)
504
505
506
507
    {}

    virtual QString description() const
    {
508
        return QLatin1String("Move declaration out of condition"); // ### tr?
509
510
    }

511
    virtual int match(const QList<AST *> &path)
512
513
514
515
516
517
518
519
520
521
522
523
524
    {
        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;

525
                    if (isCursorOn(core))
526
527
528
529
530
531
532
533
                        return index;
                }
            }
        }

        return -1;
    }

Roberto Raggi's avatar
Roberto Raggi committed
534
    virtual void createChangeSet()
535
    {
Roberto Raggi's avatar
Roberto Raggi committed
536
        setTopLevelNode(pattern);
537

538
539
540
541
542
        copy(core, startOf(condition));

        int insertPos = startOf(pattern);
        move(condition, insertPos);
        insert(insertPos, QLatin1String(";\n"));
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
562
563
564
/*
    Replace
    while (Type name = foo()) {...}

    With
    Type name;
    while ((name = foo()) != 0) {...}
*/
class MoveDeclarationOutOfWhileOp: public QuickFixOperation
{
public:
Roberto Raggi's avatar
Roberto Raggi committed
565
566
    MoveDeclarationOutOfWhileOp()
        : condition(0), pattern(0), core(0)
567
568
569
570
571
572
573
    {}

    virtual QString description() const
    {
        return QLatin1String("Move declaration out of condition"); // ### tr?
    }

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

Roberto Raggi's avatar
Roberto Raggi committed
604
    virtual void createChangeSet()
605
    {
Roberto Raggi's avatar
Roberto Raggi committed
606
        setTopLevelNode(pattern);
607
608
609
610
611
612
613
614

        insert(startOf(condition), QLatin1String("("));
        insert(endOf(condition), QLatin1String(") != 0"));

        int insertPos = startOf(pattern);
        move(startOf(condition), startOf(core), insertPos);
        copy(core, insertPos);
        insert(insertPos, QLatin1String(";\n"));
615
616
617
618
619
620
621
622
623
624
625
    }

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

Roberto Raggi's avatar
Roberto Raggi committed
626
627
628
629
630
631
632
633
634
635
/*
  Replace
     if (something && something_else) {
     }

  with
     if (something) {
        if (something_else) {
        }
     }
Roberto Raggi's avatar
Roberto Raggi committed
636
637
638
639
640
641
642
643
644
645

  and
    if (something || something_else)
      x;

  with
    if (something)
      x;
    else if (something_else)
      x;
Roberto Raggi's avatar
Roberto Raggi committed
646
647
648
649
*/
class SplitIfStatementOp: public QuickFixOperation
{
public:
Roberto Raggi's avatar
Roberto Raggi committed
650
651
    SplitIfStatementOp()
        : condition(0), pattern(0)
Roberto Raggi's avatar
Roberto Raggi committed
652
653
654
655
656
657
658
    {}

    virtual QString description() const
    {
        return QLatin1String("Split if statement"); // ### tr?
    }

659
    virtual int match(const QList<AST *> &path)
Roberto Raggi's avatar
Roberto Raggi committed
660
    {
661
        pattern = 0;
Roberto Raggi's avatar
Roberto Raggi committed
662

663
664
665
666
667
668
669
670
671
        int index = path.size() - 1;
        for (; index != -1; --index) {
            AST *node = path.at(index);
            if (IfStatementAST *stmt = node->asIfStatement()) {
                pattern = stmt;
                break;
            }
        }

672
        if (! pattern || ! pattern->statement)
673
674
            return -1;

675
        unsigned splitKind = 0;
676
677
678
679
680
681
682
        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);
683
684
685
686
687
688
689
690
691
692

            // 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()) {
693
694
                return -1;
            }
695
696
697

            if (isCursorOn(condition->binary_op_token))
                return index;
698
699
700
        }

        return -1;
Roberto Raggi's avatar
Roberto Raggi committed
701
702
    }

Roberto Raggi's avatar
Roberto Raggi committed
703
    virtual void createChangeSet()
Roberto Raggi's avatar
Roberto Raggi committed
704
    {
705
        Token binaryToken = tokenAt(condition->binary_op_token);
Roberto Raggi's avatar
Roberto Raggi committed
706

707
        if (binaryToken.is(T_AMPER_AMPER))
Roberto Raggi's avatar
Roberto Raggi committed
708
709
710
711
712
713
714
            splitAndCondition();
        else
            splitOrCondition();
    }

    void splitAndCondition()
    {
Roberto Raggi's avatar
Roberto Raggi committed
715
        setTopLevelNode(pattern);
Roberto Raggi's avatar
Roberto Raggi committed
716

717
718
719
720
721
722
723
        int startPos = startOf(pattern);
        insert(startPos, QLatin1String("if ("));
        move(condition->left_expression, startPos);
        insert(startPos, QLatin1String(") {\n"));

        remove(endOf(condition->left_expression), startOf(condition->right_expression));
        insert(endOf(pattern), QLatin1String("\n}"));
Roberto Raggi's avatar
Roberto Raggi committed
724
725
    }

Roberto Raggi's avatar
Roberto Raggi committed
726
727
728
729
730
    void splitOrCondition()
    {
        StatementAST *ifTrueStatement = pattern->statement;
        CompoundStatementAST *compoundStatement = ifTrueStatement->asCompoundStatement();

731
        setTopLevelNode(pattern);
Roberto Raggi's avatar
Roberto Raggi committed
732

733
        int insertPos = endOf(ifTrueStatement);
Roberto Raggi's avatar
Roberto Raggi committed
734
        if (compoundStatement)
735
            insert(insertPos, QLatin1String(" "));
Roberto Raggi's avatar
Roberto Raggi committed
736
        else
737
738
739
740
741
742
743
            insert(insertPos, QLatin1String("\n"));
        insert(insertPos, QLatin1String("else if ("));
        move(startOf(condition->right_expression), startOf(pattern->rparen_token), insertPos);
        insert(insertPos, QLatin1String(")"));
        copy(endOf(pattern->rparen_token), endOf(pattern->statement), insertPos);
        
        remove(endOf(condition->left_expression), startOf(condition->right_expression));
Roberto Raggi's avatar
Roberto Raggi committed
744
745
    }

Roberto Raggi's avatar
Roberto Raggi committed
746
747
748
749
private:
    BinaryExpressionAST *condition;
    IfStatementAST *pattern;
};
750

751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
/*
  Replace
    "abcd"
  With
    QLatin1String("abcd")
*/
class WrapStringLiteral: public QuickFixOperation
{
public:
    WrapStringLiteral()
        : stringLiteral(0)
    {}

    virtual QString description() const
    {
        return QLatin1String("Enclose in QLatin1String(...)"); // ### tr?
    }

    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;

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

    virtual void createChangeSet()
    {
        insert(startOf(stringLiteral), "QLatin1String(");
        insert(endOf(stringLiteral), ")");
    }

private:
    StringLiteralAST *stringLiteral;
};

815
816
817
} // end of anonymous namespace


Roberto Raggi's avatar
Roberto Raggi committed
818
819
QuickFixOperation::QuickFixOperation()
    : _editor(0), _topLevelNode(0)
820
821
822
823
824
{ }

QuickFixOperation::~QuickFixOperation()
{ }

Roberto Raggi's avatar
Roberto Raggi committed
825
826
827
828
829
830
CPlusPlus::AST *QuickFixOperation::topLevelNode() const
{ return _topLevelNode; }

void QuickFixOperation::setTopLevelNode(CPlusPlus::AST *topLevelNode)
{ _topLevelNode = topLevelNode; }

Roberto Raggi's avatar
Roberto Raggi committed
831
832
833
const Utils::ChangeSet &QuickFixOperation::changeSet() const
{ return _changeSet; }

Roberto Raggi's avatar
Roberto Raggi committed
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
Document::Ptr QuickFixOperation::document() const
{ return _document; }

void QuickFixOperation::setDocument(CPlusPlus::Document::Ptr document)
{ _document = document; }

Snapshot QuickFixOperation::snapshot() const
{ return _snapshot; }

void QuickFixOperation::setSnapshot(const CPlusPlus::Snapshot &snapshot)
{ _snapshot = snapshot; }

CPPEditor *QuickFixOperation::editor() const
{ return _editor; }

void QuickFixOperation::setEditor(CPPEditor *editor)
{ _editor = editor; }

852
853
854
QTextCursor QuickFixOperation::textCursor() const
{ return _textCursor; }

855
856
857
void QuickFixOperation::setTextCursor(const QTextCursor &cursor)
{ _textCursor = cursor; }

858
859
860
861
862
863
int QuickFixOperation::selectionStart() const
{ return _textCursor.selectionStart(); }

int QuickFixOperation::selectionEnd() const
{ return _textCursor.selectionEnd(); }

864
const CPlusPlus::Token &QuickFixOperation::tokenAt(unsigned index) const
Roberto Raggi's avatar
Roberto Raggi committed
865
{ return _document->translationUnit()->tokenAt(index); }
866

867
int QuickFixOperation::startOf(unsigned index) const
868
{
869
    unsigned line, column;
Roberto Raggi's avatar
Roberto Raggi committed
870
    _document->translationUnit()->getPosition(tokenAt(index).begin(), &line, &column);
871
872
    return _textCursor.document()->findBlockByNumber(line - 1).position() + column - 1;
}
873

874
875
876
877
878
879
int QuickFixOperation::startOf(const CPlusPlus::AST *ast) const
{
    return startOf(ast->firstToken());
}

int QuickFixOperation::endOf(unsigned index) const
880
881
{
    unsigned line, column;
Roberto Raggi's avatar
Roberto Raggi committed
882
    _document->translationUnit()->getPosition(tokenAt(index).end(), &line, &column);
883
    return _textCursor.document()->findBlockByNumber(line - 1).position() + column - 1;
884
885
}

886
887
888
889
890
int QuickFixOperation::endOf(const CPlusPlus::AST *ast) const
{
    return endOf(ast->lastToken() - 1);
}

891
892
893
894
895
896
897
898
899
void QuickFixOperation::startAndEndOf(unsigned index, int *start, int *end) const
{
    unsigned line, column;
    CPlusPlus::Token token(tokenAt(index));
    _document->translationUnit()->getPosition(token.begin(), &line, &column);
    *start = _textCursor.document()->findBlockByNumber(line - 1).position() + column - 1;
    *end = *start + token.length();
}

900
bool QuickFixOperation::isCursorOn(unsigned tokenIndex) const
901
902
903
904
905
906
907
908
909
910
911
912
913
{
    QTextCursor tc = textCursor();
    int cursorBegin = tc.selectionStart();

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

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

    return false;
}

914
bool QuickFixOperation::isCursorOn(const CPlusPlus::AST *ast) const
915
916
917
918
919
920
921
922
923
924
925
926
927
{
    QTextCursor tc = textCursor();
    int cursorBegin = tc.selectionStart();

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

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

    return false;
}

Roberto Raggi's avatar
Cleanup    
Roberto Raggi committed
928
929
930
931
932
933
934
935
936
937
QuickFixOperation::Range QuickFixOperation::createRange(AST *ast) const
{    
    QTextCursor tc = _textCursor;
    Range r(tc);
    r.begin.setPosition(startOf(ast));
    r.end.setPosition(endOf(ast));
    return r;
}

void QuickFixOperation::reindent(const Range &range)
938
{
Roberto Raggi's avatar
Cleanup    
Roberto Raggi committed
939
940
941
    QTextCursor tc = range.begin;
    tc.setPosition(range.end.position(), QTextCursor::KeepAnchor);
    _editor->indentInsertedText(tc);
942
943
}

944
945
void QuickFixOperation::move(int start, int end, int to)
{
946
    _changeSet.move(start, end-start, to);
947
948
949
950
}

void QuickFixOperation::move(unsigned tokenIndex, int to)
{
951
952
953
    int start, end;
    startAndEndOf(tokenIndex, &start, &end);
    move(start, end, to);
954
955
956
957
958
959
960
961
962
}

void QuickFixOperation::move(const CPlusPlus::AST *ast, int to)
{
    move(startOf(ast), endOf(ast), to);
}

void QuickFixOperation::replace(int start, int end, const QString &replacement)
{
963
    _changeSet.replace(start, end-start, replacement);
964
965
966
967
}

void QuickFixOperation::replace(unsigned tokenIndex, const QString &replacement)
{
968
969
970
    int start, end;
    startAndEndOf(tokenIndex, &start, &end);
    replace(start, end, replacement);
971
972
973
974
975
976
977
978
979
}

void QuickFixOperation::replace(const CPlusPlus::AST *ast, const QString &replacement)
{
    replace(startOf(ast), endOf(ast), replacement);
}

void QuickFixOperation::insert(int at, const QString &text)
{
980
981
982
983
984
985
986
987
988
989
    _changeSet.insert(at, text);
}

void QuickFixOperation::remove(int start, int end)
{
    _changeSet.remove(start, end-start);
}

void QuickFixOperation::remove(unsigned tokenIndex)
{
990
991
992
    int start, end;
    startAndEndOf(tokenIndex, &start, &end);
    remove(start, end);
993
994
995
996
997
998
999
}

void QuickFixOperation::remove(const CPlusPlus::AST *ast)
{
    remove(startOf(ast), endOf(ast));
}

Christian Kamm's avatar
Christian Kamm committed
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
void QuickFixOperation::flip(int start1, int end1, int start2, int end2)
{
    _changeSet.flip(start1, end1-start1, start2, end2-start2);
}

void QuickFixOperation::flip(const CPlusPlus::AST *ast1, const CPlusPlus::AST *ast2)
{
    flip(startOf(ast1), endOf(ast1), startOf(ast2), endOf(ast2));
}

1010
1011
1012
1013
1014
1015
1016
void QuickFixOperation::copy(int start, int end, int to)
{
    _changeSet.copy(start, end-start, to);
}

void QuickFixOperation::copy(unsigned tokenIndex, int to)
{
1017
1018
1019
    int start, end;
    startAndEndOf(tokenIndex, &start, &end);
    copy(start, end, to);
1020
1021
1022
1023
1024
}

void QuickFixOperation::copy(const CPlusPlus::AST *ast, int to)
{
    copy(startOf(ast), endOf(ast), to);
1025
1026
}

1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
QString QuickFixOperation::textOf(int firstOffset, int lastOffset) const
{
    QTextCursor tc = _textCursor;
    tc.setPosition(firstOffset);
    tc.setPosition(lastOffset, QTextCursor::KeepAnchor);
    return tc.selectedText();
}

QString QuickFixOperation::textOf(AST *ast) const
{
1037
    return textOf(startOf(ast), endOf(ast));
1038
1039
}

1040
void QuickFixOperation::apply()
1041
{
Roberto Raggi's avatar
Cleanup    
Roberto Raggi committed
1042
    Range range;
1043

Roberto Raggi's avatar
Roberto Raggi committed
1044
1045
    if (_topLevelNode)
        range = createRange(_topLevelNode);
Roberto Raggi's avatar
Cleanup    
Roberto Raggi committed
1046
1047

    _textCursor.beginEditBlock();
1048

1049
    _changeSet.apply(&_textCursor);
1050

Roberto Raggi's avatar
Roberto Raggi committed
1051
    if (_topLevelNode)
Roberto Raggi's avatar
Cleanup    
Roberto Raggi committed
1052
        reindent(range);
1053

Roberto Raggi's avatar
Cleanup    
Roberto Raggi committed
1054
    _textCursor.endEditBlock();
1055
1056
}

1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
CPPQuickFixCollector::CPPQuickFixCollector()
    : _modelManager(CppTools::CppModelManagerInterface::instance()), _editor(0)
{ }

CPPQuickFixCollector::~CPPQuickFixCollector()
{ }

bool CPPQuickFixCollector::supportsEditor(TextEditor::ITextEditable *editor)
{ return qobject_cast<CPPEditorEditable *>(editor) != 0; }

bool CPPQuickFixCollector::triggersCompletion(TextEditor::ITextEditable *)
1068
{ return false; }
1069
1070
1071
1072

int CPPQuickFixCollector::startCompletion(TextEditor::ITextEditable *editable)
{
    Q_ASSERT(editable != 0);
1073

1074
    _editor = qobject_cast<CPPEditor *>(editable->widget());
1075
1076
1077
1078
    Q_ASSERT(_editor != 0);

    const SemanticInfo info = _editor->semanticInfo();

1079
    if (info.revision != _editor->editorRevision()) {
1080
1081
1082
1083
1084
        // outdated
        qWarning() << "TODO: outdated semantic info, force a reparse.";
        return -1;
    }

1085
    if (info.doc) {
1086
1087
1088
        ASTPath astPath(info.doc);

        const QList<AST *> path = astPath(_editor->textCursor());
Roberto Raggi's avatar
Roberto Raggi committed
1089
1090
        if (path.isEmpty())
            return -1;
1091

Roberto Raggi's avatar
Roberto Raggi committed
1092
1093
1094
1095
1096
1097
        QSharedPointer<RewriteLogicalAndOp> rewriteLogicalAndOp(new RewriteLogicalAndOp());
        QSharedPointer<SplitIfStatementOp> splitIfStatementOp(new SplitIfStatementOp());
        QSharedPointer<MoveDeclarationOutOfIfOp> moveDeclarationOutOfIfOp(new MoveDeclarationOutOfIfOp());
        QSharedPointer<MoveDeclarationOutOfWhileOp> moveDeclarationOutOfWhileOp(new MoveDeclarationOutOfWhileOp());
        QSharedPointer<SplitSimpleDeclarationOp> splitSimpleDeclarationOp(new SplitSimpleDeclarationOp());
        QSharedPointer<AddBracesToIfOp> addBracesToIfOp(new AddBracesToIfOp());
1098
1099
        QSharedPointer<UseInverseOp> useInverseOp(new UseInverseOp());
        QSharedPointer<FlipBinaryOp> flipBinaryOp(new FlipBinaryOp());
1100
        QSharedPointer<WrapStringLiteral> wrapStringLiteral(new WrapStringLiteral());
1101

1102
1103
        QList<QuickFixOperationPtr> candidates;
        candidates.append(rewriteLogicalAndOp);
1104
        candidates.append(splitIfStatementOp);
1105
1106
        candidates.append(moveDeclarationOutOfIfOp);
        candidates.append(moveDeclarationOutOfWhileOp);
1107
        candidates.append(splitSimpleDeclarationOp);
1108
        candidates.append(addBracesToIfOp);
1109
1110
        candidates.append(useInverseOp);
        candidates.append(flipBinaryOp);
1111
        candidates.append(wrapStringLiteral);
Roberto Raggi's avatar
Roberto Raggi committed
1112

1113
        QMap<int, QList<QuickFixOperationPtr> > matchedOps;
Roberto Raggi's avatar
Roberto Raggi committed
1114

1115
        foreach (QuickFixOperationPtr op, candidates) {
Roberto Raggi's avatar
Roberto Raggi committed
1116
1117
1118
            op->setSnapshot(info.snapshot);
            op->setDocument(info.doc);
            op->setEditor(_editor);
1119
1120
            op->setTextCursor(_editor->textCursor());
            int priority = op->match(path);
1121
            if (priority != -1)
1122
                matchedOps[priority].append(op);
1123
        }
Roberto Raggi's avatar
Roberto Raggi committed
1124

1125
        QMapIterator<int, QList<QuickFixOperationPtr> > it(matchedOps);
1126
1127
1128
1129
        it.toBack();
        if (it.hasPrevious()) {
            it.previous();

1130
            _quickFixes = it.value();
1131
1132
        }

1133
1134
        if (! _quickFixes.isEmpty())
            return editable->position();
1135
1136
    }

1137
1138
1139
    return -1;
}

1140
void CPPQuickFixCollector::completions(QList<TextEditor::CompletionItem> *quickFixItems)
1141
{
1142
1143
1144
1145
1146
1147
1148
1149
    for (int i = 0; i < _quickFixes.size(); ++i) {
        QuickFixOperationPtr op = _quickFixes.at(i);

        TextEditor::CompletionItem item(this);
        item.text = op->description();
        item.data = QVariant::fromValue(i);
        quickFixItems->append(item);
    }
1150
1151
1152
1153
1154
1155
}

void CPPQuickFixCollector::complete(const TextEditor::CompletionItem &item)
{
    const int index = item.data.toInt();

1156
1157
    if (index < _quickFixes.size()) {
        QuickFixOperationPtr quickFix = _quickFixes.at(index);
1158
        perform(quickFix);
1159
1160
1161
    }
}

1162
1163
1164
void CPPQuickFixCollector::perform(QuickFixOperationPtr op)
{
    op->setTextCursor(_editor->textCursor());
Roberto Raggi's avatar
Roberto Raggi committed
1165
    op->createChangeSet();
1166
    op->apply();
1167
1168
}

1169
1170
void CPPQuickFixCollector::cleanup()
{
1171
    _quickFixes.clear();
1172
}