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:
7
src/libs/3rdparty/cplusplus/Bind.cpp
vendored
7
src/libs/3rdparty/cplusplus/Bind.cpp
vendored
@@ -181,6 +181,13 @@ void Bind::operator()(DeclarationAST *ast, Scope *scope)
|
|||||||
(void) switchScope(previousScope);
|
(void) switchScope(previousScope);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Bind::operator()(StatementAST *ast, Scope *scope)
|
||||||
|
{
|
||||||
|
Scope *previousScope = switchScope(scope);
|
||||||
|
statement(ast);
|
||||||
|
(void) switchScope(previousScope);
|
||||||
|
}
|
||||||
|
|
||||||
FullySpecifiedType Bind::operator()(ExpressionAST *ast, Scope *scope)
|
FullySpecifiedType Bind::operator()(ExpressionAST *ast, Scope *scope)
|
||||||
{
|
{
|
||||||
Scope *previousScope = switchScope(scope);
|
Scope *previousScope = switchScope(scope);
|
||||||
|
|||||||
1
src/libs/3rdparty/cplusplus/Bind.h
vendored
1
src/libs/3rdparty/cplusplus/Bind.h
vendored
@@ -34,6 +34,7 @@ public:
|
|||||||
|
|
||||||
void operator()(TranslationUnitAST *ast, Namespace *globalNamespace);
|
void operator()(TranslationUnitAST *ast, Namespace *globalNamespace);
|
||||||
void operator()(DeclarationAST *ast, Scope *scope);
|
void operator()(DeclarationAST *ast, Scope *scope);
|
||||||
|
void operator()(StatementAST *ast, Scope *scope);
|
||||||
FullySpecifiedType operator()(ExpressionAST *ast, Scope *scope);
|
FullySpecifiedType operator()(ExpressionAST *ast, Scope *scope);
|
||||||
FullySpecifiedType operator()(NewTypeIdAST *ast, Scope *scope);
|
FullySpecifiedType operator()(NewTypeIdAST *ast, Scope *scope);
|
||||||
|
|
||||||
|
|||||||
@@ -595,6 +595,8 @@ void Document::check(CheckMode mode)
|
|||||||
|
|
||||||
if (TranslationUnitAST *ast = _translationUnit->ast()->asTranslationUnit())
|
if (TranslationUnitAST *ast = _translationUnit->ast()->asTranslationUnit())
|
||||||
semantic(ast, _globalNamespace);
|
semantic(ast, _globalNamespace);
|
||||||
|
else if (StatementAST *ast = _translationUnit->ast()->asStatement())
|
||||||
|
semantic(ast, _globalNamespace);
|
||||||
else if (ExpressionAST *ast = _translationUnit->ast()->asExpression())
|
else if (ExpressionAST *ast = _translationUnit->ast()->asExpression())
|
||||||
semantic(ast, _globalNamespace);
|
semantic(ast, _globalNamespace);
|
||||||
else if (DeclarationAST *ast = translationUnit()->ast()->asDeclaration())
|
else if (DeclarationAST *ast = translationUnit()->ast()->asDeclaration())
|
||||||
|
|||||||
@@ -49,9 +49,6 @@ namespace CPlusPlus {
|
|||||||
|
|
||||||
class CPLUSPLUS_EXPORT Overview
|
class CPLUSPLUS_EXPORT Overview
|
||||||
{
|
{
|
||||||
Overview(const Overview &other);
|
|
||||||
void operator =(const Overview &other);
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Overview();
|
Overview();
|
||||||
|
|
||||||
|
|||||||
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 \
|
ModelManagerInterface.h \
|
||||||
TypeHierarchyBuilder.h \
|
TypeHierarchyBuilder.h \
|
||||||
cppindexingsupport.h \
|
cppindexingsupport.h \
|
||||||
builtinindexingsupport.h
|
builtinindexingsupport.h \
|
||||||
|
cpppointerdeclarationformatter.h
|
||||||
|
|
||||||
SOURCES += completionsettingspage.cpp \
|
SOURCES += completionsettingspage.cpp \
|
||||||
cppclassesfilter.cpp \
|
cppclassesfilter.cpp \
|
||||||
@@ -88,7 +89,8 @@ SOURCES += completionsettingspage.cpp \
|
|||||||
ModelManagerInterface.cpp \
|
ModelManagerInterface.cpp \
|
||||||
TypeHierarchyBuilder.cpp \
|
TypeHierarchyBuilder.cpp \
|
||||||
cppindexingsupport.cpp \
|
cppindexingsupport.cpp \
|
||||||
builtinindexingsupport.cpp
|
builtinindexingsupport.cpp \
|
||||||
|
cpppointerdeclarationformatter.cpp
|
||||||
|
|
||||||
FORMS += completionsettingspage.ui \
|
FORMS += completionsettingspage.ui \
|
||||||
cppfilesettingspage.ui \
|
cppfilesettingspage.ui \
|
||||||
@@ -99,7 +101,8 @@ equals(TEST, 1) {
|
|||||||
cppcodegen_test.cpp \
|
cppcodegen_test.cpp \
|
||||||
cppcompletion_test.cpp \
|
cppcompletion_test.cpp \
|
||||||
cppmodelmanager_test.cpp \
|
cppmodelmanager_test.cpp \
|
||||||
modelmanagertesthelper.cpp
|
modelmanagertesthelper.cpp \
|
||||||
|
cpppointerdeclarationformatter_test.cpp
|
||||||
|
|
||||||
HEADERS += \
|
HEADERS += \
|
||||||
modelmanagertesthelper.h
|
modelmanagertesthelper.h
|
||||||
|
|||||||
@@ -74,6 +74,8 @@ QtcPlugin {
|
|||||||
"cppmodelmanager.h",
|
"cppmodelmanager.h",
|
||||||
"cppqtstyleindenter.cpp",
|
"cppqtstyleindenter.cpp",
|
||||||
"cppqtstyleindenter.h",
|
"cppqtstyleindenter.h",
|
||||||
|
"cpppointerdeclarationformatter.cpp",
|
||||||
|
"cpppointerdeclarationformatter.h",
|
||||||
"cpprefactoringchanges.cpp",
|
"cpprefactoringchanges.cpp",
|
||||||
"cpprefactoringchanges.h",
|
"cpprefactoringchanges.h",
|
||||||
"cppsemanticinfo.cpp",
|
"cppsemanticinfo.cpp",
|
||||||
@@ -110,7 +112,8 @@ QtcPlugin {
|
|||||||
"cppcodegen_test.cpp",
|
"cppcodegen_test.cpp",
|
||||||
"cppcompletion_test.cpp",
|
"cppcompletion_test.cpp",
|
||||||
"cppmodelmanager_test.cpp",
|
"cppmodelmanager_test.cpp",
|
||||||
"modelmanagertesthelper.cpp", "modelmanagertesthelper.h"
|
"modelmanagertesthelper.cpp", "modelmanagertesthelper.h",
|
||||||
|
"cpppointerdeclarationformatter_test.cpp"
|
||||||
]
|
]
|
||||||
|
|
||||||
cpp.defines: outer.concat(['SRCDIR="' + FileInfo.path(filePath) + '"'])
|
cpp.defines: outer.concat(['SRCDIR="' + FileInfo.path(filePath) + '"'])
|
||||||
|
|||||||
@@ -74,9 +74,8 @@ public:
|
|||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void switchHeaderSource();
|
void switchHeaderSource();
|
||||||
#ifdef WITH_TESTS
|
|
||||||
|
|
||||||
// codegen tests
|
#ifdef WITH_TESTS
|
||||||
void test_codegen_public_in_empty_class();
|
void test_codegen_public_in_empty_class();
|
||||||
void test_codegen_public_in_nonempty_class();
|
void test_codegen_public_in_nonempty_class();
|
||||||
void test_codegen_public_before_protected();
|
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_class_when_enclosing_is_template();
|
||||||
void test_completion_instantiate_nested_of_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_paths();
|
||||||
void test_modelmanager_framework_headers();
|
void test_modelmanager_framework_headers();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user