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 }