insertionpointlocator.cpp 20.4 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
** 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
Eike Ziller's avatar
Eike Ziller committed
13 14
** conditions see http://www.qt.io/licensing.  For further information
** use the contact form at http://www.qt.io/contact-us.
15 16
**
** GNU Lesser General Public License Usage
hjk's avatar
hjk committed
17
** Alternatively, this file may be used under the terms of the GNU Lesser
Eike Ziller's avatar
Eike Ziller committed
18 19 20 21 22 23
** General Public License version 2.1 or version 3 as published by the Free
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
** LICENSE.LGPLv3 included in the packaging of this file.  Please review the
** following information to ensure the GNU Lesser General Public License
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
24
**
hjk's avatar
hjk committed
25 26
** 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
27 28
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
hjk's avatar
hjk committed
29
****************************************************************************/
30

31
#include "insertionpointlocator.h"
32

33
#include "cpptoolsreuse.h"
34
#include "symbolfinder.h"
35
#include "cpptoolsconstants.h"
36

37
#include <coreplugin/icore.h>
38

39 40
#include <utils/qtcassert.h>

41
using namespace CPlusPlus;
42
using namespace CppTools;
43 44 45

namespace {

46
static int ordering(InsertionPointLocator::AccessSpec xsSpec)
47
{
48
    static QList<InsertionPointLocator::AccessSpec> order = QList<InsertionPointLocator::AccessSpec>()
49 50 51 52 53 54 55 56 57
            << InsertionPointLocator::Public
            << InsertionPointLocator::PublicSlot
            << InsertionPointLocator::Signals
            << InsertionPointLocator::Protected
            << InsertionPointLocator::ProtectedSlot
            << InsertionPointLocator::PrivateSlot
            << InsertionPointLocator::Private
            ;

58
    return order.indexOf(xsSpec);
59 60 61 62 63 64 65
}

struct AccessRange
{
    unsigned start;
    unsigned end;
    InsertionPointLocator::AccessSpec xsSpec;
66
    unsigned colonToken;
67 68 69 70 71

    AccessRange()
        : start(0)
        , end(0)
        , xsSpec(InsertionPointLocator::Invalid)
72
        , colonToken(0)
73 74
    {}

75
    AccessRange(unsigned start, unsigned end, InsertionPointLocator::AccessSpec xsSpec, unsigned colonToken)
76 77 78
        : start(start)
        , end(end)
        , xsSpec(xsSpec)
79
        , colonToken(colonToken)
80
    {}
81 82 83 84 85 86

    bool isEmpty() const
    {
        unsigned contentStart = 1 + (colonToken ? colonToken : start);
        return contentStart == end;
    }
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
};

class FindInClass: public ASTVisitor
{
public:
    FindInClass(const Document::Ptr &doc, const Class *clazz, InsertionPointLocator::AccessSpec xsSpec)
        : ASTVisitor(doc->translationUnit())
        , _doc(doc)
        , _clazz(clazz)
        , _xsSpec(xsSpec)
    {}

    InsertionLocation operator()()
    {
        _result = InsertionLocation();

        AST *ast = translationUnit()->ast();
        accept(ast);

        return _result;
    }

protected:
    using ASTVisitor::visit;

