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"