forked from qt-creator/qt-creator
		
	C++: Introduce PointerDeclarationFormatter
For a given AST, CppRefactoringFile and Overview this will create a ChangeSet for rewriting the pointer or reference declaration according to the Overview. Task-number: QTCREATORBUG-6169 Change-Id: If6f824c1ea5e9f53a11a58ec8b6d696d01f0723e Reviewed-by: Erik Verbruggen <erik.verbruggen@digia.com>
This commit is contained in:
		
							
								
								
									
										417
									
								
								src/plugins/cpptools/cpppointerdeclarationformatter.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										417
									
								
								src/plugins/cpptools/cpppointerdeclarationformatter.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,417 @@
 | 
			
		||||
/****************************************************************************
 | 
			
		||||
**
 | 
			
		||||
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
 | 
			
		||||
** Contact: http://www.qt-project.org/legal
 | 
			
		||||
**
 | 
			
		||||
** This file is part of Qt Creator.
 | 
			
		||||
**
 | 
			
		||||
** Commercial License Usage
 | 
			
		||||
** Licensees holding valid commercial Qt licenses may use this file in
 | 
			
		||||
** accordance with the commercial license agreement provided with the
 | 
			
		||||
** Software or, alternatively, in accordance with the terms contained in
 | 
			
		||||
** a written agreement between you and Digia.  For licensing terms and
 | 
			
		||||
** conditions see http://qt.digia.com/licensing.  For further information
 | 
			
		||||
** use the contact form at http://qt.digia.com/contact-us.
 | 
			
		||||
**
 | 
			
		||||
** 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.
 | 
			
		||||
**
 | 
			
		||||
** In addition, as a special exception, Digia gives you certain additional
 | 
			
		||||
** rights.  These rights are described in the Digia Qt LGPL Exception
 | 
			
		||||
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
 | 
			
		||||
**
 | 
			
		||||
****************************************************************************/
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#include "cpppointerdeclarationformatter.h"
 | 
			
		||||
 | 
			
		||||
#include <AST.h>
 | 
			
		||||
 | 
			
		||||
#include <QTextCursor>
 | 
			
		||||
 | 
			
		||||
#define DEBUG_OUTPUT 0
 | 
			
		||||
#define CHECK_RV(cond, err, r) if (!(cond)) { if (DEBUG_OUTPUT) qDebug() << (err); return r; }
 | 
			
		||||
#define CHECK_R(cond, err) if (!(cond)) { if (DEBUG_OUTPUT) qDebug() << (err); return; }
 | 
			
		||||
#define CHECK_C(cond, err) if (!(cond)) { if (DEBUG_OUTPUT) qDebug() << (err); continue; }
 | 
			
		||||
 | 
			
		||||
using namespace CppTools;
 | 
			
		||||
 | 
			
		||||
/*!
 | 
			
		||||
   \brief Skip not type relevant specifiers and return the index of the
 | 
			
		||||
          first specifier token which is not followed by __attribute__
 | 
			
		||||
          ((T___ATTRIBUTE__)).
 | 
			
		||||
 | 
			
		||||
   This is used to get 'correct' start of the activation range in
 | 
			
		||||
   simple declarations.
 | 
			
		||||
 | 
			
		||||
   Consider these cases:
 | 
			
		||||
 | 
			
		||||
        static char *s = 0;
 | 
			
		||||
        typedef char *s cp;
 | 
			
		||||
        __attribute__((visibility("default"))) char *f();
 | 
			
		||||
 | 
			
		||||
   For all cases we want to skip all the not type relevant specifer
 | 
			
		||||
   (since these are not part of the type and thus are not rewritten).
 | 
			
		||||
 | 
			
		||||
   \param list The specifier list to iterate
 | 
			
		||||
   \param translationUnit The TranslationUnit
 | 
			
		||||
   \param endToken Do not check further than this token
 | 
			
		||||
   \param found Output parameter, must not be 0.
 | 
			
		||||
 */
 | 
			
		||||
