forked from qt-creator/qt-creator
CppEditor: Move "move function definition" quickfixes to dedicated files
Change-Id: I4e963bd7fef1f1c9f0b69dde56298a52c74e01e4 Reviewed-by: Christian Stenger <christian.stenger@qt.io> Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
This commit is contained in:
@@ -108,6 +108,7 @@ add_qtc_plugin(CppEditor
|
||||
quickfixes/cppquickfixsettingswidget.cpp quickfixes/cppquickfixsettingswidget.h
|
||||
quickfixes/convertqt4connect.cpp quickfixes/convertqt4connect.h
|
||||
quickfixes/moveclasstoownfile.cpp quickfixes/moveclasstoownfile.h
|
||||
quickfixes/movefunctiondefinition.cpp quickfixes/movefunctiondefinition.h
|
||||
quickfixes/removeusingnamespace.cpp quickfixes/removeusingnamespace.h
|
||||
resourcepreviewhoverhandler.cpp resourcepreviewhoverhandler.h
|
||||
searchsymbols.cpp searchsymbols.h
|
||||
|
@@ -245,6 +245,8 @@ QtcPlugin {
|
||||
"cppquickfixsettingswidget.h",
|
||||
"moveclasstoownfile.cpp",
|
||||
"moveclasstoownfile.h",
|
||||
"movefunctiondefinition.cpp",
|
||||
"movefunctiondefinition.h",
|
||||
"removeusingnamespace.cpp",
|
||||
"removeusingnamespace.h",
|
||||
]
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -152,40 +152,6 @@ private slots:
|
||||
void testAddForwardDeclForUndefinedIdentifier_data();
|
||||
void testAddForwardDeclForUndefinedIdentifier();
|
||||
|
||||
void testMoveFuncDefOutsideMemberFuncToCpp();
|
||||
void testMoveFuncDefOutsideMemberFuncToCppInsideNS();
|
||||
void testMoveFuncDefOutsideMemberFuncOutside1();
|
||||
void testMoveFuncDefOutsideMemberFuncOutside2();
|
||||
void testMoveFuncDefOutsideMemberFuncToCppNS();
|
||||
void testMoveFuncDefOutsideMemberFuncToCppNSUsing();
|
||||
void testMoveFuncDefOutsideMemberFuncOutsideWithNs();
|
||||
void testMoveFuncDefOutsideFreeFuncToCpp();
|
||||
void testMoveFuncDefOutsideFreeFuncToCppNS();
|
||||
void testMoveFuncDefOutsideCtorWithInitialization1();
|
||||
void testMoveFuncDefOutsideCtorWithInitialization2();
|
||||
void testMoveFuncDefOutsideAfterClass();
|
||||
void testMoveFuncDefOutsideRespectWsInOperatorNames1();
|
||||
void testMoveFuncDefOutsideRespectWsInOperatorNames2();
|
||||
void testMoveFuncDefOutsideMacroUses();
|
||||
void testMoveFuncDefOutsideTemplate();
|
||||
void testMoveFuncDefOutsideMemberFunctionTemplate();
|
||||
void testMoveFuncDefOutsideTemplateSpecializedClass();
|
||||
void testMoveFuncDefOutsideUnnamedTemplate();
|
||||
void testMoveFuncDefOutsideMemberFuncToCppStatic();
|
||||
void testMoveFuncDefOutsideMemberFuncToCppWithInlinePartOfName();
|
||||
void testMoveFuncDefOutsideMixedQualifiers();
|
||||
|
||||
void testMoveAllFuncDefOutsideMemberFuncToCpp();
|
||||
void testMoveAllFuncDefOutsideMemberFuncOutside();
|
||||
void testMoveAllFuncDefOutsideDoNotTriggerOnBaseClass();
|
||||
void testMoveAllFuncDefOutsideClassWithBaseClass();
|
||||
void testMoveAllFuncDefOutsideIgnoreMacroCode();
|
||||
|
||||
void testMoveFuncDefToDecl_data();
|
||||
void testMoveFuncDefToDecl();
|
||||
|
||||
void testMoveFuncDefToDeclMacroUses();
|
||||
|
||||
void testAssignToLocalVariableTemplates();
|
||||
|
||||
void testExtractFunction_data();
|
||||
|
@@ -24,6 +24,7 @@
|
||||
#include "cppquickfixprojectsettings.h"
|
||||
#include "convertqt4connect.h"
|
||||
#include "moveclasstoownfile.h"
|
||||
#include "movefunctiondefinition.h"
|
||||
#include "removeusingnamespace.h"
|
||||
|
||||
#include <coreplugin/icore.h>
|
||||
@@ -126,15 +127,6 @@ const QList<CppQuickFixFactory *> &CppQuickFixFactory::cppQuickFixFactories()
|
||||
|
||||
namespace Internal {
|
||||
|
||||
QString inlinePrefix(const FilePath &targetFile, const std::function<bool()> &extraCondition = {})
|
||||
{
|
||||
if (ProjectFile::isHeader(ProjectFile::classify(targetFile.path()))
|
||||
&& (!extraCondition || extraCondition())) {
|
||||
return "inline ";
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
// In the following anonymous namespace all functions are collected, which could be of interest for
|
||||
// different quick fixes.
|
||||
namespace {
|
||||
@@ -157,84 +149,6 @@ inline bool isQtStringTranslation(const QByteArray &id)
|
||||
return id == "tr" || id == "trUtf8" || id == "translate" || id == "QT_TRANSLATE_NOOP";
|
||||
}
|
||||
|
||||
Class *isMemberFunction(const LookupContext &context, Function *function)
|
||||
{
|
||||
QTC_ASSERT(function, return nullptr);
|
||||
|
||||
Scope *enclosingScope = function->enclosingScope();
|
||||
while (!(enclosingScope->asNamespace() || enclosingScope->asClass()))
|
||||
enclosingScope = enclosingScope->enclosingScope();
|
||||
QTC_ASSERT(enclosingScope != nullptr, return nullptr);
|
||||
|
||||
const Name *functionName = function->name();
|
||||
if (!functionName)
|
||||
return nullptr;
|
||||
|
||||
if (!functionName->asQualifiedNameId())
|
||||
return nullptr; // trying to add a declaration for a global function
|
||||
|
||||
const QualifiedNameId *q = functionName->asQualifiedNameId();
|
||||
if (!q->base())
|
||||
return nullptr;
|
||||
|
||||
if (ClassOrNamespace *binding = context.lookupType(q->base(), enclosingScope)) {
|
||||
const QList<Symbol *> symbols = binding->symbols();
|
||||
for (Symbol *s : symbols) {
|
||||
if (Class *matchingClass = s->asClass())
|
||||
return matchingClass;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Namespace *isNamespaceFunction(const LookupContext &context, Function *function)
|
||||
{
|
||||
QTC_ASSERT(function, return nullptr);
|
||||
if (isMemberFunction(context, function))
|
||||
return nullptr;
|
||||
|
||||
Scope *enclosingScope = function->enclosingScope();
|
||||
while (!(enclosingScope->asNamespace() || enclosingScope->asClass()))
|
||||
enclosingScope = enclosingScope->enclosingScope();
|
||||
QTC_ASSERT(enclosingScope != nullptr, return nullptr);
|
||||
|
||||
const Name *functionName = function->name();
|
||||
if (!functionName)
|
||||
return nullptr;
|
||||
|
||||
// global namespace
|
||||
if (!functionName->asQualifiedNameId()) {
|
||||
const QList<Symbol *> symbols = context.globalNamespace()->symbols();
|
||||
for (Symbol *s : symbols) {
|
||||
if (Namespace *matchingNamespace = s->asNamespace())
|
||||
return matchingNamespace;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const QualifiedNameId *q = functionName->asQualifiedNameId();
|
||||
if (!q->base())
|
||||
return nullptr;
|
||||
|
||||
if (ClassOrNamespace *binding = context.lookupType(q->base(), enclosingScope)) {
|
||||
const QList<Symbol *> symbols = binding->symbols();
|
||||
for (Symbol *s : symbols) {
|
||||
if (Namespace *matchingNamespace = s->asNamespace())
|
||||
return matchingNamespace;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool nameIncludesOperatorName(const Name *name)
|
||||
{
|
||||
return name->asOperatorNameId()
|
||||
|| (name->asQualifiedNameId() && name->asQualifiedNameId()->name()->asOperatorNameId());
|
||||
}
|
||||
|
||||
|
||||
QString nameString(const NameAST *name)
|
||||
{
|
||||
return CppCodeStyleSettings::currentProjectCodeStyleOverview().prettyName(name->name);
|
||||
@@ -4895,561 +4809,6 @@ void ApplyDeclDefLinkChanges::doMatch(const CppQuickFixInterface &interface,
|
||||
result << op;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
QString definitionSignature(const CppQuickFixInterface *assist,
|
||||
FunctionDefinitionAST *functionDefinitionAST,
|
||||
CppRefactoringFilePtr &baseFile,
|
||||
CppRefactoringFilePtr &targetFile,
|
||||
Scope *scope)
|
||||
{
|
||||
QTC_ASSERT(assist, return QString());
|
||||
QTC_ASSERT(functionDefinitionAST, return QString());
|
||||
QTC_ASSERT(scope, return QString());
|
||||
Function *func = functionDefinitionAST->symbol;
|
||||
QTC_ASSERT(func, return QString());
|
||||
|
||||
LookupContext cppContext(targetFile->cppDocument(), assist->snapshot());
|
||||
ClassOrNamespace *cppCoN = cppContext.lookupType(scope);
|
||||
if (!cppCoN)
|
||||
cppCoN = cppContext.globalNamespace();
|
||||
SubstitutionEnvironment env;
|
||||
env.setContext(assist->context());
|
||||
env.switchScope(func->enclosingScope());
|
||||
UseMinimalNames q(cppCoN);
|
||||
env.enter(&q);
|
||||
Control *control = assist->context().bindings()->control().get();
|
||||
Overview oo = CppCodeStyleSettings::currentProjectCodeStyleOverview();
|
||||
oo.showFunctionSignatures = true;
|
||||
oo.showReturnTypes = true;
|
||||
oo.showArgumentNames = true;
|
||||
oo.showEnclosingTemplate = true;
|
||||
oo.showTemplateParameters = true;
|
||||
oo.trailingReturnType = functionDefinitionAST->declarator
|
||||
&& functionDefinitionAST->declarator->postfix_declarator_list
|
||||
&& functionDefinitionAST->declarator->postfix_declarator_list->value
|
||||
&& functionDefinitionAST->declarator->postfix_declarator_list
|
||||
->value->asFunctionDeclarator()
|
||||
&& functionDefinitionAST->declarator->postfix_declarator_list
|
||||
->value->asFunctionDeclarator()->trailing_return_type;
|
||||
const Name *name = func->name();
|
||||
if (name && nameIncludesOperatorName(name)) {
|
||||
CoreDeclaratorAST *coreDeclarator = functionDefinitionAST->declarator->core_declarator;
|
||||
const QString operatorNameText = baseFile->textOf(coreDeclarator);
|
||||
oo.includeWhiteSpaceInOperatorName = operatorNameText.contains(QLatin1Char(' '));
|
||||
}
|
||||
const QString nameText = oo.prettyName(LookupContext::minimalName(func, cppCoN, control));
|
||||
oo.showTemplateParameters = false;
|
||||
const FullySpecifiedType tn = rewriteType(func->type(), &env, control);
|
||||
|
||||
return oo.prettyType(tn, nameText);
|
||||
}
|
||||
|
||||
class MoveFuncDefRefactoringHelper
|
||||
{
|
||||
public:
|
||||
enum MoveType {
|
||||
MoveOutside,
|
||||
MoveToCppFile,
|
||||
MoveOutsideMemberToCppFile
|
||||
};
|
||||
|
||||
MoveFuncDefRefactoringHelper(CppQuickFixOperation *operation, MoveType type,
|
||||
const FilePath &fromFile, const FilePath &toFile)
|
||||
: m_operation(operation), m_type(type), m_changes(m_operation->snapshot())
|
||||
{
|
||||
m_fromFile = m_changes.cppFile(fromFile);
|
||||
m_toFile = (m_type == MoveOutside) ? m_fromFile : m_changes.cppFile(toFile);
|
||||
}
|
||||
|
||||
void performMove(FunctionDefinitionAST *funcAST)
|
||||
{
|
||||
// Determine file, insert position and scope
|
||||
InsertionLocation l = insertLocationForMethodDefinition(
|
||||
funcAST->symbol, false, NamespaceHandling::Ignore,
|
||||
m_changes, m_toFile->filePath());
|
||||
const QString prefix = l.prefix();
|
||||
const QString suffix = l.suffix();
|
||||
const int insertPos = m_toFile->position(l.line(), l.column());
|
||||
Scope *scopeAtInsertPos = m_toFile->cppDocument()->scopeAt(l.line(), l.column());
|
||||
|
||||
// construct definition
|
||||
const QString funcDec = inlinePrefix(m_toFile->filePath(), [this] { return m_type == MoveOutside; })
|
||||
+ definitionSignature(m_operation, funcAST, m_fromFile, m_toFile,
|
||||
scopeAtInsertPos);
|
||||
QString funcDef = prefix + funcDec;
|
||||
const int startPosition = m_fromFile->endOf(funcAST->declarator);
|
||||
const int endPosition = m_fromFile->endOf(funcAST);
|
||||
funcDef += m_fromFile->textOf(startPosition, endPosition);
|
||||
funcDef += suffix;
|
||||
|
||||
// insert definition at new position
|
||||
m_toFileChangeSet.insert(insertPos, funcDef);
|
||||
m_toFile->setOpenEditor(true, insertPos);
|
||||
|
||||
// remove definition from fromFile
|
||||
if (m_type == MoveOutsideMemberToCppFile) {
|
||||
m_fromFileChangeSet.remove(m_fromFile->range(funcAST));
|
||||
} else {
|
||||
QString textFuncDecl = m_fromFile->textOf(funcAST);
|
||||
textFuncDecl.truncate(startPosition - m_fromFile->startOf(funcAST));
|
||||
if (textFuncDecl.left(7) == QLatin1String("inline "))
|
||||
textFuncDecl = textFuncDecl.mid(7);
|
||||
else
|
||||
textFuncDecl.replace(" inline ", QLatin1String(" "));
|
||||
textFuncDecl = textFuncDecl.trimmed() + QLatin1Char(';');
|
||||
m_fromFileChangeSet.replace(m_fromFile->range(funcAST), textFuncDecl);
|
||||
}
|
||||
}
|
||||
|
||||
void applyChanges()
|
||||
{
|
||||
if (!m_toFileChangeSet.isEmpty()) {
|
||||
m_toFile->setChangeSet(m_toFileChangeSet);
|
||||
m_toFile->apply();
|
||||
}
|
||||
if (!m_fromFileChangeSet.isEmpty()) {
|
||||
m_fromFile->setChangeSet(m_fromFileChangeSet);
|
||||
m_fromFile->apply();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
CppQuickFixOperation *m_operation;
|
||||
MoveType m_type;
|
||||
CppRefactoringChanges m_changes;
|
||||
CppRefactoringFilePtr m_fromFile;
|
||||
CppRefactoringFilePtr m_toFile;
|
||||
ChangeSet m_fromFileChangeSet;
|
||||
ChangeSet m_toFileChangeSet;
|
||||
};
|
||||
|
||||
class MoveFuncDefOutsideOp : public CppQuickFixOperation
|
||||
{
|
||||
public:
|
||||
MoveFuncDefOutsideOp(const CppQuickFixInterface &interface,
|
||||
MoveFuncDefRefactoringHelper::MoveType type,
|
||||
FunctionDefinitionAST *funcDef, const FilePath &cppFilePath)
|
||||
: CppQuickFixOperation(interface, 0)
|
||||
, m_funcDef(funcDef)
|
||||
, m_type(type)
|
||||
, m_cppFilePath(cppFilePath)
|
||||
, m_headerFilePath(funcDef->symbol->filePath())
|
||||
{
|
||||
if (m_type == MoveFuncDefRefactoringHelper::MoveOutside) {
|
||||
setDescription(Tr::tr("Move Definition Outside Class"));
|
||||
} else {
|
||||
const FilePath resolved = m_cppFilePath.relativePathFrom(m_headerFilePath.parentDir());
|
||||
setDescription(Tr::tr("Move Definition to %1").arg(resolved.displayName()));
|
||||
}
|
||||
}
|
||||
|
||||
void perform() override
|
||||
{
|
||||
MoveFuncDefRefactoringHelper helper(this, m_type, m_headerFilePath, m_cppFilePath);
|
||||
helper.performMove(m_funcDef);
|
||||
helper.applyChanges();
|
||||
}
|
||||
|
||||
private:
|
||||
FunctionDefinitionAST *m_funcDef;
|
||||
MoveFuncDefRefactoringHelper::MoveType m_type;
|
||||
const FilePath m_cppFilePath;
|
||||
const FilePath m_headerFilePath;
|
||||
};
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
void MoveFuncDefOutside::doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result)
|
||||
{
|
||||
const QList<AST *> &path = interface.path();
|
||||
SimpleDeclarationAST *classAST = nullptr;
|
||||
FunctionDefinitionAST *funcAST = nullptr;
|
||||
bool moveOutsideMemberDefinition = false;
|
||||
|
||||
const int pathSize = path.size();
|
||||
for (int idx = 1; idx < pathSize; ++idx) {
|
||||
if ((funcAST = path.at(idx)->asFunctionDefinition())) {
|
||||
// check cursor position
|
||||
if (idx != pathSize - 1 // Do not allow "void a() @ {..."
|
||||
&& funcAST->function_body
|
||||
&& !interface.isCursorOn(funcAST->function_body)) {
|
||||
if (path.at(idx - 1)->asTranslationUnit()) { // normal function
|
||||
if (idx + 3 < pathSize && path.at(idx + 3)->asQualifiedName()) // Outside member
|
||||
moveOutsideMemberDefinition = true; // definition
|
||||
break;
|
||||
}
|
||||
|
||||
if (idx > 1) {
|
||||
if ((classAST = path.at(idx - 2)->asSimpleDeclaration())) // member function
|
||||
break;
|
||||
if (path.at(idx - 2)->asNamespace()) // normal function in namespace
|
||||
break;
|
||||
}
|
||||
if (idx > 2 && path.at(idx - 1)->asTemplateDeclaration()) {
|
||||
if ((classAST = path.at(idx - 3)->asSimpleDeclaration())) // member template
|
||||
break;
|
||||
}
|
||||
}
|
||||
funcAST = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
if (!funcAST || !funcAST->symbol)
|
||||
return;
|
||||
|
||||
bool isHeaderFile = false;
|
||||
const FilePath cppFileName = correspondingHeaderOrSource(interface.filePath(), &isHeaderFile);
|
||||
|
||||
if (isHeaderFile && !cppFileName.isEmpty()) {
|
||||
const MoveFuncDefRefactoringHelper::MoveType type = moveOutsideMemberDefinition
|
||||
? MoveFuncDefRefactoringHelper::MoveOutsideMemberToCppFile
|
||||
: MoveFuncDefRefactoringHelper::MoveToCppFile;
|
||||
result << new MoveFuncDefOutsideOp(interface, type, funcAST, cppFileName);
|
||||
}
|
||||
|
||||
if (classAST)
|
||||
result << new MoveFuncDefOutsideOp(interface, MoveFuncDefRefactoringHelper::MoveOutside,
|
||||
funcAST, FilePath());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
class MoveAllFuncDefOutsideOp : public CppQuickFixOperation
|
||||
{
|
||||
public:
|
||||
MoveAllFuncDefOutsideOp(const CppQuickFixInterface &interface,
|
||||
MoveFuncDefRefactoringHelper::MoveType type,
|
||||
ClassSpecifierAST *classDef, const FilePath &cppFileName)
|
||||
: CppQuickFixOperation(interface, 0)
|
||||
, m_type(type)
|
||||
, m_classDef(classDef)
|
||||
, m_cppFilePath(cppFileName)
|
||||
, m_headerFilePath(classDef->symbol->filePath())
|
||||
{
|
||||
if (m_type == MoveFuncDefRefactoringHelper::MoveOutside) {
|
||||
setDescription(Tr::tr("Definitions Outside Class"));
|
||||
} else {
|
||||
const FilePath resolved = m_cppFilePath.relativePathFrom(m_headerFilePath.parentDir());
|
||||
setDescription(Tr::tr("Move All Function Definitions to %1")
|
||||
.arg(resolved.displayName()));
|
||||
}
|
||||
}
|
||||
|
||||
void perform() override
|
||||
{
|
||||
MoveFuncDefRefactoringHelper helper(this, m_type, m_headerFilePath, m_cppFilePath);
|
||||
for (DeclarationListAST *it = m_classDef->member_specifier_list; it; it = it->next) {
|
||||
if (FunctionDefinitionAST *funcAST = it->value->asFunctionDefinition()) {
|
||||
if (funcAST->symbol && !funcAST->symbol->isGenerated())
|
||||
helper.performMove(funcAST);
|
||||
}
|
||||
}
|
||||
helper.applyChanges();
|
||||
}
|
||||
|
||||
private:
|
||||
MoveFuncDefRefactoringHelper::MoveType m_type;
|
||||
ClassSpecifierAST *m_classDef;
|
||||
const FilePath m_cppFilePath;
|
||||
const FilePath m_headerFilePath;
|
||||
};
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
void MoveAllFuncDefOutside::doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result)
|
||||
{
|
||||
ClassSpecifierAST * const classAST = astForClassOperations(interface);
|
||||
if (!classAST)
|
||||
return;
|
||||
|
||||
// Determine if the class has at least one function definition
|
||||
bool classContainsFunctions = false;
|
||||
for (DeclarationListAST *it = classAST->member_specifier_list; it; it = it->next) {
|
||||
if (FunctionDefinitionAST *funcAST = it->value->asFunctionDefinition()) {
|
||||
if (funcAST->symbol && !funcAST->symbol->isGenerated()) {
|
||||
classContainsFunctions = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!classContainsFunctions)
|
||||
return;
|
||||
|
||||
bool isHeaderFile = false;
|
||||
const FilePath cppFileName = correspondingHeaderOrSource(interface.filePath(), &isHeaderFile);
|
||||
if (isHeaderFile && !cppFileName.isEmpty()) {
|
||||
result << new MoveAllFuncDefOutsideOp(interface,
|
||||
MoveFuncDefRefactoringHelper::MoveToCppFile,
|
||||
classAST, cppFileName);
|
||||
}
|
||||
result << new MoveAllFuncDefOutsideOp(interface, MoveFuncDefRefactoringHelper::MoveOutside,
|
||||
classAST, FilePath());
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
class MoveFuncDefToDeclOp : public CppQuickFixOperation
|
||||
{
|
||||
public:
|
||||
enum Type { Push, Pull };
|
||||
MoveFuncDefToDeclOp(const CppQuickFixInterface &interface,
|
||||
const FilePath &fromFilePath, const FilePath &toFilePath,
|
||||
FunctionDefinitionAST *funcAst, Function *func, const QString &declText,
|
||||
const ChangeSet::Range &fromRange,
|
||||
const ChangeSet::Range &toRange,
|
||||
Type type)
|
||||
: CppQuickFixOperation(interface, 0)
|
||||
, m_fromFilePath(fromFilePath)
|
||||
, m_toFilePath(toFilePath)
|
||||
, m_funcAST(funcAst)
|
||||
, m_func(func)
|
||||
, m_declarationText(declText)
|
||||
, m_fromRange(fromRange)
|
||||
, m_toRange(toRange)
|
||||
{
|
||||
if (type == Type::Pull) {
|
||||
setDescription(Tr::tr("Move Definition Here"));
|
||||
} else if (m_toFilePath == m_fromFilePath) {
|
||||
setDescription(Tr::tr("Move Definition to Class"));
|
||||
} else {
|
||||
const FilePath resolved = m_toFilePath.relativePathFrom(m_fromFilePath.parentDir());
|
||||
setDescription(Tr::tr("Move Definition to %1").arg(resolved.displayName()));
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void perform() override
|
||||
{
|
||||
CppRefactoringChanges refactoring(snapshot());
|
||||
CppRefactoringFilePtr fromFile = refactoring.cppFile(m_fromFilePath);
|
||||
CppRefactoringFilePtr toFile = refactoring.cppFile(m_toFilePath);
|
||||
|
||||
ensureFuncDefAstAndRange(*fromFile);
|
||||
if (!m_funcAST)
|
||||
return;
|
||||
|
||||
const QString wholeFunctionText = m_declarationText
|
||||
+ fromFile->textOf(fromFile->endOf(m_funcAST->declarator),
|
||||
fromFile->endOf(m_funcAST->function_body));
|
||||
|
||||
// Replace declaration with function and delete old definition
|
||||
ChangeSet toTarget;
|
||||
toTarget.replace(m_toRange, wholeFunctionText);
|
||||
if (m_toFilePath == m_fromFilePath)
|
||||
toTarget.remove(m_fromRange);
|
||||
toFile->setChangeSet(toTarget);
|
||||
toFile->setOpenEditor(true, m_toRange.start);
|
||||
toFile->apply();
|
||||
if (m_toFilePath != m_fromFilePath) {
|
||||
ChangeSet fromTarget;
|
||||
fromTarget.remove(m_fromRange);
|
||||
fromFile->setChangeSet(fromTarget);
|
||||
fromFile->apply();
|
||||
}
|
||||
}
|
||||
|
||||
void ensureFuncDefAstAndRange(CppRefactoringFile &defFile)
|
||||
{
|
||||
if (m_funcAST) {
|
||||
QTC_CHECK(m_fromRange.end > m_fromRange.start);
|
||||
return;
|
||||
}
|
||||
QTC_ASSERT(m_func, return);
|
||||
const QList<AST *> astPath = ASTPath(defFile.cppDocument())(m_func->line(),
|
||||
m_func->column());
|
||||
if (astPath.isEmpty())
|
||||
return;
|
||||
for (auto it = std::rbegin(astPath); it != std::rend(astPath); ++it) {
|
||||
m_funcAST = (*it)->asFunctionDefinition();
|
||||
if (!m_funcAST)
|
||||
continue;
|
||||
AST *astForRange = m_funcAST;
|
||||
const auto prev = std::next(it);
|
||||
if (prev != std::rend(astPath)) {
|
||||
if (const auto templAst = (*prev)->asTemplateDeclaration())
|
||||
astForRange = templAst;
|
||||
}
|
||||
m_fromRange = defFile.range(astForRange);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const FilePath m_fromFilePath;
|
||||
const FilePath m_toFilePath;
|
||||
FunctionDefinitionAST *m_funcAST;
|
||||
Function *m_func;
|
||||
const QString m_declarationText;
|
||||
ChangeSet::Range m_fromRange;
|
||||
const ChangeSet::Range m_toRange;
|
||||
};
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
void MoveFuncDefToDeclPush::doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result)
|
||||
{
|
||||
const QList<AST *> &path = interface.path();
|
||||
AST *completeDefAST = nullptr;
|
||||
FunctionDefinitionAST *funcAST = nullptr;
|
||||
|
||||
const int pathSize = path.size();
|
||||
for (int idx = 1; idx < pathSize; ++idx) {
|
||||
if ((funcAST = path.at(idx)->asFunctionDefinition())) {
|
||||
AST *enclosingAST = path.at(idx - 1);
|
||||
if (enclosingAST->asClassSpecifier())
|
||||
return;
|
||||
|
||||
// check cursor position
|
||||
if (idx != pathSize - 1 // Do not allow "void a() @ {..."
|
||||
&& funcAST->function_body
|
||||
&& !interface.isCursorOn(funcAST->function_body)) {
|
||||
completeDefAST = enclosingAST->asTemplateDeclaration() ? enclosingAST : funcAST;
|
||||
break;
|
||||
}
|
||||
funcAST = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
if (!funcAST || !funcAST->symbol)
|
||||
return;
|
||||
|
||||
const CppRefactoringChanges refactoring(interface.snapshot());
|
||||
const CppRefactoringFilePtr defFile = refactoring.cppFile(interface.filePath());
|
||||
const ChangeSet::Range defRange = defFile->range(completeDefAST);
|
||||
|
||||
// Determine declaration (file, range, text);
|
||||
ChangeSet::Range declRange;
|
||||
QString declText;
|
||||
FilePath declFilePath;
|
||||
|
||||
Function *func = funcAST->symbol;
|
||||
if (Class *matchingClass = isMemberFunction(interface.context(), func)) {
|
||||
// Dealing with member functions
|
||||
const QualifiedNameId *qName = func->name()->asQualifiedNameId();
|
||||
for (Symbol *symbol = matchingClass->find(qName->identifier());
|
||||
symbol; symbol = symbol->next()) {
|
||||
Symbol *s = symbol;
|
||||
if (func->enclosingScope()->asTemplate()) {
|
||||
if (const Template *templ = s->type()->asTemplateType()) {
|
||||
if (Symbol *decl = templ->declaration()) {
|
||||
if (decl->type()->asFunctionType())
|
||||
s = decl;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!s->name()
|
||||
|| !qName->identifier()->match(s->identifier())
|
||||
|| !s->type()->asFunctionType()
|
||||
|| !s->type().match(func->type())
|
||||
|| s->asFunction()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
declFilePath = matchingClass->filePath();
|
||||
const CppRefactoringFilePtr declFile = refactoring.cppFile(declFilePath);
|
||||
ASTPath astPath(declFile->cppDocument());
|
||||
const QList<AST *> path = astPath(s->line(), s->column());
|
||||
for (int idx = path.size() - 1; idx > 0; --idx) {
|
||||
AST *node = path.at(idx);
|
||||
if (SimpleDeclarationAST *simpleDecl = node->asSimpleDeclaration()) {
|
||||
if (simpleDecl->symbols && !simpleDecl->symbols->next) {
|
||||
declRange = declFile->range(simpleDecl);
|
||||
declText = declFile->textOf(simpleDecl);
|
||||
declText.remove(-1, 1); // remove ';' from declaration text
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!declText.isEmpty())
|
||||
break;
|
||||
}
|
||||
} else if (Namespace *matchingNamespace = isNamespaceFunction(interface.context(), func)) {
|
||||
// Dealing with free functions
|
||||
bool isHeaderFile = false;
|
||||
declFilePath = correspondingHeaderOrSource(interface.filePath(), &isHeaderFile);
|
||||
if (isHeaderFile)
|
||||
return;
|
||||
|
||||
const CppRefactoringFilePtr declFile = refactoring.cppFile(declFilePath);
|
||||
const LookupContext lc(declFile->cppDocument(), interface.snapshot());
|
||||
const QList<LookupItem> candidates = lc.lookup(func->name(), matchingNamespace);
|
||||
for (const LookupItem &candidate : candidates) {
|
||||
if (Symbol *s = candidate.declaration()) {
|
||||
if (s->asDeclaration()) {
|
||||
ASTPath astPath(declFile->cppDocument());
|
||||
const QList<AST *> path = astPath(s->line(), s->column());
|
||||
for (AST *node : path) {
|
||||
if (SimpleDeclarationAST *simpleDecl = node->asSimpleDeclaration()) {
|
||||
declRange = declFile->range(simpleDecl);
|
||||
declText = declFile->textOf(simpleDecl);
|
||||
declText.remove(-1, 1); // remove ';' from declaration text
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!declText.isEmpty()) {
|
||||
declText.prepend(inlinePrefix(declFilePath));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!declFilePath.isEmpty() && !declText.isEmpty())
|
||||
result << new MoveFuncDefToDeclOp(interface,
|
||||
interface.filePath(),
|
||||
declFilePath,
|
||||
funcAST, func, declText,
|
||||
defRange, declRange, MoveFuncDefToDeclOp::Push);
|
||||
}
|
||||
|
||||
void MoveFuncDefToDeclPull::doMatch(const CppQuickFixInterface &interface,
|
||||
QuickFixOperations &result)
|
||||
{
|
||||
const QList<AST *> &path = interface.path();
|
||||
for (auto it = std::rbegin(path); it != std::rend(path); ++it) {
|
||||
SimpleDeclarationAST * const simpleDecl = (*it)->asSimpleDeclaration();
|
||||
if (!simpleDecl)
|
||||
continue;
|
||||
const auto prev = std::next(it);
|
||||
if (prev != std::rend(path) && (*prev)->asStatement())
|
||||
return;
|
||||
if (!simpleDecl->symbols || !simpleDecl->symbols->value || simpleDecl->symbols->next)
|
||||
return;
|
||||
Declaration * const decl = simpleDecl->symbols->value->asDeclaration();
|
||||
if (!decl)
|
||||
return;
|
||||
Function * const funcDecl = decl->type()->asFunctionType();
|
||||
if (!funcDecl)
|
||||
return;
|
||||
if (funcDecl->isSignal() || funcDecl->isPureVirtual() || funcDecl->isFriend())
|
||||
return;
|
||||
|
||||
// Is there a definition?
|
||||
SymbolFinder symbolFinder;
|
||||
Function * const funcDef = symbolFinder.findMatchingDefinition(decl, interface.snapshot(),
|
||||
true);
|
||||
if (!funcDef)
|
||||
return;
|
||||
|
||||
QString declText = interface.currentFile()->textOf(simpleDecl);
|
||||
declText.chop(1); // semicolon
|
||||
declText.prepend(inlinePrefix(interface.filePath(), [funcDecl] {
|
||||
return !funcDecl->enclosingScope()->asClass();
|
||||
}));
|
||||
result << new MoveFuncDefToDeclOp(interface, funcDef->filePath(), decl->filePath(), nullptr,
|
||||
funcDef, declText, {},
|
||||
interface.currentFile()->range(simpleDecl),
|
||||
MoveFuncDefToDeclOp::Pull);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
class AssignToLocalVariableOperation : public CppQuickFixOperation
|
||||
@@ -6639,11 +5998,6 @@ void createCppQuickFixes()
|
||||
new AddDeclarationForUndeclaredIdentifier;
|
||||
new InsertDefsFromDecls;
|
||||
|
||||
new MoveFuncDefOutside;
|
||||
new MoveAllFuncDefOutside;
|
||||
new MoveFuncDefToDeclPush;
|
||||
new MoveFuncDefToDeclPull;
|
||||
|
||||
new AssignToLocalVariable;
|
||||
|
||||
registerInsertVirtualMethodsQuickfix();
|
||||
@@ -6651,6 +6005,7 @@ void createCppQuickFixes()
|
||||
registerRemoveUsingNamespaceQuickfix();
|
||||
registerCodeGenerationQuickfixes();
|
||||
registerConvertQt4ConnectQuickfix();
|
||||
registerMoveFunctionDefinitionQuickfixes();
|
||||
|
||||
new OptimizeForLoop;
|
||||
|
||||
|
@@ -462,43 +462,6 @@ public:
|
||||
void doMatch(const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result) override;
|
||||
};
|
||||
|
||||
/*!
|
||||
Moves the definition of a member function outside the class or moves the definition of a member
|
||||
function or a normal function to the implementation file.
|
||||
*/
|
||||
class MoveFuncDefOutside: public CppQuickFixFactory
|
||||
{
|
||||
public:
|
||||
void doMatch(const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result) override;
|
||||
};
|
||||
|
||||
/*!
|
||||
Moves all member function definitions outside the class or to the implementation file.
|
||||
*/
|
||||
class MoveAllFuncDefOutside: public CppQuickFixFactory
|
||||
{
|
||||
public:
|
||||
void doMatch(const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result) override;
|
||||
};
|
||||
|
||||
/*!
|
||||
Moves the definition of a function to its declaration, with the cursor on the definition.
|
||||
*/
|
||||
class MoveFuncDefToDeclPush : public CppQuickFixFactory
|
||||
{
|
||||
public:
|
||||
void doMatch(const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result) override;
|
||||
};
|
||||
|
||||
/*!
|
||||
Moves the definition of a function to its declaration, with the cursor on the declaration.
|
||||
*/
|
||||
class MoveFuncDefToDeclPull : public CppQuickFixFactory
|
||||
{
|
||||
public:
|
||||
void doMatch(const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result) override;
|
||||
};
|
||||
|
||||
/*!
|
||||
Assigns the return value of a function call or a new expression to a local variable
|
||||
*/
|
||||
|
@@ -3,6 +3,7 @@
|
||||
|
||||
#include "cppquickfixhelpers.h"
|
||||
|
||||
#include "../cppprojectfile.h"
|
||||
#include "../includeutils.h"
|
||||
#include "cppquickfixassistant.h"
|
||||
|
||||
@@ -65,4 +66,91 @@ ClassSpecifierAST *astForClassOperations(const CppQuickFixInterface &interface)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool nameIncludesOperatorName(const Name *name)
|
||||
{
|
||||
return name->asOperatorNameId()
|
||||
|| (name->asQualifiedNameId() && name->asQualifiedNameId()->name()->asOperatorNameId());
|
||||
}
|
||||
|
||||
QString inlinePrefix(const FilePath &targetFile, const std::function<bool()> &extraCondition)
|
||||
{
|
||||
if (ProjectFile::isHeader(ProjectFile::classify(targetFile.path()))
|
||||
&& (!extraCondition || extraCondition())) {
|
||||
return "inline ";
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
Class *isMemberFunction(const CPlusPlus::LookupContext &context, CPlusPlus::Function *function)
|
||||
{
|
||||
QTC_ASSERT(function, return nullptr);
|
||||
|
||||
Scope *enclosingScope = function->enclosingScope();
|
||||
while (!(enclosingScope->asNamespace() || enclosingScope->asClass()))
|
||||
enclosingScope = enclosingScope->enclosingScope();
|
||||
QTC_ASSERT(enclosingScope != nullptr, return nullptr);
|
||||
|
||||
const Name *functionName = function->name();
|
||||
if (!functionName)
|
||||
return nullptr;
|
||||
|
||||
if (!functionName->asQualifiedNameId())
|
||||
return nullptr; // trying to add a declaration for a global function
|
||||
|
||||
const QualifiedNameId *q = functionName->asQualifiedNameId();
|
||||
if (!q->base())
|
||||
return nullptr;
|
||||
|
||||
if (ClassOrNamespace *binding = context.lookupType(q->base(), enclosingScope)) {
|
||||
const QList<Symbol *> symbols = binding->symbols();
|
||||
for (Symbol *s : symbols) {
|
||||
if (Class *matchingClass = s->asClass())
|
||||
return matchingClass;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
CPlusPlus::Namespace *isNamespaceFunction(
|
||||
const CPlusPlus::LookupContext &context, CPlusPlus::Function *function)
|
||||
{
|
||||
QTC_ASSERT(function, return nullptr);
|
||||
if (isMemberFunction(context, function))
|
||||
return nullptr;
|
||||
|
||||
Scope *enclosingScope = function->enclosingScope();
|
||||
while (!(enclosingScope->asNamespace() || enclosingScope->asClass()))
|
||||
enclosingScope = enclosingScope->enclosingScope();
|
||||
QTC_ASSERT(enclosingScope != nullptr, return nullptr);
|
||||
|
||||
const Name *functionName = function->name();
|
||||
if (!functionName)
|
||||
return nullptr;
|
||||
|
||||
// global namespace
|
||||
if (!functionName->asQualifiedNameId()) {
|
||||
const QList<Symbol *> symbols = context.globalNamespace()->symbols();
|
||||
for (Symbol *s : symbols) {
|
||||
if (Namespace *matchingNamespace = s->asNamespace())
|
||||
return matchingNamespace;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const QualifiedNameId *q = functionName->asQualifiedNameId();
|
||||
if (!q->base())
|
||||
return nullptr;
|
||||
|
||||
if (ClassOrNamespace *binding = context.lookupType(q->base(), enclosingScope)) {
|
||||
const QList<Symbol *> symbols = binding->symbols();
|
||||
for (Symbol *s : symbols) {
|
||||
if (Namespace *matchingNamespace = s->asNamespace())
|
||||
return matchingNamespace;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace CppEditor::Internal
|
||||
|
@@ -25,4 +25,15 @@ void insertNewIncludeDirective(
|
||||
// correspond to an AST of its own, i.e. on "empty space".
|
||||
CPlusPlus::ClassSpecifierAST *astForClassOperations(const CppQuickFixInterface &interface);
|
||||
|
||||
bool nameIncludesOperatorName(const CPlusPlus::Name *name);
|
||||
|
||||
QString inlinePrefix(const Utils::FilePath &targetFile,
|
||||
const std::function<bool()> &extraCondition = {});
|
||||
|
||||
CPlusPlus::Class *isMemberFunction(
|
||||
const CPlusPlus::LookupContext &context, CPlusPlus::Function *function);
|
||||
|
||||
CPlusPlus::Namespace *isNamespaceFunction(
|
||||
const CPlusPlus::LookupContext &context, CPlusPlus::Function *function);
|
||||
|
||||
} // namespace CppEditor::Internal
|
||||
|
1904
src/plugins/cppeditor/quickfixes/movefunctiondefinition.cpp
Normal file
1904
src/plugins/cppeditor/quickfixes/movefunctiondefinition.cpp
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,8 @@
|
||||
// Copyright (C) 2024 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace CppEditor::Internal {
|
||||
void registerMoveFunctionDefinitionQuickfixes();
|
||||
} // namespace CppEditor::Internal
|
Reference in New Issue
Block a user