    bool visit(ClassSpecifierAST *ast)
    {
        if (!ast->lbrace_token || !ast->rbrace_token)
            return true;
116
        if (!ast->symbol || !ast->symbol->match(_clazz))
117 118 119 120 121 122 123 124
            return true;

        QList<AccessRange> ranges = collectAccessRanges(
                    ast->member_specifier_list,
                    tokenKind(ast->classkey_token) == T_CLASS ? InsertionPointLocator::Private : InsertionPointLocator::Public,
                    ast->lbrace_token,
                    ast->rbrace_token);

125
        unsigned beforeToken = 0;
126
        bool needsLeadingEmptyLine = false;
127 128
        bool needsPrefix = false;
        bool needsSuffix = false;
129
        findMatch(ranges, _xsSpec, beforeToken, needsLeadingEmptyLine, needsPrefix, needsSuffix);
130 131

        unsigned line = 0, column = 0;
132
        getTokenStartPosition(beforeToken, &line, &column);
133 134

        QString prefix;
135 136
        if (needsLeadingEmptyLine)
            prefix += QLatin1String("\n");
137
        if (needsPrefix)
138
            prefix += InsertionPointLocator::accessSpecToString(_xsSpec);
139

140 141 142 143
        QString suffix;
        if (needsSuffix)
            suffix = QLatin1Char('\n');

144 145
        _result = InsertionLocation(_doc->fileName(), prefix, suffix,
                                    line, column);
146 147 148
        return false;
    }

149 150 151
    static void findMatch(const QList<AccessRange> &ranges,
                          InsertionPointLocator::AccessSpec xsSpec,
                          unsigned &beforeToken,
152
                          bool &needsLeadingEmptyLine,
153 154
                          bool &needsPrefix,
                          bool &needsSuffix)
155
    {
156
        QTC_ASSERT(!ranges.isEmpty(), return);
157 158
        const int lastIndex = ranges.size() - 1;

159 160
        needsLeadingEmptyLine = false;

161
        // try an exact match, and ignore the first (default) access spec:
162
        for (int i = lastIndex; i > 0; --i) {
163
            const AccessRange &range = ranges.at(i);
164 165 166 167 168 169
            if (range.xsSpec == xsSpec) {
                beforeToken = range.end;
                needsPrefix = false;
                needsSuffix = (i != lastIndex);
                return;
            }
170 171
        }

172 173 174 175 176 177 178 179 180 181
        // try to find a fitting access spec to insert XXX:
        for (int i = lastIndex; i > 0; --i) {
            const AccessRange &current = ranges.at(i);

            if (ordering(xsSpec) > ordering(current.xsSpec)) {
                beforeToken = current.end;
                needsPrefix = true;
                needsSuffix = (i != lastIndex);
                return;
            }
182 183 184
        }

        // otherwise:
185
        beforeToken = ranges.first().end;
186
        needsLeadingEmptyLine = !ranges.first().isEmpty();
187 188
        needsPrefix = true;
        needsSuffix = (ranges.size() != 1);
189 190 191 192 193 194 195 196
    }

    QList<AccessRange> collectAccessRanges(DeclarationListAST *decls,
                                           InsertionPointLocator::AccessSpec initialXs,
                                           int firstRangeStart,
                                           int lastRangeEnd) const
    {
        QList<AccessRange> ranges;
197
        ranges.append(AccessRange(firstRangeStart, lastRangeEnd, initialXs, 0));
198 199 200 201 202 203

        for (DeclarationListAST *iter = decls; iter; iter = iter->next) {
            DeclarationAST *decl = iter->value;

            if (AccessDeclarationAST *xsDecl = decl->asAccessDeclaration()) {
                const unsigned token = xsDecl->access_specifier_token;
204
                InsertionPointLocator::AccessSpec newXsSpec = initialXs;
205 206
                bool isSlot = xsDecl->slots_token
                        && tokenKind(xsDecl->slots_token) == T_Q_SLOTS;
207 208 209

                switch (tokenKind(token)) {
                case T_PUBLIC:
210 211
                    newXsSpec = isSlot ? InsertionPointLocator::PublicSlot
                                       : InsertionPointLocator::Public;
212 213 214
                    break;

                case T_PROTECTED:
215 216
                    newXsSpec = isSlot ? InsertionPointLocator::ProtectedSlot
                                       : InsertionPointLocator::Protected;
217 218 219
                    break;

                case T_PRIVATE:
220 221
                    newXsSpec = isSlot ? InsertionPointLocator::PrivateSlot
                                       : InsertionPointLocator::Private;
222 223 224 225 226 227 228
                    break;

                case T_Q_SIGNALS:
                    newXsSpec = InsertionPointLocator::Signals;
                    break;

                case T_Q_SLOTS: {
229 230
                    newXsSpec = (InsertionPointLocator::AccessSpec)
                            (ranges.last().xsSpec | InsertionPointLocator::SlotBit);
231 232 233 234 235 236 237
                    break;
                }

                default:
                    break;
                }

238
                if (newXsSpec != ranges.last().xsSpec || ranges.size() == 1) {
239
                    ranges.last().end = token;
240 241
                    AccessRange r(token, lastRangeEnd, newXsSpec, xsDecl->colon_token);
                    ranges.append(r);
242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264
                }
            }
        }

        ranges.last().end = lastRangeEnd;
        return ranges;
    }

private:
    Document::Ptr _doc;
    const Class *_clazz;
    InsertionPointLocator::AccessSpec _xsSpec;