static unsigned firstTypeSpecifierWithoutFollowingAttribute(
 | 
			
		||||
    SpecifierListAST *list, TranslationUnit *translationUnit, unsigned endToken, bool *found)
 | 
			
		||||
{
 | 
			
		||||
    *found = false;
 | 
			
		||||
    if (! list || ! translationUnit || ! endToken)
 | 
			
		||||
        return 0;
 | 
			
		||||
 | 
			
		||||
    for (SpecifierListAST *it = list; it; it = it->next) {
 | 
			
		||||
        SpecifierAST *specifier = it->value;
 | 
			
		||||
        CHECK_RV(specifier, "No specifier", 0);
 | 
			
		||||
        const unsigned index = specifier->firstToken();
 | 
			
		||||
        CHECK_RV(index < endToken, "EndToken reached", 0);
 | 
			
		||||
 | 
			
		||||
        const int tokenKind = translationUnit->tokenKind(index);
 | 
			
		||||
        switch (tokenKind) {
 | 
			
		||||
        case T_VIRTUAL:
 | 
			
		||||
        case T_INLINE:
 | 
			
		||||
        case T_FRIEND:
 | 
			
		||||
        case T_REGISTER:
 | 
			
		||||
        case T_STATIC:
 | 
			
		||||
        case T_EXTERN:
 | 
			
		||||
        case T_MUTABLE:
 | 
			
		||||
        case T_TYPEDEF:
 | 
			
		||||
        case T_CONSTEXPR:
 | 
			
		||||
        case T___ATTRIBUTE__:
 | 
			
		||||
            continue;
 | 
			
		||||
        default:
 | 
			
		||||
            // Check if attributes follow
 | 
			
		||||
            for (unsigned i = index; i <= endToken; ++i) {
 | 
			
		||||
                const int tokenKind = translationUnit->tokenKind(i);
 | 
			
		||||
                if (tokenKind == T___ATTRIBUTE__)
 | 
			
		||||
                    return 0;
 | 
			
		||||
            }
 | 
			
		||||
            *found = true;
 | 
			
		||||
            return index;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
PointerDeclarationFormatter::PointerDeclarationFormatter(
 | 
			
		||||
        const CppRefactoringFilePtr refactoringFile,
 | 
			
		||||
        const Overview &overview,
 | 
			
		||||
        CursorHandling cursorHandling)
 | 
			
		||||
    : ASTVisitor(refactoringFile->cppDocument()->translationUnit())
 | 
			
		||||
    , m_cppRefactoringFile(refactoringFile)
 | 
			
		||||
    , m_overview(overview)
 | 
			
		||||
    , m_cursorHandling(cursorHandling)
 | 
			
		||||
{}
 | 
			
		||||
 | 
			
		||||
/*!
 | 
			
		||||
    Handle
 | 
			
		||||
      (1) Simple declarations like in "char *s, *t, *int foo();"
 | 
			
		||||
      (2) Return types of function declarations.
 | 
			
		||||
 */
 | 
			
		||||
bool PointerDeclarationFormatter::visit(SimpleDeclarationAST *ast)
 | 
			
		||||
{
 | 
			
		||||
    CHECK_RV(ast, "Invalid AST", true);
 | 
			
		||||
 | 
			
		||||
    DeclaratorListAST *declaratorList = ast->declarator_list;
 | 
			
		||||
    CHECK_RV(declaratorList, "No declarator list", true);
 | 
			
		||||
    DeclaratorAST *firstDeclarator = declaratorList->value;
 | 
			
		||||
    CHECK_RV(firstDeclarator, "No declarator", true);
 | 
			
		||||
    CHECK_RV(ast->symbols, "No Symbols", true);
 | 
			
		||||
    CHECK_RV(ast->symbols->value, "No Symbol", true);
 | 
			
		||||
 | 
			
		||||
    List<Symbol *> *sit = ast->symbols;
 | 
			
		||||
    DeclaratorListAST *dit = declaratorList;
 | 
			
		||||
    for (; sit && dit; sit = sit->next, dit = dit->next) {
 | 
			
		||||
        DeclaratorAST *declarator = dit->value;
 | 
			
		||||
        Symbol *symbol = sit->value;
 | 
			
		||||
 | 
			
		||||
        const bool isFirstDeclarator = declarator == firstDeclarator;
 | 
			
		||||
 | 
			
		||||
        // If were not handling the first declarator, we need to remove
 | 
			
		||||
        // characters from the beginning since our rewritten declaration
 | 
			
		||||
        // will contain all type specifiers.
 | 
			
		||||
        int charactersToRemove = 0;
 | 
			
		||||
        if (! isFirstDeclarator) {
 | 
			
		||||
            const int startAST = m_cppRefactoringFile->startOf(ast);
 | 
			
		||||
            const int startFirstDeclarator = m_cppRefactoringFile->startOf(firstDeclarator);
 | 
			
		||||
            CHECK_RV(startAST < startFirstDeclarator, "No specifier", true);
 | 
			
		||||
            charactersToRemove = startFirstDeclarator - startAST;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Specify activation range
 | 
			
		||||
        int lastActivationToken = 0;
 | 
			
		||||
        Range range;
 | 
			
		||||
        // (2) Handle function declaration's return type
 | 
			
		||||
        if (symbol->type()->asFunctionType()) {
 | 
			
		||||
            PostfixDeclaratorListAST *pfDeclaratorList = declarator->postfix_declarator_list;
 | 
			
		||||
            CHECK_RV(pfDeclaratorList, "No postfix declarator list", true);
 | 
			
		||||
            PostfixDeclaratorAST *pfDeclarator = pfDeclaratorList->value;
 | 
			
		||||
            CHECK_RV(pfDeclarator, "No postfix declarator", true);
 | 
			
		||||
            FunctionDeclaratorAST *functionDeclarator = pfDeclarator->asFunctionDeclarator();
 | 
			
		||||
            CHECK_RV(functionDeclarator, "No function declarator", true);
 | 
			
		||||
            // End the activation range before the '(' token.
 | 
			
		||||
            lastActivationToken = functionDeclarator->lparen_token - 1;
 | 
			
		||||
 | 
			
		||||
            SpecifierListAST *specifierList = isFirstDeclarator
 | 
			
		||||
                ? ast->decl_specifier_list
 | 
			
		||||
                : declarator->attribute_list;
 | 
			
		||||
 | 
			
		||||
            unsigned firstActivationToken = 0;
 | 
			
		||||
            bool foundBegin = false;
 | 
			
		||||
            firstActivationToken = firstTypeSpecifierWithoutFollowingAttribute(
 | 
			
		||||
                        specifierList,
 | 
			
		||||
                        m_cppRefactoringFile->cppDocument()->translationUnit(),
 | 
			
		||||
                        lastActivationToken,
 | 
			
		||||
                        &foundBegin);
 | 
			
		||||
            if (! foundBegin) {
 | 
			
		||||
                CHECK_RV(! isFirstDeclarator, "Declaration without attributes not supported", true);
 | 
			
		||||
                firstActivationToken = declarator->firstToken();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            range.start = m_cppRefactoringFile->startOf(firstActivationToken);
 | 
			
		||||
 | 
			
		||||
        // (1) Handle 'normal' declarations.
 | 
			
		||||
        } else {
 | 
			
		||||
            if (isFirstDeclarator) {
 | 
			
		||||
                bool foundBegin = false;
 | 
			
		||||
                unsigned firstActivationToken = firstTypeSpecifierWithoutFollowingAttribute(
 | 
			
		||||
                            ast->decl_specifier_list,
 | 
			
		||||
                            m_cppRefactoringFile->cppDocument()->translationUnit(),
 | 
			
		||||
                            declarator->firstToken(),
 | 
			
		||||
                            &foundBegin);
 | 
			
		||||
                CHECK_RV(foundBegin, "Declaration without attributes not supported", true);
 | 
			
		||||
                range.start = m_cppRefactoringFile->startOf(firstActivationToken);
 | 
			
		||||
            } else {
 | 
			
		||||
                range.start = m_cppRefactoringFile->startOf(declarator);
 | 
			
		||||
            }
 | 
			
		||||
            lastActivationToken = declarator->equal_token
 | 
			
		||||
                ? declarator->equal_token - 1
 | 
			
		||||
                : declarator->lastToken() - 1;
 | 
			
		||||
        }
 | 
			
		||||
        range.end = m_cppRefactoringFile->endOf(lastActivationToken);
 | 
			
		||||
 | 
			
		||||
        checkAndRewrite(symbol, range, charactersToRemove);
 | 
			
		||||
    }
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*! Handle return types of function definitions */
 | 
			
		||||
bool PointerDeclarationFormatter::visit(FunctionDefinitionAST *ast)
 | 
			
		||||
{
 | 
			
		||||
    DeclaratorAST *declarator = ast->declarator;
 | 
			
		||||
    CHECK_RV(declarator, "No declarator", true);
 | 
			
		||||
    CHECK_RV(declarator->ptr_operator_list, "No Pointer or references", true);
 | 
			
		||||
    Symbol *symbol = ast->symbol;
 | 
			
		||||
 | 
			
		||||
    PostfixDeclaratorListAST *pfDeclaratorList = declarator->postfix_declarator_list;
 | 
			
		||||
    CHECK_RV(pfDeclaratorList, "No postfix declarator list", true);
 | 
			
		||||
    PostfixDeclaratorAST *pfDeclarator = pfDeclaratorList->value;
 | 
			
		||||
    CHECK_RV(pfDeclarator, "No postfix declarator", true);
 | 
			
		||||
    FunctionDeclaratorAST *functionDeclarator = pfDeclarator->asFunctionDeclarator();
 | 
			
		||||
    CHECK_RV(functionDeclarator, "No function declarator", true);
 | 
			
		||||
 | 
			
		||||
    // Specify activation range
 | 
			
		||||
    bool foundBegin = false;
 | 
			
		||||
    const unsigned lastActivationToken = functionDeclarator->lparen_token - 1;
 | 
			
		||||
    const unsigned firstActivationToken = firstTypeSpecifierWithoutFollowingAttribute(
 | 
			
		||||
        ast->decl_specifier_list,
 | 
			
		||||
        m_cppRefactoringFile->cppDocument()->translationUnit(),
 | 
			
		||||
        lastActivationToken,
 | 
			
		||||
        &foundBegin);
 | 
			
		||||
    CHECK_RV(foundBegin, "Declaration without attributes not supported", true);
 | 
			
		||||
    Range range(m_cppRefactoringFile->startOf(firstActivationToken),
 | 
			
		||||
                m_cppRefactoringFile->endOf(lastActivationToken));
 | 
			
		||||
 | 
			
		||||
    checkAndRewrite(symbol, range);
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*! Handle parameters in function declarations and definitions */
 | 
			
		||||
bool PointerDeclarationFormatter::visit(ParameterDeclarationAST *ast)
 | 
			
		||||
{
 | 
			
		||||
    DeclaratorAST *declarator = ast->declarator;
 | 
			
		||||
    CHECK_RV(declarator, "No declarator", true);
 | 
			
		||||
    CHECK_RV(declarator->ptr_operator_list, "No Pointer or references", true);
 | 
			
		||||
    Symbol *symbol = ast->symbol;
 | 
			
		||||
 | 
			
		||||
    // Specify activation range
 | 
			
		||||
    const int lastActivationToken = ast->equal_token
 | 
			
		||||
        ? ast->equal_token - 1
 | 
			
		||||
        : ast->lastToken() - 1;
 | 
			
		||||
    Range range(m_cppRefactoringFile->startOf(ast),
 | 
			
		||||
                m_cppRefactoringFile->endOf(lastActivationToken));
 | 
			
		||||
 | 
			
		||||
    checkAndRewrite(symbol, range);
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*! Handle declaration in foreach statement */
 | 
			
		||||
bool PointerDeclarationFormatter::visit(ForeachStatementAST *ast)
 | 
			
		||||
{
 | 
			
		||||
    DeclaratorAST *declarator = ast->declarator;
 | 
			
		||||
    CHECK_RV(declarator, "No declarator", true);
 | 
			
		||||
    CHECK_RV(declarator->ptr_operator_list, "No Pointer or references", true);
 | 
			
		||||
    CHECK_RV(ast->type_specifier_list, "No type specifier", true);
 | 
			
		||||
    SpecifierAST *firstSpecifier = ast->type_specifier_list->value;
 | 
			
		||||
    CHECK_RV(firstSpecifier, "No first type specifier", true);
 | 
			
		||||
    CHECK_RV(ast->symbol, "No symbol", true);
 | 
			
		||||
    Symbol *symbol = ast->symbol->memberAt(0);
 | 
			
		||||
 | 
			
		||||
    // Specify activation range
 | 
			
		||||
    const int lastActivationToken = declarator->equal_token
 | 
			
		||||
        ? declarator->equal_token - 1
 | 
			
		||||
        : declarator->lastToken() - 1;
 | 
			
		||||
    Range range(m_cppRefactoringFile->startOf(firstSpecifier),
 | 
			
		||||
                m_cppRefactoringFile->endOf(lastActivationToken));
 | 
			
		||||
 | 
			
		||||
    checkAndRewrite(symbol, range);
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool PointerDeclarationFormatter::visit(IfStatementAST *ast)
 | 
			
		||||
{
 | 
			
		||||
    processIfWhileForStatement(ast->condition, ast->symbol);
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool PointerDeclarationFormatter::visit(WhileStatementAST *ast)
 | 
			
		||||
{
 | 
			
		||||
    processIfWhileForStatement(ast->condition, ast->symbol);
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool PointerDeclarationFormatter::visit(ForStatementAST *ast)
 | 
			
		||||
{
 | 
			
		||||
    processIfWhileForStatement(ast->condition, ast->symbol);
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*! Handle declaration in if, while and for statements */
 | 
			
		||||
void PointerDeclarationFormatter::processIfWhileForStatement(ExpressionAST *expression,
 | 
			
		||||
                                                             Symbol *statementSymbol)
 | 
			
		||||
{
 | 
			
		||||
    CHECK_R(expression, "No expression");
 | 
			
		||||
    CHECK_R(statementSymbol, "No symbol");
 | 
			
		||||
 | 
			
		||||
    ConditionAST *condition = expression->asCondition();
 | 
			
		||||
    CHECK_R(condition, "No condition");
 | 
			
		||||
    DeclaratorAST *declarator = condition->declarator;
 | 
			
		||||
    CHECK_R(declarator, "No declarator");
 | 
			
		||||
    CHECK_R(declarator->ptr_operator_list, "No Pointer or references");
 | 
			
		||||
    CHECK_R(declarator->equal_token, "No equal token");
 | 
			
		||||
    Block *block = statementSymbol->asBlock();
 | 
			
		||||
    CHECK_R(block, "No block");
 | 
			
		||||
    CHECK_R(block->memberCount() > 0, "No block members");
 | 
			
		||||
 | 
			
		||||
    // Get the right symbol
 | 
			
		||||
    //
 | 
			
		||||
    // This is especially important for e.g.
 | 
			
		||||
    //
 | 
			
		||||
    //    for (char *s = 0; char *t = 0;) {}
 | 
			
		||||
    //
 | 
			
		||||
    // The declaration for 's' will be handled in visit(SimpleDeclarationAST *ast),
 | 
			
		||||
    // so handle declaration for 't' here.
 | 
			
		||||
    Scope::iterator it = block->lastMember() - 1;
 | 
			
		||||
    Symbol *symbol = *it;
 | 
			
		||||
    if (symbol && symbol->asScope()) { // True if there is a  "{ ... }" following.
 | 
			
		||||
        --it;
 | 
			
		||||
        symbol = *it;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Specify activation range
 | 
			
		||||
    Range range(m_cppRefactoringFile->startOf(condition),
 | 
			
		||||
                m_cppRefactoringFile->endOf(declarator->equal_token - 1));
 | 
			
		||||
 | 
			
		||||
    checkAndRewrite(symbol, range);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*!
 | 
			
		||||
   \brief Do some further checks and rewrite the symbol's type and
 | 
			
		||||
          name into the given range
 | 
			
		||||
 | 
			
		||||
   \param symbol the symbol to be rewritten
 | 
			
		||||
   \param range the substitution range in the file
 | 
			
		||||
 */
 | 
			
		||||
void PointerDeclarationFormatter::checkAndRewrite(Symbol *symbol, Range range,
 | 
			
		||||
                                                  unsigned charactersToRemove)
 | 
			
		||||
{
 | 
			
		||||
    CHECK_R(range.start >= 0 && range.end > 0, "Range invalid");
 | 
			
		||||
    CHECK_R(range.start < range.end, "Range invalid");
 | 
			
		||||
    CHECK_R(symbol, "No symbol");
 | 
			
		||||
 | 
			
		||||
    // Check range with respect to cursor position / selection
 | 
			
		||||
    if (m_cursorHandling == RespectCursor) {
 | 
			
		||||
        const QTextCursor cursor = m_cppRefactoringFile->cursor();
 | 
			
		||||
        if (cursor.hasSelection()) {
 | 
			
		||||
            CHECK_R(cursor.selectionStart() <= range.start, "Change not in selection range");
 | 
			
		||||
            CHECK_R(range.end <= cursor.selectionEnd(), "Change not in selection range");
 | 
			
		||||
        } else {
 | 
			
		||||
            CHECK_R(range.start <= cursor.selectionStart(), "Cursor before activation range");
 | 
			
		||||
            CHECK_R(cursor.selectionEnd() <= range.end, "Cursor after activation range");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    FullySpecifiedType type = symbol->type();
 | 
			
		||||
    if (Function *function = type->asFunctionType())
 | 
			
		||||
        type = function->returnType();
 | 
			
		||||
 | 
			
		||||
    // Check if pointers or references are involved
 | 
			
		||||
    const QString originalDeclaration = m_cppRefactoringFile->textOf(range);
 | 
			
		||||
    CHECK_R(originalDeclaration.contains(QLatin1Char('&'))
 | 
			
		||||
            || originalDeclaration.contains(QLatin1Char('*')), "No pointer or references");
 | 
			
		||||
 | 
			
		||||
    // Does the rewritten declaration (part) differs from the original source (part)?
 | 
			
		||||
    QString rewrittenDeclaration = rewriteDeclaration(type, symbol->name());
 | 
			
		||||
    rewrittenDeclaration.remove(0, charactersToRemove);
 | 
			
		||||
    CHECK_R(originalDeclaration != rewrittenDeclaration, "Rewritten is same as original");
 | 
			
		||||
 | 
			
		||||
    if (DEBUG_OUTPUT) {
 | 
			
		||||
        qDebug("Rewritten: \"%s\" --> \"%s\"", originalDeclaration.toLatin1().constData(),
 | 
			
		||||
               rewrittenDeclaration.toLatin1().constData());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Creating the replacement in the changeset may fail due to operations
 | 
			
		||||
    // in the changeset that overlap with the current range.
 | 
			
		||||
    //
 | 
			
		||||
    // Consider this case:
 | 
			
		||||
    //
 | 
			
		||||
    //    void (*foo)(char * s) = 0;
 | 
			
		||||
    //
 | 
			
		||||
    // First visit(SimpleDeclarationAST *ast) will be called. It creates a
 | 
			
		||||
    // replacement that also includes the parameter.
 | 
			
		||||
    // Next visit(ParameterDeclarationAST *ast) is called with the
 | 
			
		||||
    // original source. It tries to create an replacement operation
 | 
			
		||||
    // at this position and fails due to overlapping ranges (the
 | 
			
		||||
    // simple declaration range includes parameter declaration range).
 | 
			
		||||
    ChangeSet change(m_changeSet);
 | 
			
		||||
    if (change.replace(range, rewrittenDeclaration))
 | 
			
		||||
        m_changeSet = change;
 | 
			
		||||
    else if (DEBUG_OUTPUT)
 | 
			
		||||
        qDebug() << "Replacement operation failed";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*! Rewrite/format the given type and name. */
 | 
			
		||||
QString PointerDeclarationFormatter::rewriteDeclaration(FullySpecifiedType type, const Name *name)
 | 
			
		||||
    const
 | 
			
		||||
{
 | 
			
		||||
    CHECK_RV(type.isValid(), "Invalid type", QString());
 | 
			
		||||
 | 
			
		||||
    const char *identifier = 0;
 | 
			
		||||
    if (const Name *declarationName = name) {
 | 
			
		||||
        if (const Identifier *id = declarationName->identifier())
 | 
			
		||||
            identifier = id->chars();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return m_overview.prettyType(type, QLatin1String(identifier));
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										122
									
								
								src/plugins/cpptools/cpppointerdeclarationformatter.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										122
									
								
								src/plugins/cpptools/cpppointerdeclarationformatter.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,122 @@
 | 
			
		||||
/****************************************************************************
 | 
			
		||||
**
 | 
			
		||||
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
 | 
			
		||||
** Contact: http://www.qt-project.org/legal
 | 
			
		||||
**
 | 
			
		||||
** This file is part of Qt Creator.
 | 
			
		||||
**
 | 
			
		||||
** Commercial License Usage
 | 
			
		||||
** Licensees holding valid commercial Qt licenses may use this file in
 | 
			
		||||
** accordance with the commercial license agreement provided with the
 | 
			
		||||
** Software or, alternatively, in accordance with the terms contained in
 | 
			
		||||
** a written agreement between you and Digia.  For licensing terms and
 | 
			
		||||
** conditions see http://qt.digia.com/licensing.  For further information
 | 
			
		||||
** use the contact form at http://qt.digia.com/contact-us.
 | 
			
		||||
**
 | 
			
		||||
** 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.
 | 
			
		||||
**
 | 
			
		||||
** In addition, as a special exception, Digia gives you certain additional
 | 
			
		||||
** rights.  These rights are described in the Digia Qt LGPL Exception
 | 
			
		||||
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
 | 
			
		||||
**
 | 
			
		||||
****************************************************************************/
 | 
			
		||||
 | 
			
		||||
#ifndef CPPPOINTERFORMATTER_H
 | 
			
		||||
#define CPPPOINTERFORMATTER_H
 | 
			
		||||
 | 
			
		||||
#include "cpptools_global.h"
 | 
			
		||||
 | 
			
		||||
#include <AST.h>
 | 
			
		||||
#include <ASTVisitor.h>
 | 
			
		||||
#include <Overview.h>
 | 
			
		||||
#include <Symbols.h>
 | 
			
		||||
 | 
			
		||||
#include <cpptools/cpprefactoringchanges.h>
 | 
			
		||||
#include <utils/changeset.h>
 | 
			
		||||
 | 
			
		||||
namespace CppTools {
 | 
			
		||||
 | 
			
		||||
using namespace CPlusPlus;
 | 
			
		||||
using namespace CppTools;
 | 
			
		||||
using Utils::ChangeSet;
 | 
			
		||||
 | 
			
		||||
typedef Utils::ChangeSet::Range Range;
 | 
			
		||||
 | 
			
		||||
/*!
 | 
			
		||||
    \class CppTools::PointerDeclarationFormatter
 | 
			
		||||
 | 
			
		||||
    \brief Rewrite pointer or reference declarations accordingly to an Overview.
 | 
			
		||||
 | 
			
		||||
    The following constructs are supported:
 | 
			
		||||
    \list
 | 
			
		||||
     \o Simple declarations
 | 
			
		||||
     \o Parameters and return types of function declarations and definitions
 | 
			
		||||
     \o Control flow statements like if, while, for, foreach
 | 
			
		||||
    \endlist
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
class CPPTOOLS_EXPORT PointerDeclarationFormatter: protected ASTVisitor
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
    /*!
 | 
			
		||||
        \enum PointerDeclarationFormatter::CursorHandling
 | 
			
		||||
 | 
			
		||||
        This simplifies the QuickFix implementation.
 | 
			
		||||
 | 
			
		||||
          \value RespectCursor
 | 
			
		||||
                 Consider the cursor position / selection of the CppRefactoringFile
 | 
			
		||||
                 for rejecting edit operation candidates for the resulting ChangeSet.
 | 
			
		||||
                 If there is a selection, the range of the edit operation candidate
 | 
			
		||||
                 should be inside the selection. If there is no selection, the cursor
 | 
			
		||||
                 position should be within the range of the edit operation candidate.
 | 
			
		||||
          \value IgnoreCursor
 | 
			
		||||
                 Cursor position or selection of the CppRefactoringFile will
 | 
			
		||||
                _not_ be considered for aborting.
 | 
			
		||||
     */
 | 
			
		||||
    enum CursorHandling { RespectCursor, IgnoreCursor };
 | 
			
		||||
 | 
			
		||||
    explicit PointerDeclarationFormatter(const CppRefactoringFilePtr refactoringFile,
 | 
			
		||||
                                         const Overview &overview,
 | 
			
		||||
                                         CursorHandling cursorHandling = IgnoreCursor);
 | 
			
		||||
 | 
			
		||||
    /*!
 | 
			
		||||
        Returns a ChangeSet for applying the formatting changes.
 | 
			
		||||
        The ChangeSet is empty if it was not possible to rewrite anything.
 | 
			
		||||
    */
 | 
			
		||||
    ChangeSet format(AST *ast)
 | 
			
		||||
    {
 | 
			
		||||
        if (ast)
 | 
			
		||||
            accept(ast);
 | 
			
		||||
        return m_changeSet;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
protected:
 | 
			
		||||
    bool visit(SimpleDeclarationAST *ast);
 | 
			
		||||
    bool visit(FunctionDefinitionAST *ast);
 | 
			
		||||
    bool visit(ParameterDeclarationAST *ast);
 | 
			
		||||
    bool visit(IfStatementAST *ast);
 | 
			
		||||
    bool visit(WhileStatementAST *ast);
 | 
			
		||||
    bool visit(ForStatementAST *ast);
 | 
			
		||||
    bool visit(ForeachStatementAST *ast);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    void processIfWhileForStatement(ExpressionAST *expression, Symbol *symbol);
 | 
			
		||||
    void checkAndRewrite(Symbol *symbol, Range range, unsigned charactersToRemove = 0);
 | 
			
		||||
    QString rewriteDeclaration(FullySpecifiedType type, const Name *name) const;
 | 
			
		||||
 | 
			
		||||
    const CppRefactoringFilePtr m_cppRefactoringFile;
 | 
			
		||||
    const Overview &m_overview;
 | 
			
		||||
    const CursorHandling m_cursorHandling;
 | 
			
		||||
 | 
			
		||||
    ChangeSet m_changeSet;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} // namespace CppTools
 | 
			
		||||
 | 
			
		||||
#endif // CPPPOINTERFORMATTER_H
 | 
			
		||||
							
								
								
									
										577
									
								
								src/plugins/cpptools/cpppointerdeclarationformatter_test.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										577
									
								
								src/plugins/cpptools/cpppointerdeclarationformatter_test.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,577 @@
 | 
			
		||||
/****************************************************************************
 | 
			
		||||
**
 | 
			
		||||
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
 | 
			
		||||
** Contact: http://www.qt-project.org/legal
 | 
			
		||||
**
 | 
			
		||||
** This file is part of Qt Creator.
 | 
			
		||||
**
 | 
			
		||||
** Commercial License Usage
 | 
			
		||||
** Licensees holding valid commercial Qt licenses may use this file in
 | 
			
		||||
** accordance with the commercial license agreement provided with the
 | 
			
		||||
** Software or, alternatively, in accordance with the terms contained in
 | 
			
		||||
** a written agreement between you and Digia.  For licensing terms and
 | 
			
		||||
** conditions see http://qt.digia.com/licensing.  For further information
 | 
			
		||||
** use the contact form at http://qt.digia.com/contact-us.
 | 
			
		||||
**
 | 
			
		||||
** 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.
 | 
			
		||||
**
 | 
			
		||||
** In addition, as a special exception, Digia gives you certain additional
 | 
			
		||||
** rights.  These rights are described in the Digia Qt LGPL Exception
 | 
			
		||||
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
 | 
			
		||||
**
 | 
			
		||||
****************************************************************************/
 | 
			
		||||
 | 
			
		||||
#include "cpptoolsplugin.h"
 | 
			
		||||
 | 
			
		||||
#include <AST.h>
 | 
			
		||||
#include <CppDocument.h>
 | 
			
		||||
#include <Symbols.h>
 | 
			
		||||
#include <TranslationUnit.h>
 | 
			
		||||
#include <pp.h>
 | 
			
		||||
 | 
			
		||||
#include <cpptools/cpppointerdeclarationformatter.h>
 | 
			
		||||
#include <cpptools/cpprefactoringchanges.h>
 | 
			
		||||
#include <cpptools/cpptoolsplugin.h>
 | 
			
		||||
#include <texteditor/plaintexteditor.h>
 | 
			
		||||
#include <utils/changeset.h>
 | 
			
		||||
#include <utils/fileutils.h>
 | 
			
		||||
 | 
			
		||||
#include <QDebug>
 | 
			
		||||
#include <QDir>
 | 
			
		||||
#include <QTextCursor>
 | 
			
		||||
#include <QTextDocument>
 | 
			
		||||
#include <QtTest>
 | 
			
		||||
 | 
			
		||||
using namespace CPlusPlus;
 | 
			
		||||
using namespace CppTools;
 | 
			
		||||
using namespace CppTools::Internal;
 | 
			
		||||
 | 
			
		||||
using Utils::ChangeSet;
 | 
			
		||||
typedef Utils::ChangeSet::Range Range;
 | 
			
		||||
 | 
			
		||||
Q_DECLARE_METATYPE(Overview)
 | 
			
		||||
 | 
			
		||||
static QString stripCursor(const QString &source)
 | 
			
		||||
{
 | 
			
		||||
    QString copy(source);
 | 
			
		||||
    const int pos = copy.indexOf(QLatin1Char('@'));
 | 
			
		||||
    if (pos != -1)
 | 
			
		||||
        copy.remove(pos, 1);
 | 
			
		||||
    else
 | 
			
		||||
        qDebug() << Q_FUNC_INFO << "Warning: No cursor marker to strip.";
 | 
			
		||||
    return copy;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct TestEnvironment
 | 
			
		||||
{
 | 
			
		||||
    QByteArray source;
 | 
			
		||||
    Snapshot snapshot;
 | 
			
		||||
    CppRefactoringFilePtr cppRefactoringFile;
 | 
			
		||||
    TextEditor::BaseTextEditorWidget *editor;
 | 
			
		||||
    Document::Ptr document;
 | 
			
		||||
    QTextDocument *textDocument;
 | 
			
		||||
    TranslationUnit *translationUnit;
 | 
			
		||||
    Environment env;
 | 
			
		||||
 | 
			
		||||
    TestEnvironment(const QByteArray &source, Document::ParseMode parseMode)
 | 
			
		||||
    {
 | 
			
		||||
        // Find cursor position and remove cursor marker '@'
 | 
			
		||||
        int cursorPosition = 0;
 | 
			
		||||
        QString sourceWithoutCursorMarker = QLatin1String(source);
 | 
			
		||||
        const int pos = sourceWithoutCursorMarker.indexOf(QLatin1Char('@'));
 | 
			
		||||
        if (pos != -1) {
 | 
			
		||||
            sourceWithoutCursorMarker.remove(pos, 1);
 | 
			
		||||
            cursorPosition = pos;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Write source to temprorary file
 | 
			
		||||
        const QString filePath = QDir::tempPath() + QLatin1String("/file.h");
 | 
			
		||||
        document = Document::create(filePath);
 | 
			
		||||
        Utils::FileSaver documentSaver(document->fileName());
 | 
			
		||||
        documentSaver.write(sourceWithoutCursorMarker.toLatin1());
 | 
			
		||||
        documentSaver.finalize();
 | 
			
		||||
 | 
			
		||||
        // Preprocess source
 | 
			
		||||
        Preprocessor preprocess(0, &env);
 | 
			
		||||
        const QByteArray preprocessedSource = preprocess.run(filePath, sourceWithoutCursorMarker);
 | 
			
		||||
 | 
			
		||||
        document->setUtf8Source(preprocessedSource);
 | 
			
		||||
        document->parse(parseMode);
 | 
			
		||||
        document->check();
 | 
			
		||||
        translationUnit = document->translationUnit();
 | 
			
		||||
        snapshot.insert(document);
 | 
			
		||||
        editor = new TextEditor::PlainTextEditorWidget(0);
 | 
			
		||||
        QString error;
 | 
			
		||||
        editor->open(&error, document->fileName(), document->fileName());
 | 
			
		||||
 | 
			
		||||
        // Set cursor position
 | 
			
		||||
        QTextCursor cursor = editor->textCursor();
 | 
			
		||||
        cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::MoveAnchor, cursorPosition);
 | 
			
		||||
        editor->setTextCursor(cursor);
 | 
			
		||||
 | 
			
		||||
        textDocument = editor->document();
 | 
			
		||||
        cppRefactoringFile = CppRefactoringChanges::file(editor, document);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void applyFormatting(AST *ast, PointerDeclarationFormatter::CursorHandling cursorHandling)
 | 
			
		||||
    {
 | 
			
		||||
        Overview overview;
 | 
			
		||||
        overview.showReturnTypes = true;
 | 
			
		||||
        overview.showArgumentNames = true;
 | 
			
		||||
        overview.starBindFlags = Overview::StarBindFlags(0);
 | 
			
		||||
 | 
			
		||||
        // Run the formatter
 | 
			
		||||
        PointerDeclarationFormatter formatter(cppRefactoringFile, overview, cursorHandling);
 | 
			
		||||
        ChangeSet change = formatter.format(ast); // ChangeSet may be empty.
 | 
			
		||||
 | 
			
		||||
        // Apply change
 | 
			
		||||
        QTextCursor cursor(textDocument);
 | 
			
		||||
        change.apply(&cursor);
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
void CppToolsPlugin::test_format_pointerdeclaration_in_simpledeclarations()
 | 
			
		||||
{
 | 
			
		||||
    QFETCH(QString, source);
 | 
			
		||||
    QFETCH(QString, reformattedSource);
 | 
			
		||||
 | 
			
		||||
    TestEnvironment env(source.toLatin1(), Document::ParseDeclaration);
 | 
			
		||||
    AST *ast = env.translationUnit->ast();
 | 
			
		||||
    QVERIFY(ast);
 | 
			
		||||
 | 
			
		||||
    env.applyFormatting(ast, PointerDeclarationFormatter::RespectCursor);
 | 
			
		||||
 | 
			
		||||
    QCOMPARE(env.textDocument->toPlainText(), reformattedSource);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void CppToolsPlugin::test_format_pointerdeclaration_in_simpledeclarations_data()
 | 
			
		||||
{
 | 
			
		||||
    QTest::addColumn<QString>("source");
 | 
			
		||||
    QTest::addColumn<QString>("reformattedSource");
 | 
			
		||||
 | 
			
		||||
    QString source;
 | 
			
		||||
 | 
			
		||||
    //
 | 
			
		||||
    // Naming scheme for the test cases: <description>_(in|out)-(start|middle|end)
 | 
			
		||||
    //   in/out:
 | 
			
		||||
    //      Denotes whether the cursor is in or out of the quickfix activation range
 | 
			
		||||
    //   start/middle/end:
 | 
			
		||||
    //      Denotes whether is cursor is near to the range start, middle or end
 | 
			
		||||
    //
 | 
			
		||||
 | 
			
		||||
    QTest::newRow("basic_in-start")
 | 
			
		||||
        << "@char *s;"
 | 
			
		||||
        << "char * s;";
 | 
			
		||||
 | 
			
		||||
    source = QLatin1String("char *s;@");
 | 
			
		||||
    QTest::newRow("basic_out-end")
 | 
			
		||||
        << source << stripCursor(source);
 | 
			
		||||
 | 
			
		||||
    QTest::newRow("basic_in-end")
 | 
			
		||||
        << "char *s@;"
 | 
			
		||||
        << "char * s;";
 | 
			
		||||
 | 
			
		||||
    QTest::newRow("basic-with-ws_in-start")
 | 
			
		||||
        << "\n  @char *s;  " // Add some whitespace to check position detection.
 | 
			
		||||
        << "\n  char * s;  ";
 | 
			
		||||
 | 
			
		||||
    source = QLatin1String("\n @ char *s;  ");
 | 
			
		||||
    QTest::newRow("basic-with-ws_out-start")
 | 
			
		||||
        << source << stripCursor(source);
 | 
			
		||||
 | 
			
		||||
    QTest::newRow("basic-with-const_in-start")
 | 
			
		||||
        << "@const char *s;"
 | 
			
		||||
        << "const char * s;";
 | 
			
		||||
 | 
			
		||||
    QTest::newRow("basic-with-const-and-init-value_in-end")
 | 
			
		||||
        << "const char *s@ = 0;"
 | 
			
		||||
        << "const char * s = 0;";
 | 
			
		||||
 | 
			
		||||
    source = QLatin1String("const char *s @= 0;");
 | 
			
		||||
    QTest::newRow("basic-with-const-and-init-value_out-end")
 | 
			
		||||
        << source << stripCursor(source);
 | 
			
		||||
 | 
			
		||||
    QTest::newRow("first-declarator_in-start")
 | 
			
		||||
        << "@const char *s, *t;"
 | 
			
		||||
        << "const char * s, *t;";
 | 
			
		||||
 | 
			
		||||
    QTest::newRow("first-declarator_in-end")
 | 
			
		||||
        << "const char *s@, *t;"
 | 
			
		||||
        << "const char * s, *t;";
 | 
			
		||||
 | 
			
		||||
    source = QLatin1String("const char *s,@ *t;");
 | 
			
		||||
    QTest::newRow("first-declarator_out-end")
 | 
			
		||||
        << source << stripCursor(source);
 | 
			
		||||
 | 
			
		||||
    QTest::newRow("function-declaration-param_in-start")
 | 
			
		||||
        << "int f(@char *s);"
 | 
			
		||||
        << "int f(char * s);";
 | 
			
		||||
 | 
			
		||||
    QTest::newRow("function-declaration-param_in-end")
 | 
			
		||||
        << "int f(char *s@);"
 | 
			
		||||
        << "int f(char * s);";
 | 
			
		||||
 | 
			
		||||
    QTest::newRow("function-declaration-param_in-end")
 | 
			
		||||
        << "int f(char *s@ = 0);"
 | 
			
		||||
        << "int f(char * s = 0);";
 | 
			
		||||
 | 
			
		||||
    QTest::newRow("function-declaration-param-multiple-params_in-end")
 | 
			
		||||
        << "int f(char *s@, int);"
 | 
			
		||||
        << "int f(char * s, int);";
 | 
			
		||||
 | 
			
		||||
    source = QLatin1String("int f(char *s)@;");
 | 
			
		||||
    QTest::newRow("function-declaration-param_out-end")
 | 
			
		||||
        << source << stripCursor(source);
 | 
			
		||||
 | 
			
		||||
    source = QLatin1String("int f@(char *s);");
 | 
			
		||||
    QTest::newRow("function-declaration-param_out-start")
 | 
			
		||||
        << source << stripCursor(source);
 | 
			
		||||
 | 
			
		||||
    source = QLatin1String("int f(char *s =@ 0);");
 | 
			
		||||
    QTest::newRow("function-declaration-param_out-end")
 | 
			
		||||
        << source << stripCursor(source);
 | 
			
		||||
 | 
			
		||||
    // Function definitions are handled by the same code as function
 | 
			
		||||
    // declarations, so just check a minimal example.
 | 
			
		||||
    QTest::newRow("function-definition-param_in-middle")
 | 
			
		||||
        << "int f(char @*s) {}"
 | 
			
		||||
        << "int f(char * s) {}";
 | 
			
		||||
 | 
			
		||||
    QTest::newRow("function-declaration-returntype_in-start")
 | 
			
		||||
        << "@char *f();"
 | 
			
		||||
        << "char * f();";
 | 
			
		||||
 | 
			
		||||
    QTest::newRow("function-declaration-returntype_in-end")
 | 
			
		||||
        << "char *f@();"
 | 
			
		||||
        << "char * f();";
 | 
			
		||||
 | 
			
		||||
    source = QLatin1String("char *f(@);");
 | 
			
		||||
    QTest::newRow("function-declaration-returntype_out-end")
 | 
			
		||||
        << source << stripCursor(source);
 | 
			
		||||
 | 
			
		||||
    QTest::newRow("function-definition-returntype_in-end")
 | 
			
		||||
        << "char *f@() {}"
 | 
			
		||||
        << "char * f() {}";
 | 
			
		||||
 | 
			
		||||
    QTest::newRow("function-definition-returntype_in-start")
 | 
			
		||||
        << "@char *f() {}"
 | 
			
		||||
        << "char * f() {}";
 | 
			
		||||
 | 
			
		||||
    source = QLatin1String("char *f(@) {}");
 | 
			
		||||
    QTest::newRow("function-definition-returntype_out-end")
 | 
			
		||||
        << source << stripCursor(source);
 | 
			
		||||
 | 
			
		||||
    source = QLatin1String("inline@ char *f() {}");
 | 
			
		||||
    QTest::newRow("function-definition-returntype-inline_out-start")
 | 
			
		||||
        << source << stripCursor(source);
 | 
			
		||||
 | 
			
		||||
    // Same code is also used for for other specifiers like virtual, inline, friend, ...
 | 
			
		||||
    QTest::newRow("function-definition-returntype-static-inline_in-start")
 | 
			
		||||
        << "static inline @char *f() {}"
 | 
			
		||||
        << "static inline char * f() {}";
 | 
			
		||||
 | 
			
		||||
    QTest::newRow("function-declaration-returntype-static-inline_in-start")
 | 
			
		||||
        << "static inline @char *f();"
 | 
			
		||||
        << "static inline char * f();";
 | 
			
		||||
 | 
			
		||||
    source = QLatin1String("static inline@ char *f() {}");
 | 
			
		||||
    QTest::newRow("function-definition-returntype-static-inline_out-start")
 | 
			
		||||
        << source << stripCursor(source);
 | 
			
		||||
 | 
			
		||||
    QTest::newRow("function-declaration-returntype-static-custom-type_in-start")
 | 
			
		||||
        << "static @CustomType *f();"
 | 
			
		||||
        << "static CustomType * f();";
 | 
			
		||||
 | 
			
		||||
    source = QLatin1String("static@ CustomType *f();");
 | 
			
		||||
    QTest::newRow("function-declaration-returntype-static-custom-type_out-start")
 | 
			
		||||
        << source << stripCursor(source);
 | 
			
		||||
 | 
			
		||||
    QTest::newRow("function-declaration-returntype-symbolvisibilityattributes_in-start")
 | 
			
		||||
        << "__attribute__((visibility(\"default\"))) @char *f();"
 | 
			
		||||
        << "__attribute__((visibility(\"default\"))) char * f();";
 | 
			
		||||
 | 
			
		||||
    source = QLatin1String("@__attribute__((visibility(\"default\"))) char *f();");
 | 
			
		||||
    QTest::newRow("function-declaration-returntype-symbolvisibilityattributes_out-start")
 | 
			
		||||
        << source << stripCursor(source);
 | 
			
		||||
 | 
			
		||||
    // The following two are not supported at the moment.
 | 
			
		||||
    source = QLatin1String("@char * __attribute__((visibility(\"default\"))) f();");
 | 
			
		||||
    QTest::newRow("function-declaration-returntype-symbolvisibilityattributes_out-start")
 | 
			
		||||
        << source << stripCursor(source);
 | 
			
		||||
 | 
			
		||||
    source = QLatin1String("@char * __attribute__((visibility(\"default\"))) f() {}");
 | 
			
		||||
    QTest::newRow("function-definition-returntype-symbolvisibilityattributes_out-start")
 | 
			
		||||
        << source << stripCursor(source);
 | 
			
		||||
 | 
			
		||||
    // NOTE: Since __declspec() is not parsed (but defined to nothing),
 | 
			
		||||
    // we can't detect it properly. Therefore, we fail on code with
 | 
			
		||||
    // FOO_EXPORT macros with __declspec() for pointer return types;
 | 
			
		||||
 | 
			
		||||
    QTest::newRow("typedef_in-start")
 | 
			
		||||
        << "typedef @char *s;"
 | 
			
		||||
        << "typedef char * s;";
 | 
			
		||||
 | 
			
		||||
    source = QLatin1String("@typedef char *s;");
 | 
			
		||||
    QTest::newRow("typedef_out-start")
 | 
			
		||||
        << source << stripCursor(source);
 | 
			
		||||
 | 
			
		||||
    QTest::newRow("static_in-start")
 | 
			
		||||
        << "static @char *s;"
 | 
			
		||||
        << "static char * s;";
 | 
			
		||||
 | 
			
		||||
    QTest::newRow("pointerToFunction_in-start")
 | 
			
		||||
        << "int (*bar)(@char *s);"
 | 
			
		||||
        << "int (*bar)(char * s);";
 | 
			
		||||
 | 
			
		||||
    source = QLatin1String("int (@*bar)();");
 | 
			
		||||
    QTest::newRow("pointerToFunction_in-start")
 | 
			
		||||
        << source << stripCursor(source);
 | 
			
		||||
 | 
			
		||||
    source = QLatin1String("int (@*bar)[] = 0;");
 | 
			
		||||
    QTest::newRow("pointerToArray_in-start")
 | 
			
		||||
        << source << stripCursor(source);
 | 
			
		||||
 | 
			
		||||
    //
 | 
			
		||||
    // Additional test cases that does not fit into the naming scheme
 | 
			
		||||
    //
 | 
			
		||||
 | 
			
		||||
    // The following case is a side effect of the reformatting. Though
 | 
			
		||||
    // the pointer type is according to the overview, the double space
 | 
			
		||||
    // before 'char' gets corrected.
 | 
			
		||||
    QTest::newRow("remove-extra-whitespace")
 | 
			
		||||
        << "@const  char * s = 0;"
 | 
			
		||||
        << "const char * s = 0;";
 | 
			
		||||
 | 
			
		||||
    // Nothing to pretty print since no pointer or references are involved.
 | 
			
		||||
    source = QLatin1String("@char  bla;"); // Two spaces to get sure nothing is reformatted.
 | 
			
		||||
    QTest::newRow("precondition-fail-no-pointer")
 | 
			
		||||
        << source << stripCursor(source);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void CppToolsPlugin::test_format_pointerdeclaration_in_controlflowstatements()
 | 
			
		||||
{
 | 
			
		||||
    QFETCH(QString, source);
 | 
			
		||||
    QFETCH(QString, reformattedSource);
 | 
			
		||||
 | 
			
		||||
    TestEnvironment env(source.toLatin1(), Document::ParseStatement);
 | 
			
		||||
    AST *ast = env.translationUnit->ast();
 | 
			
		||||
    QVERIFY(ast);
 | 
			
		||||
 | 
			
		||||
    env.applyFormatting(ast, PointerDeclarationFormatter::RespectCursor);
 | 
			
		||||
 | 
			
		||||
    QCOMPARE(env.textDocument->toPlainText(), reformattedSource);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void CppToolsPlugin::test_format_pointerdeclaration_in_controlflowstatements_data()
 | 
			
		||||
{
 | 
			
		||||
    QTest::addColumn<QString>("source");
 | 
			
		||||
    QTest::addColumn<QString>("reformattedSource");
 | 
			
		||||
 | 
			
		||||
    QString source;
 | 
			
		||||
 | 
			
		||||
    //
 | 
			
		||||
    // Same naming scheme as in test_format_pointerdeclaration_in_simpledeclarations_data()
 | 
			
		||||
    //
 | 
			
		||||
 | 
			
		||||
    QTest::newRow("if_in-start")
 | 
			
		||||
        << "if (@char *s = 0);"
 | 
			
		||||
        << "if (char * s = 0);";
 | 
			
		||||
 | 
			
		||||
    source = QLatin1String("if @(char *s = 0);");
 | 
			
		||||
    QTest::newRow("if_out-start")
 | 
			
		||||
        << source << stripCursor(source);
 | 
			
		||||
 | 
			
		||||
    QTest::newRow("if_in-end")
 | 
			
		||||
        << "if (char *s@ = 0);"
 | 
			
		||||
        << "if (char * s = 0);";
 | 
			
		||||
 | 
			
		||||
    source = QLatin1String("if (char *s @= 0);");
 | 
			
		||||
    QTest::newRow("if_out-end")
 | 
			
		||||
        << source << stripCursor(source);
 | 
			
		||||
 | 
			
		||||
    // if and for statements are handled by the same code, so just
 | 
			
		||||
    // check minimal examples for these
 | 
			
		||||
    QTest::newRow("while")
 | 
			
		||||
        << "while (@char *s = 0);"
 | 
			
		||||
        << "while (char * s = 0);";
 | 
			
		||||
 | 
			
		||||
    QTest::newRow("for")
 | 
			
		||||
        << "for (;@char *s = 0;);"
 | 
			
		||||
        << "for (;char * s = 0;);";
 | 
			
		||||
 | 
			
		||||
    // Should also work since it's a simple declaration
 | 
			
		||||
    // for (char *s = 0; true;);
 | 
			
		||||
 | 
			
		||||
    QTest::newRow("foreach_in-start")
 | 
			
		||||
        << "foreach (@char *s, list);"
 | 
			
		||||
        << "foreach (char * s, list);";
 | 
			
		||||
 | 
			
		||||
    source = QLatin1String("foreach @(char *s, list);");
 | 
			
		||||
    QTest::newRow("foreach-out-start")
 | 
			
		||||
        << source << stripCursor(source);
 | 
			
		||||
 | 
			
		||||
    QTest::newRow("foreach_in_end")
 | 
			
		||||
        << "foreach (const char *s@, list);"
 | 
			
		||||
        << "foreach (const char * s, list);";
 | 
			
		||||
 | 
			
		||||
    source = QLatin1String("foreach (const char *s,@ list);");
 | 
			
		||||
    QTest::newRow("foreach_out_end")
 | 
			
		||||
        << source << stripCursor(source);
 | 
			
		||||
 | 
			
		||||
    //
 | 
			
		||||
    // Additional test cases that does not fit into the naming scheme
 | 
			
		||||
    //
 | 
			
		||||
 | 
			
		||||
    source = QLatin1String("@if (char  s = 0);"); // Two spaces to get sure nothing is reformatted.
 | 
			
		||||
    QTest::newRow("precondition-fail-no-pointer") << source << stripCursor(source);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void CppToolsPlugin::test_format_pointerdeclaration_multiple_declarators()
 | 
			
		||||
{
 | 
			
		||||
    QFETCH(QString, source);
 | 
			
		||||
    QFETCH(QString, reformattedSource);
 | 
			
		||||
 | 
			
		||||
    TestEnvironment env(source.toLatin1(), Document::ParseDeclaration);
 | 
			
		||||
    AST *ast = env.translationUnit->ast();
 | 
			
		||||
    QVERIFY(ast);
 | 
			
		||||
 | 
			
		||||
    env.applyFormatting(ast, PointerDeclarationFormatter::RespectCursor);
 | 
			
		||||
 | 
			
		||||
    QCOMPARE(env.textDocument->toPlainText(), reformattedSource);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void CppToolsPlugin::test_format_pointerdeclaration_multiple_declarators_data()
 | 
			
		||||
{
 | 
			
		||||
    QTest::addColumn<QString>("source");
 | 
			
		||||
    QTest::addColumn<QString>("reformattedSource");
 | 
			
		||||
 | 
			
		||||
    QString source;
 | 
			
		||||
 | 
			
		||||
    QTest::newRow("function-declaration_in-start")
 | 
			
		||||
        << "char *s = 0, @*f(int i) = 0;"
 | 
			
		||||
        << "char *s = 0, * f(int i) = 0;";
 | 
			
		||||
 | 
			
		||||
    QTest::newRow("non-pointer-before_in-start")
 | 
			
		||||
        << "char c, @*t;"
 | 
			
		||||
        << "char c, * t;";
 | 
			
		||||
 | 
			
		||||
    QTest::newRow("pointer-before_in-start")
 | 
			
		||||
        << "char *s, @*t;"
 | 
			
		||||
        << "char *s, * t;";
 | 
			
		||||
 | 
			
		||||
    QTest::newRow("pointer-before_in-end")
 | 
			
		||||
        << "char *s, *t@;"
 | 
			
		||||
        << "char *s, * t;";
 | 
			
		||||
 | 
			
		||||
    source = QLatin1String("char *s,@ *t;");
 | 
			
		||||
    QTest::newRow("md1-out_start")
 | 
			
		||||
        << source << stripCursor(source);
 | 
			
		||||
 | 
			
		||||
    source = QLatin1String("char *s, *t;@");
 | 
			
		||||
    QTest::newRow("md1-out_end")
 | 
			
		||||
        << source << stripCursor(source);
 | 
			
		||||
 | 
			
		||||
    QTest::newRow("non-pointer-after_in-start")
 | 
			
		||||
        << "char c, @*t, d;"
 | 
			
		||||
        << "char c, * t, d;";
 | 
			
		||||
 | 
			
		||||
    QTest::newRow("pointer-after_in-start")
 | 
			
		||||
        << "char c, @*t, *d;"
 | 
			
		||||
        << "char c, * t, *d;";
 | 
			
		||||
 | 
			
		||||
    QTest::newRow("function-pointer_in-start")
 | 
			
		||||
        << "char *s, @*(*foo)(char *s) = 0;"
 | 
			
		||||
        << "char *s, *(*foo)(char * s) = 0;";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void CppToolsPlugin::test_format_pointerdeclaration_multiple_matches()
 | 
			
		||||
{
 | 
			
		||||
    QFETCH(QString, source);
 | 
			
		||||
    QFETCH(QString, reformattedSource);
 | 
			
		||||
 | 
			
		||||
    TestEnvironment env(source.toLatin1(), Document::ParseTranlationUnit);
 | 
			
		||||
    AST *ast = env.translationUnit->ast();
 | 
			
		||||
    QVERIFY(ast);
 | 
			
		||||
 | 
			
		||||
    env.applyFormatting(ast, PointerDeclarationFormatter::IgnoreCursor);
 | 
			
		||||
 | 
			
		||||
    QCOMPARE(env.textDocument->toPlainText(), reformattedSource);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void CppToolsPlugin::test_format_pointerdeclaration_multiple_matches_data()
 | 
			
		||||
{
 | 
			
		||||
    QTest::addColumn<QString>("source");
 | 
			
		||||
    QTest::addColumn<QString>("reformattedSource");
 | 
			
		||||
 | 
			
		||||
    QTest::newRow("rvalue-reference")
 | 
			
		||||
        << "int g(Bar&&c) {}"
 | 
			
		||||
        << "int g(Bar && c) {}";
 | 
			
		||||
 | 
			
		||||
    QTest::newRow("if2")
 | 
			
		||||
        << "int g() { if (char *s = 0) { char *t = 0; } }"
 | 
			
		||||
        << "int g() { if (char * s = 0) { char * t = 0; } }";
 | 
			
		||||
 | 
			
		||||
    QTest::newRow("if1")
 | 
			
		||||
        << "int g() { if (int i = 0) { char *t = 0; } }"
 | 
			
		||||
        << "int g() { if (int i = 0) { char * t = 0; } }";
 | 
			
		||||
 | 
			
		||||
    QTest::newRow("for1")
 | 
			
		||||
        << "int g() { for (char *s = 0; char *t = 0; s++); }"
 | 
			
		||||
        << "int g() { for (char * s = 0; char * t = 0; s++); }";
 | 
			
		||||
 | 
			
		||||
    QTest::newRow("for2")
 | 
			
		||||
        << "int g() { for (char *s = 0; char *t = 0; s++) { char *u = 0; } }"
 | 
			
		||||
        << "int g() { for (char * s = 0; char * t = 0; s++) { char * u = 0; } }";
 | 
			
		||||
 | 
			
		||||
    QTest::newRow("for3")
 | 
			
		||||
        << "int g() { for (char *s; char *t = 0; s++) { char *u = 0; } }"
 | 
			
		||||
        << "int g() { for (char * s; char * t = 0; s++) { char * u = 0; } }";
 | 
			
		||||
 | 
			
		||||
    QTest::newRow("multiple-declarators")
 | 
			
		||||
        << "const char c, *s, *(*foo)(char *s) = 0;"
 | 
			
		||||
        << "const char c, * s, *(*foo)(char * s) = 0;";
 | 
			
		||||
 | 
			
		||||
    QTest::newRow("complex")
 | 
			
		||||
        <<
 | 
			
		||||
            "int *foo(const int &b, int*, int *&rpi)\n"
 | 
			
		||||
            "{\n"
 | 
			
		||||
            "    int*pi = 0;\n"
 | 
			
		||||
            "    int*const*const cpcpi = π\n"
 | 
			
		||||
            "    int*const*pcpi = π\n"
 | 
			
		||||
            "    int**const cppi = π\n"
 | 
			
		||||
            "\n"
 | 
			
		||||
            "    void (*foo)(char *s) = 0;\n"
 | 
			
		||||
            "    int (*bar)[] = 0;\n"
 | 
			
		||||
            "\n"
 | 
			
		||||
            "    char *s = 0, *f(int i) = 0;\n"
 | 
			
		||||
            "    const char c, *s, *(*foo)(char *s) = 0;"
 | 
			
		||||
            "\n"
 | 
			
		||||
            "    for (char *s; char *t = 0; s++) { char *u = 0; }"
 | 
			
		||||
            "\n"
 | 
			
		||||
            "    return 0;\n"
 | 
			
		||||
            "}\n"
 | 
			
		||||
        <<
 | 
			
		||||
           "int * foo(const int & b, int *, int *& rpi)\n"
 | 
			
		||||
           "{\n"
 | 
			
		||||
           "    int * pi = 0;\n"
 | 
			
		||||
           "    int * const * const cpcpi = π\n"
 | 
			
		||||
           "    int * const * pcpi = π\n"
 | 
			
		||||
           "    int ** const cppi = π\n"
 | 
			
		||||
           "\n"
 | 
			
		||||
           "    void (*foo)(char * s) = 0;\n"
 | 
			
		||||
           "    int (*bar)[] = 0;\n"
 | 
			
		||||
           "\n"
 | 
			
		||||
           "    char * s = 0, * f(int i) = 0;\n"
 | 
			
		||||
           "    const char c, * s, *(*foo)(char * s) = 0;"
 | 
			
		||||
           "\n"
 | 
			
		||||
           "    for (char * s; char * t = 0; s++) { char * u = 0; }"
 | 
			
		||||
           "\n"
 | 
			
		||||
           "    return 0;\n"
 | 
			
		||||
           "}\n";
 | 
			
		||||
}
 | 
			
		||||
@@ -47,7 +47,8 @@ HEADERS += completionsettingspage.h \
 | 
			
		||||
    ModelManagerInterface.h \
 | 
			
		||||
    TypeHierarchyBuilder.h \
 | 
			
		||||
    cppindexingsupport.h \
 | 
			
		||||
    builtinindexingsupport.h
 | 
			
		||||
    builtinindexingsupport.h \
 | 
			
		||||
    cpppointerdeclarationformatter.h
 | 
			
		||||
 | 
			
		||||
SOURCES += completionsettingspage.cpp \
 | 
			
		||||
    cppclassesfilter.cpp \
 | 
			
		||||
@@ -88,7 +89,8 @@ SOURCES += completionsettingspage.cpp \
 | 
			
		||||
    ModelManagerInterface.cpp \
 | 
			
		||||
    TypeHierarchyBuilder.cpp \
 | 
			
		||||
    cppindexingsupport.cpp \
 | 
			
		||||
    builtinindexingsupport.cpp
 | 
			
		||||
    builtinindexingsupport.cpp \
 | 
			
		||||
    cpppointerdeclarationformatter.cpp
 | 
			
		||||
 | 
			
		||||
FORMS += completionsettingspage.ui \
 | 
			
		||||
    cppfilesettingspage.ui \
 | 
			
		||||
@@ -99,7 +101,8 @@ equals(TEST, 1) {
 | 
			
		||||
        cppcodegen_test.cpp \
 | 
			
		||||
        cppcompletion_test.cpp \
 | 
			
		||||
        cppmodelmanager_test.cpp \
 | 
			
		||||
        modelmanagertesthelper.cpp
 | 
			
		||||
        modelmanagertesthelper.cpp \
 | 
			
		||||
        cpppointerdeclarationformatter_test.cpp
 | 
			
		||||
 | 
			
		||||
    HEADERS += \
 | 
			
		||||
        modelmanagertesthelper.h
 | 
			
		||||
 
 | 
			
		||||
@@ -74,6 +74,8 @@ QtcPlugin {
 | 
			
		||||
        "cppmodelmanager.h",
 | 
			
		||||
        "cppqtstyleindenter.cpp",
 | 
			
		||||
        "cppqtstyleindenter.h",
 | 
			
		||||
        "cpppointerdeclarationformatter.cpp",
 | 
			
		||||
        "cpppointerdeclarationformatter.h",
 | 
			
		||||
        "cpprefactoringchanges.cpp",
 | 
			
		||||
        "cpprefactoringchanges.h",
 | 
			
		||||
        "cppsemanticinfo.cpp",
 | 
			
		||||
@@ -110,7 +112,8 @@ QtcPlugin {
 | 
			
		||||
            "cppcodegen_test.cpp",
 | 
			
		||||
            "cppcompletion_test.cpp",
 | 
			
		||||
            "cppmodelmanager_test.cpp",
 | 
			
		||||
            "modelmanagertesthelper.cpp", "modelmanagertesthelper.h"
 | 
			
		||||
            "modelmanagertesthelper.cpp", "modelmanagertesthelper.h",
 | 
			
		||||
            "cpppointerdeclarationformatter_test.cpp"
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
        cpp.defines: outer.concat(['SRCDIR="' + FileInfo.path(filePath) + '"'])
 | 
			
		||||
 
 | 
			
		||||
@@ -74,9 +74,8 @@ public:
 | 
			
		||||
 | 
			
		||||
private slots:
 | 
			
		||||
    void switchHeaderSource();
 | 
			
		||||
#ifdef WITH_TESTS
 | 
			
		||||
 | 
			
		||||
    // codegen tests
 | 
			
		||||
#ifdef WITH_TESTS
 | 
			
		||||
    void test_codegen_public_in_empty_class();
 | 
			
		||||
    void test_codegen_public_in_nonempty_class();
 | 
			
		||||
    void test_codegen_public_before_protected();
 | 
			
		||||
@@ -114,6 +113,15 @@ private slots:
 | 
			
		||||
    void test_completion_instantiate_nested_class_when_enclosing_is_template();
 | 
			
		||||
    void test_completion_instantiate_nested_of_nested_class_when_enclosing_is_template();
 | 
			
		||||
 | 
			
		||||
    void test_format_pointerdeclaration_in_simpledeclarations();
 | 
			
		||||
    void test_format_pointerdeclaration_in_simpledeclarations_data();
 | 
			
		||||
    void test_format_pointerdeclaration_in_controlflowstatements();
 | 
			
		||||
    void test_format_pointerdeclaration_in_controlflowstatements_data();
 | 
			
		||||
    void test_format_pointerdeclaration_multiple_declarators();
 | 
			
		||||
    void test_format_pointerdeclaration_multiple_declarators_data();
 | 
			
		||||
    void test_format_pointerdeclaration_multiple_matches();
 | 
			
		||||
    void test_format_pointerdeclaration_multiple_matches_data();
 | 
			
		||||
 | 
			
		||||
    void test_modelmanager_paths();
 | 
			
		||||
    void test_modelmanager_framework_headers();
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user