diff --git a/languages/clang/CMakeLists.txt b/languages/clang/CMakeLists.txt --- a/languages/clang/CMakeLists.txt +++ b/languages/clang/CMakeLists.txt @@ -56,6 +56,8 @@ codegen/adaptsignatureaction.cpp codegen/adaptsignatureassistant.cpp codegen/codegenhelper.cpp + codegen/simplerefactoring.cpp + codegen/sourcemanipulation.cpp duchain/builder.cpp duchain/clangdiagnosticevaluator.cpp diff --git a/languages/clang/clangsupport.h b/languages/clang/clangsupport.h --- a/languages/clang/clangsupport.h +++ b/languages/clang/clangsupport.h @@ -32,9 +32,9 @@ #include class ClangIndex; +class SimpleRefactoring; namespace KDevelop { -class BasicRefactoring; class IDocument; } @@ -92,7 +92,7 @@ private: KDevelop::ICodeHighlighting *m_highlighting; - KDevelop::BasicRefactoring *m_refactoring; + SimpleRefactoring *m_refactoring; QScopedPointer m_index; }; diff --git a/languages/clang/clangsupport.cpp b/languages/clang/clangsupport.cpp --- a/languages/clang/clangsupport.cpp +++ b/languages/clang/clangsupport.cpp @@ -41,6 +41,7 @@ #include #include +#include "codegen/simplerefactoring.h" #include "codegen/adaptsignatureassistant.h" #include "duchain/documentfinderhelpers.h" #include "duchain/clangindex.h" @@ -53,7 +54,6 @@ #include #include #include -#include #include #include #include @@ -184,7 +184,7 @@ ClangIntegration::DUChainUtils::registerDUChainItems(); m_highlighting = new ClangHighlighting(this); - m_refactoring = new BasicRefactoring(this); + m_refactoring = new SimpleRefactoring(this); m_index.reset(new ClangIndex); auto model = new KDevelop::CodeCompletion( this, new ClangCodeCompletionModel(m_index.data(), this), name() ); @@ -275,9 +275,15 @@ QAction* renameDeclarationAction = actions.addAction(QStringLiteral("code_rename_declaration")); renameDeclarationAction->setText( i18n("Rename Declaration") ); renameDeclarationAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-rename"))); - actions.setDefaultShortcut(renameDeclarationAction, Qt::CTRL | Qt::SHIFT | Qt::Key_R); + actions.setDefaultShortcut(renameDeclarationAction, Qt::CTRL | Qt::ALT | Qt::Key_R); connect(renameDeclarationAction, &QAction::triggered, - m_refactoring, &BasicRefactoring::executeRenameAction); + m_refactoring, &SimpleRefactoring::executeRenameAction); + + QAction* moveIntoSourceAction = actions.addAction(QStringLiteral("code_move_definition")); + moveIntoSourceAction->setText(i18n("Move into Source")); + actions.setDefaultShortcut(moveIntoSourceAction, Qt::CTRL | Qt::ALT | Qt::Key_S); + connect(moveIntoSourceAction, &QAction::triggered, + m_refactoring, &SimpleRefactoring::executeMoveIntoSourceAction); } KDevelop::ContextMenuExtension ClangSupport::contextMenuExtension(KDevelop::Context* context) diff --git a/languages/clang/codegen/simplerefactoring.h b/languages/clang/codegen/simplerefactoring.h new file mode 100644 --- /dev/null +++ b/languages/clang/codegen/simplerefactoring.h @@ -0,0 +1,58 @@ +/* + * This file is part of KDevelop + * + * Copyright 2015 Sergey Kalinichev + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#ifndef SIMPLEREFACTORING_H +#define SIMPLEREFACTORING_H + +#include "clangprivateexport.h" + +#include + +namespace KDevelop +{ +class Context; +class ContextMenuExtension; +class Declaration; +} + +class KDEVCLANGPRIVATE_EXPORT SimpleRefactoring : public KDevelop::BasicRefactoring +{ + Q_OBJECT + +public: + explicit SimpleRefactoring(QObject* parent = 0); + + void fillContextMenu(KDevelop::ContextMenuExtension& extension, KDevelop::Context* context) override; + + QString moveIntoSource(const KDevelop::IndexedDeclaration& iDecl); + + using KDevelop::BasicRefactoring::executeRenameAction; + +public slots: + void executeMoveIntoSourceAction(); + +private: + bool validCandidateToMoveIntoSource(KDevelop::Declaration* decl); +}; + +#endif diff --git a/languages/clang/codegen/simplerefactoring.cpp b/languages/clang/codegen/simplerefactoring.cpp new file mode 100644 --- /dev/null +++ b/languages/clang/codegen/simplerefactoring.cpp @@ -0,0 +1,251 @@ +/* + * This file is part of KDevelop + * + * Copyright 2015 Sergey Kalinichev + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include "simplerefactoring.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "duchain/clanghelpers.h" +#include "duchain/documentfinderhelpers.h" +#include "duchain/duchainutils.h" +#include "sourcemanipulation.h" +#include "util/clangdebug.h" + +using namespace KDevelop; + +SimpleRefactoring::SimpleRefactoring(QObject* parent) + : BasicRefactoring(parent) +{ + qRegisterMetaType(); +} + +void SimpleRefactoring::fillContextMenu(ContextMenuExtension& extension, Context* context) +{ + if (auto declContext = dynamic_cast(context)) { + DUChainReadLocker lock; + + if (auto declaration = declContext->declaration().data()) { + QFileInfo fileInfo(declaration->topContext()->url().str()); + if (!fileInfo.isWritable()) { + return; + } + + auto action = new QAction(i18n("Rename %1", declaration->qualifiedIdentifier().toString()), this); + action->setData(QVariant::fromValue(IndexedDeclaration(declaration))); + action->setIcon(QIcon::fromTheme(QStringLiteral("edit-rename"))); + connect(action, &QAction::triggered, this, &SimpleRefactoring::executeRenameAction); + + extension.addAction(ContextMenuExtension::RefactorGroup, action); + + if (!validCandidateToMoveIntoSource(declaration)) { + return; + } + + action = new QAction( + i18n("Create separate definition for %1", declaration->qualifiedIdentifier().toString()), this); + action->setData(QVariant::fromValue(IndexedDeclaration(declaration))); + connect(action, &QAction::triggered, this, &SimpleRefactoring::executeMoveIntoSourceAction); + extension.addAction(ContextMenuExtension::RefactorGroup, action); + } + } +} + +bool SimpleRefactoring::validCandidateToMoveIntoSource(Declaration* decl) +{ + if (!decl || !decl->isFunctionDeclaration() || !decl->type()) { + return false; + } + + if (!decl->internalContext() || decl->internalContext()->type() != DUContext::Function) { + return false; + } + + auto childCtx = decl->internalContext()->childContexts(); + if (childCtx.isEmpty()) { + return false; + } + + auto ctx = childCtx.first(); + if (!ctx || ctx->type() != DUContext::Other) { + return false; + } + + auto functionDecl = dynamic_cast(decl); + + if (!functionDecl || functionDecl->isInline()) { + return false; + } + + return true; +} + +QString SimpleRefactoring::moveIntoSource(const IndexedDeclaration& iDecl) +{ + DUChainReadLocker lock; + auto decl = iDecl.data(); + if (!decl) { + return i18n("No declaration under cursor"); + } + + const auto headerUrl = decl->url(); + auto targetUrl = headerUrl.str(); + + if (ClangHelpers::headerExtensions().contains(QFileInfo(targetUrl).suffix())) { + auto buddies = DocumentFinderHelpers::getPotentialBuddies(headerUrl.toUrl()); + for (const auto& buddy : buddies) { + const auto local = buddy.toLocalFile(); + if (QFileInfo::exists(local)) { + targetUrl = local; + break; + } + } + } + + if (targetUrl.isEmpty() || targetUrl == headerUrl.str()) { + // TODO: Create source file if it doesn't exist + return i18n("No source file available for %1.", headerUrl.str()); + } + + lock.unlock(); + const IndexedString indexedTargetUrl(targetUrl); + auto top + = DUChain::self()->waitForUpdate(headerUrl, KDevelop::TopDUContext::AllDeclarationsAndContexts); + auto targetTopContext + = DUChain::self()->waitForUpdate(indexedTargetUrl, KDevelop::TopDUContext::AllDeclarationsAndContexts); + lock.lock(); + + if (!targetTopContext) { + return i18n("Failed to update DUChain for %1.", targetUrl); + } + + if (!top || !iDecl.data() || iDecl.data() != decl) { + return i18n("Declaration lost while updating."); + } + + clangDebug() << "moving" << decl->qualifiedIdentifier(); + + if (!validCandidateToMoveIntoSource(decl)) { + return i18n("Cannot create definition for this declaration."); + } + + auto otherCtx = decl->internalContext()->childContexts().first(); + auto funcCtx = decl->internalContext(); + + auto code = createCodeRepresentation(headerUrl); + if (!code) { + return i18n("No document for %1", headerUrl.str()); + } + + auto bodyRange = otherCtx->rangeInCurrentRevision(); + + auto prefixRange(ClangIntegration::DUChainUtils::functionSignatureRange(decl)); + const auto prefixText = code->rangeText(prefixRange); + for (int i = prefixText.length() - 1; i >= 0 && prefixText.at(i).isSpace(); --i) { + if (bodyRange.start().column() == 0) { + bodyRange.setStart(bodyRange.start() - KTextEditor::Cursor(1, 0)); + if (bodyRange.start().line() == prefixRange.start().line()) { + bodyRange.setStart(KTextEditor::Cursor(bodyRange.start().line(), prefixRange.start().column() + i)); + } else { + int lastNewline = prefixText.lastIndexOf(QLatin1Char('\n'), i - 1); + bodyRange.setStart(KTextEditor::Cursor(bodyRange.start().line(), i - lastNewline - 1)); + } + } else { + bodyRange.setStart(bodyRange.start() - KTextEditor::Cursor(0, 1)); + } + } + + const QString body = code->rangeText(bodyRange); + SourceCodeInsertion ins(targetTopContext); + auto parentId = decl->internalContext()->parentContext()->scopeIdentifier(false); + + ins.setSubScope(parentId); + + QList signature; + + foreach (auto argument, funcCtx->localDeclarations()) { + SourceCodeInsertion::SignatureItem item; + item.name = argument->identifier().toString(); + item.type = argument->abstractType(); + signature.append(item); + } + + Identifier id(IndexedString(decl->qualifiedIdentifier().mid(parentId.count()).toString())); + clangDebug() << "id:" << id; + + auto funcType = decl->type(); + if (!ins.insertFunctionDeclaration(id, funcType->returnType(), signature, + funcType->modifiers() & AbstractType::ConstModifier, body)) { + return i18n("Insertion failed"); + } + lock.unlock(); + + auto applied = ins.changes().applyAllChanges(); + if (!applied) { + return i18n("Applying changes failed: %1", applied.m_failureReason); + } + + // replace function body with a semicolon + DocumentChangeSet changeHeader; + changeHeader.addChange(DocumentChange(headerUrl, bodyRange, body, QStringLiteral(";"))); + applied = changeHeader.applyAllChanges(); + if (!applied) { + return i18n("Applying changes failed: %1", applied.m_failureReason); + } + + ICore::self()->languageController()->backgroundParser()->addDocument(headerUrl); + ICore::self()->languageController()->backgroundParser()->addDocument(indexedTargetUrl); + + return {}; +} + +void SimpleRefactoring::executeMoveIntoSourceAction() +{ + auto action = qobject_cast(sender()); + Q_ASSERT(action); + + auto iDecl = action->data().value(); + if (!iDecl.isValid()) { + iDecl = declarationUnderCursor(false); + } + + const auto error = moveIntoSource(iDecl); + if (!error.isEmpty()) { + KMessageBox::error(nullptr, error); + } +} + +#include "moc_simplerefactoring.cpp" diff --git a/languages/clang/codegen/sourcemanipulation.h b/languages/clang/codegen/sourcemanipulation.h new file mode 100644 --- /dev/null +++ b/languages/clang/codegen/sourcemanipulation.h @@ -0,0 +1,80 @@ +/* + * This file is part of KDevelop + * + * Copyright 2009 David Nolden + * Copyright 2015 Sergey Kalinichev + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#ifndef CLANG_SOURCEMANIPULATION_H +#define CLANG_SOURCEMANIPULATION_H + +#include +#include +#include + +class SourceCodeInsertion : public QSharedData +{ +public: + explicit SourceCodeInsertion(KDevelop::TopDUContext* topContext); + ~SourceCodeInsertion(); + + /// Set optional sub-scope into which the code should be inserted, under 'context' + void setSubScope(const KDevelop::QualifiedIdentifier& scope); + + struct SignatureItem { + KDevelop::AbstractType::Ptr type; + QString name; + }; + + /// @param body function-body, including parens + bool insertFunctionDeclaration(const KDevelop::Identifier& name, const KDevelop::AbstractType::Ptr& returnType, + const QList& signature, + bool isConstant = false, const QString& body = QString()); + + KDevelop::DocumentChangeSet changes(); + +private: + + /// Returns the exact position where the item should be inserted so it is in the given line. + /// The inserted item has to start with a newline, and does not need to end with a newline. + KTextEditor::Range insertionRange(int line); + + /// Returns a line for inserting the given declaration + int findInsertionPoint() const; + + // Should apply m_scope to the given declaration string + QString applySubScope(const QString& decl) const; + + QString indentation() const; + QString applyIndentation(const QString& decl) const; + + KTextEditor::Cursor end() const; + +private: + KDevelop::DocumentChangeSet m_changeSet; + KDevelop::DUContext* m_context; + KDevelop::QualifiedIdentifier m_scope; + KDevelop::TopDUContext* m_topContext; + // Represents the whole code of the manipulated top-context for reading. + // Must be checked for zero before using. It is zero if the file could not be read. + const KDevelop::CodeRepresentation::Ptr m_codeRepresentation; +}; + +#endif // CLANG_SOURCEMANIPULATION_H diff --git a/languages/clang/codegen/sourcemanipulation.cpp b/languages/clang/codegen/sourcemanipulation.cpp new file mode 100644 --- /dev/null +++ b/languages/clang/codegen/sourcemanipulation.cpp @@ -0,0 +1,321 @@ +/* + * This file is part of KDevelop + * + * Copyright 2009 David Nolden + * Copyright 2015 Sergey Kalinichev + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include "sourcemanipulation.h" +#include +#include +#include + +#include +#include +#include +#include + +#include "codegenhelper.h" +#include "util/clangdebug.h" + +using namespace KDevelop; + +namespace +{ +QualifiedIdentifier stripPrefixes(DUContext* ctx, const QualifiedIdentifier& id) +{ + if (!ctx) { + return id; + } + + auto imports = ctx->fullyApplyAliases({}, ctx->topContext()); + if (imports.contains(id)) { + return {}; /// The id is a namespace that is imported into the current context + } + + auto basicDecls = ctx->findDeclarations(id, CursorInRevision::invalid(), {}, nullptr, + (DUContext::SearchFlags)(DUContext::NoSelfLookUp | DUContext::NoFiltering)); + + if (basicDecls.isEmpty()) { + return id; + } + + auto newId = id.mid(1); + auto result = id; + while (!newId.isEmpty()) { + auto foundDecls + = ctx->findDeclarations(newId, CursorInRevision::invalid(), {}, nullptr, + (DUContext::SearchFlags)(DUContext::NoSelfLookUp | DUContext::NoFiltering)); + + if (foundDecls == basicDecls) { + result = newId; // must continue to find the shortest possible identifier + // esp. for cases where nested namespaces are used (e.g. using namespace a::b::c;) + newId = newId.mid(1); + } + } + + return result; +} + +QString makeSignatureString(const QList& signature, DUContext* context) +{ + QString ret; + foreach (const auto& item, signature) { + if (!ret.isEmpty()) { + ret += QStringLiteral(", "); + } + + ret += CodegenHelper::simplifiedTypeString(item.type, context); + + if (!item.name.isEmpty()) { + ret += QStringLiteral(" ") + item.name; + } + } + return ret; +} + +// Re-indents the code so the leftmost line starts at zero +QString zeroIndentation(const QString& str, int fromLine = 0) +{ + QStringList lines = str.split(QLatin1Char('\n')); + QStringList ret; + + if (fromLine < lines.size()) { + ret = lines.mid(0, fromLine); + lines = lines.mid(fromLine); + } + + QRegExp nonWhiteSpace(QStringLiteral("\\S")); + int minLineStart = 10000; + foreach (const auto& line, lines) { + int lineStart = line.indexOf(nonWhiteSpace); + if (lineStart < minLineStart) { + minLineStart = lineStart; + } + } + + foreach (const auto& line, lines) { + ret << line.mid(minLineStart); + } + + return ret.join(QStringLiteral("\n")); +} +} + +DocumentChangeSet SourceCodeInsertion::changes() +{ + return m_changeSet; +} + +void SourceCodeInsertion::setSubScope(const QualifiedIdentifier& scope) +{ + m_scope = scope; + + if (!m_context) { + return; + } + + QStringList needNamespace = m_scope.toStringList(); + + bool foundChild = true; + while (!needNamespace.isEmpty() && foundChild) { + foundChild = false; + + foreach (DUContext* child, m_context->childContexts()) { + clangDebug() << "checking child" << child->localScopeIdentifier().toString() << "against" + << needNamespace.first(); + if (child->localScopeIdentifier().toString() == needNamespace.first() && child->type() == DUContext::Namespace) { + clangDebug() << "taking"; + m_context = child; + foundChild = true; + needNamespace.pop_front(); + break; + } + } + } + + m_scope = stripPrefixes(m_context, QualifiedIdentifier(needNamespace.join(QStringLiteral("::")))); +} + +QString SourceCodeInsertion::applySubScope(const QString& decl) const +{ + if (m_scope.isEmpty()) { + return decl; + } + + QString scopeType = QStringLiteral("namespace"); + QString scopeClose; + + if (m_context && m_context->type() == DUContext::Class) { + scopeType = QStringLiteral("struct"); + scopeClose = QStringLiteral(";"); + } + + QString ret; + foreach (const QString& scope, m_scope.toStringList()) { + ret += scopeType + QStringLiteral(" ") + scope + QStringLiteral(" {\n"); + } + + ret += decl; + ret += QStringLiteral("}") + scopeClose + QStringLiteral("\n").repeated(m_scope.count()); + + return ret; +} + +SourceCodeInsertion::SourceCodeInsertion(TopDUContext* topContext) + : m_context(topContext) + , m_topContext(topContext) + , m_codeRepresentation(createCodeRepresentation(m_topContext->url())) +{ +} + +SourceCodeInsertion::~SourceCodeInsertion() +{ +} + +KTextEditor::Cursor SourceCodeInsertion::end() const +{ + auto ret = m_context->rangeInCurrentRevision().end(); + if (m_codeRepresentation && m_codeRepresentation->lines() && dynamic_cast(m_context)) { + ret.setLine(m_codeRepresentation->lines() - 1); + ret.setColumn(m_codeRepresentation->line(ret.line()).size()); + } + return ret; +} + +QString SourceCodeInsertion::indentation() const +{ + if (!m_codeRepresentation || !m_context || m_context->localDeclarations(m_topContext).isEmpty()) { + clangDebug() << "cannot do indentation"; + return QString(); + } + + foreach (Declaration* decl, m_context->localDeclarations(m_topContext)) { + if (decl->range().isEmpty() || decl->range().start.column == 0) { + continue; // Skip declarations with empty range, that were expanded from macros + } + int spaces = 0; + + QString textLine = m_codeRepresentation->line(decl->range().start.line); + + for (int a = 0; a < textLine.size(); ++a) { + if (textLine.at(a).isSpace()) { + ++spaces; + } else { + break; + } + } + + return textLine.left(spaces); + } + + return {}; +} + +QString SourceCodeInsertion::applyIndentation(const QString& decl) const +{ + QStringList lines = decl.split(QLatin1Char('\n')); + QString ind = indentation(); + QStringList ret; + foreach (const QString& line, lines) { + if (!line.isEmpty()) { + ret << ind + line; + } else { + ret << line; + } + } + return ret.join(QStringLiteral("\n")); +} + +KTextEditor::Range SourceCodeInsertion::insertionRange(int line) +{ + if (line == 0 || !m_codeRepresentation) { + return KTextEditor::Range(line, 0, line, 0); + } + + KTextEditor::Range range(line - 1, m_codeRepresentation->line(line - 1).size(), line - 1, + m_codeRepresentation->line(line - 1).size()); + // If the context finishes on that line, then this will need adjusting + if (!m_context->rangeInCurrentRevision().contains(range)) { + range.start() = m_context->rangeInCurrentRevision().end(); + if (range.start().column() > 0) { + range.start() = range.start() - KTextEditor::Cursor(0, 1); + } + range.end() = range.start(); + } + + return range; +} + +bool SourceCodeInsertion::insertFunctionDeclaration(const Identifier& name, const AbstractType::Ptr& _returnType, + const QList& signature, bool isConstant, + const QString& body) +{ + if (!m_context) { + return false; + } + + auto returnType = _returnType; + + QString decl + = (returnType ? (CodegenHelper::simplifiedTypeString(returnType, m_context) + QStringLiteral(" ")) : QString()) + + name.toString() + QStringLiteral("(") + makeSignatureString(signature, m_context) + QStringLiteral(")"); + + if (isConstant) { + decl += QStringLiteral(" const"); + } + + if (body.isEmpty()) { + decl += QStringLiteral(";"); + } else { + if (!body.startsWith(QLatin1Char(' ')) && !body.startsWith(QLatin1Char('\n'))) { + decl += QStringLiteral(" "); + } + decl += zeroIndentation(body); + } + + int line = findInsertionPoint(); + + decl = QStringLiteral("\n\n") + applyIndentation(applySubScope(decl)); + + return m_changeSet.addChange(DocumentChange(m_context->url(), insertionRange(line), QString(), decl)); +} + +int SourceCodeInsertion::findInsertionPoint() const +{ + int line = end().line(); + + foreach (Declaration* decl, m_context->localDeclarations()) { + if (m_context->type() != DUContext::Class) { + if (dynamic_cast(decl)) { + line = decl->range().end.line + 1; + if (decl->internalContext()) { + line = decl->internalContext()->range().end.line + 1; + } + } + } + } + + clangDebug() << line << m_context->scopeIdentifier(true) << m_context->rangeInCurrentRevision() + << m_context->url().toUrl() << m_context->parentContext(); + clangDebug() << "count of declarations:" << m_context->topContext()->localDeclarations().size(); + + return line; +} diff --git a/languages/clang/tests/test_assistants.h b/languages/clang/tests/test_assistants.h --- a/languages/clang/tests/test_assistants.h +++ b/languages/clang/tests/test_assistants.h @@ -36,6 +36,9 @@ void testSignatureAssistant(); void testUnknownDeclarationAssistant_data(); void testUnknownDeclarationAssistant(); + + void testMoveIntoSource_data(); + void testMoveIntoSource(); }; #endif diff --git a/languages/clang/tests/test_assistants.cpp b/languages/clang/tests/test_assistants.cpp --- a/languages/clang/tests/test_assistants.cpp +++ b/languages/clang/tests/test_assistants.cpp @@ -19,14 +19,17 @@ #include "test_assistants.h" +#include "codegen/simplerefactoring.h" + #include #include #include #include #include #include +#include #include @@ -40,6 +43,7 @@ #include #include #include +#include #include #include @@ -532,3 +536,96 @@ QCOMPARE(hasMissingInclude, static_cast(actions & MissingInclude)); } } + +void TestAssistants::testMoveIntoSource() +{ + QFETCH(QString, origHeader); + QFETCH(QString, origImpl); + QFETCH(QString, newHeader); + QFETCH(QString, newImpl); + QFETCH(QualifiedIdentifier, id); + + TestFile header(origHeader, "h"); + TestFile impl("#include \"" + header.url().byteArray() + "\"\n" + origImpl, "cpp", &header); + + impl.parse(KDevelop::TopDUContext::AllDeclarationsContextsAndUses); + QVERIFY(impl.waitForParsed()); + + IndexedDeclaration declaration; + { + DUChainReadLocker lock; + auto headerCtx = DUChain::self()->chainForDocument(header.url()); + QVERIFY(headerCtx); + auto decls = headerCtx->findDeclarations(id); + Q_ASSERT(!decls.isEmpty()); + declaration = IndexedDeclaration(decls.first()); + QVERIFY(declaration.isValid()); + } + CodeRepresentation::setDiskChangesForbidden(false); + SimpleRefactoring refactoring; + QCOMPARE(refactoring.moveIntoSource(declaration), QString()); + CodeRepresentation::setDiskChangesForbidden(true); + + QCOMPARE(header.fileContents(), newHeader); + QVERIFY(impl.fileContents().endsWith(newImpl)); +} + +void TestAssistants::testMoveIntoSource_data() +{ + QTest::addColumn("origHeader"); + QTest::addColumn("origImpl"); + QTest::addColumn("newHeader"); + QTest::addColumn("newImpl"); + QTest::addColumn("id"); + + const QualifiedIdentifier fooId("foo"); + + QTest::newRow("globalfunction") << QString("int foo()\n{\n int i = 0;\n return 0;\n}\n") + << QString() + << QString("int foo();\n") + << QString("\nint foo()\n{\n int i = 0;\n return 0;\n}\n") + << fooId; + + QTest::newRow("staticfunction") << QString("static int foo()\n{\n int i = 0;\n return 0;\n}\n") + << QString() + << QString("static int foo();\n") + << QString("\nint foo()\n{\n int i = 0;\n return 0;\n}\n") + << fooId; + + QTest::newRow("funcsameline") << QString("int foo() {\n int i = 0;\n return 0;\n}\n") + << QString() + << QString("int foo();\n") + << QString("\nint foo() {\n int i = 0;\n return 0;\n}\n") + << fooId; + + QTest::newRow("func-comment") << QString("int foo()\n/* foobar */ {\n int i = 0;\n return 0;\n}\n") + << QString() + << QString("int foo()\n/* foobar */;\n") + << QString("\nint foo() {\n int i = 0;\n return 0;\n}\n") + << fooId; + + QTest::newRow("func-comment2") << QString("int foo()\n/*asdf*/\n{\n int i = 0;\n return 0;\n}\n") + << QString() + << QString("int foo()\n/*asdf*/;\n") + << QString("\nint foo()\n{\n int i = 0;\n return 0;\n}\n") + << fooId; + + const QualifiedIdentifier aFooId("a::foo"); + QTest::newRow("class-method") << QString("class a {\n int foo(){\n return 0;\n }\n};\n") + << QString() + << QString("class a {\n int foo();\n};\n") + << QString("\nint a::foo() {\n return 0;\n }\n") + << aFooId; + + QTest::newRow("class-method-const") << QString("class a {\n int foo() const\n {\n return 0;\n }\n};\n") + << QString() + << QString("class a {\n int foo() const;\n};\n") + << QString("\nint a::foo() const\n {\n return 0;\n }\n") + << aFooId; + + QTest::newRow("class-method-const-sameline") << QString("class a {\n int foo() const{\n return 0;\n }\n};\n") + << QString() + << QString("class a {\n int foo() const;\n};\n") + << QString("\nint a::foo() const {\n return 0;\n }\n") + << aFooId; +}