    InsertionLocation _result;
};

} // end of anonymous namespace

InsertionLocation::InsertionLocation()
    : m_line(0)
    , m_column(0)
{}

265 266 267
InsertionLocation::InsertionLocation(const QString &fileName,
                                     const QString &prefix,
                                     const QString &suffix,
268
                                     unsigned line, unsigned column)
269 270
    : m_fileName(fileName)
    , m_prefix(prefix)
271
    , m_suffix(suffix)
272 273 274 275
    , m_line(line)
    , m_column(column)
{}

276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302
QString InsertionPointLocator::accessSpecToString(InsertionPointLocator::AccessSpec xsSpec)
{
    switch (xsSpec) {
    default:
    case InsertionPointLocator::Public:
        return QLatin1String("public:\n");

    case InsertionPointLocator::Protected:
        return QLatin1String("protected:\n");

    case InsertionPointLocator::Private:
        return QLatin1String("private:\n");

    case InsertionPointLocator::PublicSlot:
        return QLatin1String("public slots:\n");

    case InsertionPointLocator::ProtectedSlot:
        return QLatin1String("protected slots:\n");

    case InsertionPointLocator::PrivateSlot:
        return QLatin1String("private slots:\n");

    case InsertionPointLocator::Signals:
        return QLatin1String("signals:\n");
    }
}

303
InsertionPointLocator::InsertionPointLocator(const CppRefactoringChanges &refactoringChanges)
304
    : m_refactoringChanges(refactoringChanges)
305 306 307
{
}

308 309 310 311
InsertionLocation InsertionPointLocator::methodDeclarationInClass(
    const QString &fileName,
    const Class *clazz,
    AccessSpec xsSpec) const
312
{
313
    const Document::Ptr doc = m_refactoringChanges.file(fileName)->cppDocument();
314 315 316 317 318 319 320 321
    if (doc) {
        FindInClass find(doc, clazz, xsSpec);
        return find();
    } else {
        return InsertionLocation();
    }
}

322 323 324 325 326 327 328 329 330
namespace {
template <class Key, class Value>
class HighestValue
{
    Key _key;
    Value _value;
    bool _set;
public:
    HighestValue()
331
        : _key(), _set(false)
332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350
    {}

    HighestValue(const Key &initialKey, const Value &initialValue)
        : _key(initialKey)
        , _value(initialValue)
        , _set(true)
    {}

    void maybeSet(const Key &key, const Value &value)
    {
        if (!_set || key > _key) {
            _value = value;
            _key = key;
            _set = true;
        }
    }

    const Value &get() const
    {
Leandro Melo's avatar
Leandro Melo committed
351
        QTC_CHECK(_set);
352 353 354 355 356 357 358 359
        return _value;
    }
};

class FindMethodDefinitionInsertPoint : protected ASTVisitor
{
    QList<const Identifier *> _namespaceNames;
    int _currentDepth;
360
    HighestValue<int, unsigned> _bestToken;
361 362 363 364 365 366

public:
    FindMethodDefinitionInsertPoint(TranslationUnit *translationUnit)
        : ASTVisitor(translationUnit)
    {}

367
    void operator()(Symbol *decl, unsigned *line, unsigned *column)
368 369
    {
        // default to end of file
370 371
        const unsigned lastToken = translationUnit()->ast()->lastToken();
        _bestToken.maybeSet(-1, lastToken);
372

373
        if (lastToken >= 2) {
374 375 376 377 378 379 380 381 382 383 384
            QList<const Name *> names = LookupContext::fullyQualifiedName(decl);
            foreach (const Name *name, names) {
                const Identifier *id = name->asNameId();
                if (!id)
                    break;
                _namespaceNames += id;
            }
            _currentDepth = 0;

            accept(translationUnit()->ast());
        }
385 386 387 388 389

        if (lastToken == _bestToken.get()) // No matching namespace found
            translationUnit()->getTokenStartPosition(lastToken, line, column);
        else // Insert at end of matching namespace
            translationUnit()->getTokenEndPosition(_bestToken.get(), line, column);
390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420
    }

protected:
    bool preVisit(AST *ast)
    {
        return ast->asNamespace() || ast->asTranslationUnit() || ast->asLinkageBody();
    }

