diff --git a/languages/clang/codegen/clangclasshelper.cpp b/languages/clang/codegen/clangclasshelper.cpp index f2fff1af50..563678ed4c 100644 --- a/languages/clang/codegen/clangclasshelper.cpp +++ b/languages/clang/codegen/clangclasshelper.cpp @@ -1,182 +1,304 @@ /* * KDevelop C++ Language Support * * Copyright 2008 Hamish Rodda * Copyright 2012 Miha Čančula * Copyright 2017 Friedrich W. H. Kossebau * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "clangclasshelper.h" #include "util/clangdebug.h" +#include "duchain/unknowndeclarationproblem.h" + +#include #include #include #include #include #include #include +#include +#include +#include #include #include using namespace KDevelop; ClangClassHelper::ClangClassHelper() { } ClangClassHelper::~ClangClassHelper() = default; TemplateClassGenerator* ClangClassHelper::createGenerator(const QUrl& baseUrl) { return new ClangTemplateNewClass(baseUrl); } QList ClangClassHelper::defaultMethods(const QString& name) const { // TODO: this is the oldcpp approach, perhaps clang provides this directly? // TODO: default destructor misses info about virtualness, possible needs ICreateClassHelper change? QTemporaryFile file(QDir::tempPath() + QLatin1String("/class_") + name + QLatin1String("_XXXXXX.cpp")); file.open(); QTextStream stream(&file); stream << "class " << name << " {\n" << " public:\n" // default ctor << " " << name << "();\n" // copy ctor << " " << name << "(const " << name << "& other);\n" // default dtor << " ~" << name << "();\n" // assignment operator << " " << name << "& operator=(const " << name << "& other);\n" // equality operator << " bool operator==(const " << name << "& other) const;\n" << "};\n"; file.close(); ReferencedTopDUContext context(DUChain::self()->waitForUpdate(IndexedString(file.fileName()), TopDUContext::AllDeclarationsAndContexts)); QList methods; { DUChainReadLocker lock; if (context && context->childContexts().size() == 1) { for (auto* declaration : context->childContexts().first()->localDeclarations()) { methods << DeclarationPointer(declaration); } } } return methods; } ClangTemplateNewClass::ClangTemplateNewClass(const QUrl& url) : TemplateClassGenerator(url) { } ClangTemplateNewClass::~ClangTemplateNewClass() = default; namespace { + +QString includeArgumentForFile(const QString& includefile, const Path::List& includePaths, + const Path& source) +{ + const auto sourceFolder = source.parent(); + const Path canonicalFile(QFileInfo(includefile).canonicalFilePath()); + + QString shortestDirective; + bool isRelative = false; + + // we can include the file directly + if (sourceFolder == canonicalFile.parent()) { + shortestDirective = canonicalFile.lastPathSegment(); + isRelative = true; + } else { + // find the include directive with the shortest length + for (const auto& includePath : includePaths) { + QString relative = includePath.relativePath(canonicalFile); + if (relative.startsWith(QLatin1String("./"))) { + relative.remove(0, 2); + } + + if (shortestDirective.isEmpty() || relative.length() < shortestDirective.length()) { + shortestDirective = relative; + isRelative = (includePath == sourceFolder); + } + } + } + + // Item not found in include path? + if (shortestDirective.isEmpty()) { + return {}; + } + + if (isRelative) { + return QLatin1Char('\"') + shortestDirective + QLatin1Char('\"'); + } + return QLatin1Char('<') + shortestDirective + QLatin1Char('>'); +} + +QString includeDirectiveArgumentFromPath(const Path& file, + const DeclarationPointer& declaration) +{ + const auto includeManager = IDefinesAndIncludesManager::manager(); + const auto filePath = file.toLocalFile(); + const auto projectModel = ICore::self()->projectController()->projectModel(); + auto item = projectModel->itemForPath(IndexedString(filePath)); + + if (!item) { + // try the folder where the file is placed and guess includes from there + // prefer target over file + const auto folderPath = IndexedString(file.parent().toLocalFile()); + clangDebug() << "File not known, guessing includes from items in folder:" << folderPath.str(); + + // default to the folder, if no targets or files + item = projectModel->itemForPath(folderPath); + if (item) { + const auto targetItems = item->targetList(); + bool itemChosen = false; + // Prefer items defined inside a target with non-empty includes. + for (const auto& targetItem : targetItems) { + item = targetItem; + if (!includeManager->includes(targetItem, IDefinesAndIncludesManager::ProjectSpecific).isEmpty()) { + clangDebug() << "Guessing includes from target" << targetItem->baseName(); + itemChosen = true; + break; + } + } + if (!itemChosen) { + const auto fileItems = item->fileList(); + // Prefer items defined inside a target with non-empty includes. + for (const auto& fileItem : fileItems) { + item = fileItem; + if (!includeManager->includes(fileItem, IDefinesAndIncludesManager::ProjectSpecific).isEmpty()) { + clangDebug() << "Guessing includes from file" << fileItem->baseName(); + break; + } + } + } + } + } + + const auto includePaths = includeManager->includes(item); + + if (includePaths.isEmpty()) { + clangDebug() << "Include path is empty"; + return {}; + } + + clangDebug() << "found include paths for" << file << ":" << includePaths; + + const auto includeFiles = UnknownDeclarationProblem::findMatchingIncludeFiles(QVector {declaration.data()}); + if (includeFiles.isEmpty()) { + // return early as the computation of the include paths is quite expensive + return {}; + } + + // create include arguments for all candidates + QStringList includeArguments; + for (const auto& includeFile : includeFiles) { + const auto includeArgument = includeArgumentForFile(includeFile, includePaths, file); + if (includeArgument.isEmpty()) { + clangDebug() << "unable to create include argument for" << includeFile << "in" << file.toLocalFile(); + } + includeArguments << includeArgument; + } + + if (includeArguments.isEmpty()) { + return {}; + } + + std::sort(includeArguments.begin(), includeArguments.end(), + [](const QString& lhs, const QString& rhs) { + return lhs.length() < rhs.length(); + }); + + return includeArguments.at(0); +} + template void addVariables(QVariantHash* variables, QLatin1String suffix, const Map& map) { for (auto it = map.begin(), end = map.end(); it != end; ++it) { variables->insert(it.key() + suffix, CodeDescription::toVariantList(it.value())); } } + } QVariantHash ClangTemplateNewClass::extraVariables() const { QVariantHash variables; const QString publicAccess = QStringLiteral("public"); QHash variableDescriptions; QHash functionDescriptions; QHash slotDescriptions; FunctionDescriptionList signalDescriptions; const auto desc = description(); for (const auto& function : desc.methods) { const QString& access = function.access.isEmpty() ? publicAccess : function.access; if (function.isSignal) { signalDescriptions << function; } else if (function.isSlot) { slotDescriptions[access] << function; } else { functionDescriptions[access] << function; } } for (const auto& variable : desc.members) { const QString& access = variable.access.isEmpty() ? publicAccess : variable.access; variableDescriptions[access] << variable; } ::addVariables(&variables, QLatin1String("_members"), variableDescriptions); ::addVariables(&variables, QLatin1String("_functions"), functionDescriptions); ::addVariables(&variables, QLatin1String("_slots"), slotDescriptions); variables[QStringLiteral("signals")] = CodeDescription::toVariantList(signalDescriptions); variables[QStringLiteral("needs_qobject_macro")] = !slotDescriptions.isEmpty() || !signalDescriptions.isEmpty(); - // TODO: port to KDev-clang -#if 0 QStringList includedFiles; DUChainReadLocker locker(DUChain::lock()); QUrl sourceUrl; - QHash urls = fileUrls(); + const auto urls = fileUrls(); if (!urls.isEmpty()) { sourceUrl = urls.constBegin().value(); - } - else { - // includeDirectiveFromUrl() expects a header URL + } else { + // includeDirectiveArgumentFromPath() expects a path to the folder where includes are used from sourceUrl = baseUrl(); - sourceUrl.setPath(sourceUrl.path() + '/' + QLatin1String(".h")); + sourceUrl.setPath(sourceUrl.path() + QLatin1String("/.h")); } + const Path sourcePath(sourceUrl); - for (const auto& base : directBaseClasses()) { - if (!base) { + for (const auto& baseClass : directBaseClasses()) { + if (!baseClass) { continue; } - clangDebug() << "Looking for includes for class" << base->identifier().toString(); - QExplicitlySharedDataPointer item = Cpp::includeDirectiveFromUrl(sourceUrl, IndexedDeclaration(base.data())); - if(item) - { - clangDebug() << "Found one in" << item->m_canonicalPath; - includedFiles << item->m_addedInclude; + clangDebug() << "Looking for includes for class" << baseClass->identifier().toString(); + + const QString includeDirective = includeDirectiveArgumentFromPath(sourcePath, baseClass); + if (!includeDirective.isEmpty()) { + includedFiles << includeDirective; } } variables[QStringLiteral("included_files")] = includedFiles; -#endif return variables; } DocumentChangeSet ClangTemplateNewClass::generate() { addVariables(extraVariables()); return TemplateClassGenerator::generate(); } diff --git a/languages/clang/duchain/unknowndeclarationproblem.cpp b/languages/clang/duchain/unknowndeclarationproblem.cpp index 2b9dedfa46..2a68184940 100644 --- a/languages/clang/duchain/unknowndeclarationproblem.cpp +++ b/languages/clang/duchain/unknowndeclarationproblem.cpp @@ -1,551 +1,556 @@ /* * Copyright 2014 Jørgen Kvalsvik * Copyright 2014 Kevin Funk * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include "unknowndeclarationproblem.h" #include "clanghelpers.h" #include "parsesession.h" #include "../util/clangdebug.h" #include "../util/clangutils.h" #include "../util/clangtypes.h" #include "../clangsettings/clangsettingsmanager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; namespace { /** Under some conditions, such as when looking up suggestions * for the undeclared namespace 'std' we will get an awful lot * of suggestions. This parameter limits how many suggestions * will pop up, as rarely more than a few will be relevant anyways * * Forward declaration suggestions are included in this number */ const int maxSuggestions = 5; /** * We don't want anything from the bits directory - * we'd rather prefer forwarding includes, such as */ bool isBlacklisted(const QString& path) { if (ClangHelpers::isSource(path)) return true; // Do not allow including directly from the bits directory. // Instead use one of the forwarding headers in other directories, when possible. if (path.contains( QLatin1String("bits") ) && path.contains(QLatin1String("/include/c++/"))) return true; return false; } QStringList scanIncludePaths( const QString& identifier, const QDir& dir, int maxDepth = 3 ) { if (!maxDepth) { return {}; } QStringList candidates; const auto path = dir.absolutePath(); if( isBlacklisted( path ) ) { return {}; } const QStringList nameFilters = {identifier, identifier + QLatin1String(".*")}; for (const auto& file : dir.entryList(nameFilters, QDir::Files)) { if (identifier.compare(file, Qt::CaseInsensitive) == 0 || ClangHelpers::isHeader(file)) { const QString filePath = path + QLatin1Char('/') + file; clangDebug() << "Found candidate file" << filePath; candidates.append( filePath ); } } maxDepth--; for( const auto& subdir : dir.entryList( QDir::Dirs | QDir::NoDotAndDotDot ) ) candidates += scanIncludePaths( identifier, QDir{ path + QLatin1Char('/') + subdir }, maxDepth ); return candidates; } /** * Find files in dir that match the given identifier. Matches common C++ header file extensions only. */ QStringList scanIncludePaths( const QualifiedIdentifier& identifier, const KDevelop::Path::List& includes ) { const auto stripped_identifier = identifier.last().toString(); QStringList candidates; for( const auto& include : includes ) { candidates += scanIncludePaths( stripped_identifier, QDir{ include.toLocalFile() } ); } std::sort( candidates.begin(), candidates.end() ); candidates.erase( std::unique( candidates.begin(), candidates.end() ), candidates.end() ); return candidates; } /** * Determine how much path is shared between two includes. * boost/tr1/unordered_map * boost/tr1/unordered_set * have a shared path of 2 where * boost/tr1/unordered_map * boost/vector * have a shared path of 1 */ int sharedPathLevel(const QString& a, const QString& b) { int shared = -1; for(auto x = a.begin(), y = b.begin(); *x == *y && x != a.end() && y != b.end() ; ++x, ++y ) { if( *x == QDir::separator() ) { ++shared; } } return shared; } /** * Try to find a proper include position from the DUChain: * * look at existing imports (i.e. #include's) and find a fitting * file with the same/similar path to the new include file and use that * * TODO: Implement a fallback scheme */ KDevelop::DocumentRange includeDirectivePosition(const KDevelop::Path& source, const QString& includeFile) { static const QRegularExpression mocFilenameExpression(QStringLiteral("(moc_[^\\/\\\\]+\\.cpp$|\\.moc$)") ); DUChainReadLocker lock; const TopDUContext* top = DUChainUtils::standardContextForUrl( source.toUrl() ); if( !top ) { clangDebug() << "unable to find standard context for" << source.toLocalFile() << "Creating null range"; return KDevelop::DocumentRange::invalid(); } int line = -1; // look at existing #include statements and re-use them int currentMatchQuality = -1; for( const auto& import : top->importedParentContexts() ) { const auto importFilename = import.context(top)->url().str(); const int matchQuality = sharedPathLevel( importFilename , includeFile ); if( matchQuality < currentMatchQuality ) { continue; } const auto match = mocFilenameExpression.match(importFilename); if (match.isValid()) { clangDebug() << "moc file detected in" << source.toUrl().toDisplayString() << ":" << importFilename << "-- not using as include insertion location"; continue; } line = import.position.line + 1; currentMatchQuality = matchQuality; } if( line == -1 ) { /* Insert at the top of the document */ return {IndexedString(source.pathOrUrl()), {0, 0, 0, 0}}; } return {IndexedString(source.pathOrUrl()), {line, 0, line, 0}}; } KDevelop::DocumentRange forwardDeclarationPosition(const QualifiedIdentifier& identifier, const KDevelop::Path& source) { DUChainReadLocker lock; const TopDUContext* top = DUChainUtils::standardContextForUrl( source.toUrl() ); if( !top ) { clangDebug() << "unable to find standard context for" << source.toLocalFile() << "Creating null range"; return KDevelop::DocumentRange::invalid(); } if (!top->findDeclarations(identifier).isEmpty()) { // Already forward-declared return KDevelop::DocumentRange::invalid(); } int line = std::numeric_limits< int >::max(); for( const auto decl : top->localDeclarations() ) { line = std::min( line, decl->range().start.line ); } if( line == std::numeric_limits< int >::max() ) { return KDevelop::DocumentRange::invalid(); } // We want it one line above the first declaration line = std::max( line - 1, 0 ); return {IndexedString(source.pathOrUrl()), {line, 0, line, 0}}; } /** * Iteratively build all levels of the current scope. A (missing) type anywhere * can be arbitrarily namespaced, so we create the permutations of possible * nestings of namespaces it can currently be in, * * TODO: add detection of namespace aliases, such as 'using namespace KDevelop;' * * namespace foo { * namespace bar { * function baz() { * type var; * } * } * } * * Would give: * foo::bar::baz::type * foo::bar::type * foo::type * type */ QVector findPossibleQualifiedIdentifiers( const QualifiedIdentifier& identifier, const KDevelop::Path& file, const KDevelop::CursorInRevision& cursor ) { DUChainReadLocker lock; const TopDUContext* top = DUChainUtils::standardContextForUrl( file.toUrl() ); if( !top ) { clangDebug() << "unable to find standard context for" << file.toLocalFile() << "Not creating duchain candidates"; return {}; } const auto* context = top->findContextAt( cursor ); if( !context ) { clangDebug() << "No context found at" << cursor; return {}; } QVector declarations{ identifier }; for( auto scopes = context->scopeIdentifier(); !scopes.isEmpty(); scopes.pop() ) { declarations.append( scopes + identifier ); } clangDebug() << "Possible declarations:" << declarations; return declarations; } -QStringList findMatchingIncludeFiles(const QVector declarations) +} + +QStringList UnknownDeclarationProblem::findMatchingIncludeFiles(const QVector& declarations) { DUChainReadLocker lock; QStringList candidates; for (const auto decl: declarations) { // skip declarations that don't belong to us const auto& file = decl->topContext()->parsingEnvironmentFile(); if (!file || file->language() != ParseSession::languageString()) { continue; } if( dynamic_cast( decl ) ) { continue; } if( decl->isForwardDeclaration() ) { continue; } const auto filepath = decl->url().toUrl().toLocalFile(); if( !isBlacklisted( filepath ) ) { candidates << filepath; clangDebug() << "Adding" << filepath << "determined from candidate" << decl->toString(); } for( const auto importer : file->importers() ) { if( importer->imports().count() != 1 && !isBlacklisted( filepath ) ) { continue; } if( importer->topContext()->localDeclarations().count() ) { continue; } const auto filePath = importer->url().toUrl().toLocalFile(); if( isBlacklisted( filePath ) ) { continue; } /* This file is a forwarder, such as * does not actually implement the functions, but include other headers that do * we prefer this to other headers */ candidates << filePath; clangDebug() << "Adding forwarder file" << filePath << "to the result set"; } } std::sort( candidates.begin(), candidates.end() ); candidates.erase( std::unique( candidates.begin(), candidates.end() ), candidates.end() ); clangDebug() << "Candidates: " << candidates; return candidates; } +namespace { + /** * Takes a filepath and the include paths and determines what directive to use. */ ClangFixit directiveForFile( const QString& includefile, const KDevelop::Path::List& includepaths, const KDevelop::Path& source ) { const auto sourceFolder = source.parent(); const Path canonicalFile( QFileInfo( includefile ).canonicalFilePath() ); QString shortestDirective; bool isRelative = false; // we can include the file directly if (sourceFolder == canonicalFile.parent()) { shortestDirective = canonicalFile.lastPathSegment(); isRelative = true; } else { // find the include directive with the shortest length for( const auto& includePath : includepaths ) { QString relative = includePath.relativePath( canonicalFile ); if( relative.startsWith( QLatin1String("./") ) ) relative = relative.mid( 2 ); if( shortestDirective.isEmpty() || relative.length() < shortestDirective.length() ) { shortestDirective = relative; isRelative = includePath == sourceFolder; } } } if( shortestDirective.isEmpty() ) { // Item not found in include path return {}; } const auto range = DocumentRange(IndexedString(source.pathOrUrl()), includeDirectivePosition(source, canonicalFile.lastPathSegment())); if( !range.isValid() ) { clangDebug() << "unable to determine valid position for" << includefile << "in" << source.pathOrUrl(); return {}; } QString directive; if( isRelative ) { directive = QStringLiteral("#include \"%1\"").arg(shortestDirective); } else { directive = QStringLiteral("#include <%1>").arg(shortestDirective); } return ClangFixit{directive + QLatin1Char('\n'), range, QObject::tr("Insert \'%1\'").arg(directive)}; } KDevelop::Path::List includePaths( const KDevelop::Path& file ) { // Find project's custom include paths const auto source = file.toLocalFile(); const auto item = ICore::self()->projectController()->projectModel()->itemForPath( KDevelop::IndexedString( source ) ); return IDefinesAndIncludesManager::manager()->includes(item); } /** * Return a list of header files viable for inclusions. All elements will be unique */ QStringList includeFiles(const QualifiedIdentifier& identifier, const QVector declarations, const KDevelop::Path& file) { const auto includes = includePaths( file ); if( includes.isEmpty() ) { clangDebug() << "Include path is empty"; return {}; } - const auto candidates = findMatchingIncludeFiles(declarations); + const auto candidates = UnknownDeclarationProblem::findMatchingIncludeFiles(declarations); if( !candidates.isEmpty() ) { // If we find a candidate from the duchain we don't bother scanning the include paths return candidates; } return scanIncludePaths(identifier, includes); } /** * Construct viable forward declarations for the type name. */ ClangFixits forwardDeclarations(const QVector& matchingDeclarations, const Path& source) { DUChainReadLocker lock; ClangFixits fixits; for (const auto decl : matchingDeclarations) { const auto qid = decl->qualifiedIdentifier(); if (qid.count() > 1) { // TODO: Currently we're not able to determine what is namespaces, class names etc // and makes a suitable forward declaration, so just suggest "vanilla" declarations. continue; } const auto range = forwardDeclarationPosition(qid, source); if (!range.isValid()) { continue; // do not know where to insert } if (const auto classDecl = dynamic_cast(decl)) { const auto name = qid.last().toString(); switch (classDecl->classType()) { case ClassDeclarationData::Class: fixits += { QLatin1String("class ") + name + QLatin1String(";\n"), range, QObject::tr("Forward declare as 'class'") }; break; case ClassDeclarationData::Struct: fixits += { QLatin1String("struct ") + name + QLatin1String(";\n"), range, QObject::tr("Forward declare as 'struct'") }; break; default: break; } } } return fixits; } /** * Search the persistent symbol table for matching declarations for identifiers @p identifiers */ QVector findMatchingDeclarations(const QVector& identifiers) { DUChainReadLocker lock; QVector matchingDeclarations; matchingDeclarations.reserve(identifiers.size()); for (const auto& declaration : identifiers) { clangDebug() << "Considering candidate declaration" << declaration; const IndexedDeclaration* declarations; uint declarationCount; PersistentSymbolTable::self().declarations( declaration , declarationCount, declarations ); for (uint i = 0; i < declarationCount; ++i) { // Skip if the declaration is invalid or if it is an alias declaration - // we want the actual declaration (and its file) if (auto decl = declarations[i].declaration()) { matchingDeclarations << decl; } } } return matchingDeclarations; } ClangFixits fixUnknownDeclaration( const QualifiedIdentifier& identifier, const KDevelop::Path& file, const KDevelop::DocumentRange& docrange ) { ClangFixits fixits; const CursorInRevision cursor{docrange.start().line(), docrange.start().column()}; const auto possibleIdentifiers = findPossibleQualifiedIdentifiers(identifier, file, cursor); const auto matchingDeclarations = findMatchingDeclarations(possibleIdentifiers); if (ClangSettingsManager::self()->assistantsSettings().forwardDeclare) { for (const auto& fixit : forwardDeclarations(matchingDeclarations, file)) { fixits << fixit; if (fixits.size() == maxSuggestions) { return fixits; } } } const auto includefiles = includeFiles(identifier, matchingDeclarations, file); if (includefiles.isEmpty()) { // return early as the computation of the include paths is quite expensive return fixits; } const auto includepaths = includePaths( file ); + clangDebug() << "found include paths for" << file << ":" << includepaths; /* create fixits for candidates */ for( const auto& includeFile : includefiles ) { const auto fixit = directiveForFile( includeFile, includepaths, file /* UP */ ); if (!fixit.range.isValid()) { clangDebug() << "unable to create directive for" << includeFile << "in" << file.toLocalFile(); continue; } fixits << fixit; if (fixits.size() == maxSuggestions) { return fixits; } } return fixits; } QString symbolFromDiagnosticSpelling(const QString& str) { /* in all error messages the symbol is in in the first pair of quotes */ const auto split = str.split( QLatin1Char('\'') ); auto symbol = split.value( 1 ); if( str.startsWith( QLatin1String("No member named") ) ) { symbol = split.value( 3 ) + QLatin1String("::") + split.value( 1 ); } return symbol; } } UnknownDeclarationProblem::UnknownDeclarationProblem(CXDiagnostic diagnostic, CXTranslationUnit unit) : ClangProblem(diagnostic, unit) { setSymbol(QualifiedIdentifier(symbolFromDiagnosticSpelling(description()))); } void UnknownDeclarationProblem::setSymbol(const QualifiedIdentifier& identifier) { m_identifier = identifier; } IAssistant::Ptr UnknownDeclarationProblem::solutionAssistant() const { const Path path(finalLocation().document.str()); const auto fixits = allFixits() + fixUnknownDeclaration(m_identifier, path, finalLocation()); return IAssistant::Ptr(new ClangFixitAssistant(fixits)); } diff --git a/languages/clang/duchain/unknowndeclarationproblem.h b/languages/clang/duchain/unknowndeclarationproblem.h index ae1d01a434..137e280f77 100644 --- a/languages/clang/duchain/unknowndeclarationproblem.h +++ b/languages/clang/duchain/unknowndeclarationproblem.h @@ -1,48 +1,54 @@ /* * Copyright 2014 Jørgen Kvalsvik * Copyright 2014 Kevin Funk * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #ifndef UNKNOWNDECLARATION_H #define UNKNOWNDECLARATION_H #include "clangprivateexport.h" #include "clangproblem.h" #include class KDEVCLANGPRIVATE_EXPORT UnknownDeclarationProblem : public ClangProblem { public: using Ptr = QExplicitlySharedDataPointer; using ConstPtr = QExplicitlySharedDataPointer; UnknownDeclarationProblem(CXDiagnostic diagnostic, CXTranslationUnit unit); void setSymbol(const KDevelop::QualifiedIdentifier& identifier); KDevelop::IAssistant::Ptr solutionAssistant() const override; + /** + * @param declarations declarations to find matching include files for + * @return list of paths of include files usable for the given declarations + */ + static QStringList findMatchingIncludeFiles(const QVector& declarations); + private: KDevelop::QualifiedIdentifier m_identifier; }; #endif // UNKNOWNDECLARATION_H