File indexing completed on 2024-05-19 15:44:51

0001 /*
0002     SPDX-FileCopyrightText: 2015 Sergey Kalinichev <kalinichev.so.0@gmail.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0005 */
0006 
0007 #include "clangrefactoring.h"
0008 
0009 #include <QAction>
0010 #include <QIcon>
0011 
0012 #include <interfaces/context.h>
0013 #include <interfaces/contextmenuextension.h>
0014 #include <language/duchain/classfunctiondeclaration.h>
0015 #include <language/duchain/duchain.h>
0016 #include <language/duchain/duchainlock.h>
0017 #include <language/duchain/functiondeclaration.h>
0018 #include <language/duchain/functiondefinition.h>
0019 #include <language/duchain/types/functiontype.h>
0020 #include <language/interfaces/codecontext.h>
0021 
0022 #include <interfaces/icore.h>
0023 #include <interfaces/ilanguagecontroller.h>
0024 #include <interfaces/iuicontroller.h>
0025 #include <language/backgroundparser/backgroundparser.h>
0026 #include <sublime/message.h>
0027 
0028 #include "duchain/clanghelpers.h"
0029 #include "duchain/documentfinderhelpers.h"
0030 #include "duchain/duchainutils.h"
0031 #include "sourcemanipulation.h"
0032 #include "util/clangdebug.h"
0033 
0034 using namespace KDevelop;
0035 
0036 namespace {
0037 
0038 bool isDestructor(Declaration* decl)
0039 {
0040     if (auto functionDef = dynamic_cast<FunctionDefinition*>(decl)) {
0041         // we found a definition, e.g. "Foo::~Foo()"
0042         const auto functionDecl = functionDef->declaration(decl->topContext());
0043         if (auto classFunctionDecl = dynamic_cast<ClassFunctionDeclaration*>(functionDecl)) {
0044             return classFunctionDecl->isDestructor();
0045         }
0046     }
0047     else if (auto classFunctionDecl = dynamic_cast<ClassFunctionDeclaration*>(decl)) {
0048         // we found a declaration, e.g. "~Foo()"
0049         return classFunctionDecl->isDestructor();
0050     }
0051 
0052     return false;
0053 }
0054 
0055 }
0056 
0057 ClangRefactoring::ClangRefactoring(QObject* parent)
0058     : BasicRefactoring(parent)
0059 {
0060     qRegisterMetaType<IndexedDeclaration>();
0061 }
0062 
0063 void ClangRefactoring::fillContextMenu(ContextMenuExtension& extension, Context* context, QWidget* parent)
0064 {
0065     auto declContext = dynamic_cast<DeclarationContext*>(context);
0066     if (!declContext) {
0067         return;
0068     }
0069 
0070     DUChainReadLocker lock;
0071 
0072     auto declaration = declContext->declaration().data();
0073     if (!declaration) {
0074         return;
0075     }
0076 
0077     QFileInfo fileInfo(declaration->topContext()->url().str());
0078     if (!fileInfo.isWritable()) {
0079         return;
0080     }
0081 
0082     auto action = new QAction(i18nc("@action", "Rename %1", declaration->qualifiedIdentifier().toString()), parent);
0083     action->setData(QVariant::fromValue(IndexedDeclaration(declaration)));
0084     action->setIcon(QIcon::fromTheme(QStringLiteral("edit-rename")));
0085     connect(action, &QAction::triggered, this, &ClangRefactoring::executeRenameAction);
0086 
0087     extension.addAction(ContextMenuExtension::RefactorGroup, action);
0088 
0089     if (!validCandidateToMoveIntoSource(declaration)) {
0090         return;
0091     }
0092 
0093     action = new QAction(
0094         i18n("Create separate definition for %1", declaration->qualifiedIdentifier().toString()), parent);
0095     action->setData(QVariant::fromValue(IndexedDeclaration(declaration)));
0096     connect(action, &QAction::triggered, this, &ClangRefactoring::executeMoveIntoSourceAction);
0097     extension.addAction(ContextMenuExtension::RefactorGroup, action);
0098 }
0099 
0100 bool ClangRefactoring::validCandidateToMoveIntoSource(Declaration* decl)
0101 {
0102     if (!decl || !decl->isFunctionDeclaration() || !decl->type<FunctionType>()) {
0103         return false;
0104     }
0105 
0106     if (!decl->internalContext() || decl->internalContext()->type() != DUContext::Function) {
0107         return false;
0108     }
0109 
0110     if (!decl->isDefinition()) {
0111         return false;
0112     }
0113 
0114     auto childCtx = decl->internalContext()->childContexts();
0115     if (childCtx.isEmpty()) {
0116         return false;
0117     }
0118 
0119     auto ctx = childCtx.first();
0120     if (!ctx || ctx->type() != DUContext::Other) {
0121         return false;
0122     }
0123 
0124     auto functionDecl = dynamic_cast<AbstractFunctionDeclaration*>(decl);
0125     if (!functionDecl || functionDecl->isInline()) {
0126         return false;
0127     }
0128 
0129     return true;
0130 }
0131 
0132 QString ClangRefactoring::moveIntoSource(const IndexedDeclaration& iDecl)
0133 {
0134     DUChainReadLocker lock;
0135     auto decl = iDecl.data();
0136     if (!decl) {
0137         return i18n("No declaration under cursor");
0138     }
0139 
0140     const auto headerUrl = decl->url();
0141     auto targetUrl = DocumentFinderHelpers::sourceForHeader(headerUrl.str());
0142 
0143     if (targetUrl.isEmpty() || targetUrl == headerUrl.str()) {
0144         // TODO: Create source file if it doesn't exist
0145         return i18n("No source file available for %1.", headerUrl.str());
0146     }
0147 
0148     lock.unlock();
0149     const IndexedString indexedTargetUrl(targetUrl);
0150     auto top
0151         = DUChain::self()->waitForUpdate(headerUrl, KDevelop::TopDUContext::AllDeclarationsAndContexts);
0152     auto targetTopContext
0153         = DUChain::self()->waitForUpdate(indexedTargetUrl, KDevelop::TopDUContext::AllDeclarationsAndContexts);
0154     lock.lock();
0155 
0156     if (!targetTopContext) {
0157         return i18n("Failed to update DUChain for %1.", targetUrl);
0158     }
0159 
0160     if (!top || !iDecl.data() || iDecl.data() != decl) {
0161         return i18n("Declaration lost while updating.");
0162     }
0163 
0164     clangDebug() << "moving" << decl->qualifiedIdentifier();
0165 
0166     if (!validCandidateToMoveIntoSource(decl)) {
0167         return i18n("Cannot create definition for this declaration.");
0168     }
0169 
0170     auto otherCtx = decl->internalContext()->childContexts().first();
0171 
0172     auto code = createCodeRepresentation(headerUrl);
0173     if (!code) {
0174         return i18n("No document for %1", headerUrl.str());
0175     }
0176 
0177     auto bodyRange = otherCtx->rangeInCurrentRevision();
0178 
0179     auto prefixRange(ClangIntegration::DUChainUtils::functionSignatureRange(decl));
0180     const auto prefixText = code->rangeText(prefixRange);
0181     for (int i = prefixText.length() - 1; i >= 0 && prefixText.at(i).isSpace(); --i) {
0182         if (bodyRange.start().column() == 0) {
0183             bodyRange.setStart(bodyRange.start() - KTextEditor::Cursor(1, 0));
0184             if (bodyRange.start().line() == prefixRange.start().line()) {
0185                 bodyRange.setStart(KTextEditor::Cursor(bodyRange.start().line(), prefixRange.start().column() + i));
0186             } else {
0187                 int lastNewline = prefixText.lastIndexOf(QLatin1Char('\n'), i - 1);
0188                 bodyRange.setStart(KTextEditor::Cursor(bodyRange.start().line(), i - lastNewline - 1));
0189             }
0190         } else {
0191             bodyRange.setStart(bodyRange.start() - KTextEditor::Cursor(0, 1));
0192         }
0193     }
0194 
0195     const QString body = code->rangeText(bodyRange);
0196     SourceCodeInsertion ins(targetTopContext);
0197     auto parentId = decl->internalContext()->parentContext()->scopeIdentifier(false);
0198 
0199     ins.setSubScope(parentId);
0200 
0201     Identifier id(IndexedString(decl->qualifiedIdentifier().mid(parentId.count()).toString()));
0202     clangDebug() << "id:" << id;
0203 
0204     if (!ins.insertFunctionDeclaration(decl, id, body)) {
0205         return i18n("Insertion failed");
0206     }
0207     lock.unlock();
0208 
0209     auto applied = ins.changes().applyAllChanges();
0210     if (!applied) {
0211         return i18n("Applying changes failed: %1", applied.m_failureReason);
0212     }
0213 
0214     // replace function body with a semicolon
0215     DocumentChangeSet changeHeader;
0216     changeHeader.addChange(DocumentChange(headerUrl, bodyRange, body, QStringLiteral(";")));
0217     applied = changeHeader.applyAllChanges();
0218     if (!applied) {
0219         return i18n("Applying changes failed: %1", applied.m_failureReason);
0220     }
0221 
0222     ICore::self()->languageController()->backgroundParser()->addDocument(headerUrl);
0223     ICore::self()->languageController()->backgroundParser()->addDocument(indexedTargetUrl);
0224 
0225     return {};
0226 }
0227 
0228 void ClangRefactoring::executeMoveIntoSourceAction()
0229 {
0230     auto action = qobject_cast<QAction*>(sender());
0231     Q_ASSERT(action);
0232 
0233     auto iDecl = action->data().value<IndexedDeclaration>();
0234     if (!iDecl.isValid()) {
0235         iDecl = declarationUnderCursor(false);
0236     }
0237 
0238     const auto error = moveIntoSource(iDecl);
0239     if (!error.isEmpty()) {
0240         auto* message = new Sublime::Message(error, Sublime::Message::Error);
0241         ICore::self()->uiController()->postMessage(message);
0242     }
0243 }
0244 
0245 DocumentChangeSet::ChangeResult ClangRefactoring::applyChangesToDeclarations(const QString& oldName,
0246                                                                              const QString& newName,
0247                                                                              DocumentChangeSet& changes,
0248                                                                              const QList<IndexedDeclaration>& declarations)
0249 {
0250     for (const IndexedDeclaration decl : declarations) {
0251         Declaration *declaration = decl.data();
0252         if (!declaration)
0253             continue;
0254 
0255         if (declaration->range().isEmpty())
0256             clangDebug() << "found empty declaration:" << declaration->toString();
0257 
0258         // special handling for dtors, their name is not "Foo", but "~Foo"
0259         // see https://bugs.kde.org/show_bug.cgi?id=373452
0260         QString fixedOldName = oldName;
0261         QString fixedNewName = newName;
0262 
0263         if (isDestructor(declaration)) {
0264             clangDebug() << "found destructor:" << declaration->toString() << "-- making sure we replace the identifier correctly";
0265             fixedOldName = QLatin1Char('~') + oldName;
0266             fixedNewName = QLatin1Char('~') + newName;
0267         }
0268 
0269         TopDUContext *top = declaration->topContext();
0270         DocumentChangeSet::ChangeResult result = changes.addChange(DocumentChange(top->url(), declaration->rangeInCurrentRevision(), fixedOldName, fixedNewName));
0271         if (!result)
0272             return result;
0273     }
0274 
0275     return KDevelop::DocumentChangeSet::ChangeResult::successfulResult();
0276 }
0277 
0278 #include "moc_clangrefactoring.cpp"