    bool visit(NamespaceAST *ast)
    {
        if (_currentDepth >= _namespaceNames.size())
            return false;

        // ignore anonymous namespaces
        if (!ast->identifier_token)
            return false;

        const Identifier *name = translationUnit()->identifier(ast->identifier_token);
        if (!name->equalTo(_namespaceNames.at(_currentDepth)))
            return false;

        // found a good namespace
        _bestToken.maybeSet(_currentDepth, ast->lastToken() - 2);

        ++_currentDepth;
        accept(ast->linkage_body);
        --_currentDepth;

        return false;
    }
};
421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462

class FindFunctionDefinition : protected ASTVisitor
{
    FunctionDefinitionAST *_result;
    unsigned _line, _column;
public:
    FindFunctionDefinition(TranslationUnit *translationUnit)
        : ASTVisitor(translationUnit)
    {
    }

    FunctionDefinitionAST *operator()(unsigned line, unsigned column)
    {
        _result = 0;
        _line = line;
        _column = column;
        accept(translationUnit()->ast());
        return _result;
    }

protected:
    bool preVisit(AST *ast)
    {
        if (_result)
            return false;
        unsigned line, column;
        translationUnit()->getTokenStartPosition(ast->firstToken(), &line, &column);
        if (line > _line || (line == _line && column > _column))
            return false;
        translationUnit()->getTokenEndPosition(ast->lastToken() - 1, &line, &column);
        if (line < _line || (line == _line && column < _column))
            return false;
        return true;
    }

    bool visit(FunctionDefinitionAST *ast)
    {
        _result = ast;
        return false;
    }
};

463 464
} // anonymous namespace

465 466 467 468 469 470 471 472 473 474 475 476 477
static Declaration *isNonVirtualFunctionDeclaration(Symbol *s)
{
    if (!s)
        return 0;
    Declaration *declaration = s->asDeclaration();
    if (!declaration)
        return 0;
    Function *type = s->type()->asFunctionType();
    if (!type || type->isPureVirtual())
        return 0;
    return declaration;
}

478 479 480
static InsertionLocation nextToSurroundingDefinitions(Symbol *declaration,
                                                      const CppRefactoringChanges &changes,
                                                      const QString& destinationFile)
481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498
{
    InsertionLocation noResult;
    Class *klass = declaration->enclosingClass();
    if (!klass)
        return noResult;

    // find the index of declaration
    int declIndex = -1;
    for (unsigned i = 0; i < klass->memberCount(); ++i) {
        Symbol *s = klass->memberAt(i);
        if (s == declaration) {
            declIndex = i;
            break;
        }
    }
    if (declIndex == -1)
        return noResult;

499 500 501
    // scan preceding declarations for a function declaration (and see if it is defined)
    CppTools::SymbolFinder symbolFinder;
    Function *definitionFunction = 0;
502 503 504 505
    QString prefix, suffix;
    Declaration *surroundingFunctionDecl = 0;
    for (int i = declIndex - 1; i >= 0; --i) {
        Symbol *s = klass->memberAt(i);
506
        if (s->isGenerated() || !(surroundingFunctionDecl = isNonVirtualFunctionDeclaration(s)))
507 508 509 510 511 512 513 514 515 516
            continue;
        if ((definitionFunction = symbolFinder.findMatchingDefinition(surroundingFunctionDecl,
                                                                      changes.snapshot())))
        {
            if (destinationFile.isEmpty() || destinationFile == QString::fromUtf8(
                        definitionFunction->fileName(), definitionFunction->fileNameLength())) {
                prefix = QLatin1String("\n\n");
                break;
            }
            definitionFunction = 0;
517 518
        }
    }
519
    if (!definitionFunction) {
520 521 522 523
        // try to find one below
        for (unsigned i = declIndex + 1; i < klass->memberCount(); ++i) {
            Symbol *s = klass->memberAt(i);
            surroundingFunctionDecl = isNonVirtualFunctionDeclaration(s);
524 525 526 527 528 529 530 531 532 533 534
            if (!surroundingFunctionDecl)
                continue;
            if ((definitionFunction = symbolFinder.findMatchingDefinition(surroundingFunctionDecl,
                                                                          changes.snapshot())))
            {
                if (destinationFile.isEmpty() || destinationFile == QString::fromUtf8(
                            definitionFunction->fileName(), definitionFunction->fileNameLength())) {
                    suffix = QLatin1String("\n\n");
                    break;
                }
                definitionFunction = 0;
535 536 537 538
            }
        }
    }

539
    if (!definitionFunction)
540 541 542 543
        return noResult;

    unsigned line, column;
    if (suffix.isEmpty()) {
544
        Document::Ptr targetDoc = changes.snapshot().document(QString::fromUtf8(definitionFunction->fileName()));
545 546 547 548 549 550
        if (!targetDoc)
            return noResult;

        targetDoc->translationUnit()->getPosition(definitionFunction->endOffset(), &line, &column);
    } else {
        // we don't have an offset to the start of the function definition, so we need to manually find it...
551
        CppRefactoringFilePtr targetFile = changes.file(QString::fromUtf8(definitionFunction->fileName()));
552 553 554 555
        if (!targetFile->isValid())
            return noResult;

        FindFunctionDefinition finder(targetFile->cppDocument()->translationUnit());
556
        FunctionDefinitionAST *functionDefinition = finder(definitionFunction->line(), definitionFunction->column());
557 558 559 560 561 562
        if (!functionDefinition)
            return noResult;

        targetFile->cppDocument()->translationUnit()->getTokenStartPosition(functionDefinition->firstToken(), &line, &column);
    }

563
    return InsertionLocation(QString::fromUtf8(definitionFunction->fileName()), prefix, suffix, line, column);
564 565
}

