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

0001 /*
0002     SPDX-FileCopyrightText: 2008 Hamish Rodda <rodda@kde.org>
0003     SPDX-FileCopyrightText: 2012 Miha Čančula <miha@noughmad.eu>
0004     SPDX-FileCopyrightText: 2017 Friedrich W. H. Kossebau <kossebau@kde.org>
0005 
0006     SPDX-License-Identifier: LGPL-2.0-or-later
0007 */
0008 
0009 #include "clangclasshelper.h"
0010 
0011 #include "util/clangdebug.h"
0012 #include "duchain/unknowndeclarationproblem.h"
0013 
0014 #include <interfaces/iprojectcontroller.h>
0015 #include <language/duchain/duchain.h>
0016 #include <language/duchain/duchainlock.h>
0017 #include <language/duchain/topducontext.h>
0018 #include <language/duchain/declaration.h>
0019 #include <language/codegen/documentchangeset.h>
0020 #include <language/codegen/codedescription.h>
0021 #include <custom-definesandincludes/idefinesandincludesmanager.h>
0022 #include <project/projectmodel.h>
0023 #include <util/path.h>
0024 
0025 #include <QTemporaryFile>
0026 #include <QDir>
0027 
0028 using namespace KDevelop;
0029 
0030 ClangClassHelper::ClangClassHelper()
0031 {
0032 }
0033 
0034 ClangClassHelper::~ClangClassHelper() = default;
0035 
0036 TemplateClassGenerator* ClangClassHelper::createGenerator(const QUrl& baseUrl)
0037 {
0038     return new ClangTemplateNewClass(baseUrl);
0039 }
0040 
0041 QList<DeclarationPointer> ClangClassHelper::defaultMethods(const QString& name) const
0042 {
0043     // TODO: this is the oldcpp approach, perhaps clang provides this directly?
0044     // TODO: default destructor misses info about virtualness, possible needs ICreateClassHelper change?
0045 
0046     QTemporaryFile file(QDir::tempPath() + QLatin1String("/class_") + name + QLatin1String("_XXXXXX.cpp"));
0047     file.open();
0048     QTextStream stream(&file);
0049     stream << "class " << name << " {\n"
0050     << "  public:\n"
0051     // default ctor
0052     << "    " << name << "();\n"
0053     // copy ctor
0054     << "    " << name << "(const " << name << "& other);\n"
0055     // default dtor
0056     << "    ~" << name << "();\n"
0057     // assignment operator
0058     << "    " << name << "& operator=(const " << name << "& other);\n"
0059     // equality operators
0060     << "    bool operator==(const " << name << "& other) const;\n"
0061     << "    bool operator!=(const " << name << "& other) const;\n"
0062     << "};\n";
0063     file.close();
0064     ReferencedTopDUContext context(DUChain::self()->waitForUpdate(IndexedString(file.fileName()),
0065                                                                   TopDUContext::AllDeclarationsAndContexts));
0066     QList<DeclarationPointer> methods;
0067     {
0068         DUChainReadLocker lock;
0069 
0070         if (context && context->childContexts().size() == 1) {
0071             const auto localDeclarations = context->childContexts().first()->localDeclarations();
0072             methods.reserve(localDeclarations.size());
0073             for (auto* declaration : localDeclarations) {
0074                 methods << DeclarationPointer(declaration);
0075             }
0076         }
0077     }
0078 
0079     return methods;
0080 }
0081 
0082 ClangTemplateNewClass::ClangTemplateNewClass(const QUrl& url)
0083     : TemplateClassGenerator(url)
0084 {
0085 }
0086 
0087 ClangTemplateNewClass::~ClangTemplateNewClass() = default;
0088 
0089 namespace {
0090 
0091 QString includeArgumentForFile(const QString& includefile, const Path::List& includePaths,
0092                                const Path& source)
0093 {
0094     const auto sourceFolder = source.parent();
0095     const Path canonicalFile(QFileInfo(includefile).canonicalFilePath());
0096 
0097     QString shortestDirective;
0098     bool isRelative = false;
0099 
0100     // we can include the file directly
0101     if (sourceFolder == canonicalFile.parent()) {
0102         shortestDirective = canonicalFile.lastPathSegment();
0103         isRelative = true;
0104     } else {
0105         // find the include directive with the shortest length
0106         for (const auto& includePath : includePaths) {
0107             QString relative = includePath.relativePath(canonicalFile);
0108             if (relative.startsWith(QLatin1String("./"))) {
0109                 relative.remove(0, 2);
0110             }
0111 
0112             if (shortestDirective.isEmpty() || relative.length() < shortestDirective.length()) {
0113                 shortestDirective = relative;
0114                 isRelative = (includePath == sourceFolder);
0115             }
0116         }
0117     }
0118 
0119     // Item not found in include path?
0120     if (shortestDirective.isEmpty()) {
0121         return {};
0122     }
0123 
0124     if (isRelative) {
0125         return QLatin1Char('\"') + shortestDirective + QLatin1Char('\"');
0126     }
0127     return QLatin1Char('<') + shortestDirective + QLatin1Char('>');
0128 }
0129 
0130 QString includeDirectiveArgumentFromPath(const Path& file,
0131                                          const DeclarationPointer& declaration)
0132 {
0133     const auto includeManager = IDefinesAndIncludesManager::manager();
0134     const auto filePath = file.toLocalFile();
0135     const auto projectModel = ICore::self()->projectController()->projectModel();
0136     auto item = projectModel->itemForPath(IndexedString(filePath));
0137 
0138     if (!item) {
0139         // try the folder where the file is placed and guess includes from there
0140         // prefer target over file
0141         const auto folderPath = IndexedString(file.parent().toLocalFile());
0142         clangDebug() << "File not known, guessing includes from items in folder:" << folderPath.str();
0143 
0144         // default to the folder, if no targets or files
0145         item = projectModel->itemForPath(folderPath);
0146         if (item) {
0147             const auto targetItems = item->targetList();
0148             bool itemChosen = false;
0149             // Prefer items defined inside a target with non-empty includes.
0150             for (const auto& targetItem : targetItems) {
0151                 item = targetItem;
0152                 if (!includeManager->includes(targetItem, IDefinesAndIncludesManager::ProjectSpecific).isEmpty()) {
0153                     clangDebug() << "Guessing includes from target" << targetItem->baseName();
0154                     itemChosen = true;
0155                     break;
0156                 }
0157             }
0158             if (!itemChosen) {
0159                 const auto fileItems = item->fileList();
0160                 // Prefer items defined inside a target with non-empty includes.
0161                 for (const auto& fileItem : fileItems) {
0162                     item = fileItem;
0163                     if (!includeManager->includes(fileItem, IDefinesAndIncludesManager::ProjectSpecific).isEmpty()) {
0164                         clangDebug() << "Guessing includes from file" << fileItem->baseName();
0165                         break;
0166                     }
0167                 }
0168             }
0169         }
0170     }
0171 
0172     const auto includePaths = includeManager->includes(item);
0173 
0174     if (includePaths.isEmpty()) {
0175         clangDebug() << "Include path is empty";
0176         return {};
0177     }
0178 
0179     clangDebug() << "found include paths for" << file << ":" << includePaths;
0180 
0181     const auto includeFiles = UnknownDeclarationProblem::findMatchingIncludeFiles(QVector<Declaration*> {declaration.data()});
0182     if (includeFiles.isEmpty()) {
0183         // return early as the computation of the include paths is quite expensive
0184         return {};
0185     }
0186 
0187     // create include arguments for all candidates
0188     QStringList includeArguments;
0189     includeArguments.reserve(includeFiles.size());
0190     for (const auto& includeFile : includeFiles) {
0191         const auto includeArgument = includeArgumentForFile(includeFile, includePaths, file);
0192         if (includeArgument.isEmpty()) {
0193             clangDebug() << "unable to create include argument for" << includeFile << "in" << file.toLocalFile();
0194         }
0195         includeArguments << includeArgument;
0196     }
0197 
0198     if (includeArguments.isEmpty()) {
0199         return {};
0200     }
0201 
0202     std::sort(includeArguments.begin(), includeArguments.end(),
0203               [](const QString& lhs, const QString& rhs) {
0204         return lhs.length() < rhs.length();
0205     });
0206 
0207     return includeArguments.at(0);
0208 }
0209 
0210 template<typename Map>
0211 void addVariables(QVariantHash* variables, QLatin1String suffix, const Map& map)
0212 {
0213     for (auto it = map.begin(), end = map.end(); it != end; ++it) {
0214         variables->insert(it.key() + suffix, CodeDescription::toVariantList(it.value()));
0215     }
0216 }
0217 
0218 }
0219 
0220 QVariantHash ClangTemplateNewClass::extraVariables() const
0221 {
0222     QVariantHash variables;
0223 
0224     const QString publicAccess = QStringLiteral("public");
0225 
0226     QHash<QString, VariableDescriptionList> variableDescriptions;
0227     QHash<QString, FunctionDescriptionList> functionDescriptions;
0228     QHash<QString, FunctionDescriptionList> slotDescriptions;
0229     FunctionDescriptionList signalDescriptions;
0230 
0231     const auto desc = description();
0232     for (const auto& function : desc.methods) {
0233         const QString& access = function.access.isEmpty() ? publicAccess : function.access;
0234 
0235         if (function.isSignal) {
0236             signalDescriptions << function;
0237         } else if (function.isSlot) {
0238             slotDescriptions[access] << function;
0239         } else {
0240             functionDescriptions[access] << function;
0241         }
0242     }
0243 
0244     for (const auto& variable : desc.members) {
0245         const QString& access = variable.access.isEmpty() ? publicAccess : variable.access;
0246 
0247         variableDescriptions[access] << variable;
0248     }
0249 
0250     ::addVariables(&variables, QLatin1String("_members"), variableDescriptions);
0251     ::addVariables(&variables, QLatin1String("_functions"), functionDescriptions);
0252     ::addVariables(&variables, QLatin1String("_slots"), slotDescriptions);
0253 
0254     variables[QStringLiteral("signals")] = CodeDescription::toVariantList(signalDescriptions);
0255     variables[QStringLiteral("needs_qobject_macro")] = !slotDescriptions.isEmpty() || !signalDescriptions.isEmpty();
0256 
0257     QStringList includedFiles;
0258     DUChainReadLocker locker(DUChain::lock());
0259 
0260     QUrl sourceUrl;
0261     const auto urls = fileUrls();
0262     if (!urls.isEmpty()) {
0263         sourceUrl = urls.constBegin().value();
0264     } else {
0265         // includeDirectiveArgumentFromPath() expects a path to the folder where includes are used from
0266         sourceUrl = baseUrl();
0267         sourceUrl.setPath(sourceUrl.path() + QLatin1String("/.h"));
0268     }
0269     const Path sourcePath(sourceUrl);
0270 
0271     const auto& directBaseClasses = this->directBaseClasses();
0272     for (const auto& baseClass : directBaseClasses) {
0273         if (!baseClass) {
0274             continue;
0275         }
0276 
0277         clangDebug() << "Looking for includes for class" << baseClass->identifier().toString();
0278 
0279         const QString includeDirective = includeDirectiveArgumentFromPath(sourcePath, baseClass);
0280         if (!includeDirective.isEmpty()) {
0281             includedFiles << includeDirective;
0282         }
0283     }
0284     variables[QStringLiteral("included_files")] = includedFiles;
0285 
0286     return variables;
0287 }
0288 
0289 
0290 DocumentChangeSet ClangTemplateNewClass::generate()
0291 {
0292     addVariables(extraVariables());
0293     return TemplateClassGenerator::generate();
0294 }