CppEditor: Add class member from member initialization

That is, if a class member being initialized is not yet declared, offer
to add the declaration.

Fixes: QTCREATORBUG-11842
Change-Id: I1d2a56ab99543e996e220aa26b7923023dd163f7
Reviewed-by: Christian Stenger <christian.stenger@qt.io>
This commit is contained in:
Christian Kandeler
2020-07-21 17:27:16 +02:00
parent a9347daf1b
commit 73f3056c3e
4 changed files with 250 additions and 0 deletions

View File

@@ -115,6 +115,9 @@ private slots:
void test_quickfix_GenerateGettersSetters_data();
void test_quickfix_GenerateGettersSetters();
void test_quickfix_InsertMemberFromInitialization_data();
void test_quickfix_InsertMemberFromInitialization();
void test_quickfix_ConvertQt4Connect_connectOutOfClass();
void test_quickfix_ConvertQt4Connect_connectWithinClass_data();
void test_quickfix_ConvertQt4Connect_connectWithinClass();

View File

@@ -2612,6 +2612,93 @@ void CppEditorPlugin::test_quickfix_GenerateGettersSetters()
QuickFixOperationTest({QuickFixTestDocument::create("file.h", original, expected)}, &factory);
}
void CppEditorPlugin::test_quickfix_InsertMemberFromInitialization_data()
{
QTest::addColumn<QByteArray>("original");
QTest::addColumn<QByteArray>("expected");
QByteArray original;
QByteArray expected;
original =
"class C {\n"
"public:\n"
" C(int x) : @m_x(x) {}\n"
"private:\n"
" int m_y;\n"
"};\n";
expected =
"class C {\n"
"public:\n"
" C(int x) : m_x(x) {}\n"
"private:\n"
" int m_y;\n"
" int m_x;\n"
"};\n";
QTest::addRow("inline constructor") << original << expected;
original =
"class C {\n"
"public:\n"
" C(int x, double d);\n"
"private:\n"
" int m_x;\n"
"};\n"
"C::C(int x, double d) : m_x(x), @m_d(d)\n";
expected =
"class C {\n"
"public:\n"
" C(int x, double d);\n"
"private:\n"
" int m_x;\n"
" double m_d;\n"
"};\n"
"C::C(int x, double d) : m_x(x), m_d(d)\n";
QTest::addRow("out-of-line constructor") << original << expected;
original =
"class C {\n"
"public:\n"
" C(int x) : @m_x(x) {}\n"
"private:\n"
" int m_x;\n"
"};\n";
expected = "";
QTest::addRow("member already present") << original << expected;
original =
"int func() { return 0; }\n"
"class C {\n"
"public:\n"
" C() : @m_x(func()) {}\n"
"private:\n"
" int m_y;\n"
"};\n";
expected =
"int func() { return 0; }\n"
"class C {\n"
"public:\n"
" C() : m_x(func()) {}\n"
"private:\n"
" int m_y;\n"
" int m_x;\n"
"};\n";
QTest::addRow("initialization via function call") << original << expected;
}
void CppEditorPlugin::test_quickfix_InsertMemberFromInitialization()
{
QFETCH(QByteArray, original);
QFETCH(QByteArray, expected);
QList<QuickFixTestDocument::Ptr> testDocuments({
QuickFixTestDocument::create("file.h", original, expected)
});
InsertMemberFromInitialization factory;
QuickFixOperationTest(testDocuments, &factory);
}
/// Check if definition is inserted right after class for insert definition outside
void CppEditorPlugin::test_quickfix_InsertDefFromDecl_afterClass()
{

View File

@@ -2987,6 +2987,149 @@ void InsertDefFromDecl::match(const CppQuickFixInterface &interface, QuickFixOpe
}
}
class InsertMemberFromInitializationOp : public CppQuickFixOperation
{
public:
InsertMemberFromInitializationOp(
const CppQuickFixInterface &interface,
const Class *theClass,
const QString &member,
const QString &type)
: CppQuickFixOperation(interface), m_class(theClass), m_member(member), m_type(type)
{
setDescription(QCoreApplication::translate("CppTools::Quickfix",
"Add class member \"%1\"").arg(m_member));
}
private:
void perform() override
{
QString type = m_type;
if (type.isEmpty()) {
type = QInputDialog::getText(
Core::ICore::dialogParent(),
QCoreApplication::translate("CppTools::Quickfix","Please provide the type"),
QCoreApplication::translate("CppTools::Quickfix","Data type:"),
QLineEdit::Normal);
}
if (type.isEmpty())
return;
const CppRefactoringChanges refactoring(snapshot());
const InsertionPointLocator locator(refactoring);
const QString filePath = QString::fromUtf8(m_class->fileName());
const InsertionLocation loc = locator.methodDeclarationInClass(
filePath, m_class, InsertionPointLocator::Private);
QTC_ASSERT(loc.isValid(), return);
CppRefactoringFilePtr targetFile = refactoring.file(filePath);
const int targetPosition1 = targetFile->position(loc.line(), loc.column());
const int targetPosition2 = qMax(0, targetFile->position(loc.line(), 1) - 1);
ChangeSet target;
target.insert(targetPosition1, loc.prefix() + type + ' ' + m_member + ";\n");
targetFile->setChangeSet(target);
targetFile->appendIndentRange(ChangeSet::Range(targetPosition2, targetPosition1));
targetFile->apply();
}
const Class * const m_class;
const QString m_member;
const QString m_type;
};
void InsertMemberFromInitialization::match(const CppQuickFixInterface &interface,
QuickFixOperations &result)
{
// First check whether we are on a member initialization.
const QList<AST *> path = interface.path();
const int size = path.size();
if (size < 4)
return;
const SimpleNameAST * const name = path.at(size - 1)->asSimpleName();
if (!name)
return;
const MemInitializerAST * const memInitializer = path.at(size - 2)->asMemInitializer();
if (!memInitializer)
return;
if (!path.at(size - 3)->asCtorInitializer())
return;
const FunctionDefinitionAST * ctor = path.at(size - 4)->asFunctionDefinition();
if (!ctor)
return;
// Now find the class.
const Class *theClass = nullptr;
if (size > 4) {
const ClassSpecifierAST * const classSpec = path.at(size - 5)->asClassSpecifier();
if (classSpec) // Inline constructor. We get the class directly.
theClass = classSpec->symbol;
}
if (!theClass) {
// Out-of-line constructor. We need to find the class.
SymbolFinder finder;
const QList<Declaration *> matches = finder.findMatchingDeclaration(
LookupContext(interface.currentFile()->cppDocument(), interface.snapshot()),
ctor->symbol);
if (!matches.isEmpty())
theClass = matches.first()->enclosingClass();
}
if (!theClass)
return;
// Check whether the member exists already.
if (theClass->find(interface.currentFile()->cppDocument()->translationUnit()->identifier(
name->identifier_token))) {
return;
}
const QString type = getType(interface, memInitializer, ctor);
const Identifier * const memberId = interface.currentFile()->cppDocument()
->translationUnit()->identifier(name->identifier_token);
const QString member = QString::fromUtf8(memberId->chars(), memberId->size());
result << new InsertMemberFromInitializationOp(interface, theClass, member, type);
}
QString InsertMemberFromInitialization::getType(
const CppQuickFixInterface &interface,
const MemInitializerAST *memInitializer,
const FunctionDefinitionAST *ctor) const
{
// Try to deduce the type: If the initialization expression is just a name
// (e.g. a constructor argument) or a function call, we don't bother the user.
if (!memInitializer->expression)
return {};
const ExpressionListParenAST * const lParenAst
= memInitializer->expression->asExpressionListParen();
if (!lParenAst || !lParenAst->expression_list || !lParenAst->expression_list->value)
return {};
const IdExpressionAST *idExpr = lParenAst->expression_list->value->asIdExpression();
if (!idExpr) { // Not a variable, so check for function call.
const CallAST * const call = lParenAst->expression_list->value->asCall();
if (!call || !call->base_expression)
return {};
idExpr = call->base_expression->asIdExpression();
}
if (!idExpr || !idExpr->name)
return {};
LookupContext context(interface.currentFile()->cppDocument(), interface.snapshot());
const QList<LookupItem> matches = context.lookup(idExpr->name->name, ctor->symbol);
if (matches.isEmpty())
return {};
Overview o = CppCodeStyleSettings::currentProjectCodeStyleOverview();
TypePrettyPrinter tpp(&o);
FullySpecifiedType type = matches.first().type();
if (!type.type())
return {};
const Function * const funcType = type.type()->asFunctionType();
if (funcType)
type = funcType->returnType();
return tpp(type);
}
namespace {
bool hasClassMemberWithGetPrefix(const Class *klass)
@@ -6680,6 +6823,7 @@ void createCppQuickFixes()
new GenerateGettersSettersForClass;
new InsertDeclFromDef;
new InsertDefFromDecl;
new InsertMemberFromInitialization;
new MoveFuncDefOutside;
new MoveAllFuncDefOutside;

View File

@@ -391,6 +391,22 @@ public:
void match(const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result) override;
};
/*!
Adds a class member from an initialization in the constructor.
*/
class InsertMemberFromInitialization : public CppQuickFixFactory
{
public:
void match(const CppQuickFixInterface &interface,
TextEditor::QuickFixOperations &result) override;
private:
QString getType(
const CppQuickFixInterface &interface,
const CPlusPlus::MemInitializerAST *memInitializer,
const CPlusPlus::FunctionDefinitionAST *ctor) const;
};
/*!
Extracts the selected code and puts it to a function
*/