566
QList<InsertionLocation> InsertionPointLocator::methodDefinition(Symbol *declaration,
567 568
                                                                 bool useSymbolFinder,
                                                                 const QString &destinationFile) const
569 570
{
    QList<InsertionLocation> result;
571 572
    if (!declaration)
        return result;
573

574 575 576 577 578
    if (useSymbolFinder) {
        CppTools::SymbolFinder symbolFinder;
        if (symbolFinder.findMatchingDefinition(declaration, m_refactoringChanges.snapshot(), true))
            return result;
    }
579

580 581 582
    const InsertionLocation location = nextToSurroundingDefinitions(declaration,
                                                                    m_refactoringChanges,
                                                                    destinationFile);
583 584 585 586 587
    if (location.isValid()) {
        result += location;
        return result;
    }

588 589 590
    const QString declFileName = QString::fromUtf8(declaration->fileName(),
                                                   declaration->fileNameLength());
    QString target = declFileName;
591
    if (!ProjectFile::isSource(ProjectFile::classify(declFileName))) {
592
        QString candidate = CppTools::correspondingHeaderOrSource(declFileName);
593 594
        if (!candidate.isEmpty())
            target = candidate;
595
    }
596

597 598
    CppRefactoringFilePtr targetFile = m_refactoringChanges.file(target);
    Document::Ptr doc = targetFile->cppDocument();
599 600 601 602
    if (doc.isNull())
        return result;

    unsigned line = 0, column = 0;
603 604
    FindMethodDefinitionInsertPoint finder(doc->translationUnit());
    finder(declaration, &line, &column);
605

606 607
    // Force empty lines before and after the new definition.
    QString prefix;
608
    QString suffix;
609 610 611 612 613
    if (!line) {
        // Totally empty file.
        line = 1;
        column = 1;
        prefix = suffix = QLatin1Char('\n');
614
    } else {
615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630
        QTC_ASSERT(column, return result);

        prefix = QLatin1String("\n\n");
        int firstNonSpace = targetFile->position(line, column);
        QChar c = targetFile->charAt(firstNonSpace);
        while (c == QLatin1Char(' ') || c == QLatin1Char('\t')) {
            ++firstNonSpace;
            c = targetFile->charAt(firstNonSpace);
        }
        if (targetFile->charAt(firstNonSpace) != QChar::ParagraphSeparator) {
            suffix.append(QLatin1String("\n\n"));
        } else {
            ++firstNonSpace;
            if (targetFile->charAt(firstNonSpace) != QChar::ParagraphSeparator)
                suffix.append(QLatin1Char('\n'));
        }
631 632 633
    }

    result += InsertionLocation(target, prefix, suffix, line, column);
634 635

    return result;
636
}