File indexing completed on 2024-05-12 04:39:09

0001 /*
0002     SPDX-FileCopyrightText: 2009 David Nolden <david.nolden.kdevelop@art-master.de>
0003     SPDX-FileCopyrightText: 2015 Sergey Kalinichev <kalinichev.so.0@gmail.com>
0004 
0005     SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0006 */
0007 
0008 #include "sourcemanipulation.h"
0009 
0010 #include <interfaces/icore.h>
0011 #include <interfaces/isourceformattercontroller.h>
0012 
0013 #include <language/codegen/coderepresentation.h>
0014 
0015 #include <language/duchain/abstractfunctiondeclaration.h>
0016 #include <language/duchain/classdeclaration.h>
0017 #include <language/duchain/classfunctiondeclaration.h>
0018 #include <language/duchain/classmemberdeclaration.h>
0019 #include <language/duchain/types/enumeratortype.h>
0020 #include <language/duchain/types/functiontype.h>
0021 
0022 #include "codegenhelper.h"
0023 #include "adaptsignatureaction.h"
0024 #include "util/clangdebug.h"
0025 
0026 using namespace KDevelop;
0027 
0028 namespace
0029 {
0030 QualifiedIdentifier stripPrefixes(const DUContextPointer& ctx, const QualifiedIdentifier& id)
0031 {
0032     if (!ctx) {
0033         return id;
0034     }
0035 
0036     auto imports = ctx->fullyApplyAliases({}, ctx->topContext());
0037     if (imports.contains(id)) {
0038         return {}; /// The id is a namespace that is imported into the current context
0039     }
0040 
0041     auto basicDecls = ctx->findDeclarations(id, CursorInRevision::invalid(), {}, nullptr,
0042                                             (DUContext::SearchFlags)(DUContext::NoSelfLookUp | DUContext::NoFiltering));
0043 
0044     if (basicDecls.isEmpty()) {
0045         return id;
0046     }
0047 
0048     auto newId = id.mid(1);
0049     auto result = id;
0050     while (!newId.isEmpty()) {
0051         auto foundDecls
0052             = ctx->findDeclarations(newId, CursorInRevision::invalid(), {}, nullptr,
0053                                     (DUContext::SearchFlags)(DUContext::NoSelfLookUp | DUContext::NoFiltering));
0054 
0055         if (foundDecls == basicDecls) {
0056             result = newId; // must continue to find the shortest possible identifier
0057             // esp. for cases where nested namespaces are used (e.g. using namespace a::b::c;)
0058             newId = newId.mid(1);
0059         }
0060     }
0061 
0062     return result;
0063 }
0064 
0065 // Re-indents the code so the leftmost line starts at zero
0066 QString zeroIndentation(const QString& str, int fromLine = 0)
0067 {
0068     QStringList lines = str.split(QLatin1Char('\n'));
0069     QStringList ret;
0070 
0071     if (fromLine < lines.size()) {
0072         ret = lines.mid(0, fromLine);
0073         lines = lines.mid(fromLine);
0074     }
0075 
0076     QRegExp nonWhiteSpace(QStringLiteral("\\S"));
0077     int minLineStart = 10000;
0078     for (const auto& line : qAsConst(lines)) {
0079         int lineStart = line.indexOf(nonWhiteSpace);
0080         if (lineStart < minLineStart) {
0081             minLineStart = lineStart;
0082         }
0083     }
0084 
0085     ret.reserve(ret.size() + lines.size());
0086     for (const auto& line : qAsConst(lines)) {
0087         ret << line.mid(minLineStart);
0088     }
0089 
0090     return ret.join(QLatin1Char('\n'));
0091 }
0092 }
0093 
0094 DocumentChangeSet SourceCodeInsertion::changes()
0095 {
0096     return m_changeSet;
0097 }
0098 
0099 void SourceCodeInsertion::setSubScope(const QualifiedIdentifier& scope)
0100 {
0101     m_scope = scope;
0102 
0103     if (!m_context) {
0104         return;
0105     }
0106 
0107     QStringList needNamespace = m_scope.toStringList();
0108 
0109     bool foundChild = true;
0110     while (!needNamespace.isEmpty() && foundChild) {
0111         foundChild = false;
0112 
0113         const auto childContexts = m_context->childContexts();
0114         for (DUContext* child : childContexts) {
0115             clangDebug() << "checking child" << child->localScopeIdentifier().toString() << "against"
0116                      << needNamespace.first();
0117             if (child->localScopeIdentifier().toString() == needNamespace.first() && child->type() == DUContext::Namespace) {
0118                 clangDebug() << "taking";
0119                 m_context = child;
0120                 foundChild = true;
0121                 needNamespace.pop_front();
0122                 break;
0123             }
0124         }
0125     }
0126 
0127     m_scope = stripPrefixes(m_context, QualifiedIdentifier(needNamespace.join(QLatin1String("::"))));
0128 }
0129 
0130 QString SourceCodeInsertion::applySubScope(const QString& decl) const
0131 {
0132     if (m_scope.isEmpty()) {
0133         return decl;
0134     }
0135 
0136     const bool isClassContext = (m_context && m_context->type() == DUContext::Class);
0137     const QLatin1String scopeType = isClassContext ? QLatin1String("struct ") : QLatin1String("namespace ");
0138     const QLatin1String scopeClose = isClassContext ? QLatin1String(";") : QLatin1String("");
0139 
0140     QString ret;
0141     const auto scopes = m_scope.toStringList();
0142     for (const QString& scope : scopes) {
0143         ret += scopeType + scope + QLatin1String(" {\n");
0144     }
0145 
0146     ret += decl;
0147     ret += QString(QLatin1Char('}') + scopeClose + QLatin1Char('\n')).repeated(m_scope.count());
0148 
0149     return ret;
0150 }
0151 
0152 SourceCodeInsertion::SourceCodeInsertion(TopDUContext* topContext)
0153     : m_context(topContext)
0154     , m_topContext(topContext)
0155     , m_codeRepresentation(createCodeRepresentation(m_topContext->url()))
0156 {
0157 }
0158 
0159 SourceCodeInsertion::~SourceCodeInsertion()
0160 {
0161 }
0162 
0163 KTextEditor::Cursor SourceCodeInsertion::end() const
0164 {
0165     auto ret = m_context->rangeInCurrentRevision().end();
0166     if (m_codeRepresentation && m_codeRepresentation->lines() && dynamic_cast<TopDUContext*>(m_context.data())) {
0167         ret.setLine(m_codeRepresentation->lines() - 1);
0168         ret.setColumn(m_codeRepresentation->line(ret.line()).size());
0169     }
0170     return ret;
0171 }
0172 
0173 KTextEditor::Range SourceCodeInsertion::insertionRange(int line)
0174 {
0175     if (line == 0 || !m_codeRepresentation) {
0176         return KTextEditor::Range(line, 0, line, 0);
0177     }
0178 
0179     KTextEditor::Range range(line - 1, m_codeRepresentation->line(line - 1).size(), line - 1,
0180                              m_codeRepresentation->line(line - 1).size());
0181     // If the context finishes on that line, then this will need adjusting
0182     if (!m_context->rangeInCurrentRevision().contains(range)) {
0183         range.start() = m_context->rangeInCurrentRevision().end();
0184         if (range.start().column() > 0) {
0185             range.start() = range.start() - KTextEditor::Cursor(0, 1);
0186         }
0187         range.end() = range.start();
0188     }
0189 
0190     return range;
0191 }
0192 
0193 bool SourceCodeInsertion::insertFunctionDeclaration(KDevelop::Declaration* declaration, const Identifier& id, const QString& body)
0194 {
0195     if (!m_context) {
0196         return false;
0197     }
0198 
0199     Signature signature;
0200     const auto localDeclarations = declaration->internalContext()->localDeclarations();
0201     signature.parameters.reserve(localDeclarations.count());
0202     std::transform(localDeclarations.begin(), localDeclarations.end(),
0203                    std::back_inserter(signature.parameters),
0204                    [] (Declaration* argument) -> ParameterItem
0205                    { return {IndexedType(argument->indexedType()), argument->identifier().toString()}; });
0206 
0207     auto funcType = declaration->type<FunctionType>();
0208     auto returnType = funcType->returnType();
0209     if (auto classFunDecl = dynamic_cast<const ClassFunctionDeclaration*>(declaration)) {
0210         if (classFunDecl->isConstructor() || classFunDecl->isDestructor()) {
0211             returnType = nullptr;
0212         }
0213     }
0214     signature.returnType = IndexedType(returnType);
0215     signature.isConst = funcType->modifiers() & AbstractType::ConstModifier;
0216 
0217     QString decl = CodegenHelper::makeSignatureString(declaration, signature, true);
0218     decl.replace(declaration->qualifiedIdentifier().toString(), id.toString());
0219 
0220     if (body.isEmpty()) {
0221         decl += QLatin1Char(';');
0222     } else {
0223         if (!body.startsWith(QLatin1Char(' ')) && !body.startsWith(QLatin1Char('\n'))) {
0224             decl += QLatin1Char(' ');
0225         }
0226         decl += zeroIndentation(body);
0227     }
0228 
0229     int line = findInsertionPoint();
0230 
0231     decl = QLatin1String("\n\n") + applySubScope(decl);
0232     const auto formatter = ICore::self()->sourceFormatterController()->fileFormatter(declaration->url().toUrl());
0233     if (formatter) {
0234         decl = formatter->format(decl);
0235     }
0236 
0237     return m_changeSet.addChange(DocumentChange(m_context->url(), insertionRange(line), QString(), decl));
0238 }
0239 
0240 int SourceCodeInsertion::findInsertionPoint() const
0241 {
0242     int line = end().line();
0243 
0244     const auto localDeclarations = m_context->localDeclarations();
0245     for (auto* decl : localDeclarations) {
0246         if (m_context->type() == DUContext::Class) {
0247             continue;
0248         }
0249 
0250         if (!dynamic_cast<AbstractFunctionDeclaration*>(decl)) {
0251             continue;
0252         }
0253 
0254         line = decl->range().end.line + 1;
0255         if (decl->internalContext()) {
0256             line = decl->internalContext()->range().end.line + 1;
0257         }
0258     }
0259 
0260     clangDebug() << line << m_context->scopeIdentifier(true) << m_context->rangeInCurrentRevision()
0261              << m_context->url().toUrl() << m_context->parentContext();
0262     clangDebug() << "count of declarations:" << m_context->topContext()->localDeclarations().size();
0263 
0264     return line;
0265 }