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

0001 /*
0002     SPDX-FileCopyrightText: 2014 Sergey Kalinichev <kalinichev.so.0@gmail.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "documentfinderhelpers.h"
0008 
0009 #include "duchain/clanghelpers.h"
0010 
0011 #include <language/duchain/duchain.h>
0012 #include <language/duchain/declaration.h>
0013 #include <language/duchain/functiondeclaration.h>
0014 #include <language/duchain/functiondefinition.h>
0015 #include <language/duchain/duchainutils.h>
0016 
0017 #include <KDesktopFile>
0018 
0019 using namespace KDevelop;
0020 
0021 namespace {
0022 
0023 enum FileType {
0024     Unknown, ///< Doesn't belong to C++
0025     Header,  ///< Is a header file
0026     Source   ///< Is a C(++) file
0027 };
0028 
0029 class PotentialBuddyCollector : public DUChainUtils::DUChainItemFilter
0030 {
0031 public:
0032     enum BuddyMode {
0033         Header,
0034         Source
0035     };
0036 
0037     explicit PotentialBuddyCollector(BuddyMode mode)
0038      : mode(mode)
0039     {}
0040 
0041     bool accept(Declaration* decl) override
0042     {
0043         if (decl->range().isEmpty())
0044             return false;
0045 
0046         if (mode == Header && decl->isFunctionDeclaration()) {
0047             // Search for definitions of our declarations
0048             auto* def = FunctionDefinition::definition(decl);
0049             if (def) {
0050                 vote(def->url().toUrl());
0051             }
0052 
0053             return true;
0054         }
0055         else if (mode == Source && decl->isFunctionDeclaration()) {
0056             auto* fdef = dynamic_cast<FunctionDefinition*>(decl);
0057             if (fdef) {
0058                 Declaration* fdecl = fdef->declaration();
0059                 if (fdecl) {
0060                     vote(fdecl->url().toUrl());
0061                 }
0062             }
0063 
0064             return true;
0065         } else {
0066             return false;
0067         }
0068     }
0069     bool accept(DUContext* ctx) override
0070     {
0071         if (ctx->type() == DUContext::Class || ctx->type() == DUContext::Namespace || ctx->type() == DUContext::Global || ctx->type() == DUContext::Other || ctx->type() == DUContext::Helper ) {
0072             return true;
0073         } else {
0074             return false;
0075         }
0076     }
0077 
0078     QUrl bestBuddy() const
0079     {
0080         QUrl ret;
0081         int bestCount = 0;
0082         for (auto it = m_buddyFiles.begin(); it != m_buddyFiles.end(); ++it) {
0083             if(it.value() > bestCount) {
0084                 bestCount = it.value();
0085                 ret = it.key();
0086             }
0087         }
0088 
0089         return ret;
0090     }
0091 private:
0092     BuddyMode mode;
0093     QHash<QUrl, int> m_buddyFiles;
0094 
0095     void vote(const QUrl& url)
0096     {
0097         m_buddyFiles[url]++;
0098     }
0099 };
0100 
0101 /**
0102  * Tries to find a buddy file to the given file by looking at the DUChain.
0103  *
0104  * The project might keep source files separate from headers. To cover
0105  * this situation, we examine DUChain for the most probable buddy file.
0106  * This of course only works if we have parsed the buddy file, but it is
0107  * better than nothing.
0108  *
0109  * @param url url of the source/header file to find a buddy for
0110  * @param type type of the file @p url
0111  *
0112  * @returns QUrl of the most probable buddy file, or an empty url
0113  **/
0114 QUrl duchainBuddyFile(const QUrl& url, FileType type)
0115 {
0116     DUChainReadLocker lock;
0117 
0118     auto ctx = DUChainUtils::standardContextForUrl(url);
0119     if (ctx) {
0120         PotentialBuddyCollector collector( type == Header ? PotentialBuddyCollector::Header : PotentialBuddyCollector::Source );
0121         DUChainUtils::collectItems(ctx, collector);
0122 
0123         return collector.bestBuddy();
0124     }
0125 
0126     return QUrl();
0127 }
0128 
0129 /**
0130  * Generates the base path (without extension) and the file type
0131  * for the specified url.
0132  *
0133  * @returns pair of base path and file type which has been found for @p url.
0134  */
0135 QPair<QString,FileType> basePathAndTypeForUrl(const QUrl &url)
0136 {
0137     QString path = url.toLocalFile();
0138     int idxSlash = path.lastIndexOf(QLatin1Char('/'));
0139     int idxDot = path.lastIndexOf(QLatin1Char('.'));
0140     FileType fileType = Unknown;
0141     QString basePath;
0142     if (idxSlash >= 0 && idxDot >= 0 && idxDot > idxSlash) {
0143         basePath = path.left(idxDot);
0144         if (idxDot + 1 < path.length()) {
0145             QString extension = path.mid(idxDot + 1);
0146             if (ClangHelpers::isHeader(extension)) {
0147                 fileType = Header;
0148             } else if (ClangHelpers::isSource(extension)) {
0149                 fileType = Source;
0150             }
0151         }
0152     } else {
0153         basePath = path;
0154     }
0155 
0156     return qMakePair(basePath, fileType);
0157 }
0158 
0159 }
0160 
0161 namespace DocumentFinderHelpers {
0162 QStringList mimeTypesList()
0163 {
0164     static const QStringList mimeTypes = {
0165         QStringLiteral("text/x-chdr"),
0166         QStringLiteral("text/x-c++hdr"),
0167         QStringLiteral("text/vnd.nvidia.cuda.chdr"),
0168         QStringLiteral("text/x-csrc"),
0169         QStringLiteral("text/x-c++src"),
0170         QStringLiteral("text/vnd.nvidia.cuda.csrc"),
0171         QStringLiteral("text/x-objcsrc")
0172     };
0173     return mimeTypes;
0174 }
0175 
0176 bool areBuddies(const QUrl &url1, const QUrl& url2)
0177 {
0178     auto type1 = basePathAndTypeForUrl(url1);
0179     auto type2 = basePathAndTypeForUrl(url2);
0180 
0181     QUrl headerPath;
0182     QUrl sourcePath;
0183 
0184     // Check that one file is a header, the other one is source
0185     if (type1.second == Header && type2.second == Source) {
0186         headerPath = url1;
0187         sourcePath = url2;
0188     } else if (type1.second == Source && type2.second == Header) {
0189         headerPath = url2;
0190         sourcePath = url1;
0191     } else {
0192         // Some other file constellation
0193         return false;
0194     }
0195 
0196     // The simplest directory layout is with header + source in one directory.
0197     // So check that first.
0198     if (type1.first == type2.first) {
0199         return true;
0200     }
0201 
0202     // Also check if the DUChain thinks this is likely
0203     if (duchainBuddyFile(sourcePath, Source) == headerPath) {
0204         return true;
0205     }
0206 
0207     return false;
0208 }
0209 
0210 bool buddyOrder(const QUrl &url1, const QUrl& url2)
0211 {
0212     auto type1 = basePathAndTypeForUrl(url1);
0213     auto type2 = basePathAndTypeForUrl(url2);
0214     // Precondition is that the two URLs are buddies, so don't check it
0215     return(type1.second == Header && type2.second == Source);
0216 }
0217 
0218 QVector<QUrl> potentialBuddies(const QUrl& url, bool checkDUChain)
0219 {
0220     auto type = basePathAndTypeForUrl(url);
0221     // Don't do anything for types we don't know
0222     if (type.second == Unknown) {
0223         return {};
0224     }
0225 
0226     // Depending on the buddy's file type we either generate source extensions (for headers)
0227     // or header extensions (for sources)
0228     const auto& extensions = ( type.second == Header ? ClangHelpers::sourceExtensions() : ClangHelpers::headerExtensions() );
0229     QVector< QUrl > buddies;
0230     buddies.reserve(extensions.size());
0231     for(const QString& extension : extensions) {
0232         if (!extension.contains(QLatin1Char('.'))) {
0233             buddies.append(QUrl::fromLocalFile(type.first + QLatin1Char('.') + extension));
0234         } else {
0235             buddies.append(QUrl::fromLocalFile(type.first + extension));
0236         }
0237     }
0238 
0239     if (checkDUChain) {
0240         // Also ask DUChain for a guess
0241         QUrl bestBuddy = duchainBuddyFile(url, type.second);
0242         if (!buddies.contains(bestBuddy)) {
0243             buddies.append(bestBuddy);
0244         }
0245     }
0246 
0247     return buddies;
0248 }
0249 
0250 QString sourceForHeader(const QString& headerPath)
0251 {
0252     if (!ClangHelpers::isHeader(headerPath)) {
0253         return {};
0254     }
0255 
0256     QString targetUrl;
0257     const auto buddies = DocumentFinderHelpers::potentialBuddies(QUrl::fromLocalFile(headerPath));
0258     for (const auto& buddy : buddies) {
0259         const auto local = buddy.toLocalFile();
0260         if (QFileInfo::exists(local)) {
0261             targetUrl = local;
0262             break;
0263         }
0264     }
0265 
0266     return targetUrl;
0267 }
0268 
